Files
2026-05-15 21:26:06 +08:00

1173 lines
44 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.
#[title: rb-aut]
#[language: python]
#[class: 工具类]
#[service: 10010] 设备重启管理(设备管理模式)
#[disable: false]
#[admin: false]
#[rule: ^(rb-now|rb管理)$]
#[priority: 999]
#[platform: qq,qb,wx,tb,tg,web,wxmp]
#[open_source: false]
#[icon: 图标url]
#[version: 1.0.0]
#[public: false]
#[price: 0.00]
#[description: 设备重启管理,支持多设备管理。<br>指令:rb-now(设备选单重启)、rb管理(设备管理),adb方式重启需安装依赖adbtool,并且首次运行需在设备授权]
import sys
import shlex
import time
import json
import logging
import subprocess
from typing import Tuple, Optional
import colorlog
import middleware
import sender
# 存储桶前缀
BUCKET = 'rb-aut'
sender = middleware.Sender(middleware.getSenderID())
class TOOL:
def __init__(self):
self.plugin_pre = BUCKET
self.color_formatter = colorlog.ColoredFormatter(
'%(log_color)s%(levelname)s: %(message)s',
log_colors={
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red,bg_white',
}
)
self.logger = self._configure_logger()
def _configure_logger(self):
logger_obj = logging.getLogger(self.plugin_pre)
logger_obj.setLevel(logging.DEBUG)
if not any(isinstance(h, logging.StreamHandler) for h in logger_obj.handlers):
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
console_handler.setFormatter(self.color_formatter)
logger_obj.addHandler(console_handler)
return logger_obj
def log(self, level, message):
self.logger.log(level, message)
def reply(self, content):
return sender.reply(content)
def bucket_get(key: str, default: Optional[str] = None) -> Optional[str]:
val = middleware.bucketGet(BUCKET, key)
return val if val is not None and val != '' else default
def bucket_set(key: str, value: str) -> None:
middleware.bucketSet(BUCKET, key, value)
def get_all_devices() -> dict:
"""获取所有设备配置"""
devices = {}
all_keys = middleware.bucketAllKeys(BUCKET)
for key in all_keys:
if key.startswith('device.'):
device_id = key[7:] # 去掉 'device.' 前缀
device_data = bucket_get(key)
if device_data:
try:
devices[device_id] = json.loads(device_data)
except Exception:
continue
return devices
def save_device(device_id: str, device_config: dict) -> None:
"""保存设备配置"""
bucket_set(f'device.{device_id}', json.dumps(device_config))
def delete_device(device_id: str) -> None:
"""删除设备配置"""
middleware.bucketDel(BUCKET, f'device.{device_id}')
def has_device_permission(device_config: dict, user_id: str, is_admin: bool) -> bool:
"""检查用户是否有设备权限
Args:
device_config: 设备配置字典
user_id: 用户ID
is_admin: 是否是管理员
Returns:
bool: 是否有权限
"""
# 管理员有所有权限
if is_admin:
return True
# 检查设备权限配置
admin_only = device_config.get('admin_only', False)
if admin_only:
return False
# 检查允许的用户列表
allowed_users = device_config.get('allowed_users', [])
if isinstance(allowed_users, str):
# 兼容旧格式(逗号分隔的字符串)
allowed_users = [u.strip() for u in allowed_users.split(',') if u.strip()]
if not isinstance(allowed_users, list):
allowed_users = []
# 如果允许的用户列表为空,则默认所有用户都可以访问
if not allowed_users:
return True
return user_id in allowed_users
def get_device_list(user_id: Optional[str] = None, is_admin: bool = False) -> list:
"""获取设备列表,返回格式化的设备信息
Args:
user_id: 用户ID,如果提供则只返回该用户有权限的设备
is_admin: 是否是管理员,管理员可以看到所有设备
"""
devices = get_all_devices()
device_list = []
for device_id, config in devices.items():
device_type = config.get('type', 'unknown')
name = config.get('name', device_id)
# 如果指定了用户ID且不是管理员,检查权限
if user_id and not is_admin:
if not has_device_permission(config, user_id, False):
continue
if device_type == 'adb':
device_list.append({
'id': device_id,
'name': name,
'type': 'ADB',
'info': name
})
elif device_type == 'ssh':
device_list.append({
'id': device_id,
'name': name,
'type': 'SSH',
'info': name
})
return device_list
def run_command(command: str, timeout_seconds: int = 20) -> Tuple[int, str, str]:
try:
process = subprocess.Popen(
shlex.split(command),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
stdout, stderr = process.communicate(timeout=timeout_seconds)
return process.returncode, (stdout or '').strip(), (stderr or '').strip()
except subprocess.TimeoutExpired:
try:
process.kill()
except Exception:
pass
return 124, '', f"Command timed out after {timeout_seconds}s: {command}"
except FileNotFoundError:
return 127, '', f"Command not found: {command.split()[0]}"
class ADBRebootClient:
def __init__(self, address: str, adb_path: str = 'adb', timeout: int = 20, tool: Optional[TOOL] = None):
self.address = address
self.adb = adb_path or 'adb'
self.timeout = timeout
self.tool = tool
def _log(self, level, message: str):
if self.tool:
self.tool.log(level, message)
def disconnect(self) -> None:
run_command(f"{self.adb} disconnect {self.address}", timeout_seconds=min(self.timeout, 5))
def kill_server(self) -> None:
run_command(f"{self.adb} kill-server", timeout_seconds=min(self.timeout, 5))
def cleanup(self) -> None:
"""清理ADB连接和服务"""
try:
# 先断开指定设备连接
self.disconnect()
# 等待一下确保断开完成
time.sleep(1)
# 然后kill整个adb服务
self.kill_server()
if self.tool:
self.tool.log(logging.INFO, "✓ ADB服务已清理完成")
except Exception as e:
if self.tool:
self.tool.log(logging.WARNING, f"ADB清理过程中出现异常: {e}")
def ensure_adb(self) -> bool:
code, out, err = run_command(f"{self.adb} version", timeout_seconds=self.timeout)
if code != 0:
self._log(logging.ERROR, "未找到可用的 adb,请安装或在参数中指定 adb 路径")
if err:
self._log(logging.ERROR, err)
return False
self._log(logging.INFO, (out or 'adb 可用'))
return True
def connect(self) -> bool:
self.disconnect()
code, out, err = run_command(f"{self.adb} connect {self.address}", timeout_seconds=self.timeout)
msg = ((out or '') + ("\n" + err if err else '')).strip()
if code != 0:
self._log(logging.WARNING, f"adb connect 首次失败(code={code}),尝试重启 adb 并重试...\n{msg}")
self.kill_server()
self.disconnect()
code, out, err = run_command(f"{self.adb} connect {self.address}", timeout_seconds=self.timeout)
msg = ((out or '') + ("\n" + err if err else '')).strip()
if code != 0:
self._log(logging.ERROR, f"adb connect 重试失败(code={code}):\n{msg}")
return False
low = msg.lower()
if ("connected to" in low) or ("already connected to" in low):
self._log(logging.INFO, "✓ ADB 连接成功")
else:
self._log(logging.WARNING, "adb connect 返回0,但输出未包含已连接提示")
if msg:
self._log(logging.INFO, msg)
ok, detail = self.get_state()
if not ok:
self._log(logging.ERROR, f"连接校验失败:{detail}")
return False
self._log(logging.INFO, f"设备状态:{detail}")
return True
def get_state(self) -> Tuple[bool, str]:
code, out, err = run_command(f"{self.adb} -s {self.address} get-state", timeout_seconds=min(self.timeout, 10))
text = (out or err or '').strip()
if code == 0 and (out or '').strip() == 'device':
return True, 'device'
return False, (text or f"get-state 失败(code={code})")
def reboot(self) -> bool:
def looks_like_reboot_sent(message: str) -> bool:
low = (message or '').lower()
hints = [
'closed', 'device offline', 'no devices/emulators found', 'cannot connect to daemon',
'daemon not running', 'failed to connect to', 'connection refused', 'connection reset',
'transport endpoint is not connected', 'broken pipe', 'read failed', 'i/o error',
'more than one device/emulator',
]
return any(h in low for h in hints)
def observe_post_reboot_disconnect(max_attempts: int = 8, interval_seconds: float = 0.5) -> bool:
for _ in range(max_attempts):
code_s, out_s, err_s = run_command(
f"{self.adb} -s {self.address} get-state",
timeout_seconds=min(self.timeout, 5)
)
if code_s != 0:
return True
if (out_s or '').strip() != 'device':
return True
time.sleep(interval_seconds)
return False
code, out, err = run_command(f"{self.adb} -s {self.address} reboot", timeout_seconds=self.timeout)
combined = ((out or '') + ("\n" + err if err else '')).strip()
if code == 0:
self._log(logging.INFO, "✓ 已发送重启指令")
return True
if looks_like_reboot_sent(combined):
self._log(logging.WARNING, f"adb reboot 返回非零(code={code}),但检测到重启迹象,视为成功:\n{combined}")
return True
self._log(logging.WARNING, f"adb reboot 失败(code={code}),尝试使用 shell reboot 回退...\n{combined}")
code2, out2, err2 = run_command(f"{self.adb} -s {self.address} shell reboot", timeout_seconds=self.timeout)
combined2 = ((out2 or '') + ("\n" + err2 if err2 else '')).strip()
if code2 == 0 or looks_like_reboot_sent(combined2):
self._log(logging.INFO, "✓ 已通过 shell reboot 发送重启指令")
return True
if observe_post_reboot_disconnect():
self._log(logging.WARNING, "检测到重启后的断连迹象,尽管命令返回失败,仍视为已下发成功")
return True
self._log(logging.ERROR, f"shell reboot 仍失败(code={code2}):\n{combined2}")
return False
class SSHRebootClient:
def __init__(self, hostname: str, username: str, password: str,
port: int = 5722, timeout: int = 30, tool: Optional[TOOL] = None):
self.hostname = hostname
self.username = username
self.password = password
self.port = port
self.timeout = timeout
self.tool = tool
self.client = None
def connect(self) -> bool:
try:
if self.tool:
self.tool.log(logging.INFO, f"正在连接到 {self.hostname}:{self.port}...")
import paramiko
self.client = paramiko.SSHClient()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.client.connect(
hostname=self.hostname,
username=self.username,
password=self.password,
port=self.port,
timeout=self.timeout,
allow_agent=False,
look_for_keys=False,
)
if self.tool:
self.tool.log(logging.INFO, "✓ SSH连接建立成功!")
return True
except Exception as e:
if self.tool:
self.tool.log(logging.ERROR, f"✗ SSH连接失败: {e}")
return False
def _execute_reboot_command(self, command: str) -> bool:
reboot_commands = [
command,
f"sudo {command}",
f"nohup {command} > /dev/null 2>&1 &",
f"sudo nohup {command} > /dev/null 2>&1 &",
]
for i, cmd in enumerate(reboot_commands):
try:
if self.tool:
self.tool.log(logging.INFO, f"尝试执行重启命令 ({i+1}/{len(reboot_commands)}): {cmd}")
stdin, stdout, stderr = self.client.exec_command(cmd, timeout=3)
try:
_ = stdout.read()
_ = stderr.read()
except Exception:
pass
time.sleep(1)
try:
t_in, t_out, t_err = self.client.exec_command("echo test", timeout=1)
_ = t_out.read()
except Exception:
if self.tool:
self.tool.log(logging.INFO, "✓ 检测到SSH连接断开,重启命令生效!")
self.tool.log(logging.INFO, "✓ 设备正在重启中...")
return True
if i < len(reboot_commands) - 1:
if self.tool:
self.tool.log(logging.INFO, "连接仍在,尝试下一种命令格式...")
time.sleep(1)
except Exception as e:
if self.tool:
self.tool.log(logging.WARNING, f"命令执行异常: {e}")
try:
_ = self.client.get_transport().is_active()
except Exception:
if self.tool:
self.tool.log(logging.INFO, "✓ 检测到SSH连接断开,重启命令生效!")
return True
if i < len(reboot_commands) - 1:
if self.tool:
self.tool.log(logging.INFO, "尝试下一种命令格式...")
time.sleep(1)
if self.tool:
self.tool.log(logging.WARNING, "⚠️ 所有重启命令格式都尝试过了;重启可能已发送")
return True
def send_reboot_command(self, command: str = 'reboot') -> bool:
if not self.client:
if self.tool:
self.tool.log(logging.ERROR, "✗ 未建立SSH连接")
return False
try:
if command.lower() in ['reboot', 'shutdown', 'poweroff']:
return self._execute_reboot_command(command)
stdin, stdout, stderr = self.client.exec_command(command, timeout=30)
exit_status = stdout.channel.recv_exit_status()
_ = stdout.read()
_ = stderr.read()
if exit_status == 0:
if self.tool:
self.tool.log(logging.INFO, "✓ 命令执行成功!")
return True
if self.tool:
self.tool.log(logging.ERROR, f"✗ 命令执行失败,退出码: {exit_status}")
return False
except Exception as e:
if self.tool:
self.tool.log(logging.ERROR, f"✗ 执行命令时出错: {e}")
return False
def is_connected(self) -> bool:
try:
if self.client and self.client.get_transport():
return self.client.get_transport().is_active()
return False
except Exception:
return False
def close(self) -> None:
if self.client:
try:
self.client.close()
if self.tool:
self.tool.log(logging.INFO, "SSH连接已关闭")
except Exception:
if self.tool:
self.tool.log(logging.INFO, "SSH连接已断开")
def run_adb_reboot(tool: TOOL, device_config: dict) -> bool:
"""执行ADB设备重启"""
addr = device_config.get('address', '')
name = device_config.get('name', '未知设备')
tool.reply(f"开始执行 {name} ADB 重启流程...")
if not addr:
tool.reply("❌ 设备配置不完整,缺少 address")
return
tool.log(logging.INFO, "配置信息:")
tool.log(logging.INFO, f" 设备名称: {name}")
tool.log(logging.INFO, f" ADB地址: {addr}")
client = ADBRebootClient(address=addr, adb_path='adb', timeout=20, tool=tool)
try:
if not client.ensure_adb():
tool.reply("❌ 未检测到 adb,请安装 adb")
return False
tool.log(logging.INFO, "🚀 正在连接设备...")
if not client.connect():
tool.reply("❌ ADB 连接失败,请检查地址与网络")
return False
tool.log(logging.INFO, "🔁 正在发送重启指令...")
if client.reboot():
tool.reply("✅ 重启指令已发送!设备即将重启")
# 等待一下让重启指令生效
time.sleep(2)
# 清理ADB连接和服务
tool.log(logging.INFO, "🔌 正在清理ADB连接...")
client.cleanup()
return True
else:
tool.reply("❌ 重启指令发送失败")
# 即使失败也清理连接
tool.log(logging.INFO, "🔌 正在清理ADB连接...")
client.cleanup()
return False
except Exception as exc:
tool.log(logging.ERROR, f"程序执行出错: {exc}")
tool.reply("❌ 程序执行出错,请检查配置与网络")
# 异常情况下也尝试清理
try:
tool.log(logging.INFO, "🔌 异常情况下清理ADB连接...")
client.cleanup()
except Exception:
pass
return False
def run_ssh_reboot(tool: TOOL, device_config: dict) -> bool:
"""执行SSH设备重启"""
host = device_config.get('hostname', '')
username = device_config.get('username', 'root')
password = device_config.get('password', '')
port = device_config.get('port', 22)
timeout = device_config.get('timeout', 30)
command = device_config.get('command', 'reboot')
name = device_config.get('name', '未知设备')
tool.reply(f"开始执行 {name} SSH 重启流程...")
if not host or not username or not password:
tool.reply("❌ 设备配置不完整,缺少 hostname/username/password")
return False
tool.log(logging.INFO, f"配置信息:")
tool.log(logging.INFO, f" 设备名称: {name}")
tool.log(logging.INFO, f" 主机: {host}")
tool.log(logging.INFO, f" 用户: {username}")
tool.log(logging.INFO, f" 端口: {port}")
tool.log(logging.INFO, f" 命令: {command}")
tool.log(logging.INFO, f" 超时: {timeout}")
client = SSHRebootClient(hostname=host, username=username, password=password,
port=port, timeout=timeout, tool=tool)
try:
if not client.connect():
tool.reply("❌ SSH连接失败,请检查设备地址和认证信息")
return False
tool.log(logging.INFO, "🚀 开始执行重启操作...")
result = client.send_reboot_command(command)
if result:
tool.reply("✅ 重启操作完成!")
tool.reply("📱 设备正在重启中,请稍后检查设备状态")
time.sleep(3)
if not client.is_connected():
tool.log(logging.INFO, "🔌 SSH连接已断开,确认设备正在重启")
else:
tool.log(logging.WARNING, "⚠️ SSH连接仍在,设备可能未重启成功")
return True
else:
tool.reply("❌ 重启命令执行失败!")
return False
except Exception as e:
tool.log(logging.ERROR, f"程序执行出错: {e}")
tool.reply("❌ 程序执行出错,请检查配置和网络连接")
return False
finally:
client.close()
tool.log(logging.INFO, "🏁 脚本执行完毕")
def reboot_device(tool: TOOL, device_config: dict) -> bool:
"""根据设备类型执行重启,返回是否成功"""
device_type = device_config.get('type')
if device_type == 'adb':
return run_adb_reboot(tool, device_config)
if device_type == 'ssh':
return run_ssh_reboot(tool, device_config)
tool.reply("❌ 未知设备类型")
return False
def cmd_rb_now(tool: TOOL, user_id: str, is_admin: bool):
"""rb-now 命令:显示设备选单并执行重启"""
device_list = get_device_list(user_id, is_admin)
if not device_list:
tool.reply("❌ 暂无可用设备,请联系管理员添加设备")
return
# 显示设备选单(只显示名称),0 为全部重启
menu_text = "请选择要重启的设备:\n0. 重启所有设备\n"
for i, device in enumerate(device_list, 1):
menu_text += f"{i}. {device['name']}\n"
tool.reply(menu_text)
# 等待用户选择
try:
choice = sender.listen(60000)
if not choice or choice == 'q' or choice == 'error':
tool.reply("已取消")
return
# 选择 0 重启所有设备
if choice == '0':
devices = get_all_devices()
total = len(device_list)
success = 0
fail = 0
for item in device_list:
cfg = devices.get(item['id'])
if not cfg:
fail += 1
continue
# 再次验证权限,确保安全
if not has_device_permission(cfg, user_id, is_admin):
fail += 1
continue
ok = reboot_device(tool, cfg)
if ok:
success += 1
else:
fail += 1
tool.reply(f"执行完成:成功 {success}/{total},失败 {fail}/{total}")
return
choice_num = int(choice)
if 1 <= choice_num <= len(device_list):
selected_device = device_list[choice_num - 1]
device_id = selected_device['id']
devices = get_all_devices()
device_config = devices.get(device_id)
if not device_config:
tool.reply("❌ 设备配置不存在")
return
# 再次验证权限,确保安全
if not has_device_permission(device_config, user_id, is_admin):
tool.reply("❌ 您没有权限重启此设备")
return
reboot_device(tool, device_config)
else:
tool.reply("❌ 无效选择")
except ValueError:
tool.reply("❌ 请输入有效数字")
except Exception as e:
tool.reply(f"❌ 选择设备时出错: {e}")
def cmd_rb_manage(tool: TOOL, user_id: str, is_admin: bool):
"""rb管理 命令:设备管理(仅管理员可用)"""
if not is_admin:
tool.reply("❌ 此命令仅管理员可使用")
return
device_list = get_device_list(user_id, is_admin)
if not device_list:
tool.reply("设备管理菜单:\n1. 添加设备\n0. 返回")
try:
choice = sender.listen(60000)
if not choice or choice == '0' or choice == 'q' or choice == 'error':
tool.reply("已返回")
return
if choice == '1':
add_device(tool)
else:
tool.reply("❌ 无效选择")
except Exception as e:
tool.reply(f"❌ 操作出错: {e}")
return
# 显示设备列表和管理选项(仅名称),提示 + 和 - 用法
menu_text = "设备管理菜单:\n"
for i, device in enumerate(device_list, 1):
menu_text += f"{i}. {device['name']}\n"
menu_text += "+ (添加设备)\n- 序号 (删除设备)\n0. 返回"
tool.reply(menu_text)
try:
choice = sender.listen(60000)
if not choice or choice == '0' or choice == 'q' or choice == 'error':
tool.reply("已返回")
return
if choice == '+':
add_device(tool)
elif choice.startswith('-'):
# 删除设备:-序号
try:
device_num = int(choice[1:])
if 1 <= device_num <= len(device_list):
delete_device_by_number(tool, device_list, device_num - 1)
else:
tool.reply("❌ 无效的设备序号")
except ValueError:
tool.reply("❌ 请输入正确的格式:-序号")
elif choice.isdigit():
# 查看/修改设备:序号
device_num = int(choice)
if 1 <= device_num <= len(device_list):
view_edit_device(tool, device_list, device_num - 1)
else:
tool.reply("❌ 无效的设备序号")
else:
tool.reply("❌ 无效选择")
except Exception as e:
tool.reply(f"❌ 操作出错: {e}")
def add_device(tool: TOOL):
"""添加设备"""
tool.reply("请选择设备类型:\n1. ADB设备\n2. SSH设备\n0. 取消")
try:
type_choice = sender.listen(60000)
if not type_choice or type_choice == '0' or type_choice == 'q' or type_choice == 'error':
tool.reply("已取消")
return
if type_choice == '1':
add_adb_device(tool)
elif type_choice == '2':
add_ssh_device(tool)
else:
tool.reply("❌ 无效选择")
except Exception as e:
tool.reply(f"❌ 添加设备出错: {e}")
def add_adb_device(tool: TOOL):
"""添加ADB设备"""
tool.reply("请输入设备名称(q:退出)")
name = sender.listen(60000)
if not name or name == 'q' or name == 'error':
tool.reply("已退出")
return
tool.reply("请输入ADB地址(示例:192.168.1.100:5555q:退出)")
address = sender.listen(60000)
if not address or address == 'q' or address == 'error':
tool.reply("已退出")
return
# 设置设备权限
tool.reply("请设置设备权限:\n1. 仅管理员可用\n2. 所有用户可用\n3. 指定用户可用\n0. 取消")
perm_choice = sender.listen(60000)
if not perm_choice or perm_choice == '0' or perm_choice == 'q' or perm_choice == 'error':
tool.reply("已取消")
return
device_config = {
'type': 'adb',
'name': name,
'address': address,
'admin_only': False,
'allowed_users': []
}
if perm_choice == '1':
device_config['admin_only'] = True
elif perm_choice == '2':
device_config['admin_only'] = False
device_config['allowed_users'] = [] # 空列表表示所有用户可用
elif perm_choice == '3':
tool.reply("请输入允许访问的用户ID(多个用户用逗号分隔,q:退出)")
users_input = sender.listen(60000)
if not users_input or users_input == 'q' or users_input == 'error':
tool.reply("已取消")
return
allowed_users = [u.strip() for u in users_input.split(',') if u.strip()]
device_config['admin_only'] = False
device_config['allowed_users'] = allowed_users
else:
tool.reply("❌ 无效选择,已取消")
return
# 生成设备ID
device_id = f"adb_{int(time.time())}"
save_device(device_id, device_config)
tool.reply(f"✅ ADB设备 '{name}' 添加成功!")
def add_ssh_device(tool: TOOL):
"""添加SSH设备"""
tool.reply("请输入设备名称(q:退出)")
name = sender.listen(60000)
if not name or name == 'q' or name == 'error':
tool.reply("已退出")
return
tool.reply("请输入SSH主机地址(示例:192.168.1.200q:退出)")
hostname = sender.listen(60000)
if not hostname or hostname == 'q' or hostname == 'error':
tool.reply("已退出")
return
tool.reply("请输入SSH用户名(默认root,直接回车跳过)")
username = sender.listen(60000)
if not username or username == 'q' or username == 'error':
username = 'root'
tool.reply("请输入SSH密码(q:退出)")
password = sender.listen(60000)
if not password or password == 'q' or password == 'error':
tool.reply("已退出")
return
tool.reply("请输入SSH端口(默认22,直接回车跳过)")
port_str = sender.listen(60000)
port = 22
if port_str and port_str.isdigit():
port = int(port_str)
tool.reply("请输入重启命令(默认reboot,直接回车跳过)")
command = sender.listen(60000)
if not command or command == 'q' or command == 'error':
command = 'reboot'
tool.reply("请输入超时时间(默认30秒,直接回车跳过)")
timeout_str = sender.listen(60000)
timeout = 30
if timeout_str and timeout_str.isdigit():
timeout = int(timeout_str)
# 设置设备权限
tool.reply("请设置设备权限:\n1. 仅管理员可用\n2. 所有用户可用\n3. 指定用户可用\n0. 取消")
perm_choice = sender.listen(60000)
if not perm_choice or perm_choice == '0' or perm_choice == 'q' or perm_choice == 'error':
tool.reply("已取消")
return
device_config = {
'type': 'ssh',
'name': name,
'hostname': hostname,
'username': username,
'password': password,
'port': port,
'command': command,
'timeout': timeout,
'admin_only': False,
'allowed_users': []
}
if perm_choice == '1':
device_config['admin_only'] = True
elif perm_choice == '2':
device_config['admin_only'] = False
device_config['allowed_users'] = [] # 空列表表示所有用户可用
elif perm_choice == '3':
tool.reply("请输入允许访问的用户ID(多个用户用逗号分隔,q:退出)")
users_input = sender.listen(60000)
if not users_input or users_input == 'q' or users_input == 'error':
tool.reply("已取消")
return
allowed_users = [u.strip() for u in users_input.split(',') if u.strip()]
device_config['admin_only'] = False
device_config['allowed_users'] = allowed_users
else:
tool.reply("❌ 无效选择,已取消")
return
# 生成设备ID
device_id = f"ssh_{int(time.time())}"
save_device(device_id, device_config)
tool.reply(f"✅ SSH设备 '{name}' 添加成功!")
def show_device_list(tool: TOOL):
"""显示设备列表"""
device_list = get_device_list()
if not device_list:
tool.reply("❌ 暂无设备")
return
text = "当前设备列表:\n"
for i, device in enumerate(device_list, 1):
text += f"{i}. {device['name']}\n"
tool.reply(text)
def view_edit_device(tool: TOOL, device_list: list, device_index: int):
"""查看/编辑设备配置"""
selected_device = device_list[device_index]
device_id = selected_device['id']
device_name = selected_device['name']
device_type = selected_device['type']
# 获取设备详细配置
devices = get_all_devices()
device_config = devices.get(device_id)
if not device_config:
tool.reply("❌ 设备配置不存在")
return
# 显示设备配置
config_text = f"设备配置详情:\n"
config_text += f"设备名称: {device_config.get('name', '未知')}\n"
config_text += f"设备类型: {device_config.get('type', '未知')}\n"
# 显示权限信息
admin_only = device_config.get('admin_only', False)
allowed_users = device_config.get('allowed_users', [])
if isinstance(allowed_users, str):
allowed_users = [u.strip() for u in allowed_users.split(',') if u.strip()]
if not isinstance(allowed_users, list):
allowed_users = []
if admin_only:
config_text += f"权限设置: 仅管理员可用\n"
elif not allowed_users:
config_text += f"权限设置: 所有用户可用\n"
else:
config_text += f"权限设置: 指定用户可用 ({', '.join(allowed_users)})\n"
if device_type == 'ADB':
config_text += f"ADB地址: {device_config.get('address', '未设置')}\n"
elif device_type == 'SSH':
config_text += f"SSH主机: {device_config.get('hostname', '未设置')}\n"
config_text += f"SSH用户: {device_config.get('username', 'root')}\n"
config_text += f"SSH密码: {'******' if device_config.get('password') else '未设置'}\n"
config_text += f"SSH端口: {device_config.get('port', 22)}\n"
config_text += f"重启命令: {device_config.get('command', 'reboot')}\n"
config_text += f"超时时间: {device_config.get('timeout', 30)}\n"
config_text += "\n操作选项:\n1. 修改配置\n2. 删除设备\n0. 返回"
tool.reply(config_text)
try:
choice = sender.listen(60000)
if not choice or choice == '0' or choice == 'q' or choice == 'error':
tool.reply("已返回")
return
if choice == '1':
edit_device_config(tool, device_id, device_config)
elif choice == '2':
# 确认删除
tool.reply(f"确认删除设备 '{device_name}' 吗?(y/n)")
confirm = sender.listen(30000)
if confirm and confirm.lower() in ['y', 'yes', '']:
delete_device(device_id)
tool.reply(f"✅ 设备 '{device_name}' 已删除")
else:
tool.reply("已取消删除")
else:
tool.reply("❌ 无效选择")
except Exception as e:
tool.reply(f"❌ 操作出错: {e}")
def edit_device_config(tool: TOOL, device_id: str, device_config: dict):
"""编辑设备配置"""
device_type = device_config.get('type')
device_name = device_config.get('name', '未知设备')
tool.reply(f"正在编辑设备 '{device_name}' 的配置...")
if device_type == 'adb':
edit_adb_device_config(tool, device_id, device_config)
elif device_type == 'ssh':
edit_ssh_device_config(tool, device_id, device_config)
else:
tool.reply("❌ 未知设备类型")
def edit_adb_device_config(tool: TOOL, device_id: str, device_config: dict):
"""编辑ADB设备配置"""
tool.reply("请选择要修改的配置项:\n1. 设备名称\n2. ADB地址\n3. 权限设置\n0. 返回")
try:
choice = sender.listen(60000)
if not choice or choice == '0' or choice == 'q' or choice == 'error':
tool.reply("已返回")
return
if choice == '1':
tool.reply("请输入新的设备名称(q:退出)")
new_name = sender.listen(60000)
if new_name and new_name != 'q' and new_name != 'error':
device_config['name'] = new_name
save_device(device_id, device_config)
tool.reply(f"✅ 设备名称已更新为: {new_name}")
else:
tool.reply("已取消")
elif choice == '2':
tool.reply("请输入新的ADB地址(示例:192.168.1.100:5555q:退出)")
new_address = sender.listen(60000)
if new_address and new_address != 'q' and new_address != 'error':
device_config['address'] = new_address
save_device(device_id, device_config)
tool.reply(f"✅ ADB地址已更新为: {new_address}")
else:
tool.reply("已取消")
elif choice == '3':
tool.reply("请设置设备权限:\n1. 仅管理员可用\n2. 所有用户可用\n3. 指定用户可用\n0. 取消")
perm_choice = sender.listen(60000)
if not perm_choice or perm_choice == '0' or perm_choice == 'q' or perm_choice == 'error':
tool.reply("已取消")
return
if perm_choice == '1':
device_config['admin_only'] = True
device_config['allowed_users'] = []
save_device(device_id, device_config)
tool.reply("✅ 权限已设置为:仅管理员可用")
elif perm_choice == '2':
device_config['admin_only'] = False
device_config['allowed_users'] = []
save_device(device_id, device_config)
tool.reply("✅ 权限已设置为:所有用户可用")
elif perm_choice == '3':
tool.reply("请输入允许访问的用户ID(多个用户用逗号分隔,q:退出)")
users_input = sender.listen(60000)
if not users_input or users_input == 'q' or users_input == 'error':
tool.reply("已取消")
return
allowed_users = [u.strip() for u in users_input.split(',') if u.strip()]
device_config['admin_only'] = False
device_config['allowed_users'] = allowed_users
save_device(device_id, device_config)
tool.reply(f"✅ 权限已设置为:指定用户可用 ({', '.join(allowed_users)})")
else:
tool.reply("❌ 无效选择")
else:
tool.reply("❌ 无效选择")
except Exception as e:
tool.reply(f"❌ 操作出错: {e}")
def edit_ssh_device_config(tool: TOOL, device_id: str, device_config: dict):
"""编辑SSH设备配置"""
tool.reply("请选择要修改的配置项:\n1. 设备名称\n2. SSH主机\n3. SSH用户\n4. SSH密码\n5. SSH端口\n6. 重启命令\n7. 超时时间\n8. 权限设置\n0. 返回")
try:
choice = sender.listen(60000)
if not choice or choice == '0' or choice == 'q' or choice == 'error':
tool.reply("已返回")
return
if choice == '1':
tool.reply("请输入新的设备名称(q:退出)")
new_name = sender.listen(60000)
if new_name and new_name != 'q' and new_name != 'error':
device_config['name'] = new_name
save_device(device_id, device_config)
tool.reply(f"✅ 设备名称已更新为: {new_name}")
else:
tool.reply("已取消")
elif choice == '2':
tool.reply("请输入新的SSH主机地址(q:退出)")
new_hostname = sender.listen(60000)
if new_hostname and new_hostname != 'q' and new_hostname != 'error':
device_config['hostname'] = new_hostname
save_device(device_id, device_config)
tool.reply(f"✅ SSH主机已更新为: {new_hostname}")
else:
tool.reply("已取消")
elif choice == '3':
tool.reply("请输入新的SSH用户名(q:退出)")
new_username = sender.listen(60000)
if new_username and new_username != 'q' and new_username != 'error':
device_config['username'] = new_username
save_device(device_id, device_config)
tool.reply(f"✅ SSH用户已更新为: {new_username}")
else:
tool.reply("已取消")
elif choice == '4':
tool.reply("请输入新的SSH密码(q:退出)")
new_password = sender.listen(60000)
if new_password and new_password != 'q' and new_password != 'error':
device_config['password'] = new_password
save_device(device_id, device_config)
tool.reply("✅ SSH密码已更新")
else:
tool.reply("已取消")
elif choice == '5':
tool.reply("请输入新的SSH端口(q:退出)")
new_port = sender.listen(60000)
if new_port and new_port != 'q' and new_port != 'error':
if new_port.isdigit():
device_config['port'] = int(new_port)
save_device(device_id, device_config)
tool.reply(f"✅ SSH端口已更新为: {new_port}")
else:
tool.reply("❌ 端口必须是数字")
else:
tool.reply("已取消")
elif choice == '6':
tool.reply("请输入新的重启命令(q:退出)")
new_command = sender.listen(60000)
if new_command and new_command != 'q' and new_command != 'error':
device_config['command'] = new_command
save_device(device_id, device_config)
tool.reply(f"✅ 重启命令已更新为: {new_command}")
else:
tool.reply("已取消")
elif choice == '7':
tool.reply("请输入新的超时时间(秒,q:退出)")
new_timeout = sender.listen(60000)
if new_timeout and new_timeout != 'q' and new_timeout != 'error':
if new_timeout.isdigit():
device_config['timeout'] = int(new_timeout)
save_device(device_id, device_config)
tool.reply(f"✅ 超时时间已更新为: {new_timeout}")
else:
tool.reply("❌ 超时时间必须是数字")
else:
tool.reply("已取消")
elif choice == '8':
tool.reply("请设置设备权限:\n1. 仅管理员可用\n2. 所有用户可用\n3. 指定用户可用\n0. 取消")
perm_choice = sender.listen(60000)
if not perm_choice or perm_choice == '0' or perm_choice == 'q' or perm_choice == 'error':
tool.reply("已取消")
return
if perm_choice == '1':
device_config['admin_only'] = True
device_config['allowed_users'] = []
save_device(device_id, device_config)
tool.reply("✅ 权限已设置为:仅管理员可用")
elif perm_choice == '2':
device_config['admin_only'] = False
device_config['allowed_users'] = []
save_device(device_id, device_config)
tool.reply("✅ 权限已设置为:所有用户可用")
elif perm_choice == '3':
tool.reply("请输入允许访问的用户ID(多个用户用逗号分隔,q:退出)")
users_input = sender.listen(60000)
if not users_input or users_input == 'q' or users_input == 'error':
tool.reply("已取消")
return
allowed_users = [u.strip() for u in users_input.split(',') if u.strip()]
device_config['admin_only'] = False
device_config['allowed_users'] = allowed_users
save_device(device_id, device_config)
tool.reply(f"✅ 权限已设置为:指定用户可用 ({', '.join(allowed_users)})")
else:
tool.reply("❌ 无效选择")
else:
tool.reply("❌ 无效选择")
except Exception as e:
tool.reply(f"❌ 操作出错: {e}")
def delete_device_by_number(tool: TOOL, device_list: list, device_index: int):
"""按序号删除设备"""
selected_device = device_list[device_index]
device_id = selected_device['id']
device_name = selected_device['name']
# 确认删除
tool.reply(f"确认删除设备 '{device_name}' 吗?(y/n)")
try:
confirm = sender.listen(30000)
if confirm and confirm.lower() in ['y', 'yes', '']:
delete_device(device_id)
tool.reply(f"✅ 设备 '{device_name}' 已删除")
else:
tool.reply("已取消删除")
except Exception as e:
tool.reply(f"❌ 删除设备时出错: {e}")
def _safe_get_message():
try:
return sender.getMessage().strip('"')
except Exception:
return ""
if __name__ == "__main__":
tool = TOOL()
msg = _safe_get_message()
# 获取用户信息
try:
user_id = sender.getUserID()
is_admin = sender.isAdmin()
except Exception:
user_id = ""
is_admin = False
if "rb-now" in msg:
cmd_rb_now(tool, user_id, is_admin)
sys.exit(0)
if "rb管理" in msg:
cmd_rb_manage(tool, user_id, is_admin)
sys.exit(0)
sender.reply("指令不支持,请发送:rb-now 或 rb管理")