新增电竞手册独立绑定流程
This commit is contained in:
@@ -42,7 +42,14 @@ class DouyuActivityClient:
|
||||
CREDIT_BALANCE_API = "https://www.douyu.com/japi/oms/web/peace/credit/balance"
|
||||
CREDIT_DIFF_API = "https://www.douyu.com/japi/oms/web/peace/credit/diff"
|
||||
PEACE_ITEM_API = "https://www.douyu.com/japi/oms/web/peace/item"
|
||||
PEACE_TIME_API = "https://www.douyu.com/japi/oms/web/peace/time"
|
||||
PAY_QR_API = "https://www.douyu.com/japi/oms/web/pay/getQrCode"
|
||||
ESPORTS_BIND_INFO_API = "https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo"
|
||||
ESPORTS_BIND_ROLE_PARAM_API = "https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindRoleParam"
|
||||
ESPORTS_CSRF_API = "https://www.douyu.com/wgapi/livenc/liveweb/csrfApi/getCsrfCookie"
|
||||
ESPORTS_AGREEMENT_API = "https://www.douyu.com/wgapi/ordnc/activity/peacematch/agreement"
|
||||
ESPORTS_ACT_BIND_API = "https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/actBind"
|
||||
ESPORTS_USER_INFO_API = "https://www.douyu.com/wgapi/ordnc/activity/peacematch/getUserInfo"
|
||||
GOLD_QR_API = "https://cz.douyu.com/m/gold/getQrCode"
|
||||
GOLD_ACCOUNT_API = "https://cz.douyu.com/item/gold/account"
|
||||
EXCHANGE_BALANCE_API = "https://www.douyu.com/wjapi/nc/exchange/fim"
|
||||
@@ -238,6 +245,10 @@ class DouyuActivityClient:
|
||||
)
|
||||
if payload.get("error") not in (0, "0", None):
|
||||
raise DouyuActivityError(payload.get("msg") or "查询绑定信息失败")
|
||||
return self._parse_bind_info(payload, act_alias=act_alias, api_version="v2" if v2 else "v1")
|
||||
|
||||
def _parse_bind_info(self, payload: dict[str, Any], *, act_alias: str, api_version: str) -> dict[str, Any]:
|
||||
"""统一解析不同活动的腾讯游戏绑定信息。"""
|
||||
data = payload.get("data") or {}
|
||||
game_account = data.get("gameAccount") or {}
|
||||
game_role = data.get("gameRole") or {}
|
||||
@@ -270,7 +281,7 @@ class DouyuActivityClient:
|
||||
can_change_role = self._first_present(tx_act, "canChangeRole", "canChangeBind")
|
||||
return {
|
||||
"act_alias": act_alias,
|
||||
"api_version": "v2" if v2 else "v1",
|
||||
"api_version": api_version,
|
||||
"bind_account": bind_account,
|
||||
"bind_role": bind_role,
|
||||
"bind_act": bind_act,
|
||||
@@ -289,6 +300,96 @@ class DouyuActivityClient:
|
||||
"raw": payload,
|
||||
}
|
||||
|
||||
def esports_bind_info(self, act_alias: str) -> dict[str, Any]:
|
||||
"""电竞手册唯一的角色状态查询,返回角色、绑定状态和换绑冷却。"""
|
||||
payload = self._request_json(
|
||||
"get",
|
||||
self.ESPORTS_BIND_INFO_API,
|
||||
"查询电竞手册绑定信息",
|
||||
params={"actAlias": act_alias},
|
||||
headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"},
|
||||
)
|
||||
if payload.get("error") not in (0, "0", None):
|
||||
raise DouyuActivityError(payload.get("msg") or "查询电竞手册绑定信息失败")
|
||||
return self._parse_bind_info(payload, act_alias=act_alias, api_version="esports")
|
||||
|
||||
def get_esports_bind_qr(self, act_alias: str) -> dict[str, Any]:
|
||||
"""生成电竞手册的腾讯角色绑定二维码链接。"""
|
||||
payload = self._request_json(
|
||||
"get",
|
||||
self.ESPORTS_BIND_ROLE_PARAM_API,
|
||||
"获取电竞手册绑定二维码",
|
||||
params={"actAlias": act_alias},
|
||||
headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"},
|
||||
)
|
||||
if payload.get("error") not in (0, "0", None):
|
||||
raise DouyuActivityError(payload.get("msg") or "获取电竞手册绑定二维码失败")
|
||||
data = payload.get("data") or {}
|
||||
# 电竞手册网页实际打开 data.url;gameRoleBindUrl 是兼容旧入口。
|
||||
bind_url = data.get("url") or data.get("gameRoleBindUrl") or ""
|
||||
if not bind_url:
|
||||
raise DouyuActivityError(payload.get("msg") or "电竞手册绑定二维码链接为空")
|
||||
return {
|
||||
"url": bind_url,
|
||||
"bind_url": bind_url,
|
||||
"role_param_url": data.get("url") or "",
|
||||
"game_role_bind_url": data.get("gameRoleBindUrl") or "",
|
||||
"act_alias": act_alias,
|
||||
"raw": payload,
|
||||
}
|
||||
|
||||
def esports_ctn(self, *, referer: str) -> str:
|
||||
"""获取电竞手册页面当前 ctn,仅在 Cookie 缺失时走页面 CSRF 接口。"""
|
||||
ctn = cookie_value(self.cookie, "acf_ccn")
|
||||
if ctn:
|
||||
return ctn
|
||||
|
||||
response = self._request(
|
||||
"get",
|
||||
self.ESPORTS_CSRF_API,
|
||||
source="获取电竞手册 ctn",
|
||||
headers={"Origin": "", "Referer": referer},
|
||||
)
|
||||
ctn = cookie_value(self.cookie, "acf_ccn")
|
||||
if not ctn:
|
||||
raise DouyuActivityError(f"获取电竞手册 ctn 失败: {response.text[:200]}")
|
||||
return ctn
|
||||
|
||||
def confirm_esports_bind(self, act_alias: str, *, room_id: str = "9263298") -> dict[str, Any]:
|
||||
"""自动完成协议前置请求后,通过 actBind 确认电竞手册绑定。"""
|
||||
referer = (
|
||||
"https://www.douyu.com/pages/live-peace-pel/web"
|
||||
f"?roomId={room_id or '9263298'}&isAnchorSide=0"
|
||||
)
|
||||
|
||||
# 页面成功流程固定为 agreement -> actBind;协议不作为独立业务状态暴露。
|
||||
# 两次 POST 都使用当前 Cookie 中的 acf_ccn,不额外主动刷新 CSRF。
|
||||
agreement_ctn = self.esports_ctn(referer=referer)
|
||||
agreement = self._request_json(
|
||||
"post",
|
||||
self.ESPORTS_AGREEMENT_API,
|
||||
"同意电竞手册协议",
|
||||
files={"ctn": (None, agreement_ctn)},
|
||||
headers={"Referer": referer},
|
||||
)
|
||||
if agreement.get("error") not in (0, "0", None):
|
||||
raise DouyuActivityError(agreement.get("msg") or "同意电竞手册协议失败")
|
||||
|
||||
bind_ctn = self.esports_ctn(referer=referer)
|
||||
bind = self._request_json(
|
||||
"post",
|
||||
self.ESPORTS_ACT_BIND_API,
|
||||
"完成电竞手册绑定",
|
||||
files={
|
||||
"actAlias": (None, act_alias),
|
||||
"ctn": (None, bind_ctn),
|
||||
},
|
||||
headers={"Referer": referer},
|
||||
)
|
||||
if bind.get("error") not in (0, "0", None):
|
||||
raise DouyuActivityError(bind.get("msg") or "完成电竞手册绑定失败")
|
||||
return {"agreement": agreement, "act_bind": bind}
|
||||
|
||||
def confirm_bind(self, act_alias: str) -> dict[str, Any]:
|
||||
"""确认绑定当前扫码选择的游戏角色。"""
|
||||
token = self.csrf_token()
|
||||
@@ -337,6 +438,67 @@ class DouyuActivityClient:
|
||||
raise DouyuActivityError(payload.get("msg") or "生成精英宝典支付码失败")
|
||||
return {"pay_url": qr, "ctn": ctn, "act_alias": act_alias, "item": item_payload, "raw": payload}
|
||||
|
||||
def create_esports_qr(self, *, ctn: str, act_alias: str, amount: int, room_id: str) -> dict[str, Any]:
|
||||
"""生成电竞手册支付二维码。"""
|
||||
self._set_cookie_value("acf_ccn", ctn)
|
||||
time_payload = self._request_json(
|
||||
"get",
|
||||
self.PEACE_TIME_API,
|
||||
"校验电竞手册开通时间",
|
||||
headers={"Referer": "https://www.douyu.com/pages/live-peace-pel/web/"},
|
||||
)
|
||||
if time_payload.get("error") not in (0, "0"):
|
||||
raise DouyuActivityError(time_payload.get("msg") or "电竞手册当前不可开通")
|
||||
|
||||
payload = self._request_json(
|
||||
"post",
|
||||
self.PAY_QR_API,
|
||||
"生成电竞手册支付码",
|
||||
json={
|
||||
"bizType": "peacePel",
|
||||
"biz": {"actAlias": act_alias, "amount": amount, "roomId": int(room_id or 0)},
|
||||
"ctn": ctn,
|
||||
},
|
||||
headers={
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
"Referer": "https://www.douyu.com/pages/live-peace-pel/web/",
|
||||
},
|
||||
)
|
||||
data = payload.get("data") or {}
|
||||
qr = data.get("qr") or ""
|
||||
if not qr:
|
||||
raise DouyuActivityError(payload.get("msg") or "生成电竞手册支付码失败")
|
||||
return {
|
||||
"pay_url": qr,
|
||||
"pay_token": data.get("token") or "",
|
||||
"pay_ttl": data.get("ttl"),
|
||||
"ctn": ctn,
|
||||
"act_alias": act_alias,
|
||||
"time": time_payload,
|
||||
"raw": payload,
|
||||
}
|
||||
|
||||
def esports_user_info(self, *, manual_id: str) -> dict[str, Any]:
|
||||
"""查询电竞手册的开通状态和积分。"""
|
||||
payload = self._request_json(
|
||||
"get",
|
||||
self.ESPORTS_USER_INFO_API,
|
||||
"查询电竞手册状态",
|
||||
params={"manualID": manual_id},
|
||||
headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"},
|
||||
)
|
||||
if payload.get("error") not in (0, "0", None):
|
||||
raise DouyuActivityError(payload.get("msg") or "查询电竞手册状态失败")
|
||||
user_info = (payload.get("data") or {}).get("userInfo") or {}
|
||||
return {
|
||||
"manual_id": manual_id,
|
||||
"manual_type": user_info.get("manualType"),
|
||||
"manual_score": user_info.get("manualScore"),
|
||||
"expire_time": user_info.get("expireTime"),
|
||||
"user_info": user_info,
|
||||
"raw": payload,
|
||||
}
|
||||
|
||||
def create_gold_qr(self, *, amount: int, pay_type: int = 1, product_id: str = "DYTV_Product_Gold_1") -> dict[str, Any]:
|
||||
"""生成鱼翅充值支付二维码。"""
|
||||
payload = self._request_json(
|
||||
|
||||
Reference in New Issue
Block a user