Files
Yangmao_Script/WX_Applet/Applet_JYHS_TNJY.py

277 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# cron: 52 8 * * *
# 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()