mirror of
https://github.com/wd210010/only_for_happly.git
synced 2026-11-05 20:26:45 +08:00
Update tyqh.py
This commit is contained in:
1 parent
1ad707fbc5
commit
a292ac4061
1 file changed
+707
-258
@@ -1,276 +1,725 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import requests
|
||||
import time
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import re
|
||||
import random
|
||||
import urllib.parse
|
||||
from requests.exceptions import RequestException
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Optional, Any, Tuple
|
||||
from notify import send
|
||||
|
||||
# ==============================================================================
|
||||
# 【环境变量QH配置说明】
|
||||
# 1. 格式:
|
||||
# - 单账号:直接填写wid(例:123456)
|
||||
# - 多账号:支持两种分隔方式,可混合使用
|
||||
# - &分隔:123&456&789
|
||||
# - 换行分隔:每个wid单独占一行(Windows/Linux换行符均兼容)
|
||||
# 2. 关键:脚本仅内部使用wid登录,所有输出不显示wid;UA自动生成(适配小程序)
|
||||
'''
|
||||
不用抓包,直接登录小程序。
|
||||
个人中心---用户设置---用户编号就是需要的wid信息
|
||||
'''
|
||||
# ==============================================================================
|
||||
# -------------------------- 【配置+工具函数】--------------------------
|
||||
def generate_random_ua():
|
||||
"""生成随机UA(适配小程序环境)"""
|
||||
os_mobile_map = [
|
||||
("15_8_3", "15E148"),
|
||||
("16_2_0", "16F203"),
|
||||
("16_5_1", "16H62"),
|
||||
("17_0_3", "17A5844a"),
|
||||
("17_1_1", "17B100"),
|
||||
("17_2_0", "17C304"),
|
||||
("17_3_1", "17D50"),
|
||||
("17_4_1", "17E262"),
|
||||
("17_6_1", "15E148")
|
||||
]
|
||||
os_version, mobile_version = random.choice(os_mobile_map)
|
||||
wechat_version = f"8.0.{random.randint(50, 75)}"
|
||||
return (
|
||||
f"Mozilla/5.0 (iPhone; CPU iPhone OS {os_version} like Mac OS X) "
|
||||
"AppleWebKit/605.1.15 (KHTML, like Gecko) "
|
||||
f"Mobile/{mobile_version} MicroMessenger/{wechat_version} "
|
||||
"NetType/WIFI Language/zh_CN miniProgram/wx532ecb3bdaaf92f9"
|
||||
)
|
||||
# ==================== 配置项 ====================
|
||||
# 环境变量名称 抓包小程序wid即可
|
||||
ENV_KEY = "TYQH"
|
||||
# 请求超时时间(秒)
|
||||
REQUEST_TIMEOUT = 15
|
||||
# 基础等待时间范围(秒)
|
||||
BASE_WAIT_RANGE = (4, 5)
|
||||
# 账号间等待时间(秒)
|
||||
ACCOUNT_INTERVAL = 3
|
||||
# 浇水最大重试次数
|
||||
WATER_MAX_RETRY = 3
|
||||
# API基础配置
|
||||
API_BASE_URL = "https://api.zhumanito.cn/api"
|
||||
# User-Agent配置
|
||||
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"
|
||||
)
|
||||
|
||||
def parse_qh_env():
|
||||
"""解析QH环境变量(支持&和换行分隔多账号)"""
|
||||
qh_env = os.getenv("QH", "")
|
||||
if not qh_env:
|
||||
print("❌ 错误:未检测到环境变量QH,请按配置说明设置!")
|
||||
return None
|
||||
|
||||
unified_env = qh_env.replace("\r\n", "&").replace("\n", "&")
|
||||
account_str_list = unified_env.split("&")
|
||||
|
||||
accounts = []
|
||||
for idx, account_str in enumerate(account_str_list, 1):
|
||||
wid = account_str.strip()
|
||||
if not wid:
|
||||
print(f"⚠️ 检测到第{idx}个无效项(空内容),已跳过")
|
||||
continue
|
||||
|
||||
ua = generate_random_ua()
|
||||
accounts.append({
|
||||
"index": idx,
|
||||
"wid": wid,
|
||||
"token": "",
|
||||
"ua": ua,
|
||||
"user_data": {},
|
||||
"land_data": []
|
||||
})
|
||||
|
||||
if not accounts:
|
||||
print("❌ 没有可用账号(所有项格式错误或为空),脚本终止")
|
||||
return None
|
||||
return accounts
|
||||
# 步骤配置
|
||||
STEP_CONFIG = {
|
||||
"login": {"name": "登录", "emoji": "🔑"},
|
||||
"get_seeds": {"name": "领取种子", "emoji": "🌱"},
|
||||
"check_in": {"name": "签到", "emoji": "📅"},
|
||||
"explore": {"name": "浏览任务", "emoji": "🔍"},
|
||||
"harvest": {"name": "收获作物", "emoji": "🌾"},
|
||||
"plant_seed": {"name": "播种", "emoji": "🌱"},
|
||||
"watering": {"name": "循环浇水", "emoji": "🔄"},
|
||||
"info": {"name": "信息", "emoji": "•"}
|
||||
}
|
||||
STEP_ORDER = [v["name"] for k, v in STEP_CONFIG.items() if k != "info"] + [STEP_CONFIG["info"]["name"]]
|
||||
|
||||
def get_account_headers(account):
|
||||
"""生成账号请求头(匹配抓包标准)"""
|
||||
headers = {
|
||||
"Authorization": account["token"],
|
||||
"User-Agent": account["ua"],
|
||||
"Origin": "https://h5.zhumanito.cn",
|
||||
"Referer": "https://h5.zhumanito.cn/",
|
||||
"Accept-Language": "zh-CN,zh-Hans;q=0.9",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Connection": "keep-alive"
|
||||
# 状态图标
|
||||
STATUS_ICONS = {
|
||||
"success": "✅",
|
||||
"warning": "⚠️",
|
||||
"error": "❌",
|
||||
"info": "ℹ️"
|
||||
}
|
||||
|
||||
# ==================== 工具函数 ====================
|
||||
def short_text(text: str, max_length: int = 120) -> str:
|
||||
"""截断文本并添加省略号"""
|
||||
text = text.strip()
|
||||
return text if len(text) <= max_length else f"{text[:max_length-1]}…"
|
||||
|
||||
def extract_status(text: str) -> str:
|
||||
"""提取状态图标"""
|
||||
if STATUS_ICONS["success"] in text:
|
||||
return STATUS_ICONS["success"]
|
||||
elif STATUS_ICONS["warning"] in text:
|
||||
return STATUS_ICONS["warning"]
|
||||
elif STATUS_ICONS["error"] in text:
|
||||
return STATUS_ICONS["error"]
|
||||
return STATUS_ICONS["info"]
|
||||
|
||||
def get_step_key(text: str) -> str:
|
||||
"""获取步骤名称"""
|
||||
for step_name in STEP_ORDER:
|
||||
if step_name in text:
|
||||
return step_name
|
||||
return STEP_CONFIG["info"]["name"]
|
||||
|
||||
def extract_resource_snapshot(lines: List[str]) -> Dict[str, int]:
|
||||
"""提取资源快照(阳光、水、番茄)"""
|
||||
resources = {}
|
||||
patterns = {
|
||||
"sun": r"☀️(\d+)",
|
||||
"water": r"💧(\d+)",
|
||||
"fruit": r"🍅(\d+)"
|
||||
}
|
||||
# 补充Cookie标识,模拟真实用户
|
||||
if account.get("wid"):
|
||||
headers["Cookie"] = f"rprm_cuid={account['wid']}; rprm_uuid={account['wid']}"
|
||||
|
||||
for line in reversed(lines):
|
||||
for res_type, pattern in patterns.items():
|
||||
if res_type in resources:
|
||||
continue
|
||||
|
||||
match = re.search(pattern, line)
|
||||
if match:
|
||||
try:
|
||||
resources[res_type] = int(match.group(1))
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
if len(resources) >= 2:
|
||||
break
|
||||
|
||||
return resources
|
||||
|
||||
def render_report(all_lines: List[str]) -> str:
|
||||
"""渲染执行报告"""
|
||||
# 按用户分组
|
||||
blocks = []
|
||||
current_block = []
|
||||
|
||||
for line in all_lines:
|
||||
if line.strip().startswith("👤 用户"):
|
||||
if current_block:
|
||||
blocks.append(current_block)
|
||||
current_block = [line.strip()]
|
||||
elif line is not None and line.strip():
|
||||
current_block.append(line.rstrip())
|
||||
|
||||
if current_block:
|
||||
blocks.append(current_block)
|
||||
|
||||
# 生成报告内容
|
||||
report_lines = []
|
||||
for block in blocks:
|
||||
if not block:
|
||||
continue
|
||||
|
||||
# 添加分隔线和用户信息
|
||||
report_lines.append("━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
report_lines.append(block[0])
|
||||
|
||||
# 按步骤分类日志
|
||||
step_buckets = defaultdict(list)
|
||||
for line in block[1:]:
|
||||
step_key = get_step_key(line)
|
||||
step_buckets[step_key].append(line)
|
||||
|
||||
# 添加资源快照
|
||||
resource_snap = extract_resource_snapshot(block)
|
||||
if resource_snap:
|
||||
resource_line = "📊 当前资源:"
|
||||
if "sun" in resource_snap:
|
||||
resource_line += f"☀️{resource_snap['sun']} "
|
||||
if "water" in resource_snap:
|
||||
resource_line += f"💧{resource_snap['water']} "
|
||||
if "fruit" in resource_snap:
|
||||
resource_line += f"🍅{resource_snap['fruit']} "
|
||||
report_lines.append(resource_line.strip())
|
||||
|
||||
# 添加各步骤执行结果
|
||||
for step in STEP_ORDER:
|
||||
if step not in step_buckets:
|
||||
continue
|
||||
|
||||
# 去重并清理日志行
|
||||
unique_lines = []
|
||||
seen = set()
|
||||
|
||||
for line in step_buckets[step]:
|
||||
# 跳过分隔线
|
||||
if set(line.strip()) == set("="):
|
||||
continue
|
||||
|
||||
normalized = re.sub(r"\s+", " ", line).strip()
|
||||
if normalized not in seen:
|
||||
seen.add(normalized)
|
||||
unique_lines.append(normalized)
|
||||
|
||||
# 选择要显示的行(循环浇水显示所有,其他显示最后一条)
|
||||
display_lines = unique_lines if step == STEP_CONFIG["watering"]["name"] else unique_lines[-1:]
|
||||
|
||||
# 添加到报告
|
||||
for line in display_lines:
|
||||
status = extract_status(line)
|
||||
emoji = next(v["emoji"] for k, v in STEP_CONFIG.items() if v["name"] == step)
|
||||
# 移除前缀
|
||||
clean_line = re.sub(
|
||||
rf"^[{''.join(v['emoji'] for v in STEP_CONFIG.values())}]\s*{re.escape(step)}[::]?\s*",
|
||||
"", line
|
||||
)
|
||||
report_lines.append(f"{emoji} {step} {status} {short_text(clean_line)}")
|
||||
|
||||
# 添加小结
|
||||
success_count = sum(STATUS_ICONS["success"] in line for line in block)
|
||||
warning_count = sum(STATUS_ICONS["warning"] in line for line in block)
|
||||
error_count = sum(STATUS_ICONS["error"] in line for line in block)
|
||||
report_lines.append(f"🧾 小结:成功 {success_count} · 预警 {warning_count} · 失败 {error_count}")
|
||||
|
||||
# 添加结束分隔线
|
||||
report_lines.append("━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
|
||||
return "\n".join(report_lines)
|
||||
|
||||
def random_sleep(min_seconds: float = None, max_seconds: float = None) -> None:
|
||||
"""随机等待"""
|
||||
min_s = min_seconds or BASE_WAIT_RANGE[0]
|
||||
max_s = max_seconds or BASE_WAIT_RANGE[1]
|
||||
time.sleep(random.uniform(min_s, max_s))
|
||||
|
||||
def create_headers(auth_token: str = None, content_type: str = "application/json") -> Dict[str, str]:
|
||||
"""创建请求头"""
|
||||
headers = {
|
||||
"User-Agent": USER_AGENT,
|
||||
"Content-Type": content_type
|
||||
}
|
||||
if auth_token:
|
||||
headers["Authorization"] = auth_token
|
||||
return headers
|
||||
|
||||
def login_account(account):
|
||||
"""账号自动登录(隐藏wid显示)"""
|
||||
login_url = "https://api.zhumanito.cn/api/login"
|
||||
headers = get_account_headers(account)
|
||||
headers["Content-Type"] = "application/json;charset=utf-8"
|
||||
payload = {"wid": account["wid"]}
|
||||
# ==================== 核心功能函数 ====================
|
||||
def login_account(wid: str, user_logs: List[str]) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
登录账号
|
||||
:param wid: 用户ID
|
||||
:param user_logs: 日志列表
|
||||
:return: 登录数据字典或None
|
||||
"""
|
||||
step = STEP_CONFIG["login"]["name"]
|
||||
emoji = STEP_CONFIG["login"]["emoji"]
|
||||
|
||||
try:
|
||||
print(f"🔐 账号{account['index']}:发起登录请求")
|
||||
response = requests.post(login_url, headers=headers, json=payload, timeout=20)
|
||||
url = f"{API_BASE_URL}/login"
|
||||
payload = {"wid": wid}
|
||||
headers = create_headers()
|
||||
|
||||
response = requests.post(
|
||||
url,
|
||||
data=json.dumps(payload),
|
||||
headers=headers,
|
||||
timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
response.raise_for_status()
|
||||
res = response.json()
|
||||
|
||||
if res.get("code") != 200:
|
||||
print(f"❌ 账号{account['index']}:登录失败,原因:{res.get('msg', '未知错误')}")
|
||||
return False
|
||||
result = response.json()
|
||||
|
||||
account["token"] = res["data"]["token"]
|
||||
account["user_data"] = res["data"]["user"]
|
||||
account["land_data"] = res["data"].get("land", [])
|
||||
|
||||
print(f"✅ 账号{account['index']}:登录成功!")
|
||||
print(f" 📌 当前资源:💧{account['user_data']['water_num']},☀️{account['user_data']['sun_num']},🍀{account['user_data']['seed_num']},🍎{account['user_data']['fruit_num']}")
|
||||
if account["land_data"]:
|
||||
print(f" 🌱 土地状态:共{len(account['land_data'])}块,生长阶段{account['land_data'][0]['seed_stage']}")
|
||||
return True
|
||||
# 检查登录结果
|
||||
if all(key in result.get("data", {}) for key in ["token", "user", "land"]):
|
||||
msg = "登录成功 ✅"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
random_sleep()
|
||||
return {
|
||||
"token": result["data"]["token"],
|
||||
"user_data": result["data"]["user"],
|
||||
"land_data": result["data"]["land"]
|
||||
}
|
||||
else:
|
||||
msg = f"登录失败,返回数据不完整: {json.dumps(result, ensure_ascii=False)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return None
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
msg = f"登录请求出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return None
|
||||
except Exception as e:
|
||||
msg = f"登录处理出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return None
|
||||
|
||||
def collect_seeds(auth_token: str, user_logs: List[str]) -> None:
|
||||
"""领取种子/引导任务"""
|
||||
step = STEP_CONFIG["get_seeds"]["name"]
|
||||
emoji = STEP_CONFIG["get_seeds"]["emoji"]
|
||||
|
||||
except RequestException as e:
|
||||
print(f"❌ 账号{account['index']}:登录异常,原因:{str(e)}")
|
||||
return False
|
||||
|
||||
def get_user_status(account):
|
||||
"""获取账号当前资源(水、阳光、种子、果实)"""
|
||||
if not account.get("user_data"):
|
||||
print(f"⚠️ 账号{account['index']}:未获取到用户数据,返回默认资源值0")
|
||||
return 0, 0, 0, 0
|
||||
water = account["user_data"].get("water_num", 0)
|
||||
sun = account["user_data"].get("sun_num", 0)
|
||||
seed = account["user_data"].get("seed_num", 0)
|
||||
fruit = account["user_data"].get("fruit_num", 0)
|
||||
return water, sun, seed, fruit
|
||||
|
||||
def check_land_mature(account):
|
||||
"""检查土地是否成熟(seed_stage=0 或 5 判定为可收获/可播种)"""
|
||||
if not account.get("land_data"):
|
||||
print(f"⚠️ 账号{account['index']}:未获取到土地数据,默认判定为未成熟")
|
||||
return False
|
||||
return any(land["seed_stage"] in (0, 5) for land in account["land_data"])
|
||||
|
||||
# -------------------------- 【核心操作函数】--------------------------
|
||||
def complete_harvest(headers, account_idx, account):
|
||||
"""执行收获操作(支持seed_stage=0/5成熟阶段)"""
|
||||
harvest_url = "https://api.zhumanito.cn/api/harvest"
|
||||
try:
|
||||
mature_stages = [land["seed_stage"] for land in account["land_data"] if land["seed_stage"] in (0, 5)]
|
||||
print("=" * 35)
|
||||
print(f"🍎 账号{account_idx}:开始执行收获操作(成熟阶段:{mature_stages})")
|
||||
harvest_headers = headers.copy()
|
||||
harvest_headers["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8"
|
||||
|
||||
response = requests.post(harvest_url, headers=harvest_headers, data=b"", timeout=(25, 30))
|
||||
response.raise_for_status()
|
||||
res = response.json()
|
||||
|
||||
if res.get("code") != 200:
|
||||
print(f"❌ 账号{account_idx}:收获失败,原因:{res.get('msg', '未知错误')}")
|
||||
return False
|
||||
|
||||
account["user_data"] = res["data"]["user"]
|
||||
account["land_data"] = res["data"].get("land", [])
|
||||
|
||||
current_water, current_sun, current_seed, current_fruit = get_user_status(account)
|
||||
land_count = len(account["land_data"])
|
||||
print(f"✅ 账号{account_idx}:收获成功!")
|
||||
print(f"📊 收获后资源:💧{current_water},☀️{current_sun},🍀{current_seed},🍎{current_fruit}")
|
||||
if land_count > 0:
|
||||
print(f"🌱 土地状态:共{land_count}块,当前阶段{account['land_data'][0]['seed_stage']}")
|
||||
print("=" * 35)
|
||||
return True
|
||||
|
||||
except RequestException as e:
|
||||
print(f"❌ 账号{account_idx}:收获异常,原因:{str(e)}")
|
||||
return False
|
||||
|
||||
def complete_seed(headers, account_idx, account):
|
||||
"""执行播种操作(无重试)"""
|
||||
seed_url = "https://api.zhumanito.cn/api/seed"
|
||||
_, _, current_seed, _ = get_user_status(account)
|
||||
if current_seed < 1:
|
||||
print(f"⚠️ 账号{account_idx}:种子数量不足(当前🍀{current_seed}),无法播种")
|
||||
return False
|
||||
|
||||
try:
|
||||
print("=" * 35)
|
||||
print(f"🌱 账号{account_idx}:开始执行播种操作(当前种子:🍀{current_seed})")
|
||||
seed_headers = headers.copy()
|
||||
seed_headers["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8"
|
||||
|
||||
response = requests.post(seed_url, headers=seed_headers, data=b"", timeout=(25, 30))
|
||||
response.raise_for_status()
|
||||
res = response.json()
|
||||
|
||||
if res.get("code") != 200:
|
||||
print(f"❌ 账号{account_idx}:播种失败,原因:{res.get('msg', '未知错误')}")
|
||||
return False
|
||||
|
||||
account["user_data"] = res["data"]["user"]
|
||||
account["land_data"] = res["data"]["land"]
|
||||
|
||||
current_water, current_sun, new_seed, current_fruit = get_user_status(account)
|
||||
land_count = len(account["land_data"])
|
||||
print(f"✅ 账号{account_idx}:播种成功!")
|
||||
print(f"📊 播种后资源:💧{current_water},☀️{current_sun},🍀{new_seed},🍎{current_fruit}")
|
||||
print(f"🌱 土地状态:共{land_count}块,生长阶段{account['land_data'][0]['seed_stage']}")
|
||||
print("=" * 35)
|
||||
return True
|
||||
|
||||
except RequestException as e:
|
||||
print(f"❌ 账号{account_idx}:播种异常,原因:{str(e)}")
|
||||
return False
|
||||
|
||||
# -------------------------- 【主流程】--------------------------
|
||||
def auto_multi_account():
|
||||
"""多账号自动处理主流程:登录→检测成熟→收获→播种"""
|
||||
accounts = parse_qh_env()
|
||||
if not accounts:
|
||||
if not auth_token:
|
||||
msg = "未获取到授权,无法领取种子 🔒"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return
|
||||
|
||||
for account in accounts:
|
||||
account_idx = account["index"]
|
||||
total_accounts = len(accounts)
|
||||
print(f"\n" + "=" * 35)
|
||||
print(f"📌 正在处理账号 {account_idx}/{total_accounts}")
|
||||
print("=" * 35)
|
||||
|
||||
# 1. 账号登录
|
||||
login_success = login_account(account)
|
||||
if not login_success:
|
||||
print(f"❌ 账号{account_idx}:登录失败,跳过后续所有操作")
|
||||
continue
|
||||
|
||||
# 2. 获取请求头
|
||||
account_headers = get_account_headers(account)
|
||||
|
||||
# 3. 收获→播种流程
|
||||
print(f"\n🔄 账号{account_idx}:进入收获→播种流程")
|
||||
|
||||
# 3.1 检测土地成熟度并收获
|
||||
if check_land_mature(account):
|
||||
print(f"\n📌 账号{account_idx}:检测到土地成熟,执行收获")
|
||||
harvest_success = complete_harvest(account_headers, account_idx, account)
|
||||
if harvest_success:
|
||||
time.sleep(2)
|
||||
else:
|
||||
print(f"\n📌 账号{account_idx}:土地未成熟,跳过收获")
|
||||
|
||||
# 3.2 播种(种子≥1且土地可播种)
|
||||
_, _, current_seed, _ = get_user_status(account)
|
||||
if current_seed >= 1 and check_land_mature(account):
|
||||
print(f"\n📌 账号{account_idx}:种子充足且土地可播种,执行播种")
|
||||
complete_seed(account_headers, account_idx, account)
|
||||
else:
|
||||
print(f"\n📌 账号{account_idx}:种子不足(🍀{current_seed})或土地不可播种,跳过播种")
|
||||
|
||||
# 单个账号处理完成
|
||||
print(f"\n✅ 账号{account_idx}/{total_accounts}:收获播种流程处理完毕")
|
||||
if account_idx < total_accounts:
|
||||
delay_time = 5
|
||||
print(f"⏳ 账号间延迟{delay_time}秒,准备处理下一个账号...\n")
|
||||
time.sleep(delay_time)
|
||||
|
||||
# 所有账号处理完成
|
||||
print("\n" + "=" * 35)
|
||||
print("🎯 所有账号收获播种流程已全部处理完成!脚本执行结束")
|
||||
print("=" * 35)
|
||||
|
||||
# 脚本入口
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
auto_multi_account()
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n" + "=" * 35)
|
||||
print("⚠️ 脚本被手动终止")
|
||||
print("=" * 35)
|
||||
url = f"{API_BASE_URL}/guide"
|
||||
headers = create_headers(auth_token)
|
||||
|
||||
# 执行引导步骤1和2
|
||||
for status in (1, 2):
|
||||
payload = {"status": status}
|
||||
response = requests.post(
|
||||
url,
|
||||
data=json.dumps(payload),
|
||||
headers=headers,
|
||||
timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
msg = "领取/引导完成 ✅"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
random_sleep()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
msg = f"领取种子请求出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
except Exception as e:
|
||||
msg = f"领取种子处理出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
|
||||
def do_check_in(auth_token: str, user_logs: List[str]) -> None:
|
||||
"""签到"""
|
||||
step = STEP_CONFIG["check_in"]["name"]
|
||||
emoji = STEP_CONFIG["check_in"]["emoji"]
|
||||
|
||||
if not auth_token:
|
||||
msg = "未获取到授权,无法签到 🔒"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return
|
||||
|
||||
try:
|
||||
url = f"{API_BASE_URL}/task/complete"
|
||||
headers = create_headers(auth_token, "application/x-www-form-urlencoded")
|
||||
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
result = response.json()
|
||||
|
||||
# 处理签到结果
|
||||
msg = result.get("msg", "未知错误")
|
||||
if msg == "成功":
|
||||
status_msg = "签到成功 ✅"
|
||||
elif msg == "不可重复完成":
|
||||
status_msg = "今日已签到,无需重复操作 ✅"
|
||||
else:
|
||||
status_msg = f"失败,原因: {msg} ❌"
|
||||
|
||||
user_logs.append(f"{emoji} {step}: {status_msg}")
|
||||
random_sleep()
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
msg = f"签到请求出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
except Exception as e:
|
||||
msg = f"签到处理出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
|
||||
def browse_tasks(auth_token: str, wid: str, user_logs: List[str]) -> None:
|
||||
"""浏览任务"""
|
||||
step = STEP_CONFIG["explore"]["name"]
|
||||
emoji = STEP_CONFIG["explore"]["emoji"]
|
||||
|
||||
if not auth_token:
|
||||
msg = "未获取到授权,无法执行浏览任务 🔒"
|
||||
user_logs.append(f"{emoji} {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': auth_token,
|
||||
'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'
|
||||
}
|
||||
|
||||
# 禁用重定向,手动处理302
|
||||
response = requests.get(
|
||||
url,
|
||||
headers=headers,
|
||||
allow_redirects=False,
|
||||
timeout=REQUEST_TIMEOUT,
|
||||
verify=True
|
||||
)
|
||||
|
||||
if response.status_code == 302:
|
||||
msg = "浏览任务完成 ✅"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
random_sleep()
|
||||
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}次)"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
time.sleep(retry_after)
|
||||
else:
|
||||
msg = f"浏览请求多次限速,放弃重试 ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
else:
|
||||
msg = f"浏览失败,状态码: {response.status_code} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
break
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
msg = f"浏览任务请求出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
break
|
||||
except Exception as e:
|
||||
msg = f"浏览任务处理出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
break
|
||||
|
||||
def harvest_crops(auth_token: str, user_logs: List[str], account: Dict[str, Any]) -> bool:
|
||||
"""收获作物"""
|
||||
step = STEP_CONFIG["harvest"]["name"]
|
||||
emoji = STEP_CONFIG["harvest"]["emoji"]
|
||||
|
||||
try:
|
||||
url = f"{API_BASE_URL}/harvest"
|
||||
headers = create_headers(auth_token, "application/x-www-form-urlencoded;charset=utf-8")
|
||||
|
||||
# 记录收获前的番茄数量
|
||||
before_fruit = int(account["user_data"].get("fruit_num", 0))
|
||||
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
data=b"",
|
||||
timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get("code") == 200:
|
||||
# 更新账号数据
|
||||
account["user_data"] = result["data"]["user"]
|
||||
account["land_data"] = result["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})✅"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
|
||||
# 记录资源快照
|
||||
snap_msg = f"📊 收获后资源:☀️{account['user_data'].get('sun_num',0)} 💧{account['user_data'].get('water_num',0)} 🍅{after_fruit}"
|
||||
user_logs.append(snap_msg)
|
||||
|
||||
random_sleep()
|
||||
return True
|
||||
else:
|
||||
msg = f"收获失败: {result.get('msg', '未知信息')} ⚠️"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return False
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
msg = f"收获请求出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return False
|
||||
except Exception as e:
|
||||
msg = f"收获处理出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return False
|
||||
|
||||
def plant_seeds(auth_token: str, user_logs: List[str], account: Dict[str, Any]) -> bool:
|
||||
"""播种"""
|
||||
step = STEP_CONFIG["plant_seed"]["name"]
|
||||
emoji = STEP_CONFIG["plant_seed"]["emoji"]
|
||||
|
||||
try:
|
||||
url = f"{API_BASE_URL}/seed"
|
||||
headers = create_headers(auth_token, "application/x-www-form-urlencoded;charset=utf-8")
|
||||
|
||||
response = requests.post(
|
||||
url,
|
||||
headers=headers,
|
||||
data=b"",
|
||||
timeout=REQUEST_TIMEOUT
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
|
||||
if result.get("code") == 200:
|
||||
msg = "播种成功!✅"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
|
||||
# 更新账号数据
|
||||
account["user_data"] = result["data"]["user"]
|
||||
account["land_data"] = result["data"]["land"]
|
||||
|
||||
random_sleep()
|
||||
return True
|
||||
else:
|
||||
msg = f"播种失败: {result.get('msg', '未知信息')} ⚠️"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return False
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
msg = f"播种请求出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return False
|
||||
except Exception as e:
|
||||
msg = f"播种处理出错: {str(e)} ❌"
|
||||
user_logs.append(f"{emoji} {step}: {msg}")
|
||||
return False
|
||||
|
||||
def water_once_request(headers: Dict[str, str], account_idx: int) -> Optional[Dict[str, Any]]:
|
||||
"""单次浇水请求(带重试)"""
|
||||
retry_count = 0
|
||||
|
||||
while retry_count < WATER_MAX_RETRY:
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{API_BASE_URL}/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 < WATER_MAX_RETRY:
|
||||
print(f"账号{account_idx}:浇水请求限速,等待{retry_after}秒后重试(第{retry_count}/{WATER_MAX_RETRY}次)")
|
||||
time.sleep(retry_after)
|
||||
else:
|
||||
raise Exception(f"浇水请求多次限速({WATER_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 >= WATER_MAX_RETRY - 1:
|
||||
raise e
|
||||
retry_count += 1
|
||||
time.sleep(1)
|
||||
|
||||
return None
|
||||
|
||||
def loop_watering_process(headers: Dict[str, str], account_idx: int,
|
||||
account: Dict[str, Any], user_logs: List[str]) -> None:
|
||||
"""循环浇水"""
|
||||
step = STEP_CONFIG["watering"]["name"]
|
||||
emoji = STEP_CONFIG["watering"]["emoji"]
|
||||
|
||||
user_logs.append(f"{emoji} {step}:进入循环浇水(需💧≥20且☀️≥20)")
|
||||
print(f"\n{emoji} 账号{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)
|
||||
|
||||
# 检查资源是否满足
|
||||
if water >= 20 and sun >= 20:
|
||||
log_msg = f"📌 账号{account_idx}:资源满足(💧{water},☀️{sun}),浇水..."
|
||||
print(log_msg)
|
||||
user_logs.append(log_msg)
|
||||
|
||||
try:
|
||||
# 执行浇水
|
||||
result = water_once_request(water_headers, account_idx)
|
||||
|
||||
if result and result.get("code") == 200:
|
||||
# 更新账号数据
|
||||
account["user_data"] = result["data"]["user"]
|
||||
|
||||
# 记录成功信息
|
||||
success_msg = f"✅ 账号{account_idx}:浇水成功!"
|
||||
status_msg = f"📊 剩余:💧{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)
|
||||
|
||||
# 记录土地状态
|
||||
land = result["data"].get("land", [])
|
||||
if land:
|
||||
land_msg = f"🌱 土地:共{len(land)}块,阶段{land[0]['seed_stage']} 🌱"
|
||||
print(land_msg)
|
||||
user_logs.append(land_msg)
|
||||
print("="*35)
|
||||
|
||||
random_sleep()
|
||||
else:
|
||||
# 浇水失败
|
||||
fail_msg = f"❌ 账号{account_idx}:浇水失败:{result.get('msg', '未知错误') if result else '无响应'}"
|
||||
print(fail_msg)
|
||||
user_logs.append(f"{emoji} {step}:{fail_msg}")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"⚠️ 账号{account_idx}:浇水请求异常:{str(e)} ❌"
|
||||
print(error_msg)
|
||||
user_logs.append(f"{emoji} {step}:{error_msg}")
|
||||
break
|
||||
else:
|
||||
# 资源不足,停止浇水
|
||||
end_msg = f"🔚 账号{account_idx}:资源不足(💧{water},☀️{sun}),停止浇水 ⏹️"
|
||||
print(end_msg)
|
||||
user_logs.append(f"{STATUS_ICONS['info']} {step}:{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
|
||||
|
||||
def process_single_user(wid: str, user_index: int) -> List[str]:
|
||||
"""处理单个用户"""
|
||||
user_logs = [f"👤 用户{user_index}: {wid}"]
|
||||
print(f"\n===== 开始处理用户 {user_index} (wid: {wid}) =====")
|
||||
|
||||
# 登录
|
||||
login_data = login_account(wid, user_logs)
|
||||
if not login_data:
|
||||
msg = "获取授权失败,无法执行后续操作 🔒"
|
||||
print(msg)
|
||||
user_logs.append(f"{STATUS_ICONS['warning']} {msg}")
|
||||
print(f"===== 完成处理用户 {user_index} =====\n")
|
||||
time.sleep(ACCOUNT_INTERVAL)
|
||||
return user_logs
|
||||
|
||||
# 登录成功,继续处理
|
||||
auth_token = login_data["token"]
|
||||
headers = create_headers(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:
|
||||
collect_seeds(auth_token, user_logs)
|
||||
|
||||
# 执行签到
|
||||
do_check_in(auth_token, user_logs)
|
||||
|
||||
# 执行浏览任务
|
||||
browse_tasks(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"{STATUS_ICONS['info']} 土地状态: {current_stage}")
|
||||
|
||||
if current_stage == 5:
|
||||
print("判断:作物已成熟。")
|
||||
user_logs.append("🧠 判断:作物已成熟。")
|
||||
print(f">> 账号{user_index}:执行 [收获]...")
|
||||
harvest_success = harvest_crops(auth_token, user_logs, account)
|
||||
if harvest_success:
|
||||
print(f">> 账号{user_index}:执行 [播种]...")
|
||||
plant_seeds(auth_token, user_logs, account)
|
||||
elif current_stage == 0:
|
||||
print("判断:土地为空。")
|
||||
user_logs.append("🧠 判断:土地为空。")
|
||||
print(f">> 账号{user_index}:执行 [播种]...")
|
||||
plant_seeds(auth_token, user_logs, account)
|
||||
else:
|
||||
print("判断:作物生长中... 无需收获或播种。")
|
||||
user_logs.append("🧠 判断:作物生长中。")
|
||||
|
||||
# 循环浇水
|
||||
loop_watering_process(headers, user_index, account, user_logs)
|
||||
|
||||
print(f"===== 完成处理用户 {user_index} =====\n")
|
||||
time.sleep(ACCOUNT_INTERVAL)
|
||||
return user_logs
|
||||
|
||||
# ==================== 主程序 ====================
|
||||
def main():
|
||||
"""主函数"""
|
||||
# 获取用户列表
|
||||
users_str = os.getenv(ENV_KEY, "")
|
||||
users = [user.strip() for user in users_str.split("&") if user.strip()]
|
||||
|
||||
if not users:
|
||||
error_msg = f"未从环境变量{ENV_KEY}中获取到任何用户信息! 🚫"
|
||||
print(error_msg)
|
||||
send("统一茄皇", error_msg)
|
||||
return
|
||||
|
||||
print(f"共检测到 {len(users)} 个用户,开始依次处理... 👥")
|
||||
|
||||
# 处理所有用户
|
||||
all_logs = []
|
||||
for index, user_wid in enumerate(users, 1):
|
||||
try:
|
||||
user_logs = process_single_user(user_wid, index)
|
||||
all_logs.extend(user_logs)
|
||||
all_logs.append("")
|
||||
except Exception as e:
|
||||
error_msg = f"用户 {index} 处理过程中发生未捕获错误: {str(e)} ❌"
|
||||
print(error_msg)
|
||||
all_logs.append(f"{STATUS_ICONS['error']} {error_msg}")
|
||||
all_logs.append("")
|
||||
|
||||
# 生成并发送报告
|
||||
report = render_report(all_logs)
|
||||
print("\n" + "="*50)
|
||||
print("执行报告:")
|
||||
print(report)
|
||||
print("="*50)
|
||||
|
||||
send("统一茄皇", report)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user