Update tyqh.py

This commit is contained in:
wd210010 authored and GitHub committed 2026-07-25 11:16:36 +08:00
1 parent 7977617238
commit 0f0e04cef5
1 file changed
+371 -489
+371 -489
View File
@@ -1,505 +1,387 @@
import requests
# cron: 25 10 * * *
# new Env('统一快乐星球茄皇(五期)')
#
# 环境变量:
# tyqh:账号凭证,格式为 wid#openId,多账号用 & 分隔
#
# 示例:tyqh=wid值#openId值
import base64
import json
import os
import time
import re
import random
from collections import defaultdict
import time
from typing import List, Tuple, Dict, Any
import requests
from notify import send
# 环境变量格式改为 wid1@手机号1&wid2@手机号2
users = os.getenv("TYQH", "").split("&")
users = [user.strip() for user in users if user.strip()]
# 解析为 (wid, 手机号) 列表
parsed_users = []
for user_str in users:
if "@" in user_str:
wid, phone = user_str.split("@", 1)
parsed_users.append((wid.strip(), phone.strip()))
try:
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
CRYPTO_BACKEND = "cryptography"
except ImportError:
try:
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
CRYPTO_BACKEND = "pycryptodome"
except ImportError:
CRYPTO_BACKEND = None
BASE_URL = "https://farmgames.ioutu.cn"
PUBLIC_KEY = (
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA70sK419vy3MabW3lEGlk"
"7Zh1u78OdnVlioVazp5Y46eBh+/TDqo/wZ9VrQ/4MmAtoP0vJ2vmwP5gqO3WPoj"
"b07WddXfF1eU+5M+Rj3s0eSRrvZvBcGZ3qK0dOgZJScK66IDQazt/c4xqhDcsI"
"tIyNRahUqB/IKc6E80GZJvMvFtZVSCseAXC0mAJXhi1AdUOlP+3Pv0fiUVejTJp"
"1j7LBNWJ7Z5/8mRcclQH0vmxsdYsaV3qZiJ2d/CfNoKcwmI2IWmeZy8NP5U8Hn"
"0AsxPEwjdHoEqG/iy/SoA46TZL+RLtWqUSHXpaKR/VFN0rbl25SE91X8FTfLqyD"
"8LfGMCwRQIDAQAB"
)
USER_AGENT = (
"Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 "
"MicroMessenger/8.0.75(0x18004b43) NetType/WIFI Language/zh_CN "
"miniProgram/wx532ecb3bdaaf92f9"
)
SUPPORTED_TASK_TYPES = {"SIGN", "BROWSE", "SHARE"}
FRIEND_TASK_TYPE = "FRIEND_STEAL_ENERGY"
FRIEND_STATUS_CLAIMABLE = "0"
def parse_users() -> List[Tuple[str, str]]:
"""解析环境变量,格式:wid#openId,多账号用 & 分隔"""
raw = os.getenv("tyqh", "")
accounts = []
for item in str(raw or "").replace("&", "\n").splitlines():
item = item.strip()
if not item:
continue
parts = item.split("#")
if len(parts) != 2:
print(f"格式错误,跳过:{item}(正确格式:wid#openId)")
continue
wid, open_id = parts[0].strip(), parts[1].strip()
if wid and open_id:
accounts.append((wid, open_id))
return accounts
def encrypt_payload(payload: Dict) -> Dict[str, str]:
"""Match the H5 client: RSA-OAEP-SHA256 + AES-256-GCM."""
if CRYPTO_BACKEND is None:
raise RuntimeError("缺少加密依赖,请安装 cryptography 或 pycryptodome")
plaintext = json.dumps(payload, ensure_ascii=False, separators=(",", ":")).encode("utf-8")
aes_key = os.urandom(32)
iv = os.urandom(12)
public_key_der = base64.b64decode(PUBLIC_KEY)
if CRYPTO_BACKEND == "cryptography":
public_key = serialization.load_der_public_key(public_key_der)
encrypted_data = AESGCM(aes_key).encrypt(iv, plaintext, None)
encrypted_key = public_key.encrypt(
aes_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
else:
# 兼容旧格式(无手机号时跳过登录)
parsed_users.append((user_str.strip(), ""))
cipher = AES.new(aes_key, AES.MODE_GCM, nonce=iv)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
encrypted_data = ciphertext + tag
public_key = RSA.import_key(public_key_der)
encrypted_key = PKCS1_OAEP.new(public_key, hashAlgo=SHA256).encrypt(aes_key)
# UA可自行替换为自己的
user_agent = "Mozilla/5.0 (Linux; Android 14; 23046RP50C Build/UKQ1.230804.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/142.0.7444.172 Safari/537.36 XWEB/1420045 MMWEBSDK/20250201 MMWEBID/5714 MicroMessenger/8.0.57.2820(0x28003956) WeChat/arm64 Weixin Android Tablet NetType/WIFI Language/zh_CN ABI/arm64 miniProgram/wx532ecb3bdaaf92f9"
STEP_ORDER = ["登录", "领取种子", "签到", "浏览任务", "收获作物", "播种", "循环浇水"]
STEP_EMOJI = {"登录": "🔑", "领取种子": "🌱", "签到": "📅", "浏览任务": "🔍", "收获作物": "🌾", "播种": "🌱", "循环浇水": "🔄"}
def _short(s, n=120):
s = s.strip()
return s if len(s) <= n else s[:n - 1] + "…"
def _pick_status(line: str) -> str:
if "✅" in line: return "✅"
if "⚠️" in line: return "⚠️"
if "❌" in line: return "❌"
return "ℹ️"
def _step_key(line: str) -> str:
for k in STEP_ORDER:
if k in line:
return k
return "信息"
def _pull_resource_snapshot(lines):
res = {}
for line in reversed(lines):
if "☀️" in line:
try: res["sun"] = int(re.findall(r"☀️(\d+)", line)[0])
except: pass
if "💧" in line:
try: res["water"] = int(re.findall(r"💧(\d+)", line)[0])
except: pass
if "🍅" in line:
try: res["fruit"] = int(re.findall(r"🍅(\d+)", line)[0])
except: pass
if len(res) >= 2:
break
return res
def render_report(all_lines):
blocks, cur = [], []
for ln in all_lines:
if ln.strip().startswith("👤 用户"):
if cur: blocks.append(cur)
cur = [ln.strip()]
elif ln is not None:
cur.append(ln.rstrip())
if cur: blocks.append(cur)
out = []
for b in blocks:
if not b: continue
header = b[0].strip()
out.append("━━━━━━━━━━━━━━━━━━━━━━")
out.append(header)
bucket = defaultdict(list)
for ln in b[1:]:
if not ln.strip(): continue
bucket[_step_key(ln)].append(ln)
snap = _pull_resource_snapshot(b)
if snap:
res_line = "📊 当前资源:"
if "sun" in snap: res_line += f"☀️{snap['sun']} "
if "water" in snap: res_line += f"💧{snap['water']} "
if "fruit" in snap: res_line += f"🍅{snap['fruit']} "
out.append(res_line.strip())
for step in STEP_ORDER + ["信息"]:
if step not in bucket: continue
lines = bucket[step]
cleaned, seen = [], set()
for ln in lines:
if set(ln.strip()) in (set("="),):
continue
# 关键修复:循环浇水日志不做去重(保留所有记录)
if step == "循环浇水":
cleaned.append(ln)
else:
norm = re.sub(r"账号\d+:", "", re.sub(r"\s+", " ", ln)).strip()
if norm not in seen:
seen.add(norm)
cleaned.append(ln)
# 循环浇水保留所有记录
if step == "循环浇水":
picked = cleaned
else:
picked = cleaned[-1:] if cleaned else []
for pln in picked:
status = _pick_status(pln)
icon = STEP_EMOJI.get(step, "•")
body = re.sub(r"^[🔑🌱📅🔍🌾🔄]+\s*" + re.escape(step) + r"[::]?\s*", "", pln)
body = re.sub(r"^[✅❌⚠️ℹ️]+\s*", "", body)
out.append(f"{icon} {step} {status} {_short(body)}")
succ = sum("✅" in ln for ln in b)
fail = sum("❌" in ln for ln in b)
warn = sum("⚠️" in ln for ln in b)
out.append(f"🧾 小结:成功 {succ} · 预警 {warn} · 失败 {fail}")
out.append("━━━━━━━━━━━━━━━━━━━━━━")
return "\n".join(out)
return {
"data": base64.b64encode(encrypted_data).decode(),
"key": base64.b64encode(encrypted_key).decode(),
"iv": base64.b64encode(iv).decode(),
}
def login(wid, phone, user_logs):
step = "登录"
# 校验手机号是否存在
if not phone:
msg = "未配置手机号,登录失败 🔒"
print(msg)
user_logs.append(f"🔑 {step}: {msg}")
return None
try:
url = "https://api.zhumanito.cn/api/login"
# 新增手机号参数 wm_phone
payload = {"wid": wid, "wm_phone": phone}
headers = {'User-Agent': user_agent, 'Content-Type': "application/json"}
response = requests.post(url, data=json.dumps(payload), headers=headers)
response.raise_for_status()
dljson = response.json()
# 响应格式新增 code 字段校验
if dljson.get("code") == 200 and 'data' in dljson and 'token' in dljson['data'] and 'user' in dljson['data'] and 'land' in dljson['data']:
msg = f"登录成功(手机号:{phone})✅"
print(msg)
user_logs.append(f"🔑 {step}: {msg}")
time.sleep(random.uniform(4, 5))
return {
"token": dljson['data']['token'],
"user_data": dljson['data']['user'],
"land_data": dljson['data']['land']
}
else:
msg = f"登录失败,返回数据: {dljson} ❌"
print(msg)
user_logs.append(f"🔑 {step}: {msg}")
return None
except Exception as e:
msg = f"登录出错(手机号:{phone}): {str(e)} ❌"
print(msg)
user_logs.append(f"🔑 {step}: {msg}")
return None
def get_seeds(authorization, user_logs):
step = "领取种子"
if not authorization:
msg = "未获取到授权,无法领取种子 🔒"
print(msg)
user_logs.append(f"🌱 {step}: {msg}")
return
try:
url = "https://api.zhumanito.cn/api/guide"
headers = {'User-Agent': user_agent, 'Content-Type': "application/json", 'authorization': authorization}
for st in (1, 2):
payload = {"status": st}
response = requests.post(url, data=json.dumps(payload), headers=headers)
response.raise_for_status()
user_logs.append(f"🌱 {step}: 领取/引导完成 ✅")
time.sleep(random.uniform(4, 5))
except Exception as e:
msg = f"领取种子出错: {str(e)} ❌"
print(msg)
user_logs.append(f"🌱 {step}: {msg}")
def check_in(authorization, user_logs):
step = "签到"
if not authorization:
msg = "未获取到授权,无法签到 🔒"
print(msg)
user_logs.append(f"📅 {step}: {msg}")
return
try:
url = "https://api.zhumanito.cn/api/task/complete"
headers = {'User-Agent': user_agent, 'Content-Type': "application/x-www-form-urlencoded", 'authorization': authorization}
response = requests.post(url, headers=headers)
response_data = response.json()
if response_data.get("msg") == "成功":
msg = "签到成功 ✅"
print(f"签到结果: {msg}")
user_logs.append(f"📅 {step}: {msg}")
elif response_data.get("msg") == "不可重复完成":
msg = "今日已签到,无需重复操作 ✅"
print(f"签到结果: {msg}")
user_logs.append(f"📅 {step}: {msg}")
else:
msg = f"失败,原因: {response_data.get('msg', '未知错误')} ❌"
print(f"签到结果: {msg}")
user_logs.append(f"📅 {step}: {msg}")
time.sleep(random.uniform(4, 5))
except Exception as e:
msg = f"签到出错: {str(e)} ❌"
print(msg)
user_logs.append(f"📅 {step}: {msg}")
def explore(authorization, wid, user_logs):
step = "浏览任务"
if not authorization:
msg = "未获取到授权,无法执行浏览任务 🔒"
print(msg)
user_logs.append(f"🔍 {step}: {msg}")
return
max_retry = 3
retry_count = 0
while retry_count < max_retry:
try:
url = f"https://api.zhumanito.cn/?wid={wid}"
headers = {
'Host': 'api.zhumanito.cn',
'User-Agent': user_agent,
'authorization': authorization,
'sec-ch-ua': '"Chromium";v="142", "Android WebView";v="142", "Not_A Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Android"',
'upgrade-insecure-requests': '1',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/wxpic,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'x-requested-with': 'com.tencent.mm',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'referer': 'https://h5.zhumanito.cn/',
'accept-encoding': 'gzip, deflate, br, zstd',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'priority': 'u=0, i'
}
response = requests.get(url, headers=headers, allow_redirects=False, timeout=10, verify=True)
if response.status_code == 302:
msg = "浏览任务完成✅"
print(f"浏览任务:{msg}")
user_logs.append(f"🔍 {step}: {msg}")
time.sleep(random.uniform(4, 5))
break
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", "1"))
retry_count += 1
if retry_count < max_retry:
msg = f"浏览请求限速,等待{retry_after}秒后重试(第{retry_count}/{max_retry}次)"
print(f"浏览任务:{msg}")
time.sleep(retry_after)
else:
msg = f"浏览请求多次限速,放弃重试 ❌"
print(f"浏览任务:{msg}")
user_logs.append(f"🔍 {step}: {msg}")
else:
msg = f"浏览失败,状态码: {response.status_code} ❌"
print(f"浏览任务:{msg}")
user_logs.append(f"🔍 {step}: {msg}")
break
except requests.exceptions.RequestException as e:
msg = f"浏览任务出错: {str(e)} ❌"
print(msg)
user_logs.append(f"🔍 {step}: {msg}")
break
def harvest(authorization, user_logs, account):
step = "收获作物"
try:
url = "https://api.zhumanito.cn/api/harvest"
headers = {
'User-Agent': user_agent,
'Content-Type': "application/x-www-form-urlencoded;charset=utf-8",
'authorization': authorization
}
before_fruit = int(account["user_data"].get("fruit_num", 0))
response = requests.post(url, headers=headers, data=b"", timeout=15)
response.raise_for_status()
res_json = response.json()
if res_json.get("code") == 200:
account["user_data"] = res_json["data"]["user"]
account["land_data"] = res_json["data"]["land"]
after_fruit = int(account["user_data"].get("fruit_num", 0))
total_after = int(account["user_data"].get("total_fruit_num", after_fruit))
delta = max(0, after_fruit - before_fruit)
msg = f"收获成功!🍅+{delta} → 现有 {after_fruit}(累计 {total_after})"
print(msg)
user_logs.append(f"🌾 {step}: {msg}")
snap_line = f"📊 收获后资源:☀️{account['user_data'].get('sun_num',0)} 💧{account['user_data'].get('water_num',0)} 🍅{after_fruit}"
print(snap_line)
user_logs.append(snap_line)
time.sleep(random.uniform(4, 5))
return True
else:
msg = f"收获失败: {res_json.get('msg', '未知信息')} ⚠️"
print(msg)
user_logs.append(f"🌾 {step}: {msg}")
return False
except Exception as e:
msg = f"收获请求出错: {str(e)} ❌"
print(msg)
user_logs.append(f"🌾 {step}: {msg}")
return False
def plant_seed(authorization, user_logs, account):
step = "播种"
try:
url = "https://api.zhumanito.cn/api/seed"
headers = {
'User-Agent': user_agent,
'Content-Type': "application/x-www-form-urlencoded;charset=utf-8",
'authorization': authorization
}
response = requests.post(url, headers=headers, data=b"", timeout=15)
response.raise_for_status()
res_json = response.json()
if res_json.get("code") == 200:
msg = "播种成功!✅"
print(msg)
user_logs.append(f"🌱 {step}: {msg}")
account["user_data"] = res_json["data"]["user"]
account["land_data"] = res_json["data"]["land"]
time.sleep(random.uniform(4, 5))
return True
else:
msg = f"播种失败: {res_json.get('msg', '未知信息')} ⚠️"
print(msg)
user_logs.append(f"🌱 {step}: {msg}")
return False
except Exception as e:
msg = f"播种请求出错: {str(e)} ❌"
print(msg)
user_logs.append(f"🌱 {step}: {msg}")
return False
def water_once(headers, account_idx):
"""单次浇水(带限速重试)"""
max_retry = 3
retry_count = 0
while retry_count < max_retry:
try:
response = requests.post(
"https://api.zhumanito.cn/api/water",
headers=headers,
data=b"",
allow_redirects=False,
timeout=(25, 30)
)
if response.status_code == 200:
return response.json()
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", "1"))
retry_count += 1
if retry_count < max_retry:
print(f"账号{account_idx}:浇水请求限速,等待{retry_after}秒后重试(第{retry_count}/{max_retry}次)")
time.sleep(retry_after)
else:
raise Exception(f"浇水请求多次限速({max_retry}次),放弃重试")
else:
raise Exception(f"响应状态码异常: {response.status_code},内容: {response.text}")
except json.JSONDecodeError:
raise Exception(f"返回非JSON数据: {response.text}")
except Exception as e:
if retry_count >= max_retry - 1:
raise e
retry_count += 1
time.sleep(1)
return None
def loop_watering(headers, account_idx, account, user_logs):
step = "循环浇水"
user_logs.append(f"🔄 {step}:进入循环浇水(需💧≥20且☀️≥20)")
print(f"\n🔄 账号{account_idx}:进入循环浇水(需💧≥20且☀️≥20)")
water_headers = headers.copy()
water_headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
while True:
water = account["user_data"].get("water_num", 0)
sun = account["user_data"].get("sun_num", 0)
class TomatoClient:
def __init__(self, wid: str, open_id: str, index: int):
self.wid = wid
self.open_id = open_id
self.index = index
self.tomato_user_id = None
self.logs: List[str] = [f"账号{index}"]
if water >= 20 and sun >= 20:
log_msg = f"🔄 {step}:📌 账号{account_idx}:资源满足(💧{water},☀️{sun}),浇水..."
print(log_msg)
user_logs.append(log_msg)
self.session = requests.Session()
self.session.headers.update({
"User-Agent": USER_AGENT,
"Content-Type": "application/json",
"Origin": BASE_URL,
"Referer": f"{BASE_URL}/?wid={wid}&openId={open_id}",
})
def log(self, msg: str):
"""记录日志并打印"""
print(msg)
self.logs.append(msg)
def format_home_status(self, data: Dict, prefix: str = "当前状态") -> str:
return (
f"{prefix}:能量 {data.get('energyBalance', 0)},"
f"番茄 {data.get('tomatoBalance', 0)},"
f"{data.get('stageName', '未知阶段')} "
f"{data.get('currentExp', 0)}/{data.get('stageRequiredExp', 0)}"
)
def request(self, method: str, path: str, payload: Any = None, encrypted: bool = True, retry: int = 2) -> Dict:
url = f"{BASE_URL}{path}"
for attempt in range(retry + 1):
kwargs = {"timeout": 20}
if payload:
kwargs["json"] = encrypt_payload(payload) if encrypted else payload
if encrypted:
kwargs["headers"] = {"X-Request-Encrypted": "true"}
response = self.session.request(method, url, **kwargs)
if response.status_code == 429 and attempt < retry:
retry_after = response.headers.get("Retry-After", "2")
try:
wait_seconds = max(1.0, float(retry_after))
except ValueError:
wait_seconds = 2.0
time.sleep(wait_seconds + attempt)
continue
response.raise_for_status()
try:
res = water_once(water_headers, account_idx)
if res.get("code") == 200:
# 浇水成功,更新用户数据
account["user_data"] = res["data"]["user"]
land = res["data"].get("land", [])
success_msg = f"🔄 {step}:✅ 账号{account_idx}:浇水成功!"
status_msg = f"🔄 {step}:📊 剩余:💧{account['user_data']['water_num']},☀️{account['user_data']['sun_num']}"
print("="*35)
print(success_msg)
print(status_msg)
# 关键修改:每条浇水成功日志都保留(不合并、不删除)
user_logs.append(success_msg)
user_logs.append(status_msg)
if land:
land_msg = f"🔄 {step}:🌱 土地:共{len(land)}块,阶段{land[0]['seed_stage']} 🌱"
print(land_msg)
user_logs.append(land_msg)
print("="*35)
time.sleep(random.uniform(4, 5))
else:
# 浇水失败(如达上限、其他错误)
fail_msg = f"🔄 {step}:❌ 账号{account_idx}:浇水失败:{res.get('msg', '未知错误')}"
print(fail_msg)
user_logs.append(fail_msg)
break
except Exception as e:
error_msg = f"🔄 {step}:⚠️ 账号{account_idx}:浇水请求异常:{str(e)} ❌"
print(error_msg)
user_logs.append(error_msg)
break
else:
end_msg = f"🔄 {step}:🔚 账号{account_idx}:资源不足(💧{water},☀️{sun}),停止浇水 ⏹️"
print(end_msg)
user_logs.append(end_msg)
fruit = account['user_data'].get('fruit_num', 0)
final_snap = f"📊 最终资源:☀️{sun} 💧{water} 🍅{fruit}"
print(final_snap)
user_logs.append(final_snap)
break
result = response.json()
except ValueError as exc:
raise RuntimeError(f"接口返回非 JSON 数据:{response.text[:200]}") from exc
def process_user(wid, phone, user_index):
user_logs = [f"👤 用户{user_index}: wid={wid} | 手机号={phone}"]
print(f"\n===== 开始处理用户 {user_index} (wid: {wid}, 手机号: {phone}) =====")
# 登录时传入 wid 和手机号
login_data = login(wid, phone, user_logs)
if login_data:
auth_token = login_data["token"]
headers = {'User-Agent': user_agent, 'authorization': auth_token}
account = {"user_data": login_data["user_data"], "land_data": login_data["land_data"]}
fruit = account['user_data'].get('fruit_num', 0)
print(f"📊 当前番茄数量:{fruit}")
user_logs.append(f"📊 当前番茄数量:{fruit}")
if account["user_data"].get("new_status", 2) != 2:
get_seeds(auth_token, user_logs)
check_in(auth_token, user_logs)
explore(auth_token, wid, user_logs)
current_stage = 0
if account["land_data"] and len(account["land_data"]) > 0:
current_stage = account["land_data"][0].get("seed_stage", 0)
print(f"\n🧠 账号{user_index}:智能判断... 当前土地状态: {current_stage}")
user_logs.append(f"ℹ️ 土地状态: {current_stage}")
if current_stage == 5:
print("判断:作物已成熟。")
user_logs.append("🧠 判断:作物已成熟。")
print(f">> 账号{user_index}:执行 [收获]...")
harvest_success = harvest(auth_token, user_logs, account)
if harvest_success:
print(f">> 账号{user_index}:执行 [播种]...")
plant_seed(auth_token, user_logs, account)
elif current_stage == 0:
print("判断:土地为空。")
user_logs.append("🧠 判断:土地为空。")
print(f">> 账号{user_index}:执行 [播种]...")
plant_seed(auth_token, user_logs, account)
else:
print("判断:作物生长中... 无需收获或播种。")
user_logs.append("🧠 判断:作物生长中。")
loop_watering(headers, user_index, account, user_logs)
else:
msg = "获取授权失败,无法执行后续操作 🔒"
msg = str(result.get("msg", ""))
if result.get("code") == 200:
return result
if attempt < retry and (response.status_code == 429 or "频繁" in msg or "稍后" in msg):
time.sleep(2.5 + attempt * 1.5)
continue
raise RuntimeError(msg or f"接口返回 code={result.get('code')}")
raise RuntimeError("请求重试后仍未成功")
def run(self) -> List[str]:
"""执行该账号的完整业务流"""
try:
self._do_login()
home_data = self._get_home()
self.log(self.format_home_status(home_data))
self._do_tasks()
self._steal_friend_energy()
home_data = self._get_home()
self.log(self.format_home_status(home_data, "任务后状态"))
self._use_energy(home_data)
final_home = self._get_home()
self.log(self.format_home_status(final_home, "最终状态"))
except Exception as exc:
self.log(f"处理异常:{exc}")
return self.logs
def _do_login(self):
result = self.request(
"POST",
"/api/web/open/tomato/login",
{"shareTomatoUserId": None, "openId": self.open_id, "wid": self.wid, "queryCardStatus": True},
)
data = result.get("data") or {}
token = data.get("token")
if not token:
raise RuntimeError("登录响应中没有 token")
self.session.headers["Authorization"] = token
self.tomato_user_id = data.get("tomatoUserId")
self.log(f"登录成功:{data.get('nickName') or '未设置昵称'}")
def _get_home(self) -> Dict:
return self.request("GET", "/api/web/member/tomato/home").get("data") or {}
def _do_tasks(self):
tasks = self.request("GET", "/api/web/member/tomato/tasks").get("data") or []
completed, skipped = 0, 0
for task in tasks:
name = task.get("taskName") or task.get("taskCode") or "未知任务"
task_type = task.get("taskType")
if task_type == FRIEND_TASK_TYPE:
self.friend_task = task
if str(task.get("completed")) == "1":
self.log(f"任务已完成:{name}")
continue
if str(task.get("completed")) == "1":
self.log(f"任务已完成:{name}")
continue
if task_type not in SUPPORTED_TASK_TYPES:
skipped += 1
self.log(f"跳过任务:{name}(需在小程序内操作)")
continue
try:
payload = {"taskType": task_type}
if task_type != "SHARE":
payload["browseTarget"] = task.get("browseTarget") or ""
elif self.tomato_user_id:
self._create_miniprogram_qrcode()
result = self.request("POST", "/api/web/member/tomato/tasks/complete", payload)
data = result.get("data") or {}
reward = data.get("rewardText") or task.get("rewardText") or "已领取"
self.log(f"任务完成:{name},{reward}")
completed += 1
except Exception as exc:
self.log(f"任务失败:{name},{exc}")
time.sleep(random.uniform(2.5, 3.5))
self.log(f"本次完成任务 {completed} 个,跳过 {skipped} 个")
def _create_miniprogram_qrcode(self):
try:
self.request(
"POST",
"/api/web/member/tomato/miniprogram/qrcode/create",
{"page": "packages/wm-cloud-qiehuang/home/index", "scene": str(self.tomato_user_id)}
)
except Exception:
pass
def _steal_friend_energy(self):
try:
friends = []
page_num = 1
while True:
result = self.request("GET", f"/api/web/member/tomato/friends?pageNum={page_num}&pageSize=20")
rows = result.get("rows") or []
friends.extend(rows)
total = int(result.get("total") or 0)
if not rows or (total and len(friends) >= total) or len(rows) < 20:
break
page_num += 1
claimable = [
f for f in friends
if str(f.get("friendStatus")) == FRIEND_STATUS_CLAIMABLE and f.get("friendTomatoUserId")
]
stolen_count, stolen_energy, failed_count = 0, 0, 0
for friend in claimable:
f_uid = friend["friendTomatoUserId"]
try:
f_home = self.request("GET", f"/api/web/member/tomato/friends/{f_uid}/home").get("data") or {}
amount = int(f_home.get("stealAmount") or 0)
if str(f_home.get("canSteal")) != "1" or amount <= 0:
continue
self.request("POST", "/api/web/member/tomato/friends/steal", {"friendTomatoUserId": f_uid})
stolen_count += 1
stolen_energy += amount
except Exception:
failed_count += 1
time.sleep(random.uniform(1.5, 2.5))
if stolen_count:
detail = f"好友能量:成功收取 {stolen_count} 位好友,共 {stolen_energy} 能量"
if failed_count:
detail += f",失败 {failed_count} 位"
self.log(detail)
elif failed_count:
self.log(f"好友能量:收取失败 {failed_count} 位")
else:
self.log("好友能量:暂无可收取能量")
except Exception as exc:
self.log(f"好友能量失败:{exc}")
def _use_energy(self, home_data: Dict):
energy = int(home_data.get("energyBalance") or 0)
if energy <= 0:
self.log("使用能量:当前没有可用能量")
return
before_tomato = int(home_data.get("tomatoBalance") or 0)
try:
grown = self.request("POST", "/api/web/member/tomato/energy/use", encrypted=False).get("data") or {}
after_tomato = int(grown.get("tomatoBalance") or 0)
gained = int(grown.get("gainedTomatoAmount") or 0)
if not gained:
gained = max(0, after_tomato - before_tomato)
self.log(
f"使用能量:消耗 {grown.get('usedEnergyAmount', energy)},"
f"成长到 {grown.get('stageName', '未知阶段')} "
f"{grown.get('currentExp', 0)}/{grown.get('stageRequiredExp', 0)},"
f"获得番茄 {gained}"
)
except Exception as exc:
self.log(f"使用能量失败:{exc}")
def render_report(all_logs: List[List[str]]) -> str:
lines = ["统一茄皇五期"]
for logs in all_logs:
lines.append("━━━━━━━━━━━━━━━━━━━━")
lines.extend(logs)
lines.append("━━━━━━━━━━━━━━━━━━━━")
return "\n".join(lines)
def safe_send(title: str, message: str):
try:
send(title, message)
except Exception as exc:
print(f"通知发送失败(不影响脚本执行结果):{exc}")
def main():
if CRYPTO_BACKEND is None:
msg = "缺少加密依赖,请安装 cryptography:pip install cryptography"
print(msg)
user_logs.append(f"⚠️ {msg}")
print(f"===== 完成处理用户 {user_index} =====\n")
time.sleep(3)
return user_logs
safe_send("统一茄皇五期", msg)
return
users = parse_users()
if not users:
msg = "没有可用账号:未读取到 tyqh 环境变量,格式:wid#openId"
print(msg)
safe_send("统一茄皇五期", msg)
return
all_logs = []
for index, (wid, open_id) in enumerate(users, 1):
print(f"\n===== 开始处理账号 {index} =====")
client = TomatoClient(wid, open_id, index)
logs = client.run()
all_logs.append(logs)
if index < len(users):
time.sleep(random.uniform(3, 5))
report = render_report(all_logs)
safe_send("统一茄皇五期", report)
if __name__ == "__main__":
if not parsed_users or len(parsed_users) == 0:
print("未从环境变量TYQH中获取到任何用户信息! 🚫")
send("统一茄皇", "未从环境变量TYQH中获取到任何用户信息! 🚫")
else:
print(f"共检测到 {len(parsed_users)} 个用户,开始依次处理... 👥")
all_logs = []
for i, (wid, phone) in enumerate(parsed_users, 1):
try:
user_logs = process_user(wid, phone, i)
all_logs.extend(user_logs)
all_logs.append("")
except Exception as e:
error_msg = f"用户 {i} 处理过程中发生未捕获错误: {str(e)} ❌"
print(error_msg)
all_logs.append(f"❌ {error_msg}")
all_logs.append("")
# 推送完整报告(包含所有浇水成功记录)
report = render_report(all_logs)
print("\n" + "="*50)
print("最终推送通知内容:")
print(report)
print("="*50)
send("统一茄皇", report)
main()