Files

245 lines
9.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()