diff --git a/default.config.yaml b/default.config.yaml index c2e6f4e..59ce497 100644 --- a/default.config.yaml +++ b/default.config.yaml @@ -1,56 +1,129 @@ +# ========================================== +# 修改时注意缩进 如果出现格式异常 可以用格式检查器 +# ========================================== Push: - # 推送方式 - # 支持qmsg酱 server酱 企业微信推送 钉钉机器人推送 - # Qmsg,Server,Epwc,Dingtalk,Wxhook - # 为空或False则不进行推送 - PushMode: Qmsg + # 推送模式开关 + # 可选值: Qmsg, Server, Epwc, Dingtalk, Wxhook + # 如果不需要推送,请保持为空或设置为 False + PushMode: "" + + # Qmsg酱 (Qmsg) Qmsg: key: "" + + # Server酱 (ServerChan) Server: - key: "your server key" + key: "" + + # 企业微信应用推送 (Enterprise WeChat) Epwc: EnterpriseId: "" AppId: "" AppSecret: "" UserUid: "" + + # 钉钉机器人 (Dingtalk) Dingtalk: - token: - secret: - atuser: + token: "" + secret: "" + atuser: "" atMobiles: isAtAll: False + + # 企业微信群机器人 (Wxhook) Wxhook: url: "" +# ========================================== +# 签到任务配置 (SignToken) +# ========================================== SignToken: + + # ---------------------------------------- + # 好游快爆 (Hykb) - 已升级新版验证 + # ---------------------------------------- + # 获取方法: + # 1. 使用抓包工具(Reqable/Charles等)抓取 "huodong3.3839.com" 的POST请求 (如 ajax.php) + # 2. cookie: 在 Request Headers 中获取 + # 3. scookie & device_id: 在 Request Body (Post Data) 中获取 + # ---------------------------------------- Hykb: - switch: False - cookie: + # 账号 1 + - switch: False # 开关:True/False + note: "账号1备注" # 备注名,用于日志区分 + cookie: "" # HTTP请求头中的 Cookie + scookie: "" # POST数据中的 scookie (关键凭证, 代码会自动解码) + device_id: "" # POST数据中的 device (设备ID, 通常以kb开头) + # 可选配置:自定义数美设备ID (需要自己抓,如果找不到 就导出抓包文件,然后把后缀改名为txt,直接搜索smdeviceid) + smdeviceid: "" + + # 账号 2 (如需更多账号,复制上方区块即可) + # - switch: False + # note: "账号2备注" + # cookie: "" # 可复用账号1的cookie + # scookie: "" # 必须填写该账号独立的 scookie + # device_id: "" # 必须填写该账号独立的 device + + # ---------------------------------------- + # MIUI 历史版本 + # ---------------------------------------- MiUI: switch: False - username: - password: + username: "" + password: "" + + # ---------------------------------------- + # 小黑盒 (XiaoHeiHe) + # ---------------------------------------- XiaoHeiHe: switch: False - cookie: - imei: - heybox_id: - version: + cookie: "" + imei: "" + heybox_id: "" + version: "" + + # ---------------------------------------- + # 交易猫 (JiaoYiMao) + # ---------------------------------------- JiaoYiMao: switch: False - cookie: + cookie: "" + + # ---------------------------------------- + # 天翼云盘 (Tyyp) + # ---------------------------------------- Tyyp: switch: False - username: - password: + username: "" + password: "" + + # ---------------------------------------- + # 网易云游戏 (wyyyx) + # ---------------------------------------- wyyyx: switch: False - cookie: - # 请在阿里云盘网页端输入后面代码获取:JSON.parse(localStorage.getItem("token")).refresh_token + cookie: "" + + # ---------------------------------------- + # 阿里云盘 (Aliyundrive) + # ---------------------------------------- + # 获取方法:在网页端控制台输入 JSON.parse(localStorage.getItem("token")).refresh_token Aliyundrive: switch: False - token: - # 明日方舟配置 打开森空岛网页 登录账号, 打开https://web-api.skland.com/account/info/hg, 复制 content 的值 + token: "" + + # ---------------------------------------- + # 明日方舟 (Arknights) - 森空岛 + # ---------------------------------------- + # 温馨提示 目前YJ用了加签算法.源代码已经不能用 + # 访问 https://web-api.skland.com/account/info/hg 复制 content 字段的值 Arknights: switch: False - token: \ No newline at end of file + token: "" + + # ---------------------------------------- + # 雨云 (RainYun) + # ---------------------------------------- + Raincloud: + switch: False + token: "" diff --git a/main.py b/main.py index 56fae0a..5f38f48 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,8 @@ -import time, yaml, os +import os +import time +import yaml +import logging +import tempfile from src.log import Log from src.Push import Push from src.Miui import Miui @@ -7,9 +11,34 @@ from src.raincloud import RainCloud from src.hykb import HaoYouKuaiBao from src.arknights import Arknights -from src.Sign import XiaoHeiHe,JiaoYiMao,wyyyx -log = Log() +from src.Sign import XiaoHeiHe, JiaoYiMao, wyyyx +# 创建日志记录器 +def setup_logger(): + # 创建日志记录器 + log = logging.getLogger("MainLogger") + log.setLevel(logging.INFO) + + # 获取临时目录中的日志文件路径 + log_directory = tempfile.gettempdir() + log_file = os.path.join(log_directory, 'app_log.txt') + + # 设置日志输出格式 + formatter = logging.Formatter('%(asctime)s [%(levelname)s]: %(message)s') + + # 输出到文件 + file_handler = logging.FileHandler(log_file, mode='a', encoding='utf-8') + file_handler.setFormatter(formatter) + log.addHandler(file_handler) + + # 输出到控制台 + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + log.addHandler(console_handler) + + return log, log_file + +# 获取配置 def getconfig(): path = os.path.dirname(os.path.realpath(__file__)) with open(f'{path}/config.yaml', 'r', encoding='utf-8') as f: @@ -17,54 +46,115 @@ def getconfig(): return config def run(): - #开始时间 + log, log_file = setup_logger() # 初始化日志 + log.info("程序开始运行") + log.info(f"日志文件路径: {log_file}") + + # 开始时间 Begin = time.time() - #程序主体 + # 程序主体 config = getconfig() SignToken = config['SignToken'] - data = "今日签到结果:\n\n" + data = "今日签到结果: \n" # 使用 \n 进行换行 + # miui历史版本签到 - if SignToken['MiUI']['switch']: + if SignToken.get('MiUI', {}).get('switch'): body = Miui(SignToken['MiUI']) - data += "MIUI历史版本:\n"+body.Sign() - if SignToken['Hykb']['switch']: - body = HaoYouKuaiBao(SignToken['Hykb']) - data += "\n\n好游快爆:\n"+ body.sgin() - if SignToken['XiaoHeiHe']['switch']: + result = body.Sign().replace("\n", "
") + log.info(f"MIUI签到结果: {result}") + data += "MIUI历史版本:\n" + result + + # -------------------------------------------------------- + # 好游快爆签到 (已修改为支持多账号列表遍历) + # -------------------------------------------------------- + hykb_config = SignToken.get('Hykb') + + # 兼容处理:如果是单个字典(旧配置),转为列表;如果是列表,直接使用 + if isinstance(hykb_config, dict): + hykb_list = [hykb_config] + elif isinstance(hykb_config, list): + hykb_list = hykb_config + else: + hykb_list = [] + + # 遍历执行 + hykb_results = "" + for index, user_config in enumerate(hykb_list): + if user_config.get('switch'): + note = user_config.get('note', f'账号{index+1}') + try: + log.info(f"正在运行好游快爆: {note}") + body = HaoYouKuaiBao(user_config) + # 注意:这里调用的是 sgin() 还是 sgin() 取决于你的 src/hykb.py 里的定义 + # 假设原方法名是 sgin + res = body.sgin().replace("\n", "
") + log.info(f"好游快爆[{note}]签到结果: {res}") + hykb_results += f"【{note}】:
{res}
" + except Exception as e: + log.info(f"好游快爆[{note}]运行出错: {e}") + hykb_results += f"【{note}】: 运行出错
" + + if hykb_results: + data += "\n\n好游快爆:\n" + hykb_results + # -------------------------------------------------------- + + if SignToken.get('XiaoHeiHe', {}).get('switch'): body = XiaoHeiHe(SignToken) - data += "\n\n小黑盒:\n"+body.Sgin() - if SignToken['JiaoYiMao']['switch']: + result = body.Sgin().replace("\n", "
") + log.info(f"小黑盒签到结果: {result}") + data += "\n\n小黑盒:\n" + result + + if SignToken.get('JiaoYiMao', {}).get('switch'): body = JiaoYiMao(SignToken) - data += "\n\n交易猫:\n"+body.Sgin() + result = body.Sgin().replace("\n", "
") + log.info(f"交易猫签到结果: {result}") + data += "\n\n交易猫:\n" + result # 天翼云盘签到 - if SignToken['Tyyp']['switch']: + if SignToken.get('Tyyp', {}).get('switch'): body = Cloud(SignToken['Tyyp']) - data += "\n天翼云盘:\n"+body.sgin() - if SignToken['wyyyx']['switch']: + result = body.sgin().replace("\n", "
") + log.info(f"天翼云盘签到结果: {result}") + data += "\n天翼云盘:\n" + result + + if SignToken.get('wyyyx', {}).get('switch'): body = wyyyx(SignToken) - data += "\n\n网易云游戏:\n"+body.Sgin() + result = body.Sgin().replace("\n", "
") + log.info(f"网易云游戏签到结果: {result}") + data += "\n\n网易云游戏:\n" + result + # 阿里云盘 - if SignToken['Aliyundrive']['switch']: + if SignToken.get('Aliyundrive', {}).get('switch'): body = Aliyundrive(SignToken['Aliyundrive']) - data += "\n\n阿里云盘:\n"+body.sgin() + result = body.sgin().replace("\n", "
") + log.info(f"阿里云盘签到结果: {result}") + data += "\n\n阿里云盘:\n" + result + # 雨云签到 - if SignToken['Raincloud']['switch']: + if SignToken.get('Raincloud', {}).get('switch'): body = RainCloud(SignToken['Raincloud']) - data += "\n\n雨云:\n"+body.sgin() - if SignToken['Arknights']['switch']: + result = body.sgin().replace("\n", "
") + log.info(f"雨云签到结果: {result}") + data += "\n\n雨云:\n" + result + + if SignToken.get('Arknights', {}).get('switch'): body = Arknights(SignToken['Arknights']) - data += "\n\n明日方舟:\n"+body.sgin() + result = body.sgin().replace("\n", "
") + log.info(f"明日方舟签到结果: {result}") + data += "\n\n明日方舟:\n" + result + # 结束时间 end = time.time() - sum = f"本次运行时间{round(end-Begin,3)}秒" - data = data + "\n\n" + sum + runtime_summary = f"本次运行时间: {round(end - Begin, 3)}秒" + log.info(runtime_summary) + data += "\n\n" + runtime_summary.replace("\n", "
") + # 推送消息 - ts = Push(data,config['Push']) + ts = Push(data, config['Push']) ts.push() - log.info(sum) - log.info("\n"+data) + log.info("签到完成,推送消息已发送") + log.info(f"完整签到结果:\n{data}") if __name__ == '__main__': - run() \ No newline at end of file + run() diff --git a/src/hykb.py b/src/hykb.py index 2186cc9..ca32460 100644 --- a/src/hykb.py +++ b/src/hykb.py @@ -1,165 +1,232 @@ +# -*- coding: utf-8 -*- +# @Time : 2025/11/22 +# @Author : Jyf0214 +# @Fixed : Jyf0214 (修复加密算法、动态Token获取及登录逻辑) +# @Format : Gemini 3 (代码格式化、注释优化及语法规范化) +# +# 【安全声明】 +# 1. HTTP请求头中的 'Cookie' 仅用于模拟真实浏览器行为,看似不影响核心业务鉴权。 +# 2. 账号的核心安全鉴权完全依赖于 POST 请求体中的 'scookie' 和 'device' (设备ID)。 +# 3. 请务必保管好您的 scookie,切勿泄露给他人。 + import requests import random -import datetime +import time +import re +import urllib.parse from src.log import Log - log = Log() - -class HaoYouKuaiBao(): - """好游快爆签到 +class HaoYouKuaiBao: + """ + 好游快爆自动签到/种玉米脚本 (修复版) """ def __init__(self, config): - self.cookie = config['cookie'] - self.url = "https://huodong3.3839.com/n/hykb/{}/ajax{}.php" - self.data = "ac={}&r=0.{}&scookie={}" - self.headers={ - "Origin": "https://huodong3.i3839.com", - "Referer": "https://huodong3.3839.com/n/hykb/cornfarm/index.php?imm=0", - "Content-Type":"application/x-www-form-urlencoded; charset=UTF-8", - "User-Agent": "Mozilla/5.0 (Linux; Android 13; M2012K11AC Build/TKQ1.220829.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.166 Mobile Safari/537.36Androidkb/1.5.7.005(android;M2012K11AC;13;1080x2320;WiFi);@4399_sykb_android_activity@" + # 基础配置 + self.cookie = config.get('cookie', '') + self.device_id = config.get('device_id', '') + + # 数美风控ID (Shumei Device ID) + # 理论上该ID可以通过算法(类似森空岛脚本)计算生成,但涉及复杂的JS逆向。 + # 这里优先读取配置,如果配置为空,则使用抓包获取的一个通用值兜底。 + self.smdeviceid = config.get('smdeviceid', '') + if not self.smdeviceid: + # 每个人的id都不一样。不要随意使用别人的! + self.smdeviceid = "" + + # 自动处理 scookie 编码 (兼容 URL 编码格式 %7C 或原始格式 |) + raw_scookie = config.get('scookie', '') + if '%' in raw_scookie: + self.scookie = urllib.parse.unquote(raw_scookie) + else: + self.scookie = raw_scookie + + self.base_url = "https://huodong3.3839.com/n/hykb/cornfarm/index.php?imm=0" + # 动态拼接 URL + self.ajax_url = "https://huodong3.3839.com/n/hykb/cornfarm/ajax{}.php" + + self.headers = { + "Host": "huodong3.3839.com", + "Connection": "keep-alive", + "X-Requested-With": "XMLHttpRequest", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; M2012K11AC Build/TKQ1.220829.002; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.166 Mobile Safari/537.36Androidkb/1.5.7.807(android;M2012K11AC;13;1080x2320;WiFi);@4399_sykb_android_activity@", + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "Origin": "https://huodong3.3839.com", + "Referer": self.base_url, + "Cookie": self.cookie } - def plant(self) -> int: - """播种 + # 页面动态参数缓存 (必须从页面获取,不可硬编码) + self.dynamic_params = { + "token": "default", + "page_token": "", + "random_str": "" + } + self.timeout = 15 + + def fetch_page_params(self): """ - url = self.url.format("cornfarm", "_plant") - data = self.data.format("Plant", random.randint(1000000000000000, 8999999999999999), self.cookie) - try: - response = requests.post(url, headers=self.headers, data=data).json() - if response['key'] == 'ok': - log.info("好游快爆-播种成功") - return 1 - else: - if response['seed'] == 0: - log.info("好游快爆-种子已用完") - return -1 - else: - log.info("好游快爆-播种失败") - return 0 - except Exception as e: - log.info(f"好游快爆-播种出现错误:{e}") - return False - - def harvest(self) -> bool: - """收获 + 初始化:访问主页,正则提取隐藏的加密 Token """ - url = self.url.format("cornfarm", "_plant") - data = self.data.format("Harvest", random.randint(1000000000000000, 8999999999999999), self.cookie) try: - response = requests.post(url, headers=self.headers, data=data).json() - if response['key'] == 'ok': - log.info("好游快爆-收获成功") + # log.info("正在获取页面初始化参数...") + response = requests.get(self.base_url, headers=self.headers, timeout=self.timeout) + response.encoding = 'utf-8' + html = response.text + + # 正则提取关键参数 + page_token_match = re.search(r'pageToken\s*=\s*[\'"](.*?)[\'"]', html) + random_str_match = re.search(r'pageRandomStr\s*=\s*[\'"](.*?)[\'"]', html) + token_val_match = re.search(r'token_value\s*=\s*[\'"](.*?)[\'"]', html) + + if page_token_match: + self.dynamic_params['page_token'] = page_token_match.group(1) + if random_str_match: + self.dynamic_params['random_str'] = random_str_match.group(1) + if token_val_match and token_val_match.group(1) != 'default': + self.dynamic_params['token'] = token_val_match.group(1) + + if self.dynamic_params['page_token']: return True - else: - log.info("好游快爆-收获失败") - return False + + log.info("初始化失败:无法获取 PageToken,请检查 Header Cookie 是否有效") + return False except Exception as e: - log.info(f"好游快爆-收获出现错误:{e}") + log.info(f"网络请求异常: {e}") return False - - def login(self): - """登录 + + def build_data(self, ac): """ - url = self.url.format("cornfarm", "") - data = self.data.format("login", random.randint(100000000000000, 8999999999999999), self.cookie) - response = requests.post(url, headers=self.headers, data=data) - try: - response = response.json() - return response - except Exception as e: - response = response.text - return response - - def watering(self): - """浇水 + 构造加密请求体 (核心签名算法) """ - url = self.url.format("cornfarm", "_sign") - data = self.data.format("Sign&verison=1.5.7.005&OpenAutoSign=", random.randint(100000000000000, 8999999999999999), self.cookie) + current_time = int(time.time() * 1000) + # 核心签名算法: (时间戳 % 7) + 21 + # 此算法由 Jyf0214 逆向 JS 获得 + token_sign = (current_time % 7) + 21 + + data = { + "ac": ac, + "smdeviceid": self.smdeviceid, # 数美风控ID + "verison": "1.5.7.807", # 官方拼写错误 verison,需保持一致 + "OpenAutoSign": "close", + "r": str(random.random()), + "token": self.dynamic_params['token'], + "token_version": "v1", + "page_token": self.dynamic_params['page_token'], + "token_time": str(current_time), + "token_sign": str(token_sign), + "random_str": self.dynamic_params['random_str'], + "device": self.device_id, + "scookie": self.scookie # 关键身份凭证 + } + return data + + def _post(self, url_suffix, ac): + """通用 POST 请求封装""" + url = self.ajax_url.format(url_suffix) + data = self.build_data(ac) try: - response = requests.post(url, headers=self.headers, data=data).json() - if response['key'] == 'ok': - log.info("好游快爆-浇水成功") - return 1, response['add_baomihua'] - elif response['key'] == '1001': - log.info("好游快爆-今日已浇水") - return 0, 0 - else: - log.info("好游快爆-浇水出现错误:{}".format(response)) - return -1, 0 + res = requests.post(url, headers=self.headers, data=data, timeout=self.timeout) + return res.json() except Exception as e: - log.info("好游快爆-浇水出现错误:{}".format(e)) - return -1, 0 - - # def buyseeds(self): - # """购买种子 - # """ - # url = self.url.format("bmhstore2/inc/virtual", "Virtual") - # print(url) - # ac = "exchange&t=" + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "&goodsid=14565" - # data = self.data.format(ac, random.randint(100000000000000, 8999999999999999), self.cookie) - # print(data) - # response = requests.post(url, headers=self.headers, data=data) - # print(response.json()) + log.info(f"请求 {ac} 失败: {e}") + return {"key": "error"} + + def login(self): + """登录接口:获取真实 Token""" + res = self._post("", "login") # ajax.php + if res.get('key') == 'ok': + # 登录成功后,服务端会返回一个新的 token,后续操作必须使用此 token + new_token = res.get('config', {}).get('token_value') + if new_token: + self.dynamic_params['token'] = new_token + return res + + def plant(self): + """播种""" + # 这里的 _plant 对应 ajax_plant.php,具体视服务端接口而定 + res = self._post("_plant", "Plant") + if res.get('key') == 'ok': + return "✅ 播种成功", 1 + elif str(res.get('seed')) == '0': + return "❎ 播种失败:种子已用完", -1 + else: + return f"❎ 播种失败: {res.get('key', 'unknown')}", 0 + + def harvest(self): + """收获""" + res = self._post("_plant", "Harvest") + if res.get('key') == 'ok': + return "✅ 收获成功", True + return f"❎ 收获失败: {res.get('key', 'unknown')}", False + + def watering(self): + """浇水 (签到)""" + # 这里的 _sign 对应 ajax_sign.php + res = self._post("_sign", "Sign") + if res.get('key') == 'ok': + add = res.get('add_baomihua', 0) + return f"✅ 浇水成功,获得 {add} 爆米花", 1 + elif str(res.get('key')) == '1001': + return "☕ 今日已浇水", 0 + else: + return f"❎ 浇水失败: {res}", -1 def sgin(self): - info = "" - # 登录 - data = self.login() - if data['key'] == 'ok': - if data['config']['csd_jdt'] == "100%": - # 收获 - if self.harvest(): - info = info+"收获成功\n" - # 播种 - b = self.plant() - if b == -1: - info = info+"播种失败,没有种子\n" - elif b == 1: - info = info+"播种成功\n" - # 浇水 - data = self.watering() - if data[0] == 1: - info = info+f"浇水成功,获得{data[1]}爆米花\n" - elif data[0] == 0: - info = info+f"今日已浇水\n" - else: - info = info+f"浇水失败\n" - else: - info = info+"播种失败\n" - else: - info = info+"收获失败\n" - - elif data['config']['grew'] == '-1': - # 播种 - b = self.plant() - if b == -1: - info = info+"播种失败,没有种子\n" - elif b == 1: - info = info+"播种成功\n" - # 浇水 - data = self.watering() - if data[0] == 1: - info = info+f"浇水成功,获得{data[1]}爆米花\n" - elif data[0] == 0: - info = info+f"今日已浇水\n" - else: - info = info+f"浇水失败\n" - else: - info = info+"播种失败\n" + """ + 执行主逻辑:初始化 -> 登录 -> 判断状态 -> 收获/播种/浇水 + """ + msg_list = [] + + # 0. 检查配置完整性 + if not self.scookie or not self.device_id: + return "❌ 配置文件错误:缺少 scookie 或 device_id,无法运行!" + + # 1. 初始化页面参数 + if not self.fetch_page_params(): + return "❌ 初始化失败,可能是 Cookie 失效或网络问题" + # 2. 登录 + data = self.login() + + if data.get('key') == 'ok': + config = data.get('config', {}) + nickname = config.get('nickname', '未知') + csd_jdt = config.get('csd_jdt', '0%') # 成熟度 + grew = str(config.get('grew')) # 生长状态:-1未种植,1生长中 + + status_msg = f"用户: {nickname} | 成熟度: {csd_jdt} | 状态: {grew}" + log.info(f"登录成功: {nickname}") + msg_list.append(status_msg) + + # 3. 根据状态执行动作 + if csd_jdt == "100%": + # 成熟 -> 收获 -> 播种 -> 浇水 + h_msg, h_res = self.harvest() + msg_list.append(h_msg) + if h_res: + p_msg, p_res = self.plant() + msg_list.append(p_msg) + if p_res == 1: + w_msg, _ = self.watering() + msg_list.append(w_msg) + + elif grew == '-1': + # 未种植 -> 播种 -> 浇水 + p_msg, p_res = self.plant() + msg_list.append(p_msg) + if p_res == 1: + w_msg, _ = self.watering() + msg_list.append(w_msg) + else: - # 浇水 - data = self.watering() - if data[0] == 1: - info = info+f"浇水成功,获得{data[1]}爆米花\n" - elif data[0] == 0: - info = info+f"今日已浇水\n" - else: - info = info+f"浇水失败\n" + # 生长中 -> 浇水 + w_msg, _ = self.watering() + msg_list.append(w_msg) else: - info = info+"登录失败\n" + err_key = data.get('key', 'unknown') + msg_list.append(f"❌ 登录失败: {err_key}") + log.info(f"登录失败详情: {data}") - return info - + return "\n".join(msg_list)