fix(huya): 查询积分改走积分中心新接口(含成长等级)
旧活动接口 webActUI.getUserScore 对任何 sid 都返回 603「活动已结束」 (接口已下线,与工作台 SID/活动配置无关),刷新积分只会报错。 - 协议层新增两个 RPC(WSS + HTTP 双侧同签名): shopMiddleUI.getRedeemNeedGoods(tag2=当前积分、tag3=兑换门槛, 兑换商品号 POINTS_REDEEM_GOODS_NUM=8315982,传错直接 500) 与 GameBizUI.getGrowLevelUserInfo(成长值/成长等级/昵称/头像) - 新增结构体 GetRedeemNeedGoodsReq / GetGrowLevelUserInfoReq, 与 HuyaEliteWssSession 透传 - 服务层 _execute_query_points 主源改为积分中心接口,结果带 points/redeem_threshold/grow_level/grow_value,并同步 account.points; 接口无响应时才回退旧活动接口并如实报告其 603 返回 - 开通支付成功后的积分刷新同样改走积分中心接口 - 前端任务页积分结果优先读 points(兼容旧记录的 available_score), 并在有成长等级时一并展示 - 新增 tests/test_huya_points_query.py:真机响应字节回归 + 请求参数 字面量断言 + 服务层积分来源/回退语义(已做反向验证) 真机验证:HTTP 通道取到积分 280 / 门槛 20 / 成长等级 3
This commit is contained in:
@@ -273,5 +273,11 @@ class HuyaEliteWssSession:
|
||||
def get_follow_wechat_status(self, **kwargs):
|
||||
return self._run(self.client.get_follow_wechat_status(**kwargs))
|
||||
|
||||
def get_redeem_need_goods(self, **kwargs):
|
||||
return self._run(self.client.get_redeem_need_goods(**kwargs))
|
||||
|
||||
def get_grow_level_user_info(self, **kwargs):
|
||||
return self._run(self.client.get_grow_level_user_info(**kwargs))
|
||||
|
||||
|
||||
__all__ = ["HuyaEliteWssSession"]
|
||||
|
||||
@@ -29,6 +29,7 @@ from .wss_client import (
|
||||
ORDER_BIZ_TYPE,
|
||||
ORDER_SCENE,
|
||||
POINTS_PAY_CALLBACK,
|
||||
POINTS_REDEEM_GOODS_NUM,
|
||||
SHOP_APP_SCENE,
|
||||
)
|
||||
from .wup_protocol import WupRequest, WupResponse
|
||||
@@ -1448,6 +1449,53 @@ class HuyaHttpClient:
|
||||
)
|
||||
return self._raw_from_wup(resp) if isinstance(resp, WupResponse) else None
|
||||
|
||||
def get_redeem_need_goods(
|
||||
self,
|
||||
uid: int,
|
||||
guid: str,
|
||||
cookie: str,
|
||||
goods_num: int = POINTS_REDEEM_GOODS_NUM,
|
||||
timeout: float = 15.0,
|
||||
):
|
||||
"""shopMiddleUI.getRedeemNeedGoods(当前积分 tag2 / 兑换门槛 tag3)。"""
|
||||
from .shop_structs import GetRedeemNeedGoodsReq
|
||||
|
||||
req = GetRedeemNeedGoodsReq()
|
||||
req.userId = self._build_shop_user(uid, cookie)
|
||||
req.shopAppInfo = self._build_shop_app("yellowcarlist", SHOP_APP_SCENE)
|
||||
req.goodsNum = int(goods_num or 0)
|
||||
resp = self.call_rpc(
|
||||
"shopMiddleUI",
|
||||
"getRedeemNeedGoods",
|
||||
req,
|
||||
None,
|
||||
uid,
|
||||
guid,
|
||||
cookie,
|
||||
timeout=timeout,
|
||||
)
|
||||
return self._raw_from_wup(resp) if isinstance(resp, WupResponse) else None
|
||||
|
||||
def get_grow_level_user_info(
|
||||
self, uid: int, guid: str, cookie: str, timeout: float = 15.0
|
||||
):
|
||||
"""GameBizUI.getGrowLevelUserInfo(成长值 tag1.tag0 / 等级 tag1.tag1)。"""
|
||||
from .shop_structs import GetGrowLevelUserInfoReq
|
||||
|
||||
req = GetGrowLevelUserInfoReq()
|
||||
req.userId = self._build_shop_user(uid, cookie)
|
||||
resp = self.call_rpc(
|
||||
"GameBizUI",
|
||||
"getGrowLevelUserInfo",
|
||||
req,
|
||||
None,
|
||||
uid,
|
||||
guid,
|
||||
cookie,
|
||||
timeout=timeout,
|
||||
)
|
||||
return self._raw_from_wup(resp) if isinstance(resp, WupResponse) else None
|
||||
|
||||
|
||||
def _self_check():
|
||||
uid = 1199647239697
|
||||
|
||||
@@ -1398,6 +1398,39 @@ class FollowWeChatStatusReq(TafStruct):
|
||||
pass
|
||||
|
||||
|
||||
class GetRedeemNeedGoodsReq(ShopAppRequest):
|
||||
"""shopMiddleUI.getRedeemNeedGoods 请求(兑换所需商品 + 当前积分)。
|
||||
|
||||
tag2 是兑换商品的数字商品号(抓包为精英宝典兑换商品 ``hy-8315982`` →
|
||||
``8315982``);传错会返回 500「系统异常」,所以调用方必须给真实商品号。
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.goodsNum: int = 0 # tag 2
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
os.write_struct(0, self.userId)
|
||||
os.write_struct(1, self.shopAppInfo)
|
||||
os.write_int64(2, self.goodsNum)
|
||||
|
||||
def read_from(self, ins: TafInputStream):
|
||||
pass
|
||||
|
||||
|
||||
class GetGrowLevelUserInfoReq(TafStruct):
|
||||
"""GameBizUI.getGrowLevelUserInfo 请求(抓包 tReq 只有 tag0 UserId)。"""
|
||||
|
||||
def __init__(self):
|
||||
self.userId = UserId() # tag 0
|
||||
|
||||
def write_to(self, os: TafOutputStream):
|
||||
os.write_struct(0, self.userId)
|
||||
|
||||
def read_from(self, ins: TafInputStream):
|
||||
self.userId = ins.read_struct(0, UserId) or self.userId
|
||||
|
||||
|
||||
def shop_spu_number(spu_id: str) -> int:
|
||||
"""把商品 ID(``hy-8328015``)转成接口需要的数字形式(``8328015``)。"""
|
||||
text = str(spu_id or "").strip()
|
||||
|
||||
@@ -73,6 +73,9 @@ ORDER_BIZ_TYPE = 5
|
||||
SHOP_APP_SCENE = 7
|
||||
ORDER_SCENE = 4
|
||||
PEACE_ELITE_GAME_ID = 9815
|
||||
# getRedeemNeedGoods 的兑换商品号:抓包里的精英宝典兑换商品 hy-8315982 → 8315982。
|
||||
# 服务端只认这个商品号(传 0/其它值直接 500「系统异常」)。
|
||||
POINTS_REDEEM_GOODS_NUM = 8315982
|
||||
# createOrderV5 的 extraParam.bizParams:标记「积分中心」来源,缺了会走成普通商城单。
|
||||
JIFENCENTER_BIZ_PARAMS = '{ "jifencenter": 1 }'
|
||||
|
||||
@@ -1448,3 +1451,34 @@ class HuyaWssClient:
|
||||
return await self.call_rpc_raw(
|
||||
"wupui", "getUserFollowWeChatOfficialStatus", req
|
||||
)
|
||||
|
||||
async def get_redeem_need_goods(
|
||||
self,
|
||||
uid: int,
|
||||
guid: str,
|
||||
cookie: str,
|
||||
goods_num: int = POINTS_REDEEM_GOODS_NUM,
|
||||
):
|
||||
"""shopMiddleUI.getRedeemNeedGoods — 兑换所需商品与当前积分。
|
||||
|
||||
响应:``tag2`` 当前积分、``tag3`` 兑换门槛积分(抓包 + 真机实证)。
|
||||
"""
|
||||
from .shop_structs import GetRedeemNeedGoodsReq
|
||||
|
||||
req = GetRedeemNeedGoodsReq()
|
||||
req.userId = self._build_biz_user(uid, cookie)
|
||||
req.shopAppInfo = self._build_shop_app("yellowcarlist", SHOP_APP_SCENE)
|
||||
req.goodsNum = int(goods_num or 0)
|
||||
return await self.call_rpc_raw("shopMiddleUI", "getRedeemNeedGoods", req)
|
||||
|
||||
async def get_grow_level_user_info(self, uid: int, guid: str, cookie: str):
|
||||
"""GameBizUI.getGrowLevelUserInfo — 成长值/成长等级/昵称。
|
||||
|
||||
响应:``tag1.tag0`` 成长值、``tag1.tag1`` 成长等级、``tag2`` 昵称、
|
||||
``tag3`` 头像(抓包 + 真机实证)。
|
||||
"""
|
||||
from .shop_structs import GetGrowLevelUserInfoReq
|
||||
|
||||
req = GetGrowLevelUserInfoReq()
|
||||
req.userId = self._build_biz_user(uid, cookie)
|
||||
return await self.call_rpc_raw("GameBizUI", "getGrowLevelUserInfo", req)
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
"""查询积分改走积分中心新接口(2026-10-15 真机实测)。
|
||||
|
||||
背景:旧活动接口 ``webActUI.getUserScore`` 对任何 sid 都返回
|
||||
603「活动已结束」(该接口已下线,与工作台里的 SID/活动配置无关);
|
||||
积分中心用 ``shopMiddleUI.getRedeemNeedGoods`` 查积分:
|
||||
``tag2``=当前积分、``tag3``=兑换门槛。下面固化的响应字节来自真机 HTTP 通道。
|
||||
|
||||
抓包/真机字面值直接写在测试里(不引用实现常量),避免断言跟着实现漂移。
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from core.huya.activity_structs import GetUserScoreResp
|
||||
from core.huya.frame_decoder import _decode_wup_body, decode_struct_bytes
|
||||
from core.huya.http_client import HuyaHttpClient
|
||||
from core.huya.shop_structs import ShopRawRsp
|
||||
from core.huya.wss_client import HuyaWssClient, WssMessage
|
||||
from web.backend.models import HuyaAccount
|
||||
from web.backend.services.huya_runner import HuyaBatchRunner
|
||||
|
||||
UID = 1199665170519
|
||||
GUID = "0a8995af9218ce6a340261946294a9e7"
|
||||
COOKIE = f"udb_uid={UID}; yyuid={UID}; guid={GUID}"
|
||||
# 真机(2026-10-15,账号 hy_297832664)getRedeemNeedGoods 响应:200 / 积分 280 / 门槛 20
|
||||
REDEEM_RSP_HEX = "0a0100c8160ce69fa5e8afa2e68890e58a9f21011830144c50016001790c0b"
|
||||
# 真机 getGrowLevelUserInfo 响应:成长值 980 / 成长等级 3 / 昵称 / 头像
|
||||
GROW_RSP_HEX = (
|
||||
"0a0a0c1606e68890e58a9f0b1a0103d41003230000019f124082043c4c50"
|
||||
"0360ff7101f48103e80b260fe8bdafe8b88fe697a5e6bdbae6b9b236386874"
|
||||
"7470733a2f2f6469792d6173736574732e6d737374617469632e636f6d2f6371"
|
||||
"68666f6c6465722f4e657744656661756c742e706e670b"
|
||||
)
|
||||
|
||||
|
||||
class _FakeWs:
|
||||
"""记录发出的帧,并立刻兑现 RPC future(离线跑完整链路)。"""
|
||||
|
||||
def __init__(self, client: HuyaWssClient):
|
||||
self.client = client
|
||||
self.sent: list[bytes] = []
|
||||
|
||||
async def send(self, data: bytes):
|
||||
self.sent.append(data)
|
||||
while self.client.rpc_queue:
|
||||
future = self.client.rpc_queue.popleft()
|
||||
if not future.done():
|
||||
future.set_result(b"")
|
||||
|
||||
def last_request(self) -> dict:
|
||||
return _decode_wup_body(WssMessage.decode(self.sent[-1]).body)
|
||||
|
||||
|
||||
def _points_client() -> HuyaWssClient:
|
||||
client = HuyaWssClient(logger=lambda _message: None)
|
||||
client._points_mode = True
|
||||
client.ws = _FakeWs(client)
|
||||
return client
|
||||
|
||||
|
||||
def _raw_rsp(payload_hex: str) -> ShopRawRsp:
|
||||
return ShopRawRsp.from_payload(
|
||||
decode_struct_bytes(bytes.fromhex("".join(payload_hex.split())))
|
||||
)
|
||||
|
||||
|
||||
class _StubClient:
|
||||
"""按需返回真机响应字节的积分中心客户端。"""
|
||||
|
||||
def __init__(self, redeem=True, grow=True):
|
||||
self.redeem = _raw_rsp(REDEEM_RSP_HEX) if redeem else None
|
||||
self.grow = _raw_rsp(GROW_RSP_HEX) if grow else None
|
||||
self.calls: list[tuple[str, dict]] = []
|
||||
|
||||
def get_redeem_need_goods(self, **kwargs):
|
||||
self.calls.append(("get_redeem_need_goods", kwargs))
|
||||
return self.redeem
|
||||
|
||||
def get_grow_level_user_info(self, **kwargs):
|
||||
self.calls.append(("get_grow_level_user_info", kwargs))
|
||||
return self.grow
|
||||
|
||||
|
||||
class _LegacyClient:
|
||||
"""旧活动接口:真机实测任何 sid 都返回 603「活动已结束」。"""
|
||||
|
||||
def query_user_score(self, uid: int, cookie: str, sid: int):
|
||||
response = GetUserScoreResp()
|
||||
response.status = 603
|
||||
response.msg = "活动已结束"
|
||||
response.score = 0
|
||||
return response
|
||||
|
||||
|
||||
def _runner(monkeypatch, client, legacy=None) -> tuple[HuyaBatchRunner, list]:
|
||||
runner = HuyaBatchRunner(db=None, batch_id="test-batch", task_type="query_points")
|
||||
marks: list[tuple[str, str, dict | None]] = []
|
||||
monkeypatch.setattr(
|
||||
runner, "_shop_client", lambda uid, cookie: client, raising=False
|
||||
)
|
||||
if legacy is not None:
|
||||
monkeypatch.setattr(
|
||||
runner, "_activity_client", lambda uid, cookie: legacy, raising=False
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
runner,
|
||||
"_mark_task",
|
||||
lambda _db, _task, status, message, result=None: marks.append(
|
||||
(status, message, result)
|
||||
),
|
||||
raising=False,
|
||||
)
|
||||
return runner, marks
|
||||
|
||||
|
||||
def test_wss_redeem_need_goods_request_matches_real_call():
|
||||
async def run():
|
||||
client = _points_client()
|
||||
await client.get_redeem_need_goods(uid=UID, guid=GUID, cookie=COOKIE)
|
||||
|
||||
message = WssMessage.decode(client.ws.sent[-1])
|
||||
# 积分中心业务帧统一 0x1d000107(只有下单/支付用 0x1d000108)
|
||||
assert message.sequence == 0x1D000107
|
||||
|
||||
decoded = client.ws.last_request()
|
||||
assert (decoded["servant"], decoded["func"]) == (
|
||||
"shopMiddleUI",
|
||||
"getRedeemNeedGoods",
|
||||
)
|
||||
t_req = decoded["tReq"]
|
||||
assert t_req["tag0"]["tag0"] == UID
|
||||
assert t_req["tag1"]["tag0"] == "huya"
|
||||
assert t_req["tag1"]["tag4"] == 7
|
||||
assert t_req["tag1"]["tag5"] == "yellowcarlist"
|
||||
# 默认兑换商品号必须是真机可用的 hy-8315982 的数字形式
|
||||
assert t_req["tag2"] == 8315982
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_wss_grow_level_request_carries_only_user_id():
|
||||
async def run():
|
||||
client = _points_client()
|
||||
await client.get_grow_level_user_info(uid=UID, guid=GUID, cookie=COOKIE)
|
||||
|
||||
decoded = client.ws.last_request()
|
||||
assert (decoded["servant"], decoded["func"]) == (
|
||||
"GameBizUI",
|
||||
"getGrowLevelUserInfo",
|
||||
)
|
||||
t_req = decoded["tReq"]
|
||||
assert t_req["tag0"]["tag0"] == UID
|
||||
# 抓包 tReq 只有 tag0,没有 shopAppInfo
|
||||
assert set(t_req) == {"tag0"}
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_http_redeem_need_goods_request_matches_real_call():
|
||||
client = HuyaHttpClient(logger=lambda _message: None)
|
||||
recorded: list[tuple] = []
|
||||
|
||||
def fake_call_rpc(
|
||||
service, method, req_struct, rsp_class=None, uid=0, guid="", cookie="", **kwargs
|
||||
):
|
||||
recorded.append((service, method, req_struct))
|
||||
|
||||
client.call_rpc = fake_call_rpc # type: ignore[method-assign]
|
||||
|
||||
client.get_redeem_need_goods(uid=UID, guid="", cookie=COOKIE)
|
||||
|
||||
service, method, req = recorded[-1]
|
||||
assert (service, method) == ("shopMiddleUI", "getRedeemNeedGoods")
|
||||
assert req.userId.lUid == UID
|
||||
assert req.shopAppInfo.scene == 7
|
||||
assert req.shopAppInfo.sourceId == "yellowcarlist"
|
||||
assert req.goodsNum == 8315982
|
||||
|
||||
|
||||
def test_real_redeem_response_bytes_are_points_and_threshold():
|
||||
rsp = _raw_rsp(REDEEM_RSP_HEX)
|
||||
|
||||
assert rsp.code == 200
|
||||
assert rsp.message == "查询成功"
|
||||
# tag2 = 当前积分,tag3 = 兑换门槛积分(真机 280 / 20)
|
||||
assert rsp.value("tag2") == 280
|
||||
assert rsp.value("tag3") == 20
|
||||
|
||||
|
||||
def test_real_grow_level_response_bytes_are_level_and_nickname():
|
||||
rsp = _raw_rsp(GROW_RSP_HEX)
|
||||
|
||||
assert rsp.code == 0
|
||||
assert rsp.message == "成功"
|
||||
assert rsp.value("tag1", "tag0") == 980 # 成长值
|
||||
assert rsp.value("tag1", "tag1") == 3 # 成长等级
|
||||
assert rsp.value("tag2") == "软踏日潺湲"
|
||||
assert str(rsp.value("tag3")).startswith("https://diy-assets.msstatic.com/")
|
||||
|
||||
|
||||
def test_query_points_reads_points_from_points_center(monkeypatch):
|
||||
client = _StubClient()
|
||||
runner, marks = _runner(monkeypatch, client)
|
||||
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
|
||||
|
||||
runner._execute_query_points(
|
||||
None, None, account, {"uid": UID, "cookie": COOKIE}, {}
|
||||
)
|
||||
|
||||
assert [name for name, _kwargs in client.calls] == [
|
||||
"get_redeem_need_goods",
|
||||
"get_grow_level_user_info",
|
||||
]
|
||||
assert client.calls[0][1]["goods_num"] == 8315982
|
||||
status, message, result = marks[-1]
|
||||
assert status == "success"
|
||||
assert message == "积分: 280"
|
||||
assert account.points == 280
|
||||
assert result["points"] == 280
|
||||
assert result["redeem_threshold"] == 20
|
||||
assert result["grow_level"] == 3
|
||||
assert result["grow_value"] == 980
|
||||
assert result["nickname"] == "软踏日潺湲"
|
||||
|
||||
|
||||
def test_query_points_keeps_working_when_grow_level_fails(monkeypatch):
|
||||
client = _StubClient(grow=False)
|
||||
runner, marks = _runner(monkeypatch, client)
|
||||
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
|
||||
|
||||
runner._execute_query_points(
|
||||
None, None, account, {"uid": UID, "cookie": COOKIE}, {}
|
||||
)
|
||||
|
||||
status, message, result = marks[-1]
|
||||
assert status == "success"
|
||||
assert message == "积分: 280"
|
||||
assert "grow_level" not in result
|
||||
|
||||
|
||||
def test_query_points_reports_server_error_of_points_center(monkeypatch):
|
||||
class _FailingClient(_StubClient):
|
||||
def get_redeem_need_goods(self, **kwargs):
|
||||
self.calls.append(("get_redeem_need_goods", kwargs))
|
||||
return ShopRawRsp.from_payload(
|
||||
decode_struct_bytes(
|
||||
bytes.fromhex(
|
||||
"0a0101f4161ee7b3bbe7bb9fe5bc82e5b8b8efbc8ce8afb7e7a88de5"
|
||||
"908ee9878de8af952c3c4c5c6c0b"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
client = _FailingClient()
|
||||
runner, marks = _runner(monkeypatch, client)
|
||||
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
|
||||
|
||||
runner._execute_query_points(
|
||||
None, None, account, {"uid": UID, "cookie": COOKIE}, {}
|
||||
)
|
||||
|
||||
status, message, result = marks[-1]
|
||||
assert status == "failed"
|
||||
assert message == "系统异常,请稍后重试"
|
||||
assert result["code"] == 500
|
||||
assert result["goods_num"] == 8315982
|
||||
|
||||
|
||||
def test_query_points_fallback_reports_legacy_interface_offline(monkeypatch):
|
||||
client = _StubClient(redeem=False)
|
||||
runner, marks = _runner(monkeypatch, client, legacy=_LegacyClient())
|
||||
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
|
||||
|
||||
runner._execute_query_points(
|
||||
None, None, account, {"uid": UID, "cookie": COOKIE}, {"sid": "2203"}
|
||||
)
|
||||
|
||||
status, message, _result = marks[-1]
|
||||
assert status == "failed"
|
||||
# 旧接口的真实返回必须如实暴露,而不是只报「活动已结束」
|
||||
assert "旧活动接口(sid=2203)返回 603 活动已结束" in message
|
||||
assert "积分中心接口无响应" in message
|
||||
|
||||
|
||||
def test_query_points_without_sid_reports_points_center_offline(monkeypatch):
|
||||
client = _StubClient(redeem=False)
|
||||
runner, marks = _runner(monkeypatch, client, legacy=_LegacyClient())
|
||||
account = HuyaAccount(uid=str(UID), cookie=COOKIE)
|
||||
|
||||
runner._execute_query_points(
|
||||
None, None, account, {"uid": UID, "cookie": COOKIE}, {}
|
||||
)
|
||||
|
||||
status, message, _result = marks[-1]
|
||||
assert status == "error"
|
||||
assert "积分中心接口无响应" in message
|
||||
@@ -8,6 +8,8 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.huya.wss_client import POINTS_REDEEM_GOODS_NUM
|
||||
|
||||
from ..models import HuyaAccount, HuyaGoodsSnapshot, HuyaTask
|
||||
|
||||
|
||||
@@ -26,6 +28,7 @@ class GoodsMixin:
|
||||
def _resolve_uid(self, account_info: dict) -> int: ...
|
||||
def _push_log(self, level: str, message: str) -> None: ...
|
||||
def _activity_client(self, uid: int, cookie: str): ...
|
||||
def _shop_client(self, uid: int, cookie: str): ...
|
||||
def _mark_task(self, *args: Any, **kwargs: Any) -> None: ...
|
||||
def _wait_until(self, when: datetime, uid: int) -> bool: ...
|
||||
def _parse_scheduled_time(self, value: Any) -> datetime | None: ...
|
||||
@@ -90,16 +93,13 @@ class GoodsMixin:
|
||||
account_info: dict,
|
||||
config_info: dict,
|
||||
):
|
||||
sid = str(self.payload.get("sid") or config_info.get("sid") or "").strip()
|
||||
if not sid:
|
||||
self._mark_task(worker_db, task, "failed", "请先配置虎牙活动 SID")
|
||||
return
|
||||
|
||||
sid_int = self._to_int(sid)
|
||||
if not sid_int:
|
||||
self._mark_task(worker_db, task, "failed", f"虎牙活动 SID 无效: {sid}")
|
||||
return
|
||||
"""查询积分:主源是积分中心的 shopMiddleUI.getRedeemNeedGoods。
|
||||
|
||||
2026-10-15 真机实测:旧活动接口 webActUI.getUserScore 对任何 sid 都返回
|
||||
603「活动已结束」(该接口已下线,与配置无关);积分中心接口返回
|
||||
``tag2``=当前积分、``tag3``=兑换门槛积分,另可顺手取 GameBizUI
|
||||
的成长值/成长等级。
|
||||
"""
|
||||
uid = self._resolve_uid(account_info)
|
||||
if not uid:
|
||||
self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid")
|
||||
@@ -110,10 +110,76 @@ class GoodsMixin:
|
||||
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
|
||||
return
|
||||
|
||||
goods_num = (
|
||||
self._to_int(self.payload.get("goods_num")) or POINTS_REDEEM_GOODS_NUM
|
||||
)
|
||||
client: Any = self._shop_client(uid, cookie)
|
||||
redeem = client.get_redeem_need_goods(
|
||||
uid=uid, guid="", cookie=cookie, goods_num=goods_num
|
||||
)
|
||||
if redeem is None:
|
||||
self._query_points_legacy(
|
||||
worker_db, task, account, uid, cookie, config_info
|
||||
)
|
||||
return
|
||||
|
||||
result = redeem.to_dict()
|
||||
result["goods_num"] = goods_num
|
||||
if redeem.code != 200:
|
||||
self._mark_task(
|
||||
worker_db,
|
||||
task,
|
||||
"failed",
|
||||
redeem.message or f"虎牙积分查询失败: {redeem.code}",
|
||||
result,
|
||||
)
|
||||
return
|
||||
|
||||
points = self._to_int(redeem.value("tag2"))
|
||||
result["points"] = points
|
||||
result["redeem_threshold"] = self._to_int(redeem.value("tag3"))
|
||||
growth = client.get_grow_level_user_info(uid=uid, guid="", cookie=cookie)
|
||||
if growth is not None and growth.code == 0:
|
||||
result["grow_value"] = self._to_int(growth.value("tag1", "tag0"))
|
||||
result["grow_level"] = self._to_int(growth.value("tag1", "tag1"))
|
||||
result["nickname"] = growth.value("tag2") or ""
|
||||
|
||||
account.points = points
|
||||
account.status = "points_queried"
|
||||
account.updated_at = datetime.now(UTC)
|
||||
self._mark_task(worker_db, task, "success", f"积分: {points}", result)
|
||||
|
||||
def _query_points_legacy(
|
||||
self,
|
||||
worker_db: Session,
|
||||
task: HuyaTask,
|
||||
account: HuyaAccount,
|
||||
uid: int,
|
||||
cookie: str,
|
||||
config_info: dict,
|
||||
):
|
||||
"""积分中心接口没有响应时回退旧活动接口,并如实报告旧接口的返回。"""
|
||||
sid = str(self.payload.get("sid") or config_info.get("sid") or "").strip()
|
||||
if not sid:
|
||||
self._mark_task(
|
||||
worker_db, task, "error", "积分中心接口无响应,且未配置可回退的活动 SID"
|
||||
)
|
||||
return
|
||||
|
||||
sid_int = self._to_int(sid)
|
||||
if not sid_int:
|
||||
self._mark_task(worker_db, task, "failed", f"虎牙活动 SID 无效: {sid}")
|
||||
return
|
||||
|
||||
client: Any = self._activity_client(uid, cookie)
|
||||
response = client.query_user_score(uid=uid, cookie=cookie, sid=sid_int)
|
||||
if response is None:
|
||||
self._mark_task(worker_db, task, "error", "虎牙积分接口无响应")
|
||||
self._mark_task(
|
||||
worker_db,
|
||||
task,
|
||||
"error",
|
||||
"虎牙积分接口无响应(积分中心与旧活动接口均失败)",
|
||||
)
|
||||
return
|
||||
|
||||
result = response.to_dict()
|
||||
@@ -123,7 +189,8 @@ class GoodsMixin:
|
||||
worker_db,
|
||||
task,
|
||||
"failed",
|
||||
response.msg or f"虎牙积分查询失败: {response.status}",
|
||||
f"积分中心接口无响应;旧活动接口(sid={sid_int})返回 "
|
||||
f"{response.status} {response.msg}".strip(),
|
||||
result,
|
||||
)
|
||||
return
|
||||
@@ -132,7 +199,9 @@ class GoodsMixin:
|
||||
account.points = points
|
||||
account.status = "points_queried"
|
||||
account.updated_at = datetime.now(UTC)
|
||||
self._mark_task(worker_db, task, "success", f"积分: {points}", result)
|
||||
self._mark_task(
|
||||
worker_db, task, "success", f"积分: {points}(旧活动接口)", result
|
||||
)
|
||||
|
||||
def _execute_query_exchange_records(
|
||||
self,
|
||||
|
||||
@@ -506,12 +506,12 @@ class RechargeMixin:
|
||||
account.updated_at = datetime.now(UTC)
|
||||
if payment_status == "paid":
|
||||
account.status = "recharge_paid"
|
||||
post_score = client.query_user_score(
|
||||
uid=uid, cookie=cookie, sid=self._to_int(config_info.get("sid")) or 2203
|
||||
)
|
||||
if post_score is not None:
|
||||
result["post_payment_score"] = post_score.to_dict()
|
||||
account.points = post_score.available_score
|
||||
# 支付后刷新积分:旧活动接口 getUserScore 已下线(603 活动已结束),
|
||||
# 改走积分中心 getRedeemNeedGoods(tag2 = 当前积分)。
|
||||
post_redeem = client.get_redeem_need_goods(uid=uid, guid="", cookie=cookie)
|
||||
if post_redeem is not None and post_redeem.code == 200:
|
||||
result["post_payment_points"] = self._to_int(post_redeem.value("tag2"))
|
||||
account.points = result["post_payment_points"]
|
||||
paid_message = f"支付成功: {product_name} x{count} {result['amount_text']}"
|
||||
if payment_order and payment_order.get("pay_time"):
|
||||
paid_message += f",支付时间 {self._format_local_time(int(payment_order['pay_time']) // 1000)}"
|
||||
|
||||
@@ -1136,9 +1136,11 @@ export default function HuyaTasksPage() {
|
||||
return <Text type="secondary">-</Text>;
|
||||
}
|
||||
|
||||
const availableScore = value?.available_score;
|
||||
if (typeof availableScore === 'number') {
|
||||
return <Tag color="blue">可用积分 {availableScore}</Tag>;
|
||||
// 积分结果:新版走积分中心(points),旧任务记录里是活动接口的 available_score
|
||||
const points = typeof value?.points === 'number' ? value.points : value?.available_score;
|
||||
if (typeof points === 'number') {
|
||||
const level = value?.grow_level;
|
||||
return <Tag color="blue">可用积分 {points}{typeof level === 'number' ? ` · 等级 ${level}` : ''}</Tag>;
|
||||
}
|
||||
|
||||
return value ? <Text code style={{ fontSize: 12 }}>{JSON.stringify(value)}</Text> : <Text type="secondary">-</Text>;
|
||||
|
||||
Reference in New Issue
Block a user