Skip to content

Commit 6fd4378

Browse files
committed
Create 提现免费券.py
1 parent 1db1f85 commit 6fd4378

1 file changed

Lines changed: 328 additions & 0 deletions

File tree

wxapp/提现免费券.py

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
4+
import secrets
5+
import string
6+
import sys
7+
import time
8+
from typing import Any
9+
10+
import os
11+
import urllib3
12+
import requests
13+
14+
urllib3.disable_warnings()
15+
16+
17+
# ── 账号 & 鉴权 ──
18+
APPID = "wxdb3c0e388702f785"
19+
OPENID = os.environ.get("OPENID", "") # 多个账号用 & 分隔
20+
APIKEY = os.environ.get("APIKEY", "")
21+
22+
# ── 桥接服务 ──
23+
BRIDGE_BASE_URL = os.environ.get("BRIDGE_BASE_URL", "") # 调用时自动拼接 /wx/code
24+
BRIDGE_TIMEOUT = 40
25+
26+
# ── 领券策略 ──
27+
CODE_COUNT = 1 # 每个账号获取的 code 数量,建议 1-3 个,过多可能导致登录失败
28+
TARGET_COUPON_ID: int | None = None # None=自动选未领取的券,或指定券ID
29+
30+
# ── 请求参数 ──
31+
API_TIMEOUT = 15
32+
DOMAIN = "https://discount.wxpapp.wechatpay.cn"
33+
PAGE = "pages/gift/index"
34+
MODULE_NAME = "mmpaytxbbsmp"
35+
PAGE_FRAME_VERSION = "180"
36+
SESSION_SCENE = "daily_reward"
37+
USER_AGENT = (
38+
"Mozilla/5.0 (Linux; Android 13; Mobile) "
39+
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
40+
"Chrome/132.0.0.0 Mobile Safari/537.36 "
41+
"MicroMessenger/8.0.50 NetType/WIFI Language/zh_CN "
42+
"ABI/arm64 MiniProgramEnv/android"
43+
)
44+
45+
46+
class ClaimError(RuntimeError):
47+
pass
48+
49+
def getjscode(openid: str) -> list[str]:
50+
url = f"{BRIDGE_BASE_URL}/wx/code"
51+
params = {"userKey": openid, "openid": openid, "appid": APPID}
52+
try:
53+
resp = requests.post(url=url, json=params, headers={"auth": APIKEY}, timeout=BRIDGE_TIMEOUT, verify=False)
54+
except requests.RequestException as err:
55+
raise ClaimError(f"桥接服务请求失败:{err}") from err
56+
57+
if resp.status_code != 200:
58+
raise ClaimError(f"桥接服务返回 HTTP {resp.status_code}{resp.text}")
59+
60+
data = resp.json()
61+
if not data.get("status"):
62+
raise ClaimError(f"桥接服务返回失败:{data}")
63+
64+
code = data.get("data", {}).get("code")
65+
if not code:
66+
raise ClaimError(f"桥接服务未返回 code:{data}")
67+
return [code]
68+
69+
def main() -> int:
70+
try:
71+
sys.stdout.reconfigure(encoding="utf-8")
72+
except AttributeError:
73+
pass
74+
75+
openids = [item.strip() for item in OPENID.split("&") if item.strip()]
76+
if not openids:
77+
print("未提供有效的 openid")
78+
return 1
79+
80+
ok_count = 0
81+
for index, openid in enumerate(openids, start=1):
82+
prefix = f"🌸 账号[{index}]"
83+
try:
84+
result = run_account(openid)
85+
print_success(prefix, openid, result)
86+
ok_count += 1
87+
except Exception as err:
88+
print(f"{prefix} ❌ 处理失败({mask_openid(openid)})")
89+
print(f"{prefix} 错误:{err}")
90+
91+
return 0 if ok_count == len(openids) else 1
92+
93+
94+
def run_account(openid: str) -> dict[str, Any]:
95+
track_id = make_track_id()
96+
session = requests.Session()
97+
session.verify = False
98+
99+
codes = getjscode(openid)
100+
if not codes:
101+
raise ClaimError("无法获取jscode")
102+
103+
session_token, used_code_index = login_with_codes(session, codes, track_id)
104+
coupons = query_coupons(session, session_token, track_id)
105+
coupon = select_coupon(coupons)
106+
107+
if coupon is None:
108+
claimed_coupon = next((item for item in coupons if item.get("is_claimed")), None)
109+
return {
110+
"code_count": len(codes),
111+
"used_code_index": used_code_index,
112+
"status": "already_claimed" if claimed_coupon else "no_daily",
113+
"coupon": claimed_coupon,
114+
}
115+
116+
if coupon.get("is_claimed"):
117+
status = "already_claimed"
118+
else:
119+
claim_coupon(session, session_token, track_id, coupon)
120+
status = "claimed"
121+
122+
return {
123+
"code_count": len(codes),
124+
"used_code_index": used_code_index,
125+
"status": status,
126+
"coupon": coupon,
127+
}
128+
129+
130+
def login_with_codes(session: requests.Session, codes: list[str], track_id: str) -> tuple[str, int]:
131+
errors: list[str] = []
132+
for index, code in enumerate(codes, start=1):
133+
try:
134+
data = api_get(
135+
session,
136+
"/txbbs-user/user/login",
137+
headers=make_headers(track_id, jscode=code),
138+
)
139+
token = data.get("session_token")
140+
if not isinstance(token, str) or not token:
141+
raise ClaimError(f"登录返回缺少 session_token:{data}")
142+
return token, index
143+
except Exception as err:
144+
errors.append(f"第{index}个code失败:{err}")
145+
raise ClaimError("全部 code 登录失败:" + ";".join(errors))
146+
147+
148+
def query_coupons(session: requests.Session, session_token: str, track_id: str) -> list[dict[str, Any]]:
149+
data = api_get(
150+
session,
151+
"/txbbs-mall/coupon/querydailygiftcoupons",
152+
headers=make_headers(track_id, session_token=session_token),
153+
)
154+
items = data.get("coupon_items")
155+
if not isinstance(items, list):
156+
raise ClaimError(f"查询返回缺少 coupon_items:{data}")
157+
return [item for item in items if isinstance(item, dict)]
158+
159+
160+
def select_coupon(coupons: list[dict[str, Any]]) -> dict[str, Any] | None:
161+
if TARGET_COUPON_ID is not None:
162+
return next((item for item in coupons if coupon_id(item) == TARGET_COUPON_ID), None)
163+
return next((item for item in coupons if not item.get("is_claimed") and coupon_id(item)), None)
164+
165+
166+
def claim_coupon(
167+
session: requests.Session,
168+
session_token: str,
169+
track_id: str,
170+
coupon: dict[str, Any],
171+
) -> None:
172+
cid = coupon_id(coupon)
173+
gift_type = coupon.get("daily_gift_type")
174+
amount = coupon_face_value(coupon)
175+
176+
if not isinstance(cid, int):
177+
raise ClaimError(f"券缺少 coupon_id:{coupon}")
178+
if not isinstance(gift_type, str) or not gift_type:
179+
raise ClaimError(f"券缺少 daily_gift_type:{coupon}")
180+
if not isinstance(amount, int):
181+
raise ClaimError(f"券缺少 face_value:{coupon}")
182+
183+
api_post(
184+
session,
185+
"/txbbs-mall/coupon/claimdailygiftcoupon",
186+
headers=make_headers(
187+
track_id,
188+
session_token=session_token,
189+
session_id=make_session_id(),
190+
),
191+
json={
192+
"daily_gift_type": gift_type,
193+
"coupon_id": cid,
194+
"expected_send_amount": amount,
195+
},
196+
)
197+
198+
199+
def print_success(prefix: str, openid: str, result: dict[str, Any]) -> None:
200+
print(f"{prefix} ✅ 登录成功({mask_openid(openid)})")
201+
print(f"{prefix} Code:共获取{result['code_count']}个,使用第{result['used_code_index']}个")
202+
203+
coupon = result.get("coupon")
204+
if not isinstance(coupon, dict):
205+
print(f"{prefix} 未查询到每日额度")
206+
return
207+
208+
name = coupon_name(coupon)
209+
amount = coupon_amount(coupon)
210+
status = result.get("status")
211+
212+
if status == "claimed":
213+
print(f"{prefix} ✅ 领取成功:{name}")
214+
print(f"{prefix} 到账额度:{amount}")
215+
elif status == "already_claimed":
216+
print(f"{prefix} 今日已领取:{name}")
217+
print(f"{prefix} 当前额度:{amount}")
218+
else:
219+
print(f"{prefix} 未查询到每日额度")
220+
221+
222+
def api_get(session: requests.Session, path: str, *, headers: dict[str, str]) -> dict[str, Any]:
223+
response = session.get(f"{DOMAIN}{path}", headers=headers, timeout=API_TIMEOUT)
224+
return unwrap_response(response, path)
225+
226+
227+
def api_post(
228+
session: requests.Session,
229+
path: str,
230+
*,
231+
headers: dict[str, str],
232+
json: dict[str, Any],
233+
) -> dict[str, Any]:
234+
response = session.post(f"{DOMAIN}{path}", headers=headers, json=json, timeout=API_TIMEOUT)
235+
return unwrap_response(response, path)
236+
237+
238+
def unwrap_response(response: requests.Response, action: str) -> dict[str, Any]:
239+
try:
240+
response.raise_for_status()
241+
payload = response.json()
242+
except Exception as err:
243+
raise ClaimError(f"{action} 请求失败:{err},响应:{response.text}") from err
244+
245+
if not isinstance(payload, dict):
246+
raise ClaimError(f"{action} 返回格式异常:{payload!r}")
247+
if payload.get("errcode") != 0:
248+
raise ClaimError(f"{action} 返回失败:errcode={payload.get('errcode')}{payload}")
249+
250+
data = payload.get("data")
251+
return data if isinstance(data, dict) else {}
252+
253+
254+
def make_headers(
255+
track_id: str,
256+
*,
257+
jscode: str | None = None,
258+
session_token: str | None = None,
259+
session_id: str | None = None,
260+
) -> dict[str, str]:
261+
headers = {
262+
"User-Agent": USER_AGENT,
263+
"Content-Type": "application/json",
264+
"X-Page": PAGE,
265+
"X-Track-Id": track_id,
266+
"xweb_xhr": "1",
267+
"X-Module-Name": MODULE_NAME,
268+
"X-Appid": APPID,
269+
"Sec-Fetch-Site": "cross-site",
270+
"Sec-Fetch-Mode": "cors",
271+
"Sec-Fetch-Dest": "empty",
272+
"Referer": f"https://servicewechat.com/{APPID}/{PAGE_FRAME_VERSION}/page-frame.html",
273+
"Accept-Language": "zh-CN,zh;q=0.9",
274+
}
275+
if jscode:
276+
headers["jscode"] = jscode
277+
if session_token:
278+
headers["session-token"] = session_token
279+
if session_id:
280+
headers["session-id"] = session_id
281+
return headers
282+
283+
284+
def make_track_id() -> str:
285+
return "T" + "".join(secrets.choice("0123456789ABCDEF") for _ in range(31))
286+
287+
288+
def make_session_id() -> str:
289+
alphabet = string.ascii_lowercase + string.digits
290+
random_part = "".join(secrets.choice(alphabet) for _ in range(10))
291+
return f"{SESSION_SCENE}-{int(time.time() * 1000)}-{random_part}"
292+
293+
294+
def coupon_info(coupon: dict[str, Any]) -> dict[str, Any]:
295+
value = coupon.get("coupon_info")
296+
return value if isinstance(value, dict) else {}
297+
298+
299+
def coupon_id(coupon: dict[str, Any]) -> int | None:
300+
value = coupon_info(coupon).get("coupon_id")
301+
return value if isinstance(value, int) else None
302+
303+
304+
def coupon_face_value(coupon: dict[str, Any]) -> int | None:
305+
value = coupon_info(coupon).get("face_value")
306+
return value if isinstance(value, int) else None
307+
308+
309+
def coupon_name(coupon: dict[str, Any]) -> str:
310+
name = coupon_info(coupon).get("name")
311+
if isinstance(name, str) and name:
312+
return name
313+
return f"coupon_id={coupon_id(coupon)}"
314+
315+
316+
def coupon_amount(coupon: dict[str, Any]) -> str:
317+
amount = coupon_face_value(coupon)
318+
if not isinstance(amount, int):
319+
return "未知额度"
320+
return f"{amount // 100}元" if amount % 100 == 0 else f"{amount / 100:.2f}元"
321+
322+
323+
def mask_openid(openid: str) -> str:
324+
return openid if len(openid) <= 12 else f"{openid[:6]}...{openid[-4:]}"
325+
326+
327+
if __name__ == "__main__":
328+
raise SystemExit(main())

0 commit comments

Comments
 (0)