上传文件至「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