409 lines
15 KiB
Python
409 lines
15 KiB
Python
"""虎牙任务执行器:积分与商城(由 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)
|