diff --git a/WX_Applet/Applet_JYHS_TNJY.py b/WX_Applet/Applet_JYHS_TNJY.py new file mode 100644 index 0000000..0fd2efa --- /dev/null +++ b/WX_Applet/Applet_JYHS_TNJY.py @@ -0,0 +1,277 @@ +# cron: 52 7 * * * +# new Env("旧衣回收_天牛旧衣") +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +天牛旧衣回收全自动化脚本 - 修复登录异常版 +""" + +import os +import sys +import json +import time +import logging +import requests +from datetime import datetime +from typing import Optional, Dict, Any, List, Tuple + +# ==================== 配置区 ==================== +WX_CLOUD = os.getenv('wx_cloud', '') +AUTH_TOKEN = os.getenv('wx_token', '') +TIAN_NIU_APPID = "wx887c2f947bffa76e" +TIAN_NIU_NEW_URL = "https://tianniunew.fzjingzhou.com" +TIAN_NIU_OLD_URL = "https://tianniu.fzjingzhou.com" +LOG_DIR = "/opt/data/logs/tianniu" + +SINGLE_TEST_WXID = os.getenv('SINGLE_TEST_WXID', '') +WX_IDS_ENV = os.getenv('WX_IDS', '') + +# 全局变量 +logger: logging.Logger = None +log_file_path: str = "" + +# ==================== 获取 wxid 列表 ==================== +def fetch_all_wxids_from_yjc() -> List[str]: + """从养鸡场获取全部可用的 wxid 列表""" + if not WX_CLOUD or not AUTH_TOKEN: + print("❌ 养鸡场配置缺失,无法获取全部账号") + return [] + url = f"{WX_CLOUD}/prod-api/wechat/wechat/list?pageNum=1&pageSize=1000" + headers = {"Authorization": f"Bearer {AUTH_TOKEN}"} + try: + resp = requests.get(url, headers=headers, timeout=30) + if resp.status_code != 200: + print(f"❌ 获取账号列表失败: HTTP {resp.status_code}") + return [] + data = resp.json() + rows = data.get('rows', []) + if not rows: + print("⚠️ 养鸡场返回账号列表为空") + return [] + wxids = [] + for item in rows: + wxid = item.get('wxId') or item.get('wxid') + if wxid: + wxids.append(wxid) + print(f"✅ 成功获取 {len(wxids)} 个账号") + return wxids + except Exception as e: + print(f"❌ 请求异常: {e}") + return [] + +def get_wxid_list() -> List[str]: + """获取待处理的 wxid 列表""" + if SINGLE_TEST_WXID: + return [SINGLE_TEST_WXID] + if WX_IDS_ENV: + return [wxid.strip() for wxid in WX_IDS_ENV.split(',') if wxid.strip()] + print("📡 未指定 wxid,正在从养鸡场获取全部账号...") + all_wxids = fetch_all_wxids_from_yjc() + if not all_wxids: + print("❌ 从养鸡场获取全部账号失败") + return [] + return all_wxids + +# ==================== 初始化日志 ==================== +def init_logger(wxid: str) -> None: + global logger, log_file_path + os.makedirs(LOG_DIR, exist_ok=True) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + safe_wxid = wxid.replace('@', '_').replace('/', '_') + log_file_path = os.path.join(LOG_DIR, f"tianniu_{safe_wxid}_{timestamp}.log") + + logger = logging.getLogger(f"tianniu_{wxid}") + logger.setLevel(logging.INFO) + if logger.handlers: + logger.handlers.clear() + + fh = logging.FileHandler(log_file_path, encoding="utf-8") + fh.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) + logger.addHandler(fh) + + ch = logging.StreamHandler(sys.stdout) + ch.setFormatter(logging.Formatter("%(message)s")) + logger.addHandler(ch) + + logger.info("=" * 50) + logger.info(f" 天牛旧衣回收脚本 - 账号: {wxid}") + logger.info("=" * 50) + logger.info(f"时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + logger.info("=" * 50) + +def check_config() -> bool: + if not WX_CLOUD: + logger.error("❌ 环境变量 wx_cloud 未设置") + return False + if not AUTH_TOKEN: + logger.error("❌ 环境变量 wx_token 未设置") + return False + logger.info(f"✅ 养鸡场地址: {WX_CLOUD}") + return True + +# ==================== HTTP 请求 ==================== +def http_post(url: str, data=None, json_data=None, headers=None, timeout=30) -> Optional[Dict]: + try: + resp = requests.post(url, data=data, json=json_data, headers=headers, timeout=timeout) + if resp.status_code != 200: + logger.error(f"HTTP {resp.status_code}: {url}") + return None + # 确保响应是 JSON + try: + return resp.json() + except Exception as e: + logger.error(f"JSON解析失败: {e}, 响应预览: {resp.text[:200]}") + return None + except Exception as e: + logger.error(f"请求失败 {url}: {str(e)}") + return None + +# ==================== 业务步骤 ==================== +def get_miniprogram_code(wxid: str) -> Optional[str]: + logger.info("\n步骤1/4:获取小程序code...") + start = time.time() + url = f"{WX_CLOUD}/prod-api/wechat/api/getMiniProgramCode" + payload = {"wxid": wxid, "appid": TIAN_NIU_APPID} + headers = {"Authorization": f"Bearer {AUTH_TOKEN}", "Content-Type": "application/json"} + result = http_post(url, json_data=payload, headers=headers, timeout=30) + elapsed = time.time() - start + if result and isinstance(result, dict) and result.get("code") == 200: + code = result.get("data", {}).get("code") + if code: + logger.info(f"✅ 获取成功(耗时:{elapsed:.2f}秒)") + return code + logger.error("❌ 获取失败") + return None + +def login_tianniu(code: str) -> Optional[Tuple[str, Dict]]: + logger.info("\n步骤2/4:登录天牛...") + start = time.time() + url = f"{TIAN_NIU_NEW_URL}/api/login/getWxMiniProgramSessionKey" + payload = {"code": code, "appid": TIAN_NIU_APPID} + headers = {"Content-Type": "application/json"} + result = http_post(url, json_data=payload, headers=headers, timeout=30) + elapsed = time.time() - start + # 增加类型和字段检查 + if result and isinstance(result, dict) and result.get("code") == 1000: + data = result.get("data") + if isinstance(data, dict) and data.get("token"): + token = data["token"] + logger.info(f"✅ 登录成功(耗时:{elapsed:.2f}秒)") + info = data.get("personInfo", {}) + user_info = { + "mobile": info.get("mobile", "未知"), + "exchange": info.get("exchange", 0), + "sign_in_num": info.get("sign_in_num", 0) + } + logger.info(f" 手机号:{user_info['mobile']} 可兑换:{user_info['exchange']}元 已签到:{user_info['sign_in_num']}天") + return token, user_info + logger.error(f"❌ 登录失败,响应类型: {type(result)} 内容: {result}") + return None + +def do_sign_in(token: str) -> None: + logger.info("\n步骤3/4:执行签到...") + start = time.time() + headers = {"content-type": "application/x-www-form-urlencoded", "platform": "MP-WEIXIN"} + data = {"token": token} + result = None + try: + resp = requests.post(f"{TIAN_NIU_NEW_URL}/api/Person/sign", data=data, headers=headers, timeout=30) + if resp.status_code == 200: + result = resp.json() + except: + pass + if not result: + try: + resp = requests.post(f"{TIAN_NIU_OLD_URL}/api/Person/sign", data=data, headers=headers, timeout=30) + if resp.status_code == 200: + result = resp.json() + except: + pass + elapsed = time.time() - start + if result: + code = result.get("code", 0) + msg = result.get("msg", "") + if code == 1000: + logger.info(f"✅ 签到成功(耗时:{elapsed:.2f}秒)") + elif code == 1001 and "已签到" in msg: + logger.info(f"📅 今日已签到(耗时:{elapsed:.2f}秒)") + else: + logger.info(f"⚠️ 签到失败:{msg}") + else: + logger.info("❌ 签到请求失败") + +def check_withdraw(token: str, exchange: float) -> None: + logger.info("\n步骤4/4:提现检查...") + if exchange >= 2.0: + logger.info(f"✅ 余额满足提现条件:¥{exchange:.2f},执行提现...") + url = f"{TIAN_NIU_OLD_URL}/api/cash/scoreWithdraw" + headers = {"content-type": "application/x-www-form-urlencoded", "platform": "MP-WEIXIN"} + data = {"type": "wx_account", "score": 20, "token": token} + try: + resp = requests.post(url, data=data, headers=headers, timeout=30) + if resp.status_code == 200: + result = resp.json() + if result.get("code") == 1000 or "成功" in str(result): + logger.info("✅ 提现成功") + else: + logger.warning("⚠️ 提现失败") + else: + logger.warning("⚠️ 提现请求异常") + except Exception as e: + logger.error(f"提现异常: {e}") + else: + logger.info(f"📊 余额不足:¥{exchange:.2f} < ¥2,不执行提现") + +def process_one_wxid(wxid: str) -> bool: + """处理单个 wxid,捕获所有异常不中断主流程""" + try: + init_logger(wxid) + if not check_config(): + return False + code = get_miniprogram_code(wxid) + if not code: + return False + login_result = login_tianniu(code) + if not login_result: + return False + token, user_info = login_result + do_sign_in(token) + check_withdraw(token, user_info.get("exchange", 0)) + # 保存最新日志副本 + try: + latest = f"/opt/data/tianniu_latest_{wxid}.log" + os.makedirs(os.path.dirname(latest), exist_ok=True) + with open(log_file_path, "r", encoding="utf-8") as src, open(latest, "w", encoding="utf-8") as dst: + dst.write(src.read()) + except: + pass + logger.info(f"\n✅ 账号 {wxid} 处理完毕") + return True + except Exception as e: + # 异常捕获,避免脚本崩溃 + print(f"❌ 账号 {wxid} 处理异常: {e}") + return False + +# ==================== 主函数 ==================== +def main(): + wxid_list = get_wxid_list() + if not wxid_list: + print("❌ 没有可处理的 wxid,请检查环境变量或养鸡场接口") + sys.exit(1) + total = len(wxid_list) + success_count = 0 + for idx, wxid in enumerate(wxid_list, 1): + print(f"\n>>> 正在处理第 {idx}/{total} 个账号: {wxid}") + if process_one_wxid(wxid): + success_count += 1 + # 重置日志句柄,避免多个账号干扰 + for handler in logging.root.handlers[:]: + logging.root.removeHandler(handler) + if idx < total: + time.sleep(2) + print(f"\n{'='*50}") + print(f"批量处理完成:成功 {success_count}/{total} 个账号") + print(f"{'='*50}") + +if __name__ == "__main__": + main() \ No newline at end of file