上传文件至「py」
This commit is contained in:
@@ -0,0 +1,645 @@
|
|||||||
|
# [author: authook]
|
||||||
|
# [title: jd_账密登陆]
|
||||||
|
# [create_at: 2023-09-19 15:29:17]
|
||||||
|
# [version: 2.3]
|
||||||
|
# [pin: true]
|
||||||
|
# [price: 0]
|
||||||
|
# [rule: ^账密登录$]
|
||||||
|
# [rule: ^账密登陆$]
|
||||||
|
# [rule: ^账密刷新$]
|
||||||
|
# [rule: ^账密强制刷新$]
|
||||||
|
# [rule: ^账密管理$]
|
||||||
|
# [priority: 6666666]优先级
|
||||||
|
# [admin: false]
|
||||||
|
# [service: authook]
|
||||||
|
# [icon: https://bbs.autman.cn/assets/files/2024-05-16/1715842374-826456-2584e965be6ed12773865dbb8e29a449.png]
|
||||||
|
# [description: jd_账密登陆插件 兼容兔子,pro等框架</br>指令:账密登录,账密登录,账密管理 </br> 配置:在插件配参中配置,</br>权限:需开启qls,jdNotify权限</br>注意⚠️账密刷新请勿手动发送,否则会将ck绑定至管理员账号上!!!(插件已自动内置定时刷新账密)]
|
||||||
|
|
||||||
|
# [param: {"required":true,"key":"jd.rabbit_host","placeholder":"http://192.168.1.1:81","name":"【rabbit】登陆地址:","desc":"登陆地址,例:http://192.168.3.22"}]
|
||||||
|
# [param: {"required":true,"key":"jd.pro_host","placeholder":"http://192.168.1.1:81","name":"【pro】登陆地址:","desc":"pro登陆地址"}]
|
||||||
|
# [param: {"required":true,"key":"jd.login_ql_name","placeholder":"ql容器名称","name":"【ql】容器名称","desc":"ql容器名称"}]
|
||||||
|
# [param: {"required":true,"key":"jd.login_type","placeholder":"登陆框架","name":"登陆框架","desc":"pro框架请输入pro rabbit兔子框架请输入rabbit"}]
|
||||||
|
|
||||||
|
# =======================
|
||||||
|
# 项目:jd_账密登陆.py
|
||||||
|
# 构建时间:2025-02-10 20:52:29
|
||||||
|
import datetime
|
||||||
|
from email import message
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import inspect
|
||||||
|
from base64 import b64encode
|
||||||
|
from urllib.parse import quote, urlparse
|
||||||
|
|
||||||
|
import colorlog
|
||||||
|
import middleware
|
||||||
|
import requests
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
from Crypto.Util.Padding import pad
|
||||||
|
|
||||||
|
|
||||||
|
class TOOL:
|
||||||
|
def __init__(self):
|
||||||
|
self.plugin_pre = "rabbit_zm"
|
||||||
|
self.remark = ""
|
||||||
|
self.platform_arr = self.platformArr()
|
||||||
|
# 将日期和时间格式化为字符串
|
||||||
|
self.cut_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
self.logger = logging.getLogger()
|
||||||
|
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 对象"""
|
||||||
|
logger = logging.getLogger(self.plugin_pre)
|
||||||
|
logger.setLevel(logging.DEBUG) # 设置日志默认最低级别
|
||||||
|
|
||||||
|
# 检查是否已添加过控制台处理器,避免重复添加
|
||||||
|
if not any(isinstance(handler, logging.StreamHandler) for handler in logger.handlers):
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.DEBUG) # 控制台日志最低级别
|
||||||
|
console_handler.setFormatter(self.color_formatter)
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|
||||||
|
def log(self, level, message):
|
||||||
|
"""通用日志方法"""
|
||||||
|
if level == logging.INFO:
|
||||||
|
self.logger.info(message)
|
||||||
|
elif level == logging.ERROR:
|
||||||
|
self.logger.error(message)
|
||||||
|
elif level == logging.WARNING:
|
||||||
|
self.logger.warning(message)
|
||||||
|
elif level == logging.DEBUG:
|
||||||
|
self.logger.debug(message)
|
||||||
|
elif level == logging.CRITICAL:
|
||||||
|
self.logger.critical(message)
|
||||||
|
|
||||||
|
def log_info(self, message):
|
||||||
|
self.log(logging.INFO, message)
|
||||||
|
|
||||||
|
def log_err(self, message):
|
||||||
|
self.log(logging.ERROR, message)
|
||||||
|
|
||||||
|
def log_warn(self, message):
|
||||||
|
self.log(logging.WARNING, message)
|
||||||
|
|
||||||
|
def platformArr(self):
|
||||||
|
plat_arr = []
|
||||||
|
for p in ["qq", "wx", "wb", "qb", "bt", "tg"]:
|
||||||
|
t = {"platform": f"{p}", "imType": p}
|
||||||
|
plat_arr.append(t)
|
||||||
|
return plat_arr
|
||||||
|
|
||||||
|
def pushMsg(self, userId, chatId, imType, title, content):
|
||||||
|
# 获取当前日期和时间
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
# 将日期和时间格式化为字符串
|
||||||
|
formatted_datetime = now.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
self.remark = ""
|
||||||
|
return middleware.push(imType, chatId, userId, title, f"{formatted_datetime}\n{content}")
|
||||||
|
|
||||||
|
def pushGroup(self, imtype, groupCode, content):
|
||||||
|
self.pushMsg(0, groupCode, imtype, "", content)
|
||||||
|
|
||||||
|
def autman_host(self):
|
||||||
|
file_path = inspect.getframeinfo(inspect.currentframe()).filename
|
||||||
|
dir_path = os.path.dirname(os.path.abspath(file_path))
|
||||||
|
setConfPath = dir_path.split("develop/scripts")[0] + "/middleware.py"
|
||||||
|
try:
|
||||||
|
os.setuid(0) # 强制转为root 前提是autman账户以root用户启用
|
||||||
|
with open(setConfPath, "r", encoding="utf-8") as f:
|
||||||
|
data = f.read()
|
||||||
|
lines = data.splitlines()
|
||||||
|
for line in lines:
|
||||||
|
if "url =" in line:
|
||||||
|
res = line.split('"')
|
||||||
|
url = res[1][0:-5]
|
||||||
|
self.log_info(url)
|
||||||
|
return url
|
||||||
|
except Exception as e:
|
||||||
|
self.log_info(f"❌autman_host error {e}")
|
||||||
|
|
||||||
|
def replyMsg(self, content):
|
||||||
|
return sender.reply(content)
|
||||||
|
|
||||||
|
def pushMaster(self, content):
|
||||||
|
arr = ["qq", "wx", "tg", "qb", 'wb']
|
||||||
|
for p in arr:
|
||||||
|
masters = sender.bucketGet(p, "masters")
|
||||||
|
if masters:
|
||||||
|
masters_arr = masters.split(",") if "," in masters else masters.split("&")
|
||||||
|
for m in masters_arr:
|
||||||
|
middleware.push(p, "", m, "", content)
|
||||||
|
|
||||||
|
def qls(self):
|
||||||
|
qls = []
|
||||||
|
ql_id_arr = sender.bucketAllKeys("qls")
|
||||||
|
if not ql_id_arr:
|
||||||
|
return []
|
||||||
|
for ql_id in ql_id_arr:
|
||||||
|
value = sender.bucketGet("qls", ql_id)
|
||||||
|
if value:
|
||||||
|
qls.append(json.loads(value))
|
||||||
|
return qls
|
||||||
|
|
||||||
|
def autman_host(self):
|
||||||
|
file_path = inspect.getframeinfo(inspect.currentframe()).filename
|
||||||
|
dir_path = os.path.dirname(os.path.abspath(file_path))
|
||||||
|
setConfPath = dir_path.split("develop/scripts")[0] + "/middleware.py"
|
||||||
|
try:
|
||||||
|
os.setuid(0) # 强制转为root 前提是autman账户以root用户启用
|
||||||
|
with open(setConfPath, "r", encoding="utf-8") as f:
|
||||||
|
data = f.read()
|
||||||
|
lines = data.splitlines()
|
||||||
|
for line in lines:
|
||||||
|
if "url =" in line:
|
||||||
|
res = line.split('"')
|
||||||
|
url = res[1][0:-5]
|
||||||
|
self.log_info(url)
|
||||||
|
return url
|
||||||
|
except Exception as e:
|
||||||
|
self.log_info(f"❌autman_host error {e}")
|
||||||
|
|
||||||
|
|
||||||
|
class QL:
|
||||||
|
def __init__(self, ql):
|
||||||
|
self.host = ql["host"]
|
||||||
|
self.client_id = ql["client_id"]
|
||||||
|
self.client_secret = ql["client_secret"]
|
||||||
|
self.token = self.get_token()
|
||||||
|
self.name = ql['name']
|
||||||
|
self.content = ""
|
||||||
|
self.version = self.system()
|
||||||
|
|
||||||
|
def gettimestamp(self):
|
||||||
|
return str(int(time.time() * 1000))
|
||||||
|
|
||||||
|
def get_token(self):
|
||||||
|
url = f"{self.host}/open/auth/token?client_id={self.client_id}&client_secret={self.client_secret}"
|
||||||
|
print(url)
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': '*/*',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Host': "",
|
||||||
|
'Connection': 'keep-alive'
|
||||||
|
}
|
||||||
|
host = urlparse(url).netloc
|
||||||
|
if ":" in host:
|
||||||
|
host = host.split(":")[0]
|
||||||
|
headers['Host'] = host
|
||||||
|
try:
|
||||||
|
response = requests.get(url, timeout=10, headers=headers)
|
||||||
|
res = response.json()
|
||||||
|
if response.status_code == 200 and res["code"] == 200:
|
||||||
|
return res["data"]["token"]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def Api(self, api, apd, method, payload):
|
||||||
|
url = f"{self.host}/open/{api}/{apd}"
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json;charset=UTF-8",
|
||||||
|
"Authorization": f"Bearer {self.token}",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = requests.request(method, url, headers=headers, data=payload, timeout=5)
|
||||||
|
return response.json()
|
||||||
|
except Exception as e:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 环境变量
|
||||||
|
def envGet(self, keyword):
|
||||||
|
return self.Api('envs', f"?searchValue={keyword}&t={self.gettimestamp()}", 'GET', {})
|
||||||
|
|
||||||
|
def envSet(self, env):
|
||||||
|
return self.Api('envs', f"?t={self.gettimestamp()}", 'POST', env)
|
||||||
|
|
||||||
|
def envUpdate(self, env):
|
||||||
|
return self.Api('envs', f"?t={self.gettimestamp()}", 'PUT', env)
|
||||||
|
|
||||||
|
def envEnable(self, ids):
|
||||||
|
return self.Api('envs', f"/enable?t={self.gettimestamp()}", 'PUT', json.dumps(ids))
|
||||||
|
|
||||||
|
def envDisable(self, ids):
|
||||||
|
return self.Api('envs', f"/disable?t={self.gettimestamp()}", 'PUT', json.dumps(ids))
|
||||||
|
|
||||||
|
def envDel(self, ids):
|
||||||
|
return self.Api('envs', f"?t={self.gettimestamp()}", 'delete', json.dumps(ids))
|
||||||
|
|
||||||
|
def envMove(self, id, data):
|
||||||
|
return self.Api('envs', f"/{id}/move?t={self.gettimestamp()}", 'PUT', json.dumps(data))
|
||||||
|
|
||||||
|
# 定时任务
|
||||||
|
def cronGet(self, keyword):
|
||||||
|
return self.Api('crons', f"?searchValue={keyword}&t={self.gettimestamp()}", 'GET', None)
|
||||||
|
|
||||||
|
def cronRun(self, ids):
|
||||||
|
return self.Api('crons', f"run?t={self.gettimestamp()}", 'PUT', ids)
|
||||||
|
|
||||||
|
def cronStop(self, ids):
|
||||||
|
return self.Api('crons', f"stop?t={self.gettimestamp()}", 'PUT', ids)
|
||||||
|
|
||||||
|
def cronEnable(self, ids):
|
||||||
|
return self.Api('crons', f"enable?t={self.gettimestamp()}", 'PUT', ids)
|
||||||
|
|
||||||
|
def cronDisable(self, ids):
|
||||||
|
return self.Api('crons', f"disable?t={self.gettimestamp()}", 'PUT', ids)
|
||||||
|
|
||||||
|
def cronDetail(self, cronId):
|
||||||
|
return self.Api('crons', f"{cronId}?t={self.gettimestamp()}", 'GET', None)
|
||||||
|
|
||||||
|
def cronLogs(self, cronId):
|
||||||
|
return self.Api('crons', f"{cronId}/log?t={self.gettimestamp()}", 'GET', None)
|
||||||
|
|
||||||
|
def cronLog(self, cronId):
|
||||||
|
return self.Api('crons', f"{cronId}/log?t={self.gettimestamp()}", 'GET', None)
|
||||||
|
|
||||||
|
# 配置文件
|
||||||
|
def configsGet(self, name):
|
||||||
|
if self.version < "2.17":
|
||||||
|
return self.Api('configs', f"{name}?t={self.gettimestamp()}", 'GET', None)
|
||||||
|
else:
|
||||||
|
return self.Api('configs/detail', f"?path={name}&t={self.gettimestamp()}", 'GET', None)
|
||||||
|
|
||||||
|
def configsSet(self, data):
|
||||||
|
return self.Api('configs', f"save?t={self.gettimestamp()}", 'POST', data)
|
||||||
|
|
||||||
|
# 订阅任务
|
||||||
|
def subsGet(self, keyword):
|
||||||
|
return self.Api('subscriptions', f"?searchValue={keyword}&t={self.gettimestamp()}", 'GET', None)
|
||||||
|
|
||||||
|
def subsSet(self, data):
|
||||||
|
return self.Api('subscriptions', f"save?t={self.gettimestamp()}", 'PUT', data)
|
||||||
|
|
||||||
|
def subsRun(self, ids):
|
||||||
|
return self.Api('subscriptions', f"run?t={self.gettimestamp()}", 'PUT', ids)
|
||||||
|
|
||||||
|
def subsEnable(self, ids):
|
||||||
|
return self.Api('subscriptions', f"enable?t={self.gettimestamp()}", 'PUT', ids)
|
||||||
|
|
||||||
|
def subsDisable(self, ids):
|
||||||
|
return self.Api('subscriptions', f"disable?t={self.gettimestamp()}", 'PUT', ids)
|
||||||
|
|
||||||
|
def subsLog(self, subId):
|
||||||
|
return self.Api('subscriptions', f"{subId}/log?t={self.gettimestamp()}", 'GET', None)
|
||||||
|
|
||||||
|
def system(self):
|
||||||
|
url = f"{self.host}/open/system?t={self.gettimestamp()}"
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json;charset=UTF-8",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
res = requests.get(url)
|
||||||
|
if res.status_code == 200:
|
||||||
|
rj = res.json()
|
||||||
|
return rj['data']['version']
|
||||||
|
except Exception as e:
|
||||||
|
return 2.16
|
||||||
|
|
||||||
|
|
||||||
|
class ZM_RABBIT():
|
||||||
|
def __init__(self):
|
||||||
|
self.pins = self.get_pins()
|
||||||
|
self.conf = self.get_conf()
|
||||||
|
self.init()
|
||||||
|
self.s = requests.session()
|
||||||
|
pass
|
||||||
|
|
||||||
|
def hide_phone(self, text):
|
||||||
|
if not text:
|
||||||
|
return text
|
||||||
|
if len(text) != 11:
|
||||||
|
return text
|
||||||
|
return re.sub(r'(\d{3})\d{4}(\d{4})', r'\1****\2', text)
|
||||||
|
|
||||||
|
def check_ck_status(self, ck):
|
||||||
|
headers = {
|
||||||
|
"Cookie": ck,
|
||||||
|
"referer": "https://h5.m.jd.com/",
|
||||||
|
"User-Agent": "jdapp;iPhone;10.1.2;15.0;network/wifi;Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148;supportJDSHWK/1",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get('https://plogin.m.jd.com/cgi-bin/ml/islogin', headers=headers)
|
||||||
|
data = response.json()
|
||||||
|
return data['islogin'] == "1" if 'islogin' in data else False
|
||||||
|
|
||||||
|
def en_pwd(self, pwd):
|
||||||
|
key = b"wn:_/imCQF;rBNS{V=|kML[(+$%Tjgy)"
|
||||||
|
iv = b"zA#f.x>jN!h-0[{$"
|
||||||
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
||||||
|
encrypted = cipher.encrypt(pad(pwd.encode(), AES.block_size))
|
||||||
|
return b64encode(encrypted).decode()
|
||||||
|
|
||||||
|
def get_conf(self):
|
||||||
|
self.conf = {
|
||||||
|
"paid": "n",
|
||||||
|
"fee": 0,
|
||||||
|
"qr_code": "",
|
||||||
|
"host": "",
|
||||||
|
"ql_name": None
|
||||||
|
}
|
||||||
|
if middleware.bucketGet("otto", "rabbit_pro_paid"):
|
||||||
|
self.conf["paid"] = middleware.bucketGet("otto", "rabbit_pro_paid")
|
||||||
|
if middleware.bucketGet("otto", "rabbit_pro_fee"):
|
||||||
|
self.conf['fee'] = middleware.bucketGet("otto", "rabbit_pro_fee")
|
||||||
|
if middleware.bucketGet("otto", "rabbit_pro_qr_code"):
|
||||||
|
self.conf['qr_code'] = middleware.bucketGet("otto", "rabbit_pro_qr_code")
|
||||||
|
if not middleware.bucketGet("otto", "rabbit_pro_host"):
|
||||||
|
tool.pushMaster("未配置rabbit登录地址,请先去【配参】配置")
|
||||||
|
exit()
|
||||||
|
self.conf['host'] = middleware.bucketGet("otto", "rabbit_pro_host")
|
||||||
|
if not middleware.bucketGet("otto", "rabbit_ql_name"):
|
||||||
|
tool.pushMaster("未配置rabbit登陆容器名称,请先去【配参】配置")
|
||||||
|
exit()
|
||||||
|
self.conf['ql_name'] = middleware.bucketGet("otto", "rabbit_ql_name")
|
||||||
|
return self.conf
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
res = requests.get(self.conf['host'] + "/api/Config")
|
||||||
|
tool.log_info(res.text)
|
||||||
|
if res.status_code == 200:
|
||||||
|
rj = res.json()
|
||||||
|
if rj['success']:
|
||||||
|
self.conf['container_id'] = rj['data']['list'][0]['container_id']
|
||||||
|
self.conf['container_name'] = rj['data']['list'][0]['container_name']
|
||||||
|
self.conf['autocount'] = rj['data']['autocount']
|
||||||
|
return self.conf
|
||||||
|
tool.replyMsg(rj['message'] + ",请完善rabbitPro配置")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
def get_pins(self):
|
||||||
|
return sender.bucketKeys("pin" + imType.upper(), userId)
|
||||||
|
|
||||||
|
def account_list(self):
|
||||||
|
acc_list = []
|
||||||
|
acc_key = set()
|
||||||
|
if self.pins and len(self.pins) > 0:
|
||||||
|
for pin in self.pins:
|
||||||
|
acc_str = sender.bucketGet("", pin)
|
||||||
|
if acc_str:
|
||||||
|
acc = {
|
||||||
|
"username": acc_str.split("#")[0],
|
||||||
|
"pwd": acc_str.split("#")[1],
|
||||||
|
"ck": acc_str.split("#")[2],
|
||||||
|
}
|
||||||
|
if acc_str.split("#")[0] not in acc_key:
|
||||||
|
acc_list.append(acc)
|
||||||
|
acc_key.add(acc_str.split("#")[0])
|
||||||
|
|
||||||
|
return acc_list
|
||||||
|
|
||||||
|
def login_init(self, mobile):
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User_Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1"
|
||||||
|
}
|
||||||
|
data = {"account": mobile, "container_id": self.conf['container_id']}
|
||||||
|
url = self.conf['host'] + "/pwd/init"
|
||||||
|
res = self.s.post(url, headers=headers, data=json.dumps(data))
|
||||||
|
tool.log_info(f"{mobile}: login_init res {res.text}")
|
||||||
|
if res.status_code == 200:
|
||||||
|
rj = res.json()
|
||||||
|
if not rj['success']:
|
||||||
|
tool.replyMsg(f"[{self.hide_phone(mobile)}]登陆初始化失败:{rj['message']}")
|
||||||
|
return
|
||||||
|
if rj['data']['status'] == 505:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
tool.replyMsg(f"f{rj['message']}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def login(self, mobile, pwd):
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User_Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1"
|
||||||
|
}
|
||||||
|
data = {"account": mobile, "pwd": self.en_pwd(pwd), "container_id": self.conf['container_id']}
|
||||||
|
res = self.s.post(f"{self.conf['host']}/pwd/login", headers=headers, data=json.dumps(data))
|
||||||
|
tool.log_info(f"{mobile}: login res {res.text}")
|
||||||
|
if res.status_code == 200:
|
||||||
|
rj = res.json()
|
||||||
|
if rj['code'] == 601:
|
||||||
|
tool.replyMsg(f"[{self.hide_phone(mobile)}]账号需要点击验证之后,重新登陆\n{rj['RiskUrl']}")
|
||||||
|
return False, None
|
||||||
|
if rj['code'] == 200:
|
||||||
|
tool.replyMsg(f"【登陆账户】:{self.hide_phone(mobile)}\n【JD账户】:{rj['pin']}\n【登陆结果】:{rj['message']}")
|
||||||
|
value_json = {
|
||||||
|
"mobile": mobile,
|
||||||
|
"pwd": pwd
|
||||||
|
}
|
||||||
|
sender.bucketSet("rabbit_zm_acc", rj['pin'], json.dumps(value_json))
|
||||||
|
return True, rj['pin']
|
||||||
|
elif rj['code'] == 503:
|
||||||
|
tool.replyMsg(f"[{self.hide_phone(mobile)}]{rj['message']}")
|
||||||
|
return False, None
|
||||||
|
elif rj['code'] == 555:
|
||||||
|
# 需要验证
|
||||||
|
riskUrl = rj['RiskUrl']
|
||||||
|
tool.replyMsg(f"[{self.hide_phone(mobile)}]请手动点击验证链接,然后再次返回登陆:{riskUrl}")
|
||||||
|
return False, None
|
||||||
|
else:
|
||||||
|
tool.replyMsg(f"[{self.hide_phone(mobile)}]登陆异常:{rj['message']}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
def after_login(self, pin):
|
||||||
|
time.sleep(random.randint(3,5))
|
||||||
|
sender.bucketSet('pin' + imType.upper(), pin, userId)
|
||||||
|
qls = tool.qls()
|
||||||
|
if not qls or len(qls) == 0:
|
||||||
|
tool.pushMaster("未配置青龙容器,无法同步更新容器ck")
|
||||||
|
exit()
|
||||||
|
default_ql = list(filter(lambda item: self.conf['ql_name'] in item['name'],qls))
|
||||||
|
if not default_ql:
|
||||||
|
tool.pushMaster(f"容器【{self.conf['ql_name']}】,不存在,请重新配置")
|
||||||
|
return
|
||||||
|
tql = QL(default_ql[0])
|
||||||
|
if not tql.token:
|
||||||
|
return
|
||||||
|
res = tql.envGet("JD_COOKIE")
|
||||||
|
if res['code'] != 200 or len(res['data']) == 0:
|
||||||
|
return
|
||||||
|
if len(res['data']) <= 0:
|
||||||
|
return
|
||||||
|
cookie = list(filter(lambda item: pin in item['value'], res['data']))
|
||||||
|
if not cookie:
|
||||||
|
return
|
||||||
|
if cookie[0]['status'] == 1:
|
||||||
|
return
|
||||||
|
if not self.check_ck_status(cookie[0]['value']):
|
||||||
|
return
|
||||||
|
ck = cookie[0]['value']
|
||||||
|
pt_key = re.findall(r"pt_key=(.*?);", ck)[0]
|
||||||
|
val = {
|
||||||
|
"ID": pin,
|
||||||
|
"Pet": False,
|
||||||
|
"Fruit": False,
|
||||||
|
"DreamFactory": False,
|
||||||
|
"Note": "",
|
||||||
|
"PtKey": pt_key,
|
||||||
|
"AssetCron": "",
|
||||||
|
"PushPlus": "",
|
||||||
|
"LoginedAt": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
"ClientID": tql.client_id
|
||||||
|
}
|
||||||
|
sender.bucketSet('jdNotify', pin, json.dumps(val))
|
||||||
|
|
||||||
|
for ql in qls:
|
||||||
|
if self.conf['ql_name'] in ql['name']:
|
||||||
|
continue
|
||||||
|
tql = QL(ql)
|
||||||
|
if not tql.token:
|
||||||
|
continue
|
||||||
|
res = tql.envGet("JD_COOKIE")
|
||||||
|
if res['code'] != 200 or len(res['data']) == 0:
|
||||||
|
continue
|
||||||
|
if len(res['data']) <= 0:
|
||||||
|
continue
|
||||||
|
cookie = list(filter(lambda item: pin in item['value'], res['data']))
|
||||||
|
if not cookie:
|
||||||
|
continue
|
||||||
|
ck = cookie[0]['value']
|
||||||
|
id = cookie[0].get('id', cookie[0].get("_id"))
|
||||||
|
remarks = cookie[0].get('remarks', pin)
|
||||||
|
if ck:
|
||||||
|
updateEnv = {
|
||||||
|
"name": "JD_COOKIE",
|
||||||
|
"value": ck,
|
||||||
|
"remarks": remarks,
|
||||||
|
"id": id,
|
||||||
|
}
|
||||||
|
res = tql.envUpdate(json.dumps(updateEnv))
|
||||||
|
tool.log_info(f"update res {res}")
|
||||||
|
res = tql.envEnable([id])
|
||||||
|
tool.log_info(f"envEnable res {res}")
|
||||||
|
|
||||||
|
def refresh_account(self):
|
||||||
|
need = 0
|
||||||
|
success = 0
|
||||||
|
failed = 0
|
||||||
|
failed_user = []
|
||||||
|
tool.log_info(f"[rabbit]账密刷新开始......")
|
||||||
|
acc_map = sender.bucketAll("rabbit_zm_acc")
|
||||||
|
if not acc_map:
|
||||||
|
sender.reply("暂无账密数据,已退出")
|
||||||
|
# 查询pin对应的ck是否有效
|
||||||
|
for pin, accStr in acc_map.items():
|
||||||
|
jdStr = sender.bucketGet("jdNotify", pin)
|
||||||
|
if not jdStr:
|
||||||
|
continue
|
||||||
|
jd_json = json.loads(jdStr)
|
||||||
|
ck = f"pt_pin={pin}; pt_key={jd_json['PtKey']}"
|
||||||
|
if self.check_ck_status(ck):
|
||||||
|
tool.log_info(f"账号【{pin}】有效✅,跳过刷新")
|
||||||
|
continue
|
||||||
|
need += 1
|
||||||
|
acc_json = json.loads(accStr)
|
||||||
|
refresh_res = self.run_login(acc_json['mobile'], acc_json['pwd'])
|
||||||
|
if not refresh_res:
|
||||||
|
failed += 1
|
||||||
|
failed_user.append(f"{quote(pin)}[{self.hide_phone(acc_json['mobile'])}]")
|
||||||
|
else:
|
||||||
|
success += 1
|
||||||
|
content = f"[rabbit]账密刷新结束"
|
||||||
|
if need > 0:
|
||||||
|
content += f"\n本次需要刷新:{need}个"
|
||||||
|
content += f"\n本次刷新成功:{success}个🎉"
|
||||||
|
if failed_user:
|
||||||
|
content += f"\n本次刷新失败:{failed}个😭"
|
||||||
|
s = "\n".join(failed_user)
|
||||||
|
content += f"\n刷新失败用户:{s}"
|
||||||
|
else:
|
||||||
|
content += f"\n本次所有账户有效,无需刷新🎉"
|
||||||
|
tool.pushMaster(content)
|
||||||
|
|
||||||
|
def account_manage(self):
|
||||||
|
acc_map = sender.bucketAll("rabbit_zm_acc")
|
||||||
|
if not acc_map:
|
||||||
|
tool.replyMsg("暂无账密记录,请先登录")
|
||||||
|
exit()
|
||||||
|
content = f"账密列表如下:"
|
||||||
|
no = 0
|
||||||
|
for pin,accStr in acc_map.items():
|
||||||
|
acc_json = json.loads(accStr)
|
||||||
|
no +=1
|
||||||
|
content += f"\n{no}、{quote(pin)}[{self.hide_phone(acc_json['mobile'])}]"
|
||||||
|
tool.replyMsg(content)
|
||||||
|
|
||||||
|
|
||||||
|
def run_login(self, mobile, pwd):
|
||||||
|
if not self.login_init(mobile):
|
||||||
|
return False
|
||||||
|
login_res, pin = self.login(mobile, pwd)
|
||||||
|
if not login_res:
|
||||||
|
return False
|
||||||
|
self.after_login(pin)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sender = middleware.Sender(middleware.getSenderID())
|
||||||
|
plugin_ver = sender.getPluginVersion()
|
||||||
|
plugin_name = sender.getPluginName()
|
||||||
|
msg = sender.getMessage().strip('"')
|
||||||
|
imType = "fake" if sender.getImtype() == "cron" else sender.getImtype()
|
||||||
|
chatId = sender.getChatID()
|
||||||
|
userId = sender.getUserID()
|
||||||
|
isAdmin = sender.isAdmin()
|
||||||
|
username = sender.getUserName()
|
||||||
|
tool = TOOL()
|
||||||
|
#### 定时任务 #####
|
||||||
|
cron = middleware.Cron(None)
|
||||||
|
crons = list(filter(lambda item: "兔子账密刷新" in item['cmd'],cron.getCrons()))
|
||||||
|
if not crons:
|
||||||
|
tool.log_info(f"【兔子账密刷新】定时任务不存在,重新添加")
|
||||||
|
id = cron.addCron("0 0,3,5 * * *","兔子账密刷新",True,False,"兔子账密刷新","","","")
|
||||||
|
tool.log_info(f"add cron id = {id}")
|
||||||
|
#### 定时任务 ####
|
||||||
|
tz = ZM_RABBIT()
|
||||||
|
if "登录" in msg or "登陆" in msg:
|
||||||
|
sms_content = "请在60秒内输入手机号(q:退出)"
|
||||||
|
tool.replyMsg(sms_content)
|
||||||
|
mobile = sender.listen(60000)
|
||||||
|
if not mobile or mobile == "q" or mobile == "error":
|
||||||
|
tool.replyMsg("已退出!")
|
||||||
|
exit()
|
||||||
|
pattern = r'^1[3-9]\d{9}$'
|
||||||
|
if not re.match(pattern, mobile):
|
||||||
|
tool.replyMsg("手机号格式错误,已退出!")
|
||||||
|
exit()
|
||||||
|
tool.replyMsg("请在60秒内输入登录密码(q:退出)")
|
||||||
|
pwd = sender.listen(60000)
|
||||||
|
if not pwd or pwd == "q" or pwd == "error":
|
||||||
|
tool.replyMsg("已退出!")
|
||||||
|
exit()
|
||||||
|
if len(pwd) < 8:
|
||||||
|
tool.replyMsg("密码格式错误,已退出!")
|
||||||
|
exit()
|
||||||
|
tz.run_login(mobile, pwd)
|
||||||
|
exit()
|
||||||
|
if "刷新" in msg:
|
||||||
|
tz.refresh_account()
|
||||||
|
exit()
|
||||||
|
if "管理" in msg:
|
||||||
|
tz.account_manage()
|
||||||
@@ -0,0 +1,296 @@
|
|||||||
|
#[title: mi10rb-aut]
|
||||||
|
#[language: python]
|
||||||
|
#[class: 工具类]
|
||||||
|
#[service: 10010] ADB设备重启管理
|
||||||
|
#[disable: false]
|
||||||
|
#[admin: false]
|
||||||
|
#[rule: ^重启Mi10$]
|
||||||
|
#[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: 通过ADB(TCP)连接到设备并执行重启;发送"重启Mi10"执行。<br>]
|
||||||
|
|
||||||
|
# [param: {"required":true,"key":"mi10rb.address","placeholder":"127.0.0.1:5555","name":"ADB地址:","desc":"设备的 ADB 地址,如 192.168.1.100:5555"}]
|
||||||
|
# [param: {"required":false,"key":"mi10rb.adb_path","placeholder":"adb","name":"adb路径:","desc":"adb 可执行文件路径,默认使用系统 adb"}]
|
||||||
|
# [param: {"required":false,"key":"mi10rb.timeout","placeholder":"20","name":"超时:","desc":"单次命令超时时间(秒),默认20"}]
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shlex
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import colorlog
|
||||||
|
import middleware
|
||||||
|
import sender
|
||||||
|
from typing import Tuple, Optional
|
||||||
|
|
||||||
|
|
||||||
|
# 读取插件参数
|
||||||
|
CONF_ADDRESS = middleware.bucketGet('mi10rb', 'address')
|
||||||
|
CONF_ADB_PATH = middleware.bucketGet('mi10rb', 'adb_path') or 'adb'
|
||||||
|
CONF_TIMEOUT = int(middleware.bucketGet('mi10rb', 'timeout') or '20')
|
||||||
|
|
||||||
|
|
||||||
|
sender = middleware.Sender(middleware.getSenderID())
|
||||||
|
|
||||||
|
|
||||||
|
class TOOL:
|
||||||
|
def __init__(self):
|
||||||
|
self.plugin_pre = "mi10rb"
|
||||||
|
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 run_command(command: str, timeout_seconds: int = 20) -> Tuple[int, str, str]:
|
||||||
|
"""
|
||||||
|
以非交互方式执行命令,返回 (exit_code, stdout, stderr)
|
||||||
|
"""
|
||||||
|
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:
|
||||||
|
# 尝试重启 adb 服务端,忽略错误
|
||||||
|
run_command(f"{self.adb} kill-server", timeout_seconds=min(self.timeout, 5))
|
||||||
|
|
||||||
|
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}")
|
||||||
|
# 重启 adb 后重试一次
|
||||||
|
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'
|
||||||
|
# 某些 ROM 会返回 unknown/unauthorized,这里给出更友好的提示
|
||||||
|
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()
|
||||||
|
# 设备重启会导致连接被关闭,adb 可能返回如下错误,但指令已下发
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
text_s = ((out_s or '') + ('\n' + err_s if err_s else '')).strip().lower()
|
||||||
|
if code_s != 0:
|
||||||
|
# 命令失败,多半是设备掉线,视为重启进行中
|
||||||
|
return True
|
||||||
|
if (out_s or '').strip() != 'device':
|
||||||
|
# 非 device 状态(例如 offline/unknown),也视为重启进行中
|
||||||
|
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
|
||||||
|
|
||||||
|
# 回退方案:走 shell reboot
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def run_reboot_flow(tool: TOOL):
|
||||||
|
"""执行 ADB 设备重启流程"""
|
||||||
|
tool.reply("开始执行 Mi10 ADB 重启流程...")
|
||||||
|
|
||||||
|
# 校验参数
|
||||||
|
if not CONF_ADDRESS:
|
||||||
|
tool.reply("❌ 配置参数不完整,请设置 mi10rb.address (如 192.168.1.100:5555)")
|
||||||
|
return
|
||||||
|
|
||||||
|
tool.log(logging.INFO, "配置信息:")
|
||||||
|
tool.log(logging.INFO, f" ADB地址: {CONF_ADDRESS}")
|
||||||
|
tool.log(logging.INFO, f" adb路径: {CONF_ADB_PATH}")
|
||||||
|
tool.log(logging.INFO, f" 超时: {CONF_TIMEOUT}秒")
|
||||||
|
|
||||||
|
client = ADBRebootClient(address=CONF_ADDRESS, adb_path=CONF_ADB_PATH, timeout=CONF_TIMEOUT, tool=tool)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not client.ensure_adb():
|
||||||
|
tool.reply("❌ 未检测到 adb,请安装或指定有效的 adb 路径")
|
||||||
|
return
|
||||||
|
|
||||||
|
tool.log(logging.INFO, "🚀 正在连接设备...")
|
||||||
|
if not client.connect():
|
||||||
|
tool.reply("❌ ADB 连接失败,请检查地址与网络")
|
||||||
|
return
|
||||||
|
|
||||||
|
tool.log(logging.INFO, "🔁 正在发送重启指令...")
|
||||||
|
if client.reboot():
|
||||||
|
tool.reply("✅ 重启指令已发送!设备即将重启")
|
||||||
|
else:
|
||||||
|
tool.reply("❌ 重启指令发送失败")
|
||||||
|
except Exception as exc:
|
||||||
|
tool.log(logging.ERROR, f"程序执行出错: {exc}")
|
||||||
|
tool.reply("❌ 程序执行出错,请检查配置与网络")
|
||||||
|
|
||||||
|
|
||||||
|
def exit_with_msg(msg: str):
|
||||||
|
if msg:
|
||||||
|
sender.reply(msg)
|
||||||
|
sender.reply("已退出!")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_get_message():
|
||||||
|
try:
|
||||||
|
return sender.getMessage().strip('"')
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
tool = TOOL()
|
||||||
|
msg = _safe_get_message()
|
||||||
|
|
||||||
|
if "重启Mi10" in msg:
|
||||||
|
run_reboot_flow(tool)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
sender.reply("指令不支持,请发送:重启Mi10")
|
||||||
|
|
||||||
|
|
||||||
+459
@@ -0,0 +1,459 @@
|
|||||||
|
#[title: nazha]
|
||||||
|
#[language: python]
|
||||||
|
#[service: 10086] 售后联系方式
|
||||||
|
#[disable: false] 禁用开关,true表示禁用,false表示可用
|
||||||
|
#[admin: true] 是否为管理员指令
|
||||||
|
#[rule: ^哪吒查询$]
|
||||||
|
#[rule: ^哪吒管理$]
|
||||||
|
#[cron: 0 7 * * *]
|
||||||
|
#[priority: 300] 优先级,数字越大表示优先级越高
|
||||||
|
#[platform: qq,wx,tg,tb] 适用的平台
|
||||||
|
#[open_source: true]是否开源
|
||||||
|
#[icon: 图标url]图标链接地址,请使用48像素的正方形图标,支持http和https
|
||||||
|
#[version: 1.0.0]版本号
|
||||||
|
#[public: false] 是否发布?值为true或false,不设置则上传aut云时会自动设置为true,false时上传后不显示在市场中,但是搜索能搜索到,方便开发者测试
|
||||||
|
#[price: 0.01] 上架价格
|
||||||
|
# [description: 【简介】:哪吒监控查询 支持多账号管理,可查询服务器状态、服务状态等]
|
||||||
|
# [icon: https://cdn.jsdelivr.net/gh/naiba/nezha@master/resource/static/img/nezha.png]
|
||||||
|
# =======================
|
||||||
|
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import math
|
||||||
|
from math import fabs
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from multiprocessing import Pool
|
||||||
|
from multiprocessing.pool import ThreadPool
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
from wsgiref import headers
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import aiohttp
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import middleware
|
||||||
|
|
||||||
|
plugin_pre = 'vhook_nezha_'
|
||||||
|
|
||||||
|
class TOOL:
|
||||||
|
def __init__(self):
|
||||||
|
self.plat = ["qq", "wx", "wb", "qb", "bt", "tg"]
|
||||||
|
self.platform_arr = self.platformArr()
|
||||||
|
self.sender = middleware.Sender(middleware.getSenderID())
|
||||||
|
self.plugin_title = self.sender.getPluginName()
|
||||||
|
self.plugin_ver = self.sender.getPluginVersion()
|
||||||
|
self.plugin_name = self.sender.getPluginName()
|
||||||
|
self.msg = self.sender.getMessage().strip('"')
|
||||||
|
self.imType = "fake" if self.sender.getImtype() == "cron" else self.sender.getImtype()
|
||||||
|
self.chatId = self.sender.getChatID()
|
||||||
|
self.userId = self.sender.getUserID()
|
||||||
|
self.isAdmin = self.sender.isAdmin()
|
||||||
|
self.remark = ""
|
||||||
|
self.timeStr = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
# 安装必须依赖
|
||||||
|
self.dependencies = ['colorlog', "requests", "aiohttp"]
|
||||||
|
self.install_dependencies()
|
||||||
|
self.logger = logging.getLogger()
|
||||||
|
self.color_formatter = sys.modules['colorlog'].ColoredFormatter(
|
||||||
|
'%(log_color)s%(levelname)s: %(message)s',
|
||||||
|
log_colors={
|
||||||
|
'DEBUG': 'cyan',
|
||||||
|
'INFO': 'green',
|
||||||
|
'WARNING': 'yellow',
|
||||||
|
'ERROR': 'red',
|
||||||
|
'CRITICAL': 'red,bg_white',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def install_dependencies(self):
|
||||||
|
for dependency in self.dependencies:
|
||||||
|
try:
|
||||||
|
importlib.import_module(dependency)
|
||||||
|
except ImportError as e:
|
||||||
|
self.pushErr2Master(f"【错误】:缺少【{dependency}】模块, 正在自动安装,请稍后....")
|
||||||
|
if self.check_pip_version("pip3"):
|
||||||
|
cmd = "pip3"
|
||||||
|
else:
|
||||||
|
cmd = "pip"
|
||||||
|
result = subprocess.run([cmd, 'install', dependency], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
self.pushErr2Master(f"依赖【{dependency}】安装完成,请重新发送指令,运行插件")
|
||||||
|
|
||||||
|
def check_pip_version(self, pip_command):
|
||||||
|
try:
|
||||||
|
result = subprocess.run([pip_command, '--version'], capture_output=True, text=True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return True
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_log(self, level=logging.INFO):
|
||||||
|
self.logger.setLevel(level)
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(level)
|
||||||
|
console_handler.setFormatter(self.color_formatter)
|
||||||
|
for handler in self.logger.handlers:
|
||||||
|
self.logger.removeHandler(handler)
|
||||||
|
self.logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
def log_info(self, msg):
|
||||||
|
self.get_log(logging.INFO)
|
||||||
|
return self.logger.info(f"{msg}")
|
||||||
|
|
||||||
|
def log_err(self, msg):
|
||||||
|
self.get_log(logging.ERROR)
|
||||||
|
return self.logger.error(f"{msg}")
|
||||||
|
|
||||||
|
def log_warn(self, msg):
|
||||||
|
self.get_log(logging.WARN)
|
||||||
|
return self.logger.warning(f"{msg}")
|
||||||
|
|
||||||
|
def platformArr(self):
|
||||||
|
plat_arr = []
|
||||||
|
for item in self.plat:
|
||||||
|
t = {"platform": f"{plugin_pre}{item}", "imType": item}
|
||||||
|
plat_arr.append(t)
|
||||||
|
return plat_arr
|
||||||
|
|
||||||
|
def pushMsg(self, userId, chatId, imType, title, content):
|
||||||
|
now = datetime.now()
|
||||||
|
formatted_datetime = now.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
self.remark = ""
|
||||||
|
msg = f"*********{tool.plugin_title}*********"
|
||||||
|
msg += f'\n【任务时间】:{formatted_datetime}'
|
||||||
|
msg += f"\n{content}"
|
||||||
|
middleware.push(imType, chatId, userId, title, msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
def pushGroup(self, imtype, groupCode, content):
|
||||||
|
self.pushMsg(0, groupCode, imtype, "", content)
|
||||||
|
|
||||||
|
def pushErr2Master(self, content):
|
||||||
|
middleware.notifyMasters(
|
||||||
|
f"【插件】:{self.plugin_title}\n {content}",
|
||||||
|
['qq', 'tg', 'wx', 'wb', ])
|
||||||
|
|
||||||
|
def replyMsg(self, content):
|
||||||
|
return self.sender.reply(content)
|
||||||
|
|
||||||
|
class ACCOUNT:
|
||||||
|
def __init__(self):
|
||||||
|
self.method = "account"
|
||||||
|
self.plugin_title = "哪吒监控"
|
||||||
|
self.attr_arr = [
|
||||||
|
{
|
||||||
|
"title": "备注",
|
||||||
|
"key": "name",
|
||||||
|
"timeOut": 60000
|
||||||
|
}, {
|
||||||
|
"title": "面板地址",
|
||||||
|
"key": "dashboard_url",
|
||||||
|
"timeOut": 60000
|
||||||
|
}, {
|
||||||
|
"title": "用户名",
|
||||||
|
"key": "username",
|
||||||
|
"timeOut": 60000
|
||||||
|
}, {
|
||||||
|
"title": "密码",
|
||||||
|
"key": "password",
|
||||||
|
"timeOut": 60000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def setVal(self, item, title, key, timeOut):
|
||||||
|
tool.log_info(item)
|
||||||
|
tool.replyMsg(f"请输入{title}:")
|
||||||
|
value = tool.sender.listen(timeOut)
|
||||||
|
tool.log_info(f"{key}==={value}")
|
||||||
|
|
||||||
|
if value == "q" or value == "error":
|
||||||
|
tool.replyMsg("输入有误/超时,已退出!")
|
||||||
|
return False
|
||||||
|
item[key] = value
|
||||||
|
return True
|
||||||
|
|
||||||
|
def addCount(self):
|
||||||
|
tool.replyMsg("哪吒监控配置说明:\n1. 备注:用于区分不同账号\n2. 面板地址:哪吒面板的完整URL\n3. 用户名:面板登录用户名\n4. 密码:面板登录密码")
|
||||||
|
item = {}
|
||||||
|
go = True
|
||||||
|
for attr in self.attr_arr:
|
||||||
|
if not self.setVal(item, attr['title'], attr['key'], attr['timeOut']):
|
||||||
|
go = False
|
||||||
|
break
|
||||||
|
if not go:
|
||||||
|
tool.log_info("数据配置有误,已退出!")
|
||||||
|
return tool.replyMsg("数据配置有误,已退出!")
|
||||||
|
item_arr = []
|
||||||
|
item_str = middleware.bucketGet(f"{plugin_pre}{tool.imType}", tool.userId)
|
||||||
|
if item_str:
|
||||||
|
item_arr = json.loads(item_str)
|
||||||
|
item_arr.append(item)
|
||||||
|
middleware.bucketSet(f"{plugin_pre}{tool.imType}", tool.userId, json.dumps(item_arr))
|
||||||
|
tool.replyMsg("数据已配置完成,开始自动任务")
|
||||||
|
item['push_user_id'] = tool.userId
|
||||||
|
item['push_im_type'] = tool.imType
|
||||||
|
item['index'] = 1
|
||||||
|
item['total'] = 1
|
||||||
|
READ(item).run()
|
||||||
|
|
||||||
|
def editCount(self, item_arr, no):
|
||||||
|
item = item_arr[no]
|
||||||
|
content = f"请在【2分钟】内输入 序号,编辑对应的属性(q:退出)"
|
||||||
|
content += f"\n--------------------"
|
||||||
|
content += f"\n输入数字:0 删除此账号!"
|
||||||
|
content += f"\n--------------------"
|
||||||
|
for index, attr in enumerate(self.attr_arr):
|
||||||
|
if attr['key'] not in item:
|
||||||
|
item[attr['key']] = ""
|
||||||
|
content += f"\n{index + 1}.【{attr['title']}】:{item[attr['key']]}"
|
||||||
|
tool.replyMsg(content)
|
||||||
|
value = tool.sender.listen(120000)
|
||||||
|
if value == "q" or value == "error":
|
||||||
|
middleware.bucketSet(f"{plugin_pre}{tool.imType}", tool.userId, json.dumps(item_arr))
|
||||||
|
return tool.replyMsg("已退出!")
|
||||||
|
if not value.isdigit():
|
||||||
|
return self.editCount(item_arr, no)
|
||||||
|
if 0 < int(value) <= len(self.attr_arr):
|
||||||
|
attr = self.attr_arr[int(value) - 1]
|
||||||
|
if self.setVal(item, attr['title'], attr['key'], 12000):
|
||||||
|
item_arr[no] = item
|
||||||
|
self.editCount(item_arr, no)
|
||||||
|
elif value == "0":
|
||||||
|
item_arr.pop(no)
|
||||||
|
if len(item_arr) == 0:
|
||||||
|
middleware.bucketDel(f"{plugin_pre}{tool.imType}", tool.userId)
|
||||||
|
else:
|
||||||
|
middleware.bucketSet(f"{plugin_pre}{tool.imType}", tool.userId, json.dumps(item_arr))
|
||||||
|
return tool.replyMsg(f"已删除第{no + 1}个账号信息!请重新发送:哪吒管理 !")
|
||||||
|
else:
|
||||||
|
return self.editCount(item_arr, no)
|
||||||
|
|
||||||
|
def accoount_manager(self):
|
||||||
|
item_str = middleware.bucketGet(f"{plugin_pre}{tool.imType}", tool.userId)
|
||||||
|
if not item_str or item_str == "":
|
||||||
|
tool.replyMsg(f"未配置账号,请发送:哪吒查询")
|
||||||
|
exit(1)
|
||||||
|
item_arr = json.loads(item_str)
|
||||||
|
content = f"请选择要账号查看详情:(0增加, q退出)\n"
|
||||||
|
for index, item in enumerate(item_arr):
|
||||||
|
if not hasattr(item, ""):
|
||||||
|
item['disable'] = True
|
||||||
|
disable = item['disable']
|
||||||
|
status = '已启用' if disable else '已禁用'
|
||||||
|
content = "".join([content, f"\n{index + 1}、{item['name']}"])
|
||||||
|
tool.replyMsg(content)
|
||||||
|
value = tool.sender.listen(60000)
|
||||||
|
if value == "q" or value == "error":
|
||||||
|
tool.replyMsg("输入有误,已退出!")
|
||||||
|
elif value == "0":
|
||||||
|
self.addCount()
|
||||||
|
elif value.isdigit() and 0 < int(value) <= len(item_arr):
|
||||||
|
self.editCount(item_arr, int(value) - 1)
|
||||||
|
else:
|
||||||
|
tool.replyMsg(f"输入有误,请重新发送:哪吒管理,并输入正确的序号!")
|
||||||
|
|
||||||
|
def cron_account_arr(self):
|
||||||
|
account_arr = []
|
||||||
|
for plat in tool.platformArr():
|
||||||
|
p = plat["platform"]
|
||||||
|
user_id_arr = middleware.bucketAllKeys(p)
|
||||||
|
if not user_id_arr:
|
||||||
|
continue
|
||||||
|
for index, user_id in enumerate(user_id_arr):
|
||||||
|
user_data_str = middleware.bucketGet(p, user_id)
|
||||||
|
if not user_data_str or user_id == '':
|
||||||
|
continue
|
||||||
|
user_data_arr_temp = json.loads(user_data_str)
|
||||||
|
for n, account_data in enumerate(user_data_arr_temp):
|
||||||
|
account_data["push_user_id"] = user_id
|
||||||
|
account_data["push_im_type"] = plat["imType"]
|
||||||
|
account_data['index'] = n + 1
|
||||||
|
account_data['total'] = len(user_data_arr)
|
||||||
|
account_arr.append(account_data)
|
||||||
|
return account_arr
|
||||||
|
|
||||||
|
def account_task(self, item, no):
|
||||||
|
read = READ(item)
|
||||||
|
read.run()
|
||||||
|
|
||||||
|
class READ:
|
||||||
|
def __init__(self, account):
|
||||||
|
self.name = account['name']
|
||||||
|
self.dashboard_url = account['dashboard_url']
|
||||||
|
self.username = account['username']
|
||||||
|
self.password = account['password']
|
||||||
|
self.index = account['index']
|
||||||
|
self.total = account['total']
|
||||||
|
self.push_user_id = account['push_user_id']
|
||||||
|
self.push_im_type = account['push_im_type']
|
||||||
|
self.msg = ''
|
||||||
|
self.dateStr = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
self.timeStr = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
self.servers = [] # 初始化servers属性
|
||||||
|
self.chat_id = None # 初始化chat_id属性
|
||||||
|
|
||||||
|
def pushMsg(self, msg):
|
||||||
|
content = f"【账号备注】:{self.name}"
|
||||||
|
content += f'{msg}'
|
||||||
|
tool.pushMsg(self.push_user_id, None, self.push_im_type, "", content)
|
||||||
|
|
||||||
|
def format_bytes(self, size_in_bytes):
|
||||||
|
if size_in_bytes == 0:
|
||||||
|
return "0B"
|
||||||
|
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||||
|
power = int(math.floor(math.log(size_in_bytes, 1024)))
|
||||||
|
power = min(power, len(units) - 1)
|
||||||
|
size = size_in_bytes / (1024 ** power)
|
||||||
|
formatted_size = f"{size:.2f}{units[power]}"
|
||||||
|
return formatted_size
|
||||||
|
|
||||||
|
async def get_server_info(self):
|
||||||
|
try:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
# 登录获取token
|
||||||
|
login_url = f"{self.dashboard_url.rstrip('/')}/api/v1/login"
|
||||||
|
login_data = {
|
||||||
|
"username": self.username,
|
||||||
|
"password": self.password
|
||||||
|
}
|
||||||
|
async with session.post(login_url, json=login_data) as resp:
|
||||||
|
login_result = await resp.json()
|
||||||
|
if not login_result.get('success'):
|
||||||
|
return False, "登录失败,请检查用户名和密码"
|
||||||
|
token = login_result['data']['token']
|
||||||
|
|
||||||
|
# 获取服务器信息
|
||||||
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
server_url = f"{self.dashboard_url.rstrip('/')}/api/v1/server"
|
||||||
|
async with session.get(server_url, headers=headers) as resp:
|
||||||
|
server_result = await resp.json()
|
||||||
|
if not server_result.get('success'):
|
||||||
|
return False, "获取服务器信息失败"
|
||||||
|
|
||||||
|
self.servers = server_result['data'] # 填充servers属性
|
||||||
|
# 计算总体统计信息
|
||||||
|
online_count = sum(1 for server in self.servers if server.get('state', {}).get('last_active'))
|
||||||
|
total_mem = sum(server.get('host', {}).get('mem_total', 0) for server in self.servers)
|
||||||
|
used_mem = sum(server.get('state', {}).get('mem_used', 0) for server in self.servers)
|
||||||
|
total_disk = sum(server.get('host', {}).get('disk_total', 0) for server in self.servers)
|
||||||
|
used_disk = sum(server.get('state', {}).get('disk_used', 0) for server in self.servers)
|
||||||
|
total_swap = sum(server.get('host', {}).get('swap_total', 0) for server in self.servers)
|
||||||
|
used_swap = sum(server.get('state', {}).get('swap_used', 0) for server in self.servers)
|
||||||
|
total_net_in = sum(server.get('state', {}).get('net_in_transfer', 0) for server in self.servers)
|
||||||
|
total_net_out = sum(server.get('state', {}).get('net_out_transfer', 0) for server in self.servers)
|
||||||
|
|
||||||
|
# 返回总体统计信息
|
||||||
|
self.msg += f"\n【服务器数量】:{len(self.servers)}"
|
||||||
|
self.msg += f"\n【在线服务器】:{online_count}"
|
||||||
|
self.msg += f"\n【内存使用】:{self.format_bytes(used_mem)}/{self.format_bytes(total_mem)}"
|
||||||
|
self.msg += f"\n【磁盘使用】:{self.format_bytes(used_disk)}/{self.format_bytes(total_disk)}"
|
||||||
|
self.msg += f"\n【交换使用】:{self.format_bytes(used_swap)}/{self.format_bytes(total_swap)}"
|
||||||
|
self.msg += f"\n【总流量】:↓{self.format_bytes(total_net_in)} ↑{self.format_bytes(total_net_out)}"
|
||||||
|
|
||||||
|
return True, "获取成功"
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"获取信息失败:{str(e)}"
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
success, message = loop.run_until_complete(self.get_server_info())
|
||||||
|
loop.close()
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
self.msg += f"\n【错误】:{message}"
|
||||||
|
self.pushMsg(self.msg)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.display_menu()
|
||||||
|
|
||||||
|
def display_menu(self):
|
||||||
|
# 只返回总体数据和选单
|
||||||
|
self.msg = f"\n【服务器数量】:{len(self.servers)}"
|
||||||
|
self.msg += f"\n【在线服务器】:{sum(1 for server in self.servers if server.get('state', {}).get('last_active'))}"
|
||||||
|
self.msg += f"\n【内存使用】:{self.format_bytes(sum(server.get('state', {}).get('mem_used', 0) for server in self.servers))}/{self.format_bytes(sum(server.get('host', {}).get('mem_total', 0) for server in self.servers))}"
|
||||||
|
self.msg += f"\n【磁盘使用】:{self.format_bytes(sum(server.get('state', {}).get('disk_used', 0) for server in self.servers))}/{self.format_bytes(sum(server.get('host', {}).get('disk_total', 0) for server in self.servers))}"
|
||||||
|
self.msg += f"\n【交换使用】:{self.format_bytes(sum(server.get('state', {}).get('swap_used', 0) for server in self.servers))}/{self.format_bytes(sum(server.get('host', {}).get('swap_total', 0) for server in self.servers))}"
|
||||||
|
self.msg += f"\n【总流量】:↓{self.format_bytes(sum(server.get('state', {}).get('net_in_transfer', 0) for server in self.servers))} ↑{self.format_bytes(sum(server.get('state', {}).get('net_out_transfer', 0) for server in self.servers))}"
|
||||||
|
|
||||||
|
self.msg += "\n\n【服务器编号选单】:"
|
||||||
|
for index, server in enumerate(self.servers):
|
||||||
|
server_name = server.get('name', '未知')
|
||||||
|
self.msg += f"\n{index + 1}. {server_name}"
|
||||||
|
self.msg += "\n0. 查询所有服务器信息"
|
||||||
|
|
||||||
|
self.pushMsg(self.msg)
|
||||||
|
|
||||||
|
# 等待用户输入
|
||||||
|
user_input = tool.sender.listen(30000) # 30秒超时
|
||||||
|
if user_input.lower() == 'q':
|
||||||
|
tool.replyMsg("对话已结束。")
|
||||||
|
elif user_input.isdigit():
|
||||||
|
index = int(user_input) - 1
|
||||||
|
if index == -1:
|
||||||
|
# 查询所有服务器信息
|
||||||
|
all_details = ""
|
||||||
|
for i in range(len(self.servers)):
|
||||||
|
all_details += self.query_server_by_index(i) + "\n"
|
||||||
|
tool.replyMsg(all_details)
|
||||||
|
#tool.replyMsg("查询结束。")
|
||||||
|
elif 0 <= index < len(self.servers):
|
||||||
|
details = self.query_server_by_index(index)
|
||||||
|
tool.replyMsg(details)
|
||||||
|
self.display_menu() # 返回到二级菜单
|
||||||
|
else:
|
||||||
|
tool.replyMsg("无效的编号,请重新输入。")
|
||||||
|
self.display_menu()
|
||||||
|
else:
|
||||||
|
tool.replyMsg("超时或无效输入,已自动退出。")
|
||||||
|
|
||||||
|
def query_server_by_index(self, index):
|
||||||
|
if index < 0 or index >= len(self.servers):
|
||||||
|
return "无效的服务器编号。"
|
||||||
|
|
||||||
|
server = self.servers[index]
|
||||||
|
server_name = server.get('name', '未知')
|
||||||
|
status = "在线" if server.get('state', {}).get('last_active') else ""
|
||||||
|
details = f"\n【{server_name}】 {status}"
|
||||||
|
details += f"\nCPU使用率:{server.get('state', {}).get('cpu', 0):.2f}%"
|
||||||
|
details += f"\n内存使用:{self.format_bytes(server.get('state', {}).get('mem_used', 0))}/{self.format_bytes(server.get('host', {}).get('mem_total', 0))}"
|
||||||
|
details += f"\n磁盘使用:{self.format_bytes(server.get('state', {}).get('disk_used', 0))}/{self.format_bytes(server.get('host', {}).get('disk_total', 0))}"
|
||||||
|
details += f"\n流量统计:↓{self.format_bytes(server.get('state', {}).get('net_in_transfer', 0))} ↑{self.format_bytes(server.get('state', {}).get('net_out_transfer', 0))}"
|
||||||
|
details += f"\n当前下载速率:↓{self.format_bytes(server.get('state', {}).get('net_in_speed', 0))}/s"
|
||||||
|
details += f"\n当前上传速率:↑{self.format_bytes(server.get('state', {}).get('net_out_speed', 0))}/s"
|
||||||
|
|
||||||
|
return details
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
tool = TOOL()
|
||||||
|
account = ACCOUNT()
|
||||||
|
if tool.msg == '哪吒管理':
|
||||||
|
account.accoount_manager()
|
||||||
|
exit(1)
|
||||||
|
if tool.imType == 'fake':
|
||||||
|
user_data_arr = account.cron_account_arr()
|
||||||
|
else:
|
||||||
|
user_str = middleware.bucketGet(f"{plugin_pre}{tool.imType}", tool.userId)
|
||||||
|
if not user_str or user_str == "":
|
||||||
|
account.addCount()
|
||||||
|
exit(1)
|
||||||
|
user_data_arr = json.loads(user_str)
|
||||||
|
for i, user_data in enumerate(user_data_arr):
|
||||||
|
user_data["push_user_id"] = tool.userId
|
||||||
|
user_data["push_im_type"] = tool.imType
|
||||||
|
user_data['index'] = i + 1
|
||||||
|
user_data['total'] = len(user_data_arr)
|
||||||
|
if tool.sender.param(1):
|
||||||
|
user_data_arr = [user_data_arr[int(tool.sender.param(1))-1]]
|
||||||
|
for account_data in user_data_arr:
|
||||||
|
account.account_task(account_data, account_data['index'])
|
||||||
|
exit(1)
|
||||||
+1172
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user