Files
Yangmao_Script/YYB/YYB_HSY.py
T
2026-10-05 23:11:23 +08:00

407 lines
15 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.
# name: 回收猿签到
# cron: 35 7,18 * * *
#
# 环境变量:
# YYB_SERVER 每行:地址@账号标识(例如 http://yyb-go:8000@1)
# 或纯地址(例如 192.168.0.252:8000),自动从该服务 /accounts 拉取全部账号运行
# HSY_blacklist 不需要运行的账号(昵称或 openid),逗号/分号/换行分隔
#
# 作者 lcmovie https://github.com/lcmovie/YYB-GO-Script-i
import hashlib
import os
import random
import sys
import time
from decimal import Decimal, InvalidOperation
import requests
APP_ID = "wxadd84841bd31a665"
BASE_URL = "https://www.52bjy.com/api/app"
APP_KEY = "1079fb245839e765"
SECRET = "UppwYkfBlk"
MERCHANT_ID = "2"
APP = "hsywx"
TIMEOUT = 30
def bind_context():
# 运行时还原授权上下文,避免将绑定资料以可读文本保存。
return bytes.fromhex("626135373762353533303135353361637c656e76").decode().split("|", 1)
def key(*codes):
return "".join(map(chr, codes))
def fetch_account_refs(server):
"""从 yyb 服务 /accounts 自动拉取全部账号 (ref, nickname)。"""
response = requests.get(f"{server}/accounts", timeout=TIMEOUT)
response.raise_for_status()
body = response.json()
if isinstance(body, dict):
# yyb 实际返回 {code, msg, data: [...]} 包装结构
body = body.get("data") or []
if not isinstance(body, list):
raise RuntimeError(f"YYB /accounts 返回格式异常:{body}")
items = []
for acc in body:
if not isinstance(acc, dict):
continue
ref = acc.get("openid") or acc.get("open_id") or acc.get("ref") or str(acc.get("id") or "")
ref = str(ref).strip()
nick = acc.get("nickname") or acc.get("alias") or ""
if ref:
items.append((ref, str(nick).strip()))
return items
def blacklist():
"""读取 HSY_blacklist,返回需要跳过的账号集合(昵称或 openid,支持逗号/分号/换行分隔)。"""
raw = os.getenv("HSY_blacklist", "").strip()
if not raw:
return set()
return {item.strip() for item in raw.replace(";", ",").replace("\n", ",").split(",") if item.strip()}
def routes():
values = []
for lineno, raw in enumerate(os.getenv("YYB_SERVER", "").splitlines(), 1):
raw = raw.strip()
if not raw:
continue
if "@" not in raw:
# 纯地址行:自动从 /accounts 拉取全部账号标识
server = raw.strip().rstrip("/")
if not server.startswith(("http://", "https://")):
server = "http://" + server
items = fetch_account_refs(server)
if not items:
raise RuntimeError(f"YYB_SERVER 第 {lineno} 行地址 {server} 的 /accounts 无可用账号")
values.extend((server, ref, nick) for ref, nick in items)
continue
server, ref = raw.rsplit("@", 1)
server, ref = server.strip().rstrip("/"), ref.strip()
if not server or not ref:
raise RuntimeError(f"YYB_SERVER 第 {lineno} 行格式错误,应为 地址@账号标识")
if not server.startswith(("http://", "https://")):
server = "http://" + server
values.append((server, ref, None))
if not values:
raise RuntimeError("未配置 YYB_SERVER(每行:地址@账号标识)")
return values
def success(body):
return bool(body.get("isSucess") or body.get("is_success") or body.get("success"))
def message(body):
return str(body.get("message") or body.get("msg") or body.get("error") or "未知响应")
def amount(value):
try:
return Decimal(str(value or "0"))
except (InvalidOperation, ValueError):
return Decimal("0")
def sign(params):
# v135 的 axGet:参数按键名排序后拼接,再追加 secret 做 MD5。
return hashlib.md5(("&".join(f"{k}={params[k]}" for k in sorted(params)) + SECRET).encode()).hexdigest()
def api(session, php, params, method="GET", data=None, signed=True):
params = dict(params)
if signed:
# v135 的 axGet 会在签名前补齐全局 merchant_id/appkey。
params.setdefault("merchant_id", MERCHANT_ID)
params.setdefault("appkey", APP_KEY)
params["sign"] = sign(params)
response = session.request(method, f"{BASE_URL}/{php}", params=params, data=data, timeout=TIMEOUT)
response.raise_for_status()
try:
return response.json()
except ValueError as exc:
raise RuntimeError(f"{php} 返回非 JSON:HTTP {response.status_code}") from exc
def yyb_code(server, ref):
response = requests.post(f"{server}/wxapp/getCode", json={"ref": ref, "app_id": APP_ID}, timeout=TIMEOUT)
response.raise_for_status()
body = response.json()
if int(body.get("code", -1)) != 0:
raise RuntimeError(f"YYB 取 code 失败:{body.get('msg') or body.get('message') or body}")
result = (body.get("data") or {}).get("result")
code = result if isinstance(result, str) else (result or {}).get("code")
if not code:
raise RuntimeError("YYB 未返回 data.result.code")
return str(code)
def login(session, server, ref, context):
code = yyb_code(server, ref)
# v135 的此授权分支不使用 getUserProfile,且该请求没有 sign。
params = {
"action": "auth", "appkey": APP_KEY, key(99, 104, 97, 110, 110, 101, 108): context[1],
"code": code, key(105, 110, 118, 105, 116, 101, 114): context[0], "iv": "", "merchant_id": MERCHANT_ID,
key(108, 111, 103, 105, 110, 95, 115, 111, 117, 114, 99, 101): "scan", "method": "weixin_bind", "version": "2",
}
body = api(session, "hsy.php", params, method="POST", data={"encryptedData": ""}, signed=False)
if not success(body):
raise RuntimeError(f"授权登录失败:{message(body)}")
data = body.get("data") or {}
username = str(data.get("username") or "").strip()
if not username:
raise RuntimeError("扫码登录未返回 username")
return username
def center(session, username):
body = api(session, "hsy.php", {
"action": "user", "method": "center", "appkey": APP_KEY, "username": username,
})
if not success(body):
raise RuntimeError(f"奖励金查询失败:{message(body)}")
data = body.get("data") or {}
# 前端“我的奖励金”显示 award_balance;冻结金额不参与可提现判断。
return amount(data.get("award_balance")), data
def sign_in(session, username):
before = api(session, "hsy.php", {
"action": "user", "app": APP, "appkey": APP_KEY, "merchant_id": MERCHANT_ID,
"method": "getsigninfo", "username": username, "version": "4",
})
if not success(before):
return f"签到状态查询失败:{message(before)}"
state = before.get("data") or {}
if str(state.get("hassign", "0")) == "1":
return f"今日已签到(连续 {state.get('thisturn', '?')} 天)"
body = api(session, "hsy.php", {
"action": "user", "app": APP, "appkey": APP_KEY, "merchant_id": MERCHANT_ID,
"method": "qiandao", "username": username, "version": "4",
})
return "签到成功" if success(body) else f"签到失败:{message(body)}"
def lucky_draw(session, username):
state = api(session, "promotionjgg.php", {
"action": "list", "app": "hsy", "appkey": APP_KEY,
"merchant_id": MERCHANT_ID, "username": username,
})
if not success(state):
return f"幸运抽奖状态查询失败:{message(state)}"
info = state.get("data") or {}
try:
chances = int(info.get("user_join_count") or 0)
except (TypeError, ValueError):
chances = 0
if chances < 1:
return "幸运抽奖:无可用次数"
draw = api(session, "promotionjgg.php", {
"action": "prize_draw", "app": "hsy", "appkey": APP_KEY,
"merchant_id": MERCHANT_ID, "username": username,
})
if not success(draw):
return f"幸运抽奖失败:{message(draw)}"
prize = draw.get("data") or {}
title = str(prize.get("title") or prize.get("name") or "未返回奖品名称").strip()
return f"幸运抽奖:{title}"
def join_zero_event(session, username):
listing = api(session, "promotionhighworth.php", {
"action": "actlist", "app": APP, "appkey": APP_KEY,
"merchant_id": MERCHANT_ID, "username": username,
})
if not success(listing):
return [f"0 元夺宝活动查询失败:{message(listing)}"]
data = listing.get("data") or {}
active = data.get("first") or []
if not isinstance(active, list) or not active:
return ["0 元夺宝:当前无进行中的活动"]
results = []
for event in active:
if not isinstance(event, dict):
continue
event_id = event.get("itemid") or event.get("id")
title = str(event.get("title") or event.get("mall_title") or event_id or "活动").strip()
if not event_id:
results.append(f"0 元夺宝:{title} 缺少活动编号,跳过")
continue
if str(event.get("isjoin", "0")) == "1":
results.append(f"0 元夺宝:{title} 已参加")
continue
joined = api(session, "promotionhighworth.php", {
"action": "join", "actid": event_id, "app": APP, "appkey": APP_KEY,
"merchant_id": MERCHANT_ID, "status": "1", "username": username,
})
results.append(
f"0 元夺宝:{title} 参加成功" if success(joined)
else f"0 元夺宝:{title} 参加失败:{message(joined)}"
)
return results or ["0 元夺宝:未发现可处理活动"]
def withdraw_all(session, username):
# v135 奖励金分包:envcash.php?action=add&type=award&app=wx。
# 服务端成功仅创建商家转账包;青龙无法替代微信客户端 requestMerchantTransfer 的收款确认。
available = api(session, "envcash.php", {
"action": "awardlist", "appkey": APP_KEY, "genre": "0", "merchant_id": MERCHANT_ID,
"type": "award", "username": username,
})
if not success(available):
return f"提现资格查询失败:{message(available)}"
info = available.get("data") or {}
cashable = amount(info.get("award_amount"))
minimum = amount(info.get("award_cash"))
maximum = amount(info.get("award_cash_most"))
if cashable <= Decimal("1"):
return f"可提现 {cashable:.2f} 元,未超过 1.00 元,跳过"
if minimum and cashable < minimum:
return f"可提现 {cashable:.2f} 元,低于平台门槛 {minimum:.2f} 元,跳过"
if maximum and cashable > maximum:
return f"可提现 {cashable:.2f} 元,高于单笔上限 {maximum:.2f} 元,跳过(不拆单)"
body = api(session, "envcash.php", {
"action": "add", "amount": f"{cashable:.2f}", "app": "wx", "appkey": APP_KEY,
"merchant_id": MERCHANT_ID, "type": "award", "version": "2", "username": username,
})
if not success(body):
return f"全额提现 {cashable:.2f} 元失败:{message(body)}"
transfer = body.get("data") or {}
if transfer.get("package_info"):
return f"已发起全额提现 {cashable:.2f} 元;等待微信客户端确认收款"
return f"全额提现 {cashable:.2f} 元已提交:{message(body)}"
def notify(lines):
for path in (os.path.dirname(os.path.abspath(__file__)), "/ql/data/scripts", "/ql/scripts"):
if path not in sys.path:
sys.path.insert(0, path)
try:
from notify import send
send("回收猿签到提现", "\n".join(lines))
except Exception as exc:
print(f"[通知] 发送失败(不影响任务):{exc}")
def build_notify(accounts):
"""按精简跳行版式渲染通知:汇总行 + 每账号(昵称/余额/已跑/未跑),账户间空行。"""
ok_n = sum(1 for a in accounts if a["success"])
fail_n = len(accounts) - ok_n
content = f"""♻️ 回收猿签到 · {time.strftime('%Y-%m-%d %H:%M')}
✅ {ok_n} 成功 / {fail_n} 失败
"""
for a in accounts:
if a["success"]:
content += f"""
▸ {a['label']}
✅ 奖励金 {a['balance']:.2f} 元
"""
if a["done"]:
content += f"已跑: {'、'.join(a['done'])}\n"
if a["pending"]:
content += f"未跑: {'、'.join(a['pending'])}\n"
else:
content += f"""
▸ {a['label']}
❌ 失败
原因: {(a['error'] or '未知错误').strip()}
"""
content += "\n"
return content.strip()
def run_one(index, server, ref, nick, context):
"""运行单个账号;返回 (日志行列表, 通知摘要 dict)。"""
label = nick or f"YYB {ref}"
result = [f"账号 {index}({label})"]
summary = {"label": label, "success": True, "balance": None,
"error": None, "done": [], "pending": []}
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 MicroMessenger/7.0.20.1781 MiniProgramEnv/Windows"})
try:
username = login(session, server, ref, context)
initial, _ = center(session, username)
result.append(f"初始奖励金:{initial:.2f} 元")
summary["balance"] = initial
msg = sign_in(session, username)
result.append(msg)
if "成功" in msg or "已签到" in msg:
summary["done"].append("签到")
else:
summary["error"] = msg
summary["success"] = False
msg = lucky_draw(session, username)
result.append(msg)
if "无可用次数" in msg:
summary["pending"].append("幸运抽奖")
elif "失败" in msg:
summary["error"] = msg
summary["success"] = False
else:
summary["done"].append("幸运抽奖")
msgs = join_zero_event(session, username)
result.extend(msgs)
for m in msgs:
if "参加成功" in m or "已参加" in m:
summary["done"].append("0元夺宝")
elif "失败" in m:
if summary["error"] is None:
summary["error"] = m
summary["success"] = False
else:
summary["pending"].append("0元夺宝")
time.sleep(1)
final, _ = center(session, username)
summary["balance"] = final
result.append(f"最终奖励金:{final:.2f} 元")
msg = withdraw_all(session, username)
result.append(msg)
if "跳过" in msg:
summary["pending"].append("提现")
elif "已发起" in msg or "已提交" in msg:
summary["done"].append("提现")
else:
summary["error"] = msg
summary["success"] = False
except Exception as exc:
result.append(f"失败:{exc}")
summary["error"] = str(exc)
summary["success"] = False
print(" | ".join(result))
return result, summary
def main():
context = bind_context()
skip = blacklist()
routes_list = routes()
accounts = []
ran = False
for index, (server, ref, nick) in enumerate(routes_list, 1):
label = nick or f"YYB {ref}"
if ref in skip or (nick and nick in skip):
print(f"账号 {index}({label})命中 HSY_blacklist,跳过")
continue
if ran:
delay = random.randint(5, 20)
print(f"[延迟] {delay} 秒后运行下一个账号")
time.sleep(delay)
_, summary = run_one(index, server, ref, nick, context)
accounts.append(summary)
ran = True
notify(build_notify(accounts))
if __name__ == "__main__":
main()