mirror of
https://github.com/hlt1995/qlScripts.git
synced 2026-11-05 23:26:44 +08:00
245 lines
9.2 KiB
Python
245 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
# const $ = new Env('网络唤醒')
|
||
"""
|
||
Wake-on-LAN 网络唤醒 支持多网络多设备唤醒
|
||
|
||
变量示例
|
||
局域网单设备唤醒:
|
||
export WOL_MAC=AA-BB-CC-DD-EE-FF
|
||
局域网多设备唤醒:
|
||
export WOL_MAC=AA-BB-CC-DD-EE-FF&AA-BB-CC-DD-EE-GG
|
||
外网单设备唤醒:
|
||
export WOL_MAC=AA-BB-CC-DD-EE-FF
|
||
export WOL_IP=123.123.123.123(支持动态域名)
|
||
export WOL_PORT=9001(不填则为默认端口9)
|
||
外网多设备单网络唤醒:
|
||
export WOL_MAC=AA-BB-CC-DD-EE-FF&AA-BB-CC-DD-EE-GG
|
||
export WOL_IP=123.123.123.123(支持动态域名)
|
||
export WOL_PORT=9001&9002(不填则为默认端口9)
|
||
外网多设备多网络唤醒:
|
||
export WOL_MAC=AA-BB-CC-DD-EE-FF&AA-BB-CC-DD-EE-GG
|
||
export WOL_IP=123.123.123.123&124.124.124.124(支持动态域名)
|
||
export WOL_PORT=9001&9002(不填则为默认端口9)
|
||
"""
|
||
|
||
import socket
|
||
import struct
|
||
import argparse
|
||
import sys
|
||
import os
|
||
import re
|
||
from typing import Optional, List, Tuple
|
||
|
||
class WOLWakeOnLan:
|
||
def __init__(self, broadcast_address: str = '225.225.225.255', port: int = 9):
|
||
self.broadcast_address = broadcast_address
|
||
self.port = port
|
||
|
||
def create_magic_packet(self, mac_address: str) -> bytes:
|
||
mac_address = mac_address.replace(':', '').replace('-', '')
|
||
|
||
if len(mac_address) != 12:
|
||
raise ValueError("❌ MAC地址格式不正确")
|
||
|
||
try:
|
||
mac_bytes = bytes.fromhex(mac_address)
|
||
except ValueError:
|
||
raise ValueError("❌ MAC地址包含无效字符")
|
||
|
||
magic_packet = b'\xff' * 6 + mac_bytes * 16
|
||
return magic_packet
|
||
|
||
def wake_up(self, mac_address: str, interface: Optional[str] = None) -> bool:
|
||
try:
|
||
ip_pattern = re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||
|
||
if not ip_pattern.match(self.broadcast_address):
|
||
print(f"🔍 正在解析域名: {self.broadcast_address}")
|
||
try:
|
||
resolved_ip = socket.gethostbyname(self.broadcast_address)
|
||
print(f"🔍 解析结果: {resolved_ip}")
|
||
target_address = resolved_ip
|
||
except socket.gaierror as e:
|
||
print(f"❌ 域名解析失败: {e}")
|
||
return False
|
||
else:
|
||
target_address = self.broadcast_address
|
||
|
||
magic_packet = self.create_magic_packet(mac_address)
|
||
|
||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||
|
||
if interface:
|
||
try:
|
||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, interface.encode())
|
||
except OSError:
|
||
print(f"⚠️ 无法绑定到接口 {interface},使用默认接口")
|
||
|
||
sock.sendto(magic_packet, (target_address, self.port))
|
||
|
||
print(f"📍 目标 MAC: {mac_address}")
|
||
print(f"📡 广播地址: {target_address}:{self.port}")
|
||
if interface:
|
||
print(f"🔌 网络接口: {interface}")
|
||
print(f"✅ 幻数据包已发送!")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 发送幻数据包失败: {e}")
|
||
return False
|
||
|
||
def is_multi_device_mode() -> bool:
|
||
macs_env = os.getenv('WOL_MAC', '')
|
||
return '&' in macs_env
|
||
|
||
def parse_device_configs() -> List[Tuple[str, str, int]]:
|
||
macs_env = os.getenv('WOL_MAC', '')
|
||
ips_env = os.getenv('WOL_IP', '225.225.225.255')
|
||
ports_env = os.getenv('WOL_PORT', '9')
|
||
|
||
if not macs_env:
|
||
print("❌ 请设置 WOL_MAC 环境变量")
|
||
sys.exit(1)
|
||
|
||
macs = [mac.strip() for mac in macs_env.split('&') if mac.strip()]
|
||
ips = [ip.strip() for ip in ips_env.split('&') if ip.strip()]
|
||
ports = [port.strip() for port in ports_env.split('&') if port.strip()]
|
||
|
||
print(f"🔍️ 检测到 {len(macs)} 台设备")
|
||
|
||
if len(ips) == 1 and len(macs) > 1:
|
||
print("📝 当前配置:所有设备处于同一IP地址")
|
||
ips = ips * len(macs)
|
||
|
||
if len(ports) == 1 and len(macs) > 1:
|
||
print("📝 当前配置:所有设备使用同一唤醒端口")
|
||
ports = ports * len(macs)
|
||
|
||
if len(macs) != len(ips) or len(macs) != len(ports):
|
||
print("❌ 环境变量配置不匹配")
|
||
print(f" MAC地址数量: {len(macs)}")
|
||
print(f" IP地址数量: {len(ips)}")
|
||
print(f" 端口数量: {len(ports)}")
|
||
print("💡 请确保:")
|
||
print(" - 单设备: WOL_MAC=AA-BB-CC-DD-EE-FF")
|
||
print(" - 多设备: WOL_MAC=AA-BB-CC-DD-EE-FF&BB-CC-DD-EE-FF-GG")
|
||
print(" - 多IP/端口: 使用相同数量的&分隔符")
|
||
sys.exit(1)
|
||
|
||
try:
|
||
ports = [int(port) for port in ports]
|
||
except ValueError:
|
||
print("❌ 端口号必须为整数")
|
||
sys.exit(1)
|
||
|
||
return list(zip(macs, ips, ports))
|
||
|
||
def get_single_device_config() -> Tuple[str, str, int]:
|
||
mac_env = os.getenv('WOL_MAC', '')
|
||
ip_env = os.getenv('WOL_IP', '225.225.225.255')
|
||
port_env = os.getenv('WOL_PORT', '9')
|
||
|
||
if not mac_env:
|
||
print("❌ 请设置 WOL_MAC 环境变量")
|
||
sys.exit(1)
|
||
|
||
mac = mac_env.split('&')[0].strip() if '&' in mac_env else mac_env
|
||
ip = ip_env.split('&')[0].strip() if '&' in ip_env else ip_env
|
||
|
||
try:
|
||
port = int(port_env.split('&')[0].strip()) if '&' in port_env else int(port_env)
|
||
except ValueError:
|
||
print("❌ 端口号必须为整数")
|
||
sys.exit(1)
|
||
|
||
return mac, ip, port
|
||
|
||
def wake_multiple_devices(interface: Optional[str] = None) -> bool:
|
||
devices = parse_device_configs()
|
||
|
||
print(f"🖥️ 正在唤醒设备...\n")
|
||
|
||
success_count = 0
|
||
for i, (mac, ip, port) in enumerate(devices, 1):
|
||
print(f"--- 设备 {i}/{len(devices)} ---")
|
||
wol = WOLWakeOnLan(broadcast_address=ip, port=port)
|
||
if wol.wake_up(mac, interface):
|
||
success_count += 1
|
||
print() # 空行分隔
|
||
|
||
return success_count > 0
|
||
|
||
def wake_single_device(mac: str = None, ip: str = None, port: int = None, interface: Optional[str] = None) -> bool:
|
||
if mac is None or ip is None or port is None:
|
||
mac, ip, port = get_single_device_config()
|
||
|
||
print(f"🖥️ 正在唤醒设备...\n")
|
||
wol = WOLWakeOnLan(broadcast_address=ip, port=port)
|
||
return wol.wake_up(mac, interface)
|
||
|
||
def main():
|
||
# 从环境变量读取配置
|
||
env_interface = os.getenv('WOL_INTERFACE')
|
||
|
||
parser = argparse.ArgumentParser(description='WOL远程开机脚本')
|
||
parser.add_argument('mac', nargs='?', help='目标设备的MAC地址 (格式: XX:XX:XX:XX:XX:XX)')
|
||
parser.add_argument('-b', '--broadcast',
|
||
help='广播地址 (默认从WOL_IP环境变量读取)')
|
||
parser.add_argument('-p', '--port', type=int,
|
||
help='端口号 (默认从WOL_PORT环境变量读取)')
|
||
parser.add_argument('-i', '--interface', default=env_interface,
|
||
help='网络接口名称')
|
||
parser.add_argument('--list-interfaces', action='store_true',
|
||
help='列出可用的网络接口')
|
||
parser.add_argument('--single', action='store_true',
|
||
help='强制单设备模式')
|
||
parser.add_argument('--multi', action='store_true',
|
||
help='强制多设备模式')
|
||
|
||
args = parser.parse_args()
|
||
|
||
if args.list_interfaces:
|
||
print("可用的网络接口:")
|
||
try:
|
||
import netifaces
|
||
interfaces = netifaces.interfaces()
|
||
for iface in interfaces:
|
||
addrs = netifaces.ifaddresses(iface)
|
||
if netifaces.AF_INET in addrs:
|
||
ip = addrs[netifaces.AF_INET][0]['addr']
|
||
print(f" {iface}: {ip}")
|
||
except ImportError:
|
||
print(" ℹ️ 需要安装netifaces库来显示接口信息")
|
||
print(" 💡 运行: pip install netifaces")
|
||
return
|
||
|
||
if args.multi:
|
||
print("🔧 强制多设备模式")
|
||
success = wake_multiple_devices(args.interface)
|
||
elif args.single:
|
||
print("🔧 强制单设备模式")
|
||
if args.mac:
|
||
ip = args.broadcast if args.broadcast else os.getenv('WOL_IP', '225.225.225.255')
|
||
port = args.port if args.port else int(os.getenv('WOL_PORT', '9'))
|
||
success = wake_single_device(args.mac, ip, port, args.interface)
|
||
else:
|
||
success = wake_single_device(interface=args.interface)
|
||
else:
|
||
if is_multi_device_mode():
|
||
success = wake_multiple_devices(args.interface)
|
||
else:
|
||
if args.mac:
|
||
ip = args.broadcast if args.broadcast else os.getenv('WOL_IP', '225.225.225.255')
|
||
port = args.port if args.port else int(os.getenv('WOL_PORT', '9'))
|
||
success = wake_single_device(args.mac, ip, port, args.interface)
|
||
else:
|
||
success = wake_single_device(interface=args.interface)
|
||
|
||
sys.exit(0 if success else 1)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|