type: 收窄豆鱼服务层类型
This commit is contained in:
+275
-103
@@ -11,7 +11,12 @@ from urllib.parse import parse_qs, unquote, urlsplit, urlunsplit
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .cookie_utils import cookie_pairs, cookie_value, normalize_cookie_pairs, normalize_douyu_cookie
|
from .cookie_utils import (
|
||||||
|
cookie_pairs,
|
||||||
|
cookie_value,
|
||||||
|
normalize_cookie_pairs,
|
||||||
|
normalize_douyu_cookie,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
PC_UA = (
|
PC_UA = (
|
||||||
@@ -37,9 +42,15 @@ class DouyuActivityClient:
|
|||||||
BIND_INFO_V2_API = "https://www.douyu.com/japi/carnivalApi/v2/tencent/bindInfo"
|
BIND_INFO_V2_API = "https://www.douyu.com/japi/carnivalApi/v2/tencent/bindInfo"
|
||||||
BIND_API = "https://www.douyu.com/japi/carnivalApi/tencent/bind"
|
BIND_API = "https://www.douyu.com/japi/carnivalApi/tencent/bind"
|
||||||
GOODS_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/storehome"
|
GOODS_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/storehome"
|
||||||
CREATE_EXCHANGE_ORDER_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/createExchangeOrder"
|
CREATE_EXCHANGE_ORDER_API = (
|
||||||
PAY_EXCHANGE_ORDER_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/payExchangeOrder"
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/createExchangeOrder"
|
||||||
EXCHANGE_ORDER_LIST_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/getExchangeOrderList"
|
)
|
||||||
|
PAY_EXCHANGE_ORDER_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/payExchangeOrder"
|
||||||
|
)
|
||||||
|
EXCHANGE_ORDER_LIST_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/getExchangeOrderList"
|
||||||
|
)
|
||||||
EXCHANGE_LIST_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/exchangeList"
|
EXCHANGE_LIST_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/exchangeList"
|
||||||
# 2026-08-30 页面新增链路(对齐浏览器):
|
# 2026-08-30 页面新增链路(对齐浏览器):
|
||||||
# 预兑 user/queryExchange -> user/confirmExchange;取消预兑 user/queryCancelExchange -> user/cancelExchange;
|
# 预兑 user/queryExchange -> user/confirmExchange;取消预兑 user/queryCancelExchange -> user/cancelExchange;
|
||||||
@@ -47,29 +58,63 @@ class DouyuActivityClient:
|
|||||||
ELITE_USER_INFO_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/getUserInfo"
|
ELITE_USER_INFO_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/getUserInfo"
|
||||||
GOODS_DETAIL_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/storedetail"
|
GOODS_DETAIL_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/storedetail"
|
||||||
EXCHANGE_GIFT_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/exchange"
|
EXCHANGE_GIFT_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/exchange"
|
||||||
PRE_EXCHANGE_CHECK_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/user/queryExchange"
|
PRE_EXCHANGE_CHECK_API = (
|
||||||
PRE_EXCHANGE_CONFIRM_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/user/confirmExchange"
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/user/queryExchange"
|
||||||
PRE_EXCHANGE_CANCEL_CHECK_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/user/queryCancelExchange"
|
)
|
||||||
PRE_EXCHANGE_CANCEL_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/user/cancelExchange"
|
PRE_EXCHANGE_CONFIRM_API = (
|
||||||
COMMODITY_SUBSCRIBE_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/user/sub"
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/user/confirmExchange"
|
||||||
COMMODITY_UNSUBSCRIBE_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/user/cancelSub"
|
)
|
||||||
BATCH_EXCHANGE_LIMIT_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/batchExchangeLimit"
|
PRE_EXCHANGE_CANCEL_CHECK_API = (
|
||||||
AUTO_CONVERT_QUERY_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/activity/automaticallyConvert"
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/user/queryCancelExchange"
|
||||||
|
)
|
||||||
|
PRE_EXCHANGE_CANCEL_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/user/cancelExchange"
|
||||||
|
)
|
||||||
|
COMMODITY_SUBSCRIBE_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/user/sub"
|
||||||
|
)
|
||||||
|
COMMODITY_UNSUBSCRIBE_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/user/cancelSub"
|
||||||
|
)
|
||||||
|
BATCH_EXCHANGE_LIMIT_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/batchExchangeLimit"
|
||||||
|
)
|
||||||
|
AUTO_CONVERT_QUERY_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/activity/automaticallyConvert"
|
||||||
|
)
|
||||||
AUTO_CONVERT_SAVE_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/activity/automaticallyConvertSave"
|
AUTO_CONVERT_SAVE_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/activity/automaticallyConvertSave"
|
||||||
EXCHANGE_HOME_API = "https://www.douyu.com/wgapi/ordnc/activity/peace/activity/exchangeHome"
|
EXCHANGE_HOME_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peace/activity/exchangeHome"
|
||||||
|
)
|
||||||
CREDIT_BALANCE_API = "https://www.douyu.com/japi/oms/web/peace/credit/balance"
|
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"
|
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_ITEM_API = "https://www.douyu.com/japi/oms/web/peace/item"
|
||||||
PEACE_TIME_API = "https://www.douyu.com/japi/oms/web/peace/time"
|
PEACE_TIME_API = "https://www.douyu.com/japi/oms/web/peace/time"
|
||||||
PAY_QR_API = "https://www.douyu.com/japi/oms/web/pay/getQrCode"
|
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_INFO_API = (
|
||||||
ESPORTS_BIND_ROLE_PARAM_API = "https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindRoleParam"
|
"https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindInfo"
|
||||||
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_BIND_ROLE_PARAM_API = (
|
||||||
ESPORTS_ACT_BIND_API = "https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/actBind"
|
"https://www.douyu.com/wgapi/ordnc/live/memberthird/tencent/bindRoleParam"
|
||||||
ESPORTS_USER_INFO_API = "https://www.douyu.com/wgapi/ordnc/activity/peacematch/getUserInfo"
|
)
|
||||||
ESPORTS_GOODS_API = "https://www.douyu.com/wgapi/ordnc/activity/peacematch/storehome"
|
ESPORTS_CSRF_API = (
|
||||||
ESPORTS_EXCHANGE_API = "https://www.douyu.com/wgapi/ordnc/activity/peacematch/exchange"
|
"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"
|
||||||
|
)
|
||||||
|
ESPORTS_GOODS_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peacematch/storehome"
|
||||||
|
)
|
||||||
|
ESPORTS_EXCHANGE_API = (
|
||||||
|
"https://www.douyu.com/wgapi/ordnc/activity/peacematch/exchange"
|
||||||
|
)
|
||||||
GOLD_QR_API = "https://cz.douyu.com/m/gold/getQrCode"
|
GOLD_QR_API = "https://cz.douyu.com/m/gold/getQrCode"
|
||||||
GOLD_ACCOUNT_API = "https://cz.douyu.com/item/gold/account"
|
GOLD_ACCOUNT_API = "https://cz.douyu.com/item/gold/account"
|
||||||
EXCHANGE_BALANCE_API = "https://www.douyu.com/wjapi/nc/exchange/fim"
|
EXCHANGE_BALANCE_API = "https://www.douyu.com/wjapi/nc/exchange/fim"
|
||||||
@@ -91,11 +136,13 @@ class DouyuActivityClient:
|
|||||||
if proxies:
|
if proxies:
|
||||||
self.session.proxies.update(proxies)
|
self.session.proxies.update(proxies)
|
||||||
self.session.trust_env = False
|
self.session.trust_env = False
|
||||||
self.session.headers.update({
|
self.session.headers.update(
|
||||||
"User-Agent": PC_UA,
|
{
|
||||||
"Accept": "application/json, text/plain, */*",
|
"User-Agent": PC_UA,
|
||||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
"Accept": "application/json, text/plain, */*",
|
||||||
})
|
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||||
|
}
|
||||||
|
)
|
||||||
self._load_cookie(self.cookie)
|
self._load_cookie(self.cookie)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -114,18 +161,22 @@ class DouyuActivityClient:
|
|||||||
def _merge_response_cookie(self, response: requests.Response) -> None:
|
def _merge_response_cookie(self, response: requests.Response) -> None:
|
||||||
if not response.cookies:
|
if not response.cookies:
|
||||||
return
|
return
|
||||||
self.cookie = normalize_cookie_pairs([
|
self.cookie = normalize_cookie_pairs(
|
||||||
*cookie_pairs(self.cookie),
|
[
|
||||||
*[(key, value) for key, value in response.cookies.items()],
|
*cookie_pairs(self.cookie),
|
||||||
])
|
*[(key, value) for key, value in response.cookies.items()],
|
||||||
|
]
|
||||||
|
)
|
||||||
self._load_cookie(self.cookie)
|
self._load_cookie(self.cookie)
|
||||||
|
|
||||||
def _set_cookie_value(self, key: str, value: str) -> None:
|
def _set_cookie_value(self, key: str, value: str) -> None:
|
||||||
"""写入单个 Cookie 值,确保手动 Cookie 请求头与 Session 一致。"""
|
"""写入单个 Cookie 值,确保手动 Cookie 请求头与 Session 一致。"""
|
||||||
self.cookie = normalize_cookie_pairs([
|
self.cookie = normalize_cookie_pairs(
|
||||||
*cookie_pairs(self.cookie),
|
[
|
||||||
(key, value),
|
*cookie_pairs(self.cookie),
|
||||||
])
|
(key, value),
|
||||||
|
]
|
||||||
|
)
|
||||||
self._load_cookie(self.cookie)
|
self._load_cookie(self.cookie)
|
||||||
|
|
||||||
def _headers(self, referer: str = "https://www.douyu.com/") -> dict[str, str]:
|
def _headers(self, referer: str = "https://www.douyu.com/") -> dict[str, str]:
|
||||||
@@ -158,7 +209,9 @@ class DouyuActivityClient:
|
|||||||
f"location={safe_location} history={response.status_code}->{safe_location}"
|
f"location={safe_location} history={response.status_code}->{safe_location}"
|
||||||
)
|
)
|
||||||
self.logger(f"{source}: 已拦截重定向 | {trace}")
|
self.logger(f"{source}: 已拦截重定向 | {trace}")
|
||||||
return DouyuActivityError(f"{source} 请求被重定向,Cookie 已失效,请重新登录({trace})")
|
return DouyuActivityError(
|
||||||
|
f"{source} 请求被重定向,Cookie 已失效,请重新登录({trace})"
|
||||||
|
)
|
||||||
|
|
||||||
def _request(
|
def _request(
|
||||||
self,
|
self,
|
||||||
@@ -203,7 +256,9 @@ class DouyuActivityClient:
|
|||||||
except json.JSONDecodeError as exc:
|
except json.JSONDecodeError as exc:
|
||||||
raise DouyuActivityError(f"{source} 返回不是 JSON: {text[:200]}") from exc
|
raise DouyuActivityError(f"{source} 返回不是 JSON: {text[:200]}") from exc
|
||||||
|
|
||||||
def _request_json(self, method: str, url: str, source: str, **kwargs) -> dict[str, Any]:
|
def _request_json(
|
||||||
|
self, method: str, url: str, source: str, **kwargs
|
||||||
|
) -> dict[str, Any]:
|
||||||
return self._json(self._request(method, url, source=source, **kwargs), source)
|
return self._json(self._request(method, url, source=source, **kwargs), source)
|
||||||
|
|
||||||
def csrf_token(self, *, force_refresh: bool = False) -> str:
|
def csrf_token(self, *, force_refresh: bool = False) -> str:
|
||||||
@@ -252,7 +307,9 @@ class DouyuActivityClient:
|
|||||||
"""精英手册商城页 Referer(对齐浏览器 create/pay/storedetail 等接口)。"""
|
"""精英手册商城页 Referer(对齐浏览器 create/pay/storedetail 等接口)。"""
|
||||||
if rid:
|
if rid:
|
||||||
return f"https://www.douyu.com/pages/live-peace-handbook/web/shop?ditchname=pass0&roomId={rid}"
|
return f"https://www.douyu.com/pages/live-peace-handbook/web/shop?ditchname=pass0&roomId={rid}"
|
||||||
return "https://www.douyu.com/pages/live-peace-handbook/web/shop?ditchname=pass0"
|
return (
|
||||||
|
"https://www.douyu.com/pages/live-peace-handbook/web/shop?ditchname=pass0"
|
||||||
|
)
|
||||||
|
|
||||||
def _post_csrf_form(
|
def _post_csrf_form(
|
||||||
self,
|
self,
|
||||||
@@ -363,7 +420,9 @@ class DouyuActivityClient:
|
|||||||
)
|
)
|
||||||
if payload.get("error") not in (0, "0", None):
|
if payload.get("error") not in (0, "0", None):
|
||||||
raise DouyuActivityError(payload.get("msg") or "查询绑定信息失败")
|
raise DouyuActivityError(payload.get("msg") or "查询绑定信息失败")
|
||||||
parsed = self._parse_bind_info(payload, act_alias=act_alias, api_version="v2" if v2 else "v1")
|
parsed = self._parse_bind_info(
|
||||||
|
payload, act_alias=act_alias, api_version="v2" if v2 else "v1"
|
||||||
|
)
|
||||||
self._log_bind_info(parsed)
|
self._log_bind_info(parsed)
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
@@ -385,7 +444,9 @@ class DouyuActivityClient:
|
|||||||
f"can_change_time={parsed.get('can_change_time') if parsed.get('can_change_time') is not None else '-'}"
|
f"can_change_time={parsed.get('can_change_time') if parsed.get('can_change_time') is not None else '-'}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _parse_bind_info(self, payload: dict[str, Any], *, act_alias: str, api_version: str) -> dict[str, Any]:
|
def _parse_bind_info(
|
||||||
|
self, payload: dict[str, Any], *, act_alias: str, api_version: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""统一解析不同活动的腾讯游戏绑定信息。"""
|
"""统一解析不同活动的腾讯游戏绑定信息。"""
|
||||||
data = payload.get("data") or {}
|
data = payload.get("data") or {}
|
||||||
game_account = data.get("gameAccount") or {}
|
game_account = data.get("gameAccount") or {}
|
||||||
@@ -416,14 +477,18 @@ class DouyuActivityClient:
|
|||||||
"canChangeBind",
|
"canChangeBind",
|
||||||
)
|
)
|
||||||
if can_change_role is None and isinstance(tx_act, dict):
|
if can_change_role is None and isinstance(tx_act, dict):
|
||||||
can_change_role = self._first_present(tx_act, "canChangeRole", "canChangeBind")
|
can_change_role = self._first_present(
|
||||||
|
tx_act, "canChangeRole", "canChangeBind"
|
||||||
|
)
|
||||||
can_change_time = self._first_present(
|
can_change_time = self._first_present(
|
||||||
data,
|
data,
|
||||||
"canChangeTime",
|
"canChangeTime",
|
||||||
"can_change_time",
|
"can_change_time",
|
||||||
)
|
)
|
||||||
if can_change_time is None and isinstance(tx_act, dict):
|
if can_change_time is None and isinstance(tx_act, dict):
|
||||||
can_change_time = self._first_present(tx_act, "canChangeTime", "can_change_time")
|
can_change_time = self._first_present(
|
||||||
|
tx_act, "canChangeTime", "can_change_time"
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"act_alias": act_alias,
|
"act_alias": act_alias,
|
||||||
"api_version": api_version,
|
"api_version": api_version,
|
||||||
@@ -453,11 +518,16 @@ class DouyuActivityClient:
|
|||||||
self.ESPORTS_BIND_INFO_API,
|
self.ESPORTS_BIND_INFO_API,
|
||||||
"查询电竞手册绑定信息",
|
"查询电竞手册绑定信息",
|
||||||
params={"actAlias": act_alias},
|
params={"actAlias": act_alias},
|
||||||
headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"},
|
headers={
|
||||||
|
"Origin": "",
|
||||||
|
"Referer": "https://www.douyu.com/pages/live-peace-pel/web/",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if payload.get("error") not in (0, "0", None):
|
if payload.get("error") not in (0, "0", None):
|
||||||
raise DouyuActivityError(payload.get("msg") or "查询电竞手册绑定信息失败")
|
raise DouyuActivityError(payload.get("msg") or "查询电竞手册绑定信息失败")
|
||||||
parsed = self._parse_bind_info(payload, act_alias=act_alias, api_version="esports")
|
parsed = self._parse_bind_info(
|
||||||
|
payload, act_alias=act_alias, api_version="esports"
|
||||||
|
)
|
||||||
self._log_bind_info(parsed)
|
self._log_bind_info(parsed)
|
||||||
return parsed
|
return parsed
|
||||||
|
|
||||||
@@ -468,7 +538,10 @@ class DouyuActivityClient:
|
|||||||
self.ESPORTS_BIND_ROLE_PARAM_API,
|
self.ESPORTS_BIND_ROLE_PARAM_API,
|
||||||
"获取电竞手册绑定二维码",
|
"获取电竞手册绑定二维码",
|
||||||
params={"actAlias": act_alias},
|
params={"actAlias": act_alias},
|
||||||
headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"},
|
headers={
|
||||||
|
"Origin": "",
|
||||||
|
"Referer": "https://www.douyu.com/pages/live-peace-pel/web/",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if payload.get("error") not in (0, "0", None):
|
if payload.get("error") not in (0, "0", None):
|
||||||
raise DouyuActivityError(payload.get("msg") or "获取电竞手册绑定二维码失败")
|
raise DouyuActivityError(payload.get("msg") or "获取电竞手册绑定二维码失败")
|
||||||
@@ -503,7 +576,9 @@ class DouyuActivityClient:
|
|||||||
raise DouyuActivityError(f"获取电竞手册 ctn 失败: {response.text[:200]}")
|
raise DouyuActivityError(f"获取电竞手册 ctn 失败: {response.text[:200]}")
|
||||||
return ctn
|
return ctn
|
||||||
|
|
||||||
def confirm_esports_bind(self, act_alias: str, *, room_id: str = "9263298") -> dict[str, Any]:
|
def confirm_esports_bind(
|
||||||
|
self, act_alias: str, *, room_id: str = "9263298"
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""自动完成协议前置请求后,通过 actBind 确认电竞手册绑定。"""
|
"""自动完成协议前置请求后,通过 actBind 确认电竞手册绑定。"""
|
||||||
referer = (
|
referer = (
|
||||||
"https://www.douyu.com/pages/live-peace-pel/web"
|
"https://www.douyu.com/pages/live-peace-pel/web"
|
||||||
@@ -555,7 +630,9 @@ class DouyuActivityClient:
|
|||||||
raise DouyuActivityError(payload.get("msg") or "确认绑定失败")
|
raise DouyuActivityError(payload.get("msg") or "确认绑定失败")
|
||||||
return {"csrf_token": token, "raw": payload}
|
return {"csrf_token": token, "raw": payload}
|
||||||
|
|
||||||
def create_elite_qr(self, *, ctn: str, act_alias: str, amount: int, room_id: str) -> dict[str, Any]:
|
def create_elite_qr(
|
||||||
|
self, *, ctn: str, act_alias: str, amount: int, room_id: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""生成精英宝典支付二维码。"""
|
"""生成精英宝典支付二维码。"""
|
||||||
# E 语言实现会在 peace/item 和 getQrCode 两次请求里显式拼上 acf_ccn=ctn。
|
# E 语言实现会在 peace/item 和 getQrCode 两次请求里显式拼上 acf_ccn=ctn。
|
||||||
# 这里也同步 Cookie,避免接口只看 Cookie 时误判未绑定游戏账号。
|
# 这里也同步 Cookie,避免接口只看 Cookie 时误判未绑定游戏账号。
|
||||||
@@ -576,7 +653,11 @@ class DouyuActivityClient:
|
|||||||
"生成精英宝典支付码",
|
"生成精英宝典支付码",
|
||||||
json={
|
json={
|
||||||
"bizType": "peace",
|
"bizType": "peace",
|
||||||
"biz": {"actAlias": act_alias, "amount": amount, "roomId": int(room_id or 0)},
|
"biz": {
|
||||||
|
"actAlias": act_alias,
|
||||||
|
"amount": amount,
|
||||||
|
"roomId": int(room_id or 0),
|
||||||
|
},
|
||||||
"ctn": ctn,
|
"ctn": ctn,
|
||||||
},
|
},
|
||||||
headers={"Content-Type": "application/json;charset=UTF-8"},
|
headers={"Content-Type": "application/json;charset=UTF-8"},
|
||||||
@@ -584,9 +665,17 @@ class DouyuActivityClient:
|
|||||||
qr = (payload.get("data") or {}).get("qr") or ""
|
qr = (payload.get("data") or {}).get("qr") or ""
|
||||||
if not qr:
|
if not qr:
|
||||||
raise DouyuActivityError(payload.get("msg") or "生成精英宝典支付码失败")
|
raise DouyuActivityError(payload.get("msg") or "生成精英宝典支付码失败")
|
||||||
return {"pay_url": qr, "ctn": ctn, "act_alias": act_alias, "item": item_payload, "raw": payload}
|
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]:
|
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)
|
self._set_cookie_value("acf_ccn", ctn)
|
||||||
time_payload = self._request_json(
|
time_payload = self._request_json(
|
||||||
@@ -604,7 +693,11 @@ class DouyuActivityClient:
|
|||||||
"生成电竞手册支付码",
|
"生成电竞手册支付码",
|
||||||
json={
|
json={
|
||||||
"bizType": "peacePel",
|
"bizType": "peacePel",
|
||||||
"biz": {"actAlias": act_alias, "amount": amount, "roomId": int(room_id or 0)},
|
"biz": {
|
||||||
|
"actAlias": act_alias,
|
||||||
|
"amount": amount,
|
||||||
|
"roomId": int(room_id or 0),
|
||||||
|
},
|
||||||
"ctn": ctn,
|
"ctn": ctn,
|
||||||
},
|
},
|
||||||
headers={
|
headers={
|
||||||
@@ -633,7 +726,10 @@ class DouyuActivityClient:
|
|||||||
self.ESPORTS_USER_INFO_API,
|
self.ESPORTS_USER_INFO_API,
|
||||||
"查询电竞手册状态",
|
"查询电竞手册状态",
|
||||||
params={"manualID": manual_id},
|
params={"manualID": manual_id},
|
||||||
headers={"Origin": "", "Referer": "https://www.douyu.com/pages/live-peace-pel/web/"},
|
headers={
|
||||||
|
"Origin": "",
|
||||||
|
"Referer": "https://www.douyu.com/pages/live-peace-pel/web/",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if payload.get("error") not in (0, "0", None):
|
if payload.get("error") not in (0, "0", None):
|
||||||
raise DouyuActivityError(payload.get("msg") or "查询电竞手册状态失败")
|
raise DouyuActivityError(payload.get("msg") or "查询电竞手册状态失败")
|
||||||
@@ -712,7 +808,9 @@ class DouyuActivityClient:
|
|||||||
"raw": payload,
|
"raw": payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
def create_gold_qr(self, *, amount: int, pay_type: int = 1, product_id: str = "DYTV_Product_Gold_1") -> dict[str, Any]:
|
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(
|
payload = self._request_json(
|
||||||
"post",
|
"post",
|
||||||
@@ -846,12 +944,13 @@ class DouyuActivityClient:
|
|||||||
"""查询当前账号限兑商品。"""
|
"""查询当前账号限兑商品。"""
|
||||||
result = self.list_goods(manual_id=manual_id, rid=rid)
|
result = self.list_goods(manual_id=manual_id, rid=rid)
|
||||||
limited = [
|
limited = [
|
||||||
item for item in result["goods"]
|
item for item in result["goods"] if str(item.get("status") or "") == "1"
|
||||||
if str(item.get("status") or "") == "1"
|
|
||||||
]
|
]
|
||||||
return {"limited_goods": limited, "raw": result["raw"]}
|
return {"limited_goods": limited, "raw": result["raw"]}
|
||||||
|
|
||||||
def exchange_records(self, *, manual_id: str, limit: int = 100, offset: int = 0) -> dict[str, Any]:
|
def exchange_records(
|
||||||
|
self, *, manual_id: str, limit: int = 100, offset: int = 0
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""查询兑换记录。"""
|
"""查询兑换记录。"""
|
||||||
payload = self._request_json(
|
payload = self._request_json(
|
||||||
"get",
|
"get",
|
||||||
@@ -918,7 +1017,9 @@ class DouyuActivityClient:
|
|||||||
"raw": payload,
|
"raw": payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
def pay_exchange_order(self, *, manual_id: str, order_id: str, rid: str = "") -> dict[str, Any]:
|
def pay_exchange_order(
|
||||||
|
self, *, manual_id: str, order_id: str, rid: str = ""
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""Pay a previously locked exchange order with handbook points."""
|
"""Pay a previously locked exchange order with handbook points."""
|
||||||
normalized_order_id = str(order_id).strip()
|
normalized_order_id = str(order_id).strip()
|
||||||
if not normalized_order_id:
|
if not normalized_order_id:
|
||||||
@@ -946,7 +1047,9 @@ class DouyuActivityClient:
|
|||||||
"raw": payload,
|
"raw": payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
def exchange_orders(self, *, manual_id: str, page: int = 1, page_size: int = 10) -> dict[str, Any]:
|
def exchange_orders(
|
||||||
|
self, *, manual_id: str, page: int = 1, page_size: int = 10
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""Query current locked exchange orders."""
|
"""Query current locked exchange orders."""
|
||||||
payload = self._request_json(
|
payload = self._request_json(
|
||||||
"get",
|
"get",
|
||||||
@@ -967,7 +1070,9 @@ class DouyuActivityClient:
|
|||||||
"raw": payload,
|
"raw": payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
def exchange_goods(self, *, manual_id: str, rid: str, commodity_id: str) -> dict[str, Any]:
|
def exchange_goods(
|
||||||
|
self, *, manual_id: str, rid: str, commodity_id: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""Lock an activity commodity and immediately pay the locked order."""
|
"""Lock an activity commodity and immediately pay the locked order."""
|
||||||
locked = self.create_exchange_order(
|
locked = self.create_exchange_order(
|
||||||
manual_id=manual_id,
|
manual_id=manual_id,
|
||||||
@@ -1011,7 +1116,9 @@ class DouyuActivityClient:
|
|||||||
"raw": payload,
|
"raw": payload,
|
||||||
}
|
}
|
||||||
|
|
||||||
def query_goods_detail(self, *, manual_id: str, commodity_id: str, rid: str = "") -> dict[str, Any]:
|
def query_goods_detail(
|
||||||
|
self, *, manual_id: str, commodity_id: str, rid: str = ""
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""查询商品详情 storedetail(对齐浏览器 getGoodsDetail)。
|
"""查询商品详情 storedetail(对齐浏览器 getGoodsDetail)。
|
||||||
|
|
||||||
详情里带有兑换状态机所需的全部标记:preExchange.isExists / subscribeType /
|
详情里带有兑换状态机所需的全部标记:preExchange.isExists / subscribeType /
|
||||||
@@ -1021,7 +1128,11 @@ class DouyuActivityClient:
|
|||||||
"get",
|
"get",
|
||||||
self.GOODS_DETAIL_API,
|
self.GOODS_DETAIL_API,
|
||||||
"查询商品详情",
|
"查询商品详情",
|
||||||
params={"manualID": manual_id, "commodityID": commodity_id, "rid": rid or "0"},
|
params={
|
||||||
|
"manualID": manual_id,
|
||||||
|
"commodityID": commodity_id,
|
||||||
|
"rid": rid or "0",
|
||||||
|
},
|
||||||
headers={"Origin": "", "Referer": self._peace_shop_referer(rid)},
|
headers={"Origin": "", "Referer": self._peace_shop_referer(rid)},
|
||||||
)
|
)
|
||||||
if payload.get("error") not in (0, "0"):
|
if payload.get("error") not in (0, "0"):
|
||||||
@@ -1103,7 +1214,9 @@ class DouyuActivityClient:
|
|||||||
plan.update(action="classic", text="立即兑换")
|
plan.update(action="classic", text="立即兑换")
|
||||||
return plan
|
return plan
|
||||||
|
|
||||||
def batch_exchange_limit(self, *, manual_id: str, commodity_id: str, rid: str = "") -> dict[str, Any]:
|
def batch_exchange_limit(
|
||||||
|
self, *, manual_id: str, commodity_id: str, rid: str = ""
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""查询批量兑换上限(对齐页面 batchExchangeLimit,仅 batchExchange=1 商品)。"""
|
"""查询批量兑换上限(对齐页面 batchExchangeLimit,仅 batchExchange=1 商品)。"""
|
||||||
payload = self._post_csrf_form(
|
payload = self._post_csrf_form(
|
||||||
self.BATCH_EXCHANGE_LIMIT_API,
|
self.BATCH_EXCHANGE_LIMIT_API,
|
||||||
@@ -1116,7 +1229,9 @@ class DouyuActivityClient:
|
|||||||
data = payload.get("data") or {}
|
data = payload.get("data") or {}
|
||||||
return {"limit": data.get("limit"), "raw": payload}
|
return {"limit": data.get("limit"), "raw": payload}
|
||||||
|
|
||||||
def pre_exchange_check(self, *, manual_id: str, commodity_id: str) -> dict[str, Any]:
|
def pre_exchange_check(
|
||||||
|
self, *, manual_id: str, commodity_id: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""查询预兑状态(对齐页面 user/queryExchange,预兑弹窗前必查)。"""
|
"""查询预兑状态(对齐页面 user/queryExchange,预兑弹窗前必查)。"""
|
||||||
payload = self._request_json(
|
payload = self._request_json(
|
||||||
"get",
|
"get",
|
||||||
@@ -1129,7 +1244,9 @@ class DouyuActivityClient:
|
|||||||
raise DouyuActivityError(payload.get("msg") or "查询预兑状态失败")
|
raise DouyuActivityError(payload.get("msg") or "查询预兑状态失败")
|
||||||
return {"data": payload.get("data") or {}, "raw": payload}
|
return {"data": payload.get("data") or {}, "raw": payload}
|
||||||
|
|
||||||
def pre_exchange_cancel_check(self, *, manual_id: str, commodity_id: str) -> dict[str, Any]:
|
def pre_exchange_cancel_check(
|
||||||
|
self, *, manual_id: str, commodity_id: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""查询取消预兑信息(对齐页面 user/queryCancelExchange)。"""
|
"""查询取消预兑信息(对齐页面 user/queryCancelExchange)。"""
|
||||||
payload = self._request_json(
|
payload = self._request_json(
|
||||||
"get",
|
"get",
|
||||||
@@ -1142,7 +1259,9 @@ class DouyuActivityClient:
|
|||||||
raise DouyuActivityError(payload.get("msg") or "查询取消预兑信息失败")
|
raise DouyuActivityError(payload.get("msg") or "查询取消预兑信息失败")
|
||||||
return {"data": payload.get("data") or {}, "raw": payload}
|
return {"data": payload.get("data") or {}, "raw": payload}
|
||||||
|
|
||||||
def confirm_pre_exchange(self, *, manual_id: str, commodity_id: str, rid: str = "") -> dict[str, Any]:
|
def confirm_pre_exchange(
|
||||||
|
self, *, manual_id: str, commodity_id: str, rid: str = ""
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""确认预兑(对齐页面 user/confirmExchange,成功提示"预兑成功")。"""
|
"""确认预兑(对齐页面 user/confirmExchange,成功提示"预兑成功")。"""
|
||||||
payload = self._post_csrf_form(
|
payload = self._post_csrf_form(
|
||||||
self.PRE_EXCHANGE_CONFIRM_API,
|
self.PRE_EXCHANGE_CONFIRM_API,
|
||||||
@@ -1154,7 +1273,9 @@ class DouyuActivityClient:
|
|||||||
raise DouyuActivityError(payload.get("msg") or "确认预兑失败")
|
raise DouyuActivityError(payload.get("msg") or "确认预兑失败")
|
||||||
return {"raw": payload}
|
return {"raw": payload}
|
||||||
|
|
||||||
def cancel_pre_exchange(self, *, manual_id: str, commodity_id: str, rid: str = "") -> dict[str, Any]:
|
def cancel_pre_exchange(
|
||||||
|
self, *, manual_id: str, commodity_id: str, rid: str = ""
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""取消预兑(对齐页面 user/cancelExchange)。"""
|
"""取消预兑(对齐页面 user/cancelExchange)。"""
|
||||||
payload = self._post_csrf_form(
|
payload = self._post_csrf_form(
|
||||||
self.PRE_EXCHANGE_CANCEL_API,
|
self.PRE_EXCHANGE_CANCEL_API,
|
||||||
@@ -1166,7 +1287,9 @@ class DouyuActivityClient:
|
|||||||
raise DouyuActivityError(payload.get("msg") or "取消预兑失败")
|
raise DouyuActivityError(payload.get("msg") or "取消预兑失败")
|
||||||
return {"raw": payload}
|
return {"raw": payload}
|
||||||
|
|
||||||
def subscribe_commodity(self, *, manual_id: str, commodity_id: str) -> dict[str, Any]:
|
def subscribe_commodity(
|
||||||
|
self, *, manual_id: str, commodity_id: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""预约到货(对齐页面 user/sub,成功提示"预约成功")。"""
|
"""预约到货(对齐页面 user/sub,成功提示"预约成功")。"""
|
||||||
payload = self._post_csrf_form(
|
payload = self._post_csrf_form(
|
||||||
self.COMMODITY_SUBSCRIBE_API,
|
self.COMMODITY_SUBSCRIBE_API,
|
||||||
@@ -1178,7 +1301,9 @@ class DouyuActivityClient:
|
|||||||
raise DouyuActivityError(payload.get("msg") or "预约到货失败")
|
raise DouyuActivityError(payload.get("msg") or "预约到货失败")
|
||||||
return {"raw": payload}
|
return {"raw": payload}
|
||||||
|
|
||||||
def unsubscribe_commodity(self, *, manual_id: str, commodity_id: str) -> dict[str, Any]:
|
def unsubscribe_commodity(
|
||||||
|
self, *, manual_id: str, commodity_id: str
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""取消预约到货(对齐页面 user/cancelSub)。"""
|
"""取消预约到货(对齐页面 user/cancelSub)。"""
|
||||||
payload = self._post_csrf_form(
|
payload = self._post_csrf_form(
|
||||||
self.COMMODITY_UNSUBSCRIBE_API,
|
self.COMMODITY_UNSUBSCRIBE_API,
|
||||||
@@ -1190,7 +1315,9 @@ class DouyuActivityClient:
|
|||||||
raise DouyuActivityError(payload.get("msg") or "取消预约到货失败")
|
raise DouyuActivityError(payload.get("msg") or "取消预约到货失败")
|
||||||
return {"raw": payload}
|
return {"raw": payload}
|
||||||
|
|
||||||
def exchange_gift(self, *, manual_id: str, commodity_id: str, rid: str = "", num: int = 1) -> dict[str, Any]:
|
def exchange_gift(
|
||||||
|
self, *, manual_id: str, commodity_id: str, rid: str = "", num: int = 1
|
||||||
|
) -> dict[str, Any]:
|
||||||
"""单步兑换(对齐页面 postExchangeGift -> /ordnc/activity/peace/exchange)。
|
"""单步兑换(对齐页面 postExchangeGift -> /ordnc/activity/peace/exchange)。
|
||||||
|
|
||||||
2026-08-30 bundle 中新链路,页面主要用于兑换中心/自动兑换场景;字段按页面
|
2026-08-30 bundle 中新链路,页面主要用于兑换中心/自动兑换场景;字段按页面
|
||||||
@@ -1263,7 +1390,10 @@ class DouyuActivityClient:
|
|||||||
"get",
|
"get",
|
||||||
self.GOLD_ACCOUNT_API,
|
self.GOLD_ACCOUNT_API,
|
||||||
"查询鱼翅余额",
|
"查询鱼翅余额",
|
||||||
headers={"Origin": "https://cz.douyu.com", "Referer": "https://cz.douyu.com/"},
|
headers={
|
||||||
|
"Origin": "https://cz.douyu.com",
|
||||||
|
"Referer": "https://cz.douyu.com/",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
data = payload.get("data") or {}
|
data = payload.get("data") or {}
|
||||||
return {"gold": data.get("gold"), "raw": payload}
|
return {"gold": data.get("gold"), "raw": payload}
|
||||||
@@ -1315,7 +1445,7 @@ class DouyuActivityClient:
|
|||||||
match = re.search(rf"var\s+{varname}\s*=", text)
|
match = re.search(rf"var\s+{varname}\s*=", text)
|
||||||
if not match:
|
if not match:
|
||||||
raise DouyuActivityError(f"响应中找不到 var {varname}=: {text[:200]}")
|
raise DouyuActivityError(f"响应中找不到 var {varname}=: {text[:200]}")
|
||||||
chunk = text[match.end():].strip()
|
chunk = text[match.end() :].strip()
|
||||||
if chunk.endswith(";"):
|
if chunk.endswith(";"):
|
||||||
chunk = chunk[:-1]
|
chunk = chunk[:-1]
|
||||||
try:
|
try:
|
||||||
@@ -1342,7 +1472,9 @@ class DouyuActivityClient:
|
|||||||
stk = cookie_value(self.cookie, "acf_stk")
|
stk = cookie_value(self.cookie, "acf_stk")
|
||||||
ltkid = cookie_value(self.cookie, "acf_ltkid")
|
ltkid = cookie_value(self.cookie, "acf_ltkid")
|
||||||
if not uid or not stk or not ltkid:
|
if not uid or not stk or not ltkid:
|
||||||
raise DouyuActivityError("Cookie 缺少 acf_uid/acf_stk/acf_ltkid,无法获取小店参数")
|
raise DouyuActivityError(
|
||||||
|
"Cookie 缺少 acf_uid/acf_stk/acf_ltkid,无法获取小店参数"
|
||||||
|
)
|
||||||
return f"{uid}_1_{stk}_0_{ltkid}"
|
return f"{uid}_1_{stk}_0_{ltkid}"
|
||||||
|
|
||||||
def xpd_embed_query(self, *, act_alias: str, rid: str) -> dict[str, Any]:
|
def xpd_embed_query(self, *, act_alias: str, rid: str) -> dict[str, Any]:
|
||||||
@@ -1473,9 +1605,15 @@ class DouyuActivityClient:
|
|||||||
)
|
)
|
||||||
recommend = self._xpd_parse_var(response.text, "recommend")
|
recommend = self._xpd_parse_var(response.text, "recommend")
|
||||||
if str(recommend.get("errcode") or "0") != "0":
|
if str(recommend.get("errcode") or "0") != "0":
|
||||||
raise DouyuActivityError(str(recommend.get("errdesc") or "小店商品查询失败"))
|
raise DouyuActivityError(
|
||||||
|
str(recommend.get("errdesc") or "小店商品查询失败")
|
||||||
|
)
|
||||||
pages.append(recommend)
|
pages.append(recommend)
|
||||||
items = (((recommend.get("data") or {}).get("client_data") or {}).get("itemsdetail")) or []
|
items = (
|
||||||
|
((recommend.get("data") or {}).get("client_data") or {}).get(
|
||||||
|
"itemsdetail"
|
||||||
|
)
|
||||||
|
) or []
|
||||||
if not isinstance(items, list):
|
if not isinstance(items, list):
|
||||||
raise DouyuActivityError("小店商品响应格式异常")
|
raise DouyuActivityError("小店商品响应格式异常")
|
||||||
|
|
||||||
@@ -1554,8 +1692,12 @@ class DouyuActivityClient:
|
|||||||
info = self._xpd_parse_var(response.text, "info")
|
info = self._xpd_parse_var(response.text, "info")
|
||||||
if str(info.get("ret") or "0") != "0":
|
if str(info.get("ret") or "0") != "0":
|
||||||
raise DouyuActivityError(str(info.get("msg") or "小店购买记录查询失败"))
|
raise DouyuActivityError(str(info.get("msg") or "小店购买记录查询失败"))
|
||||||
data = info.get("data") if isinstance(info.get("data"), dict) else {}
|
data: dict[str, Any] = (
|
||||||
page_records = data.get("list") if isinstance(data.get("list"), list) else []
|
info.get("data") if isinstance(info.get("data"), dict) else {}
|
||||||
|
)
|
||||||
|
page_records: list[Any] = (
|
||||||
|
data.get("list") if isinstance(data.get("list"), list) else []
|
||||||
|
)
|
||||||
total = self._xpd_int(data.get("cnt")) or total
|
total = self._xpd_int(data.get("cnt")) or total
|
||||||
pages.append(info)
|
pages.append(info)
|
||||||
|
|
||||||
@@ -1568,10 +1710,14 @@ class DouyuActivityClient:
|
|||||||
goods_info = json.loads(goods_info)
|
goods_info = json.loads(goods_info)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
goods_info = {}
|
goods_info = {}
|
||||||
goods_list = goods_info.get("list") if isinstance(goods_info, dict) else []
|
goods_list = (
|
||||||
|
goods_info.get("list") if isinstance(goods_info, dict) else []
|
||||||
|
)
|
||||||
goods = (
|
goods = (
|
||||||
goods_list[0]
|
goods_list[0]
|
||||||
if isinstance(goods_list, list) and goods_list and isinstance(goods_list[0], dict)
|
if isinstance(goods_list, list)
|
||||||
|
and goods_list
|
||||||
|
and isinstance(goods_list[0], dict)
|
||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
app_ext = record.get("sAppExt")
|
app_ext = record.get("sAppExt")
|
||||||
@@ -1580,31 +1726,41 @@ class DouyuActivityClient:
|
|||||||
app_ext = json.loads(app_ext)
|
app_ext = json.loads(app_ext)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
app_ext = {}
|
app_ext = {}
|
||||||
records.append({
|
records.append(
|
||||||
"serial": str(record.get("sSerialNum") or ""),
|
{
|
||||||
"goods_name": str(
|
"serial": str(record.get("sSerialNum") or ""),
|
||||||
goods.get("sGoodsName")
|
"goods_name": str(
|
||||||
or goods.get("sPacketName")
|
goods.get("sGoodsName")
|
||||||
or record.get("sActionDesc")
|
or goods.get("sPacketName")
|
||||||
or ""
|
or record.get("sActionDesc")
|
||||||
),
|
or ""
|
||||||
"goods_image": str(goods.get("sGoodsPic") or (app_ext or {}).get("sGoodsPic") or ""),
|
),
|
||||||
"buy_time": str(record.get("dtBuyTime") or ""),
|
"goods_image": str(
|
||||||
"pay_time": str(record.get("dtPayTime") or ""),
|
goods.get("sGoodsPic")
|
||||||
"status": str(record.get("iStatus") or ""),
|
or (app_ext or {}).get("sGoodsPic")
|
||||||
"price": self._xpd_int(record.get("iPrice")),
|
or ""
|
||||||
"pay_amount": self._xpd_int(record.get("iPayAmount")),
|
),
|
||||||
"pay_type": str(record.get("sPayType") or ""),
|
"buy_time": str(record.get("dtBuyTime") or ""),
|
||||||
"role_name": str(record.get("sRoleName") or ""),
|
"pay_time": str(record.get("dtPayTime") or ""),
|
||||||
"send_status": str(goods.get("iSendStatus") or ""),
|
"status": str(record.get("iStatus") or ""),
|
||||||
"delivery_message": str(goods.get("deliveryMsg") or ""),
|
"price": self._xpd_int(record.get("iPrice")),
|
||||||
"raw": record,
|
"pay_amount": self._xpd_int(record.get("iPayAmount")),
|
||||||
})
|
"pay_type": str(record.get("sPayType") or ""),
|
||||||
|
"role_name": str(record.get("sRoleName") or ""),
|
||||||
|
"send_status": str(goods.get("iSendStatus") or ""),
|
||||||
|
"delivery_message": str(goods.get("deliveryMsg") or ""),
|
||||||
|
"raw": record,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if len(page_records) < page_size or len(records) >= total:
|
if len(page_records) < page_size or len(records) >= total:
|
||||||
break
|
break
|
||||||
|
|
||||||
return {"records": records, "total": total or len(records), "raw": {"pages": pages}}
|
return {
|
||||||
|
"records": records,
|
||||||
|
"total": total or len(records),
|
||||||
|
"raw": {"pages": pages},
|
||||||
|
}
|
||||||
|
|
||||||
def xpd_bind_qr(self, *, act_alias: str) -> dict[str, Any]:
|
def xpd_bind_qr(self, *, act_alias: str) -> dict[str, Any]:
|
||||||
"""生成和平小店绑定二维码链接(微信扫码进入 livelink 小程序绑定角色)。
|
"""生成和平小店绑定二维码链接(微信扫码进入 livelink 小程序绑定角色)。
|
||||||
@@ -1653,9 +1809,15 @@ class DouyuActivityClient:
|
|||||||
)
|
)
|
||||||
if payload.get("error") not in (0, "0", None):
|
if payload.get("error") not in (0, "0", None):
|
||||||
raise DouyuActivityError(payload.get("msg") or "查询小店绑定信息失败")
|
raise DouyuActivityError(payload.get("msg") or "查询小店绑定信息失败")
|
||||||
data = payload.get("data") or {}
|
data: dict[str, Any] = (
|
||||||
game_role = data.get("gameRole") if isinstance(data.get("gameRole"), dict) else {}
|
payload.get("data") if isinstance(payload.get("data"), dict) else {}
|
||||||
game_account = data.get("gameAccount") if isinstance(data.get("gameAccount"), dict) else {}
|
)
|
||||||
|
game_role: dict[str, Any] = (
|
||||||
|
data.get("gameRole") if isinstance(data.get("gameRole"), dict) else {}
|
||||||
|
)
|
||||||
|
game_account: dict[str, Any] = (
|
||||||
|
data.get("gameAccount") if isinstance(data.get("gameAccount"), dict) else {}
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"bind_account": self._xpd_int(data.get("bindAccount")),
|
"bind_account": self._xpd_int(data.get("bindAccount")),
|
||||||
"bind_role": self._xpd_int(data.get("bindRole")),
|
"bind_role": self._xpd_int(data.get("bindRole")),
|
||||||
@@ -1790,7 +1952,8 @@ class DouyuActivityClient:
|
|||||||
"_biz_code": "cjm",
|
"_biz_code": "cjm",
|
||||||
"_act_id": act_id,
|
"_act_id": act_id,
|
||||||
"sLiveUserInfo": json.dumps(
|
"sLiveUserInfo": json.dumps(
|
||||||
{"sAnchorId": rid, "sVideoId": 0}, separators=(",", ":"),
|
{"sAnchorId": rid, "sVideoId": 0},
|
||||||
|
separators=(",", ":"),
|
||||||
),
|
),
|
||||||
"appext": json.dumps(
|
"appext": json.dumps(
|
||||||
{"user_price": price, "sGoodsPic": picture, "paytype": pay_type},
|
{"user_price": price, "sGoodsPic": picture, "paytype": pay_type},
|
||||||
@@ -1801,7 +1964,16 @@ class DouyuActivityClient:
|
|||||||
"_sid": "8",
|
"_sid": "8",
|
||||||
"set": "8",
|
"set": "8",
|
||||||
}
|
}
|
||||||
for key in ("gameId", "actId", "appId", "livePlatId", "code", "timestamp", "v", "sig"):
|
for key in (
|
||||||
|
"gameId",
|
||||||
|
"actId",
|
||||||
|
"appId",
|
||||||
|
"livePlatId",
|
||||||
|
"code",
|
||||||
|
"timestamp",
|
||||||
|
"v",
|
||||||
|
"sig",
|
||||||
|
):
|
||||||
value = str(embed_query.get(key) or "")
|
value = str(embed_query.get(key) or "")
|
||||||
if value:
|
if value:
|
||||||
params[key] = value
|
params[key] = value
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Iterable, Mapping
|
from collections.abc import Iterable, Mapping
|
||||||
|
|
||||||
import requests
|
from requests.cookies import RequestsCookieJar
|
||||||
|
|
||||||
|
|
||||||
def cookie_pairs(cookie: str) -> list[tuple[str, str]]:
|
def cookie_pairs(cookie: str) -> list[tuple[str, str]]:
|
||||||
@@ -21,7 +21,7 @@ def cookie_pairs(cookie: str) -> list[tuple[str, str]]:
|
|||||||
return pairs
|
return pairs
|
||||||
|
|
||||||
|
|
||||||
def normalize_cookie_pairs(pairs: Iterable[tuple[str, str]]) -> str:
|
def normalize_cookie_pairs(pairs: Iterable[tuple[str, str | None]]) -> str:
|
||||||
"""按 Cookie key 去重,保留最后一次出现的值。"""
|
"""按 Cookie key 去重,保留最后一次出现的值。"""
|
||||||
ordered_keys: list[str] = []
|
ordered_keys: list[str] = []
|
||||||
values: dict[str, str] = {}
|
values: dict[str, str] = {}
|
||||||
@@ -36,7 +36,7 @@ def normalize_cookie_pairs(pairs: Iterable[tuple[str, str]]) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def normalize_douyu_cookie(
|
def normalize_douyu_cookie(
|
||||||
cookie: str | Mapping[str, str] | requests.cookies.RequestsCookieJar | None,
|
cookie: str | Mapping[str, str] | RequestsCookieJar | None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""把 Cookie 字符串、dict 或 CookieJar 转成去重后的浏览器 Cookie 字符串。"""
|
"""把 Cookie 字符串、dict 或 CookieJar 转成去重后的浏览器 Cookie 字符串。"""
|
||||||
if not cookie:
|
if not cookie:
|
||||||
@@ -47,7 +47,7 @@ def normalize_douyu_cookie(
|
|||||||
|
|
||||||
|
|
||||||
def cookie_value(
|
def cookie_value(
|
||||||
cookie: str | Mapping[str, str] | requests.cookies.RequestsCookieJar | None,
|
cookie: str | Mapping[str, str] | RequestsCookieJar | None,
|
||||||
key: str,
|
key: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""从 Cookie 中读取 key;有重复时以最后一次出现为准。"""
|
"""从 Cookie 中读取 key;有重复时以最后一次出现为准。"""
|
||||||
|
|||||||
@@ -10,8 +10,6 @@
|
|||||||
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from .proxy_resolver import ProxyResolver
|
from .proxy_resolver import ProxyResolver
|
||||||
@@ -28,8 +26,8 @@ class ProxyFetcher:
|
|||||||
self,
|
self,
|
||||||
api_url: str,
|
api_url: str,
|
||||||
whitelist_platform: str = "xiequ",
|
whitelist_platform: str = "xiequ",
|
||||||
whitelist_credentials: dict = None,
|
whitelist_credentials: dict | None = None,
|
||||||
stop_event: Optional[threading.Event] = None,
|
stop_event: threading.Event | None = None,
|
||||||
):
|
):
|
||||||
self.api_url = api_url
|
self.api_url = api_url
|
||||||
self.stop_event = stop_event
|
self.stop_event = stop_event
|
||||||
@@ -66,7 +64,7 @@ class ProxyFetcher:
|
|||||||
return True, f"出口 IP {local_ip} 已同步: {msg}"
|
return True, f"出口 IP {local_ip} 已同步: {msg}"
|
||||||
return False, f"出口 IP {local_ip} 同步失败: {msg}"
|
return False, f"出口 IP {local_ip} 同步失败: {msg}"
|
||||||
|
|
||||||
def fetch_new_proxy(self, max_attempts: int = 3) -> Optional[str]:
|
def fetch_new_proxy(self, max_attempts: int = 3) -> str | None:
|
||||||
"""从代理 API 获取 1 个可用代理,失败返回 None。
|
"""从代理 API 获取 1 个可用代理,失败返回 None。
|
||||||
|
|
||||||
不主动检测/同步本地出口 IP;如果代理 API 返回"白名单错误",
|
不主动检测/同步本地出口 IP;如果代理 API 返回"白名单错误",
|
||||||
@@ -115,7 +113,7 @@ class ProxyFetcher:
|
|||||||
def get_proxy_fetcher(
|
def get_proxy_fetcher(
|
||||||
api_url: str,
|
api_url: str,
|
||||||
whitelist_platform: str = "xiequ",
|
whitelist_platform: str = "xiequ",
|
||||||
whitelist_credentials: dict = None,
|
whitelist_credentials: dict | None = None,
|
||||||
) -> ProxyFetcher:
|
) -> ProxyFetcher:
|
||||||
"""获取代理获取器实例。"""
|
"""获取代理获取器实例。"""
|
||||||
return ProxyFetcher(
|
return ProxyFetcher(
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ class ProxyResolver:
|
|||||||
def resolve_working_proxy(
|
def resolve_working_proxy(
|
||||||
api_url: str,
|
api_url: str,
|
||||||
whitelist_platform: str = "xiequ",
|
whitelist_platform: str = "xiequ",
|
||||||
whitelist_credentials: dict = None,
|
whitelist_credentials: dict | None = None,
|
||||||
max_attempts: int = 4,
|
max_attempts: int = 4,
|
||||||
log_func: Optional[LogFunc] = None,
|
log_func: Optional[LogFunc] = None,
|
||||||
) -> tuple[Optional[str], str]:
|
) -> tuple[Optional[str], str]:
|
||||||
|
|||||||
Reference in New Issue
Block a user