# [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等框架
指令:账密登录,账密登录,账密管理
配置:在插件配参中配置,
权限:需开启qls,jdNotify权限
注意⚠️账密刷新请勿手动发送,否则会将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()