From 905a7ef22ce8377d583ef0352b1b2df46911a6c2 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Thu, 15 Oct 2026 15:32:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(huya):=20=E6=9F=A5=E8=AF=A2=E7=A7=AF?= =?UTF-8?q?=E5=88=86=E6=94=B9=E8=B5=B0=E7=A7=AF=E5=88=86=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E6=96=B0=E6=8E=A5=E5=8F=A3=EF=BC=88=E5=90=AB=E6=88=90=E9=95=BF?= =?UTF-8?q?=E7=AD=89=E7=BA=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧活动接口 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 --- core/huya/elite_session.py | 6 + core/huya/http_client.py | 48 +++ core/huya/shop_structs.py | 33 +++ core/huya/wss_client.py | 34 +++ tests/test_huya_points_query.py | 295 +++++++++++++++++++ web/backend/services/huya_runner_goods.py | 93 +++++- web/backend/services/huya_runner_recharge.py | 12 +- web/frontend/src/pages/HuyaTasksPage.tsx | 8 +- 8 files changed, 508 insertions(+), 21 deletions(-) create mode 100644 tests/test_huya_points_query.py diff --git a/core/huya/elite_session.py b/core/huya/elite_session.py index 1a7ebfe..5ab88f6 100644 --- a/core/huya/elite_session.py +++ b/core/huya/elite_session.py @@ -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"] diff --git a/core/huya/http_client.py b/core/huya/http_client.py index e5acf4c..dba8707 100644 --- a/core/huya/http_client.py +++ b/core/huya/http_client.py @@ -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 diff --git a/core/huya/shop_structs.py b/core/huya/shop_structs.py index 317fe4a..cb0c2d6 100644 --- a/core/huya/shop_structs.py +++ b/core/huya/shop_structs.py @@ -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() diff --git a/core/huya/wss_client.py b/core/huya/wss_client.py index c03388f..293490d 100644 --- a/core/huya/wss_client.py +++ b/core/huya/wss_client.py @@ -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) diff --git a/tests/test_huya_points_query.py b/tests/test_huya_points_query.py new file mode 100644 index 0000000..dbb08c0 --- /dev/null +++ b/tests/test_huya_points_query.py @@ -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 diff --git a/web/backend/services/huya_runner_goods.py b/web/backend/services/huya_runner_goods.py index 7e80376..a3f3380 100644 --- a/web/backend/services/huya_runner_goods.py +++ b/web/backend/services/huya_runner_goods.py @@ -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, diff --git a/web/backend/services/huya_runner_recharge.py b/web/backend/services/huya_runner_recharge.py index 004bf72..0719534 100644 --- a/web/backend/services/huya_runner_recharge.py +++ b/web/backend/services/huya_runner_recharge.py @@ -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)}" diff --git a/web/frontend/src/pages/HuyaTasksPage.tsx b/web/frontend/src/pages/HuyaTasksPage.tsx index 96cf3f5..242baef 100644 --- a/web/frontend/src/pages/HuyaTasksPage.tsx +++ b/web/frontend/src/pages/HuyaTasksPage.tsx @@ -1136,9 +1136,11 @@ export default function HuyaTasksPage() { return -; } - const availableScore = value?.available_score; - if (typeof availableScore === 'number') { - return 可用积分 {availableScore}; + // 积分结果:新版走积分中心(points),旧任务记录里是活动接口的 available_score + const points = typeof value?.points === 'number' ? value.points : value?.available_score; + if (typeof points === 'number') { + const level = value?.grow_level; + return 可用积分 {points}{typeof level === 'number' ? ` · 等级 ${level}` : ''}; } return value ? {JSON.stringify(value)} : -;