"""斗鱼任务执行器:送礼(由 douyu_runner.py 按功能域拆分)。""" from __future__ import annotations from datetime import UTC, datetime from typing import TYPE_CHECKING, Any from sqlalchemy.orm import Session from core.douyu import DouyuActivityClient from ..models import Account, DouyuTask DOUYU_GIFT_POINTS_REFRESH_TIMES = 3 DOUYU_GIFT_POINTS_REFRESH_INTERVAL = 2 class DonateMixin: """送礼域:精英令/电竞任务礼物赠送与积分确认。""" if TYPE_CHECKING: _stop: Any def _client(self, cookie: str) -> DouyuActivityClient: ... def _task_payload(self, task: DouyuTask) -> dict: ... def _push_log(self, level: str, message: str) -> None: ... def _mark_task(self, *args: Any, **kwargs: Any) -> None: ... def _update_task_progress(self, *args: Any, **kwargs: Any) -> None: ... def _refresh_account_points( self, client: DouyuActivityClient, account: Account, cookie: str, *, ctn: str | None = None, ) -> dict: ... def _refresh_account_gold_balance( self, client: DouyuActivityClient, account: Account ) -> dict: ... def _refresh_esports_handbook( self, client: DouyuActivityClient, account: Account, *, manual_id: str ) -> dict: ... def _refresh_points_after_elite_gift( self, db: Session, task: DouyuTask, account: Account, client: DouyuActivityClient, cookie: str, ctn: str | None, result: dict, baseline_points: int | None, gift_count: int, ) -> dict: """赠送精英令后短轮询积分;1 个精英令约等于 10 积分。""" expected_gain = max(0, gift_count) * 10 target_points = ( baseline_points + expected_gain if baseline_points is not None else None ) result["gift_points_baseline"] = baseline_points result["gift_points_expected_gain"] = expected_gain result["gift_points_target"] = target_points last_points = None refresh_result: dict = {} for index in range(1, DOUYU_GIFT_POINTS_REFRESH_TIMES + 1): refresh_result = self._refresh_account_points( client, account, cookie, ctn=ctn ) db.commit() last_points = refresh_result["points"] result.update(refresh_result) result["gift_points_refresh_count"] = index if target_points is None or ( last_points is not None and last_points >= target_points ): result["gift_points_confirmed"] = ( target_points is None or last_points is not None ) return refresh_result if index < DOUYU_GIFT_POINTS_REFRESH_TIMES: self._update_task_progress( db, task, "running", f"赠送精英令成功,等待积分同步(当前 {last_points if last_points is not None else '-'},预期 {target_points})", result, ) if self._stop.wait(DOUYU_GIFT_POINTS_REFRESH_INTERVAL): break result["gift_points_confirmed"] = False result["points"] = last_points return refresh_result def _execute_donate_esports_gift( self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict, *, gift_name: str, config_gift_id_key: str, config_skin_id_key: str, ): """赠送电竞手册任务礼物并刷新独立积分。""" payload = self._task_payload(task) try: gift_count = max( 1, int(payload.get("gift_count") or payload.get("count") or 1) ) except (TypeError, ValueError): self._mark_task(db, task, "failed", "赠送数量必须是正整数") return manual_id = str(config.get("esports_manual_id") or "").strip() gift_id = str( payload.get("gift_id") or config.get(config_gift_id_key) or "" ).strip() skin_id = str( payload.get("skin_id") or config.get(config_skin_id_key) or "" ).strip() room_id = str(payload.get("room_id") or config.get("room_id") or "").strip() if not manual_id or not gift_id or not skin_id or not room_id: self._mark_task(db, task, "failed", "请先完整配置电竞手册、房间和礼物参数") return client = self._client(cookie) baseline_points = account.esports_points try: baseline = self._refresh_esports_handbook( client, account, manual_id=manual_id ) baseline_points = baseline["esports_points"] db.commit() except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底 self._push_log("warning", f"赠送{gift_name}前刷新电竞积分失败: {exc}") result = client.donate_esports_gift( gift_name=gift_name, gift_count=gift_count, room_id=room_id, gift_id=gift_id, skin_id=skin_id, ) result.update( { "gift_name": gift_name, "gift_id": gift_id, "skin_id": skin_id, "gift_count": gift_count, "esports_points_baseline": baseline_points, } ) refresh_errors = [] try: result.update(self._refresh_account_gold_balance(client, account)) except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底 refresh_errors.append(f"鱼翅余额: {exc}") try: points_result = self._refresh_esports_handbook( client, account, manual_id=manual_id ) result.update(points_result) result["esports_points_after_gift"] = points_result["esports_points"] result["esports_points_changed"] = ( baseline_points is not None and points_result["esports_points"] is not None and points_result["esports_points"] != baseline_points ) except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底 refresh_errors.append(f"电竞积分: {exc}") if refresh_errors: result["refresh_errors"] = refresh_errors account.esports_bind_status = "esports_gift_donated" account.updated_at = datetime.now(UTC) message = f"赠送{gift_name}成功: {gift_count}" if account.gold_balance is not None: message += f",鱼翅余额: {account.gold_balance}" if account.esports_points is not None: message += f",电竞积分: {account.esports_points}" self._mark_task(db, task, "success", message, result) def _execute_donate_esports_chicken_gift( self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict, ): """赠送冠军鸡腿。""" self._execute_donate_esports_gift( db, task, account, cookie, config, gift_name="冠军鸡腿", config_gift_id_key="esports_chicken_gift_id", config_skin_id_key="esports_chicken_skin_id", ) def _execute_donate_esports_firework_gift( self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict, ): """赠送冠军烟花。""" self._execute_donate_esports_gift( db, task, account, cookie, config, gift_name="冠军烟花", config_gift_id_key="esports_firework_gift_id", config_skin_id_key="esports_firework_skin_id", ) def _execute_donate_elite_gift( self, db: Session, task: DouyuTask, account: Account, cookie: str, config: dict ): payload = self._task_payload(task) gift_count = int(payload.get("gift_count") or payload.get("count") or 1) client = self._client(cookie) ctn = None baseline_points = account.points try: ctn = client.acf_ccn(refresh_subscribe=False) baseline_result = self._refresh_account_points( client, account, cookie, ctn=ctn ) db.commit() baseline_points = baseline_result["points"] except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底 self._push_log("warning", f"赠送精英令前刷新积分失败: {exc}") result = client.donate_elite_gift( gift_count=gift_count, room_id=str(payload.get("room_id") or config["room_id"]), gift_id=str(payload.get("gift_id") or config["gift_id"]), skin_id=str(payload.get("skin_id") or config["skin_id"]), ) result["gift_points_baseline"] = baseline_points refresh_errors = [] try: result.update(self._refresh_account_gold_balance(client, account)) except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底 refresh_errors.append(f"鱼翅余额: {exc}") try: result.update( self._refresh_points_after_elite_gift( db, task, account, client, cookie, ctn, result, baseline_points, gift_count, ) ) except Exception as exc: # noqa: BLE001 外部接口与任务边界需要保留宽泛异常兜底 refresh_errors.append(f"积分: {exc}") if refresh_errors: result["refresh_errors"] = refresh_errors account.bind_status = "gift_donated" account.updated_at = datetime.now(UTC) message = f"赠送精英令成功: {gift_count}" if account.gold_balance is not None: message += f",鱼翅余额: {account.gold_balance}" if account.points is not None: message += f",积分: {account.points}" if result.get("gift_points_target") is not None and not result.get( "gift_points_confirmed" ): message += f"(未确认涨到 {result['gift_points_target']})" self._mark_task(db, task, "success", message, result)