feat(douyu): 支持和平小店商品兑换

This commit is contained in:
yml2213
2026-08-07 22:47:13 +08:00
parent a67d144b78
commit ec5eb637bb
6 changed files with 350 additions and 10 deletions
+78 -1
View File
@@ -826,6 +826,7 @@ class DouyuActivityClient:
# 2026-08 抓包(8.6-和平小店)实证:本期走 xn_live_cjm 变体(旧期为 recommend_live/common)
XPD_RECOMMEND_API = "https://apps.game.qq.com/daoju/v3/recommend_xn_live_cjm/common"
XPD_BALANCE_API = "https://apps.game.qq.com/daoju/igw/live/"
XPD_LIVE_BUY_API = "https://apps.game.qq.com/daoju/igw/livebuy/"
XPD_UA = (
"Mozilla/5.0 (Linux; Android 12; HBN-AL00 Build/V417IR; wv) "
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 "
@@ -965,7 +966,7 @@ class DouyuActivityClient:
"iRelationGoodsid,dtModifyTime,sBuyLimitInfo,sGoodsWaterMark,dtShowBeginTime,"
"dtShowEndTime,iSort,iGoodsId,sGoodsName,iJbPrice,iJbOrgPrice,iJb2Price,"
"iJb2OrgPrice,iPrice,iOrgPrice,sGoodsPic,sGoodsDesc,sPayType,dtBeginTime,"
"dtEndTime,iActionId,sExtShowInfo,sExtInfo,iCategoryId,dtRushBegin,dtRushEnd"
"dtEndTime,iActionId,sExtShowInfo,sExtInfo,iCategoryId,iGoodsLeft,dtRushBegin,dtRushEnd"
),
"actionFields": "iActionId,sActionName,dtBeginTime,dtEndTime",
"order_by": "dtShowBeginTime",
@@ -1146,3 +1147,79 @@ class DouyuActivityClient:
)
info = self._xpd_parse_var(response.text, "jbInfos")
return {"fragments": self._xpd_int(info.get("jb2")), "raw": info}
def xpd_exchange_goods(
self,
*,
embed_query: dict[str, str],
act_id: str,
rid: str,
commodity_id: str,
price: int,
picture: str = "",
pay_type: int = 1,
action_id: str = "",
) -> dict[str, Any]:
"""兑换和平小店商品。
道聚城的 code/sig 为短时授权参数,调用方必须在每次兑换前重新获取。
该接口会实际扣除点券或扭蛋碎片,因此遇到网络错误不做自动重试。
"""
if pay_type not in (1, 5):
raise DouyuActivityError("小店兑换仅支持点券或扭蛋碎片")
if not commodity_id:
raise DouyuActivityError("小店兑换缺少商品 ID")
if price < 0:
raise DouyuActivityError("小店兑换商品价格无效")
params = {
"_service": "buy.plug.svr.web",
"_plug_id": "7200",
"_jsvar": "buyInfo",
"acctype": "livelink",
"_app_id": "2123",
"iActionId": action_id or act_id,
"propid": commodity_id,
"paytype": str(pay_type),
"source": rid,
"sAnchorId": rid,
"attach": json.dumps({"ch": "0"}, separators=(",", ":")),
"apptype": "3",
"_biz_code": "cjm",
"_act_id": act_id,
"sLiveUserInfo": json.dumps(
{"sAnchorId": rid, "sVideoId": 0}, separators=(",", ":"),
),
"appext": json.dumps(
{"user_price": price, "sGoodsPic": picture, "paytype": pay_type},
separators=(",", ":"),
),
"isCode": "1",
"authType": "delegate",
"_sid": "8",
"set": "8",
}
for key in ("gameId", "actId", "appId", "livePlatId", "code", "timestamp", "v", "sig"):
value = str(embed_query.get(key) or "")
if value:
params[key] = value
response = self._request(
"get",
self.XPD_LIVE_BUY_API,
source="兑换小店商品",
params=params,
headers=self._xpd_daoju_headers(),
)
info = self._xpd_parse_var(response.text, "buyInfo")
if str(info.get("ret")) != "0" or str(info.get("msg") or "").lower() != "ok":
raise DouyuActivityError(str(info.get("msg") or "小店兑换失败"))
return {
"commodity_id": commodity_id,
"pay_type": pay_type,
"price": price,
"serial": str(info.get("serial") or info.get("sSerialNum") or ""),
"event_id": str(info.get("event_id") or ""),
"new_balance": self._xpd_int(info.get("newBalance")),
"raw": info,
}