# 解析 ZY100_USER_TOKEN (格式: TOKEN1&REMARK1@TOKEN2@TOKEN3 或 多行输入) # SCRIPT_NAME = "新疆联通开盒子 V1.2 (by:转变)" # 变量值抓取”userToken“字段,userToken有效期 3天 import requests import json import os import sys import time import random from datetime import datetime from typing import List, Dict, Any import base64 # <-- 新增: 用于解码 JWT Token import re # <-- 新增: 用于检查手机号格式 # 引入青龙环境下的通用通知模块 try: from notify import send except ImportError: def send(title, content): sys.stdout.write(f"\n【通知模块缺失,仅打印结果】{title}:\n{content}\n") # --- 辅助函数 --- def get_phone_from_token(token: str) -> str: """尝试从JWT token中提取手机号,用于生成默认备注 (脱敏格式: ****)""" try: # JWT 结构: header.payload.signature, 提取 payload 部分 parts = token.split('.') if len(parts) != 3: return "" # Base64URL 解码需要手动添加 padding payload_b64 = parts[1] padding = '=' * (4 - (len(payload_b64) % 4)) # 使用 urlsafe_b64decode 处理 Base64URL 编码 decoded_payload = base64.urlsafe_b64decode(payload_b64 + padding).decode('utf-8') # 解析 JSON 查找 'user_phone' 或 'sub' payload_data = json.loads(decoded_payload) phone = payload_data.get('user_phone') or payload_data.get('sub') # 确保提取的是一个有效的手机号格式 if phone and re.match(r'^\d{11}$', str(phone)): # 返回脱敏后的手机号 return str(phone)[:3] + '****' + str(phone)[7:] except Exception: # 如果解析失败(不是JWT或格式错误),返回空字符串 return "" return "" # --- 全局配置 --- SCRIPT_NAME = "新疆联通开盒子 V1.2 (by:转变)" # 关键配置:单账号尝试次数 # 用户请求将默认值修改为 '1' ATTEMPT_COUNT = int(os.environ.get("UNICOM_ATTEMPT_COUNT", "1")) # API配置 DRAW_URL = "https://zy100.xj169.com/touchpoint/openapi/marchAct/draw_Nov2025Act" DRAW_PAYLOAD = { 'activityId': "Nov2025Act", 'prizeId': "" } # 获取奖品详情的API配置 PRIZE_DETAIL_URL = "https://zy100.xj169.com/touchpoint/openapi/drawAct/getMyPrize" PRIZE_DETAIL_PAYLOAD = { 'activityId': "Nov2025Act" } # --- 账号解析逻辑 (支持备注/灵活格式) --- ACCOUNT_LIST: List[Dict[str, str]] = [] # 1. 解析 ZY100_USER_TOKEN (格式: TOKEN1&REMARK1@TOKEN2@TOKEN3 或 多行输入) ALL_TOKENS_RAW = os.environ.get("ZY100_USER_TOKEN", "") # V15.3 改进: 统一分隔符。将所有换行符替换为 '@',并清理多余的 '@'。 ALL_TOKENS_RAW = ALL_TOKENS_RAW.replace('\n', '@').replace('\r', '').replace('@@', '@') if ALL_TOKENS_RAW.startswith('@'): ALL_TOKENS_RAW = ALL_TOKENS_RAW[1:] for t_r in ALL_TOKENS_RAW.split('@'): t_r = t_r.strip() if not t_r: continue parts = t_r.split('&', 1) # 只分割一次,以支持备注中包含& token = parts[0] # 尝试从 token 提取手机号作为默认备注的基础 phone_suffix = get_phone_from_token(token) # 如果提取到手机号,使用脱敏手机号作为默认备注,否则使用通用Account default_remark = phone_suffix if phone_suffix else f"Account {len(ACCOUNT_LIST) + 1} (Main)" # 如果提供了 & 分隔的备注,则使用;否则使用基于手机号或通用Account的默认备注 remark = parts[1] if len(parts) > 1 else default_remark # 避免重复的 token if token and token not in [acc['token'] for acc in ACCOUNT_LIST]: ACCOUNT_LIST.append({"token": token, "remark": remark}) # 2. 解析 indexed 变量 (ZY100_USER_TOKEN_1, ZY100_REMARK_1) i = 1 while os.environ.get(f"ZY100_USER_TOKEN_{i}"): # 获取 token,并检查是否内联了备注 raw_token_value = os.environ.get(f"ZY100_USER_TOKEN_{i}", "").strip() # V15.3 改进: 确保 indexed 变量也支持内嵌换行符 raw_token_value = raw_token_value.replace('\n', '@').replace('\r', '').replace('@@', '@') parts = raw_token_value.split('&', 1) token = parts[0].split('@')[0] # 只需要第一个 token,避免多行 token 影响 # 尝试从 token 提取手机号作为默认备注的基础 phone_suffix = get_phone_from_token(token) default_remark = phone_suffix if phone_suffix else f"Account {len(ACCOUNT_LIST) + 1} (Indexed {i})" # 优先使用独立的 ZY100_REMARK_i 变量 explicit_remark = os.environ.get(f"ZY100_REMARK_{i}", "").strip() if explicit_remark: remark = explicit_remark elif len(parts) > 1: # 其次使用内联在 token 中的备注 remark = parts[1] else: # 最后使用基于手机号的默认备注或通用默认备注 remark = default_remark # 避免重复的 token if token and token not in [acc['token'] for acc in ACCOUNT_LIST]: ACCOUNT_LIST.append({"token": token, "remark": remark}) i += 1 # --- 脚本函数 (以下内容与 V15.1 保持一致) --- def write_log(text): """自定义日志写入函数""" sys.stdout.write(text + "\n") def get_prize_details(user_token, remark, activity_id="Nov2025Act"): """ 获取奖品详情信息 返回: (是否成功, 日志字符串, 奖品详情字典) """ log_messages = [f"--- 👤 账号备注: {remark} 的奖品详情 ---"] prize_details = {} headers = { 'User-Agent': "Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 unicom{version:iphone_c@12.0701};ltst;OSVersion/16.2", 'Origin': "https://zy100.xj169.com", 'Referer': "https://zy100.xj169.com/touchpoint/openapi/jumpHandRoom1G?source=...", 'X-Requested-With': "XMLHttpRequest", 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', 'Accept': '*/*', 'Accept-Language': 'zh-CN,zh-Hans;q=0.9', 'userToken': user_token, 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive' } payload = { 'activityId': activity_id } try: response = requests.post(PRIZE_DETAIL_URL, data=payload, headers=headers, timeout=10) if response.status_code != 200: log_messages.append(f"❌ 获取奖品详情失败,HTTP 状态码: {response.status_code}") return False, "\n".join(log_messages), {} try: res_json = response.json() except json.JSONDecodeError: log_messages.append(f"⚠️ 奖品详情响应不是有效的 JSON 格式。") return False, "\n".join(log_messages), {} if res_json.get("code") == 0 or res_json.get("code") == "SUCCESS": data = res_json.get('data', []) # 确保 data 是列表,即使只返回一个奖品 if isinstance(data, dict): data = [data] if data: prize_details = data log_messages.append(f"🎁 成功查询到 {len(data)} 个奖品记录:") # 格式化奖品详情信息,只显示prizeId和drawDate for i, prize in enumerate(data, 1): prize_id = prize.get('prizeId', '未知奖品') draw_date_timestamp = prize.get('drawDate', 0) # 转换时间戳为可读格式 draw_date = '未知时间' if draw_date_timestamp: try: draw_date = datetime.fromtimestamp(draw_date_timestamp / 1000).strftime('%Y-%m-%d %H:%M:%S') except: draw_date = str(draw_date_timestamp) log_messages.append(f" ├── 奖品 {i}: {prize_id}") log_messages.append(f" └── 获得时间: {draw_date}") else: log_messages.append("📝 当前暂无奖品记录。") else: log_messages.append(f"⚠️ 获取奖品详情失败: {res_json.get('msg', '未知错误')}") # 如果失败,显示原始响应以便调试 if res_json.get('code') not in [0, "SUCCESS"]: log_messages.append(json.dumps(res_json, indent=2, ensure_ascii=False)) except requests.exceptions.RequestException as e: log_messages.append(f"❌ 获取奖品详情时网络请求异常: {e}") return True, "\n".join(log_messages), prize_details def perform_single_draw(user_token): """ 单个抽奖请求逻辑,只执行一次 返回: (是否需要终止所有尝试的布尔值, 日志字符串, 状态码) """ log_messages = [] status = 'continue' headers = { 'User-Agent': "Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 unicom{version:iphone_c@12.0701};ltst;OSVersion/15.2", 'Origin': "https://zy100.xj169.com", 'Referer': "https://zy100.xj169.com/touchpoint/openapi/jumpHandRoom1G?source=...", 'X-Requested-With': "XMLHttpRequest", 'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json, text/plain, */*', 'userToken': user_token, 'Accept-Language': "zh-CN,zh-Hans;q=0.9" } try: response = requests.post(DRAW_URL, data=DRAW_PAYLOAD, headers=headers, timeout=10) if response.status_code != 200: log_messages.append(f"❌ 请求失败,HTTP 状态码: {response.status_code}") else: try: res_json = response.json() except json.JSONDecodeError: log_messages.append(f"⚠️ 响应不是有效的 JSON 格式。") return False, "\n".join(log_messages), status # 检查:请求频率过高 (立即终止) if res_json.get("code") == 500 and "频率过高" in res_json.get("msg", ""): log_messages.append("❌ 错误代码 500: 请求频率过高,强制终止!") status = 'rate_limit' # 检查:缺少参数错误(Token 失效) elif res_json.get("code") == 500 and "缺少参数" in res_json.get("msg", ""): log_messages.append("⚠️ 错误代码 500: Token 无效或已过期!终止尝试。") status = 'invalid_token' # 匹配:今日抽奖机会已用完 elif res_json.get("code") == "ERROR" and res_json.get("msg") == "thanks1": info = res_json.get('data', '未知信息') log_messages.append(f"✅ 抽奖机会状态: 今日已抽完。信息: {info}") status = 'done' # 标记为完成,以便停止后续尝试 # 匹配:成功中奖 elif res_json.get("code") == "SUCCESS": prize_name = res_json.get('data', '恭喜中奖') log_messages.append(f"🎉 抽奖中奖!结果: {prize_name}") status = 'won' # 其他非预期响应 else: log_messages.append("⚠️ 收到其他非预期响应:") log_messages.append(json.dumps(res_json, indent=2, ensure_ascii=False)) except requests.exceptions.RequestException as e: log_messages.append(f"❌ 网络请求异常发生: {e}") return False, "\n".join(log_messages), status def run_sign_in(): """主执行逻辑:多账号串行处理,单账号中延迟多次尝试""" all_results = [f"【{SCRIPT_NAME}】\n执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"] if not ACCOUNT_LIST: error_msg = "❌ 错误:环境变量 ZY100_USER_TOKEN(s) 未配置或为空。" all_results.append(error_msg) write_log(error_msg) return "\n".join(all_results) write_log(f"✅ 成功检测到 {len(ACCOUNT_LIST)} 个账号,每个账号将被串行尝试 {ATTEMPT_COUNT} 次...") # 当尝试次数为 1 时,此行日志无意义 if ATTEMPT_COUNT > 1: write_log(f"每次尝试之间将随机延迟 0.5 秒到 1.5 秒。") final_push_messages = [] for idx, acc in enumerate(ACCOUNT_LIST, 1): token = acc['token'] remark = acc['remark'] account_log_buffer = [] # 存储当前账号的详细日志 account_final_status = "未尝试" account_header = f"\n\n================ 👤 账号 {idx}/{len(ACCOUNT_LIST)} [备注: {remark}] 开始执行 ================" write_log(account_header) account_log_buffer.append(account_header) for attempt in range(ATTEMPT_COUNT): log_header = f"--- 尝试次数 {attempt + 1}/{ATTEMPT_COUNT} ---" write_log(log_header) account_log_buffer.append(log_header) _, result_log, status = perform_single_draw(token) write_log(result_log) account_log_buffer.append(result_log) # 更新最终状态,如果成功中奖/已抽完/致命错误,则中断 if status == 'done': account_final_status = "今日机会已用完" break elif status == 'won': account_final_status = "✅ 中奖成功" break elif status == 'invalid_token': account_final_status = "❌ Token 无效或过期" break elif status == 'rate_limit': account_final_status = "❌ 请求频率过高" break else: account_final_status = "抽奖失败/未中奖" # 如果不是最后一次尝试(并且尝试次数大于 1),引入延迟 if ATTEMPT_COUNT > 1 and attempt < ATTEMPT_COUNT - 1 and status not in ['done', 'won']: wait_time = random.uniform(0.5, 1.5) write_log(f"等待 {wait_time:.3f} 秒后进行下一次尝试...") time.sleep(wait_time) # --- 任务结束:获取奖品详情并汇总 --- write_log(f"\n--- 账号 {idx} 抽奖完成,获取奖品详情 ---") detail_success, detail_log, prize_details = get_prize_details(token, remark) write_log(detail_log) account_log_buffer.append(detail_log) write_log(f"================ 👤 账号 {idx} [备注: {remark}] 执行完毕 (状态: {account_final_status}) ================") account_log_buffer.append(f"================ 状态: {account_final_status} ================") # 将当前账号的详细日志加入总日志 all_results.extend(account_log_buffer) # 构造推送消息摘要 detail_summary_lines = [line for line in detail_log.splitlines() if line.startswith((' ├──', ' └──'))] detail_summary = "\n".join(detail_summary_lines) if not detail_summary_lines: # 检查是否有“暂无奖品记录”的提示 if "暂无奖品记录" in detail_log: detail_summary = "📝 当前暂无奖品记录。" else: detail_summary = "❌ 奖品查询失败,请检查日志。" final_push_messages.append(f"👤 [备注: {remark}] 状态: {account_final_status}\n{detail_summary}\n" + "-"*30) # 账号切换间歇休息 1-2 秒 time.sleep(random.uniform(1.0, 2.0)) final_log_content = "\n".join(all_results) final_notification = f"【{SCRIPT_NAME}】\n执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n--- 🛎️ 执行摘要 (按备注区分) 🛎️ ---\n" + "\n".join(final_push_messages) write_log("\n--- 推送日志开始 ---") write_log(final_notification) write_log("--- 推送日志结束 ---") return final_notification if __name__ == "__main__": # 1. 执行任务并获取推送内容 push_content = run_sign_in() # 2. 使用通用的 send 函数进行推送 send(SCRIPT_NAME, push_content)