"""虎牙任务执行器:积分与商城(由 huya_runner.py 按功能域拆分)。""" from __future__ import annotations import time from datetime import UTC, datetime from typing import TYPE_CHECKING, Any from sqlalchemy.orm import Session from ..models import HuyaAccount, HuyaGoodsSnapshot, HuyaTask class GoodsMixin: """积分与商城域:积分/兑换记录/商品刷新/兑换。""" if TYPE_CHECKING: payload: dict @staticmethod def _to_int(value: Any) -> int: ... @staticmethod def _format_local_time(timestamp: int) -> str: ... 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 _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: ... def _execute_query_act_tasks( self, worker_db: Session, task: HuyaTask, account: HuyaAccount, account_info: dict, config_info: dict, ): """读取精英宝典任务详情,供专用工作台展示购买/观看任务。""" uid = self._resolve_uid(account_info) cookie = account_info.get("cookie") or "" if not uid or not cookie: self._mark_task(worker_db, task, "failed", "账号 UID 或 Cookie 为空") return act_id = self._to_int(self.payload.get("act_id") or 25135) if not act_id: self._mark_task(worker_db, task, "failed", "精英宝典活动 ID 无效") return client: Any = self._activity_client(uid, cookie) response = client.get_act_task_detail(uid=uid, cookie=cookie, act_id=act_id) if response is None: self._mark_task(worker_db, task, "error", "虎牙活动任务接口无响应") return result = response.to_dict() result["act_id"] = act_id act_info = client.get_act_info(act_id=act_id) user_tasks = client.get_act_user_task_detail(uid=uid, cookie=cookie, act_id=act_id) if act_info is not None: result["act_info"] = act_info.to_dict() if user_tasks is not None: result["user_task_detail"] = user_tasks.to_dict() if response.status != 200: self._mark_task( worker_db, task, "failed", response.msg or f"虎牙活动任务查询失败: {response.status}", result, ) return account.status = "tasks_queried" account.updated_at = datetime.now(UTC) self._mark_task( worker_db, task, "success", f"已读取 {len(result.get('tasks', []))} 项精英宝典任务", result, ) def _execute_query_points( self, worker_db: Session, task: HuyaTask, account: HuyaAccount, 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 uid = self._resolve_uid(account_info) if not uid: self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid") return cookie = account_info.get("cookie") or "" if not cookie: self._mark_task(worker_db, task, "failed", "账号 Cookie 为空") 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", "虎牙积分接口无响应") return result = response.to_dict() result["sid"] = sid_int if response.status != 200: self._mark_task( worker_db, task, "failed", response.msg or f"虎牙积分查询失败: {response.status}", result, ) return points = response.available_score account.points = points account.status = "points_queried" account.updated_at = datetime.now(UTC) self._mark_task(worker_db, task, "success", f"积分: {points}", result) def _execute_query_exchange_records( self, worker_db: Session, task: HuyaTask, account: HuyaAccount, 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 uid = self._resolve_uid(account_info) if not uid: self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid") return cookie = account_info.get("cookie") or "" if not cookie: self._mark_task(worker_db, task, "failed", "账号 Cookie 为空") return client: Any = self._activity_client(uid, cookie) response = client.get_user_prize_records(uid=uid, cookie=cookie, sid=sid_int) if response is None: self._mark_task(worker_db, task, "error", "虎牙兑换记录接口无响应") return result = response.to_dict() result["sid"] = sid_int if response.status != 200: self._mark_task( worker_db, task, "failed", response.msg or f"虎牙兑换记录查询失败: {response.status}", result, ) return records = result.get("records", []) for index, item in enumerate(records, start=1): item["index"] = index item["exchange_time_text"] = self._format_local_time( int(item.get("exchange_time") or 0) ) if item.get("score") is not None: item["score_text"] = f"{int(item.get('score') or 0)}积分" account.status = "exchange_records_queried" account.updated_at = datetime.now(UTC) count = len(records) message = f"兑换记录 {count} 条" if count else "暂无兑换记录" self._mark_task(worker_db, task, "success", message, result) def _execute_refresh_goods( self, worker_db: Session, task: HuyaTask, account: HuyaAccount, 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 uid = self._resolve_uid(account_info) if not uid: self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid") return cookie = account_info.get("cookie") or "" if not cookie: self._mark_task(worker_db, task, "failed", "账号 Cookie 为空") return client: Any = self._activity_client(uid, cookie) response = client.get_act_prize_list(uid=uid, cookie=cookie, sid=sid_int) if response is None: self._mark_task(worker_db, task, "error", "虎牙商品列表接口无响应") return result = response.to_dict() result["sid"] = sid_int if response.status != 200: self._mark_task( worker_db, task, "failed", response.msg or f"虎牙商品列表刷新失败: {response.status}", result, ) return goods = [ item for item in result.get("goods", []) if item.get("product_id") and item.get("name") ] now = datetime.now(UTC) worker_db.query(HuyaGoodsSnapshot).filter( HuyaGoodsSnapshot.account_id == account.id, HuyaGoodsSnapshot.sid == sid_int, ).delete(synchronize_session=False) for item in goods: worker_db.add( HuyaGoodsSnapshot( account_id=account.id, sid=sid_int, product_id=item["product_id"], name=item["name"], price=item["price"], remain_text=item["remain_text"], raw=item, updated_at=now, ) ) account.status = "goods_refreshed" account.updated_at = now message = f"已刷新商品 {len(goods)} 个" self._mark_task(worker_db, task, "success", message, {**result, "goods": goods}) def _execute_exchange_goods( self, worker_db: Session, task: HuyaTask, account: HuyaAccount, 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 product_id = self._to_int(self.payload.get("product_id")) if not product_id: self._mark_task(worker_db, task, "failed", "请选择兑换商品") return uid = self._resolve_uid(account_info) if not uid: self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid") return cookie = account_info.get("cookie") or "" if not cookie: self._mark_task(worker_db, task, "failed", "账号 Cookie 为空") return snapshot = ( worker_db.query(HuyaGoodsSnapshot) .filter( HuyaGoodsSnapshot.account_id == account.id, HuyaGoodsSnapshot.sid == sid_int, HuyaGoodsSnapshot.product_id == str(product_id), ) .first() ) product_name = str( self.payload.get("product_name") or (snapshot.name if snapshot else "") or product_id ) scheduled_at = self._parse_scheduled_time(self.payload.get("scheduled_at")) if self.payload.get("scheduled_at") and scheduled_at is None: self._mark_task(worker_db, task, "failed", "定时兑换时间格式无效") return if ( scheduled_at and scheduled_at.timestamp() > time.time() and not self._wait_until(scheduled_at, uid) ): self._mark_task(worker_db, task, "stopped", "兑换任务已停止") return client: Any = self._activity_client(uid, cookie) detail = client.get_act_prize_detail( uid=uid, cookie=cookie, sid=sid_int, pid=product_id ) if detail is None: self._mark_task(worker_db, task, "error", "虎牙兑换详情接口无响应") return detail_result = detail.to_dict() prize = detail.prize if detail.status != 200 or prize is None: self._mark_task( worker_db, task, "failed", detail.msg or f"虎牙兑换详情获取失败: {detail.status}", detail_result, ) return now_ts = int(time.time()) if prize.isCanExchange == 0: self._mark_task(worker_db, task, "failed", "该商品当前不可兑换", detail_result) return if prize.isShowNum and prize.leftNum <= 0: self._mark_task(worker_db, task, "failed", "该商品库存不足", detail_result) return if prize.exchangeStartTime and now_ts < prize.exchangeStartTime: self._mark_task(worker_db, task, "failed", "该商品尚未开始兑换", detail_result) return if prize.exchangeEndTime and now_ts > prize.exchangeEndTime: self._mark_task(worker_db, task, "failed", "该商品兑换已结束", detail_result) return score = client.query_user_score(uid=uid, cookie=cookie, sid=sid_int) if score is None: self._mark_task(worker_db, task, "error", "兑换前积分查询无响应", detail_result) return if score.status != 200 or score.available_score < prize.newScore: detail_result["score"] = score.to_dict() self._mark_task(worker_db, task, "failed", "可用积分不足", detail_result) return response = client.score_exchange_prize( uid=uid, cookie=cookie, sid=sid_int, pid=product_id ) if response is None: self._mark_task(worker_db, task, "error", "虎牙兑换接口无响应") return result = response.to_dict() result.update( { "sid": sid_int, "product_id": str(product_id), "product_name": product_name, "scheduled_at": scheduled_at.isoformat() if scheduled_at else "", "executed_at": datetime.now(UTC).isoformat(), "goods": snapshot.raw if snapshot else None, "prize_detail": detail_result, } ) if response.status != 200: self._mark_task( worker_db, task, "failed", response.msg or f"虎牙兑换失败: {response.status}", result, ) return post_score = client.query_user_score(uid=uid, cookie=cookie, sid=sid_int) post_records = client.get_user_prize_records(uid=uid, cookie=cookie, sid=sid_int) if post_score is not None: result["post_exchange_score"] = post_score.to_dict() account.points = post_score.available_score if post_records is not None: result["post_exchange_records"] = post_records.to_dict() account.status = "goods_exchanged" account.updated_at = datetime.now(UTC) message = response.msg or f"兑换成功: {product_name}" self._mark_task(worker_db, task, "success", message, result)