执行 Ruff 安全自动修复

This commit is contained in:
yml2213
2026-08-31 10:28:14 +08:00
parent 2a1d27f953
commit b58c6b4357
145 changed files with 940 additions and 993 deletions
@@ -8,9 +8,9 @@ import uuid
import zipfile
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass, field
from datetime import datetime, timezone
from datetime import UTC, datetime
from types import SimpleNamespace
from typing import Optional, cast
from typing import cast
from core.douyu import DouyuLogin, WgapiLoginAPI
from core.douyu.login import AccountLike
@@ -21,7 +21,7 @@ from ..models import ProxyConfig as ProxyConfigModel
def _now() -> datetime:
"""返回时区感知 UTC 时间。"""
return datetime.now(timezone.utc)
return datetime.now(UTC)
STATUS_LABELS = {
@@ -139,7 +139,7 @@ class AccountCheckRunner:
self,
batch: AccountCheckBatch,
accounts: list[AccountCheckInput],
proxy_config: Optional[ProxyConfigModel] = None,
proxy_config: ProxyConfigModel | None = None,
):
self.batch = batch
self.accounts = accounts
@@ -394,7 +394,7 @@ class AccountCheckRegistry:
max_login_retries: int,
max_total_time: float,
use_proxy: bool = False,
proxy_config: Optional[ProxyConfigModel] = None,
proxy_config: ProxyConfigModel | None = None,
) -> AccountCheckRunner:
batch_id = uuid.uuid4().hex[:12]
normalized_retries = max_login_retries if max_login_retries > 0 else 20
-1
View File
@@ -7,7 +7,6 @@ from sqlalchemy.orm import Session
from ..models import Account, LoginTask
EMAIL_PATTERN = re.compile(r"^[^\s@|]+@[^\s@|]+\.[^\s@|]+$")
-1
View File
@@ -10,7 +10,6 @@ from sqlalchemy.orm import Session
from ..models import AuditLog, User
_SENSITIVE_KEY_PARTS = (
"cookie",
"token",
+2 -3
View File
@@ -1,10 +1,9 @@
"""斗鱼 Cookie 有效性检测服务。"""
from datetime import datetime, timezone
from datetime import UTC, datetime
import requests
CHECK_UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
@@ -15,7 +14,7 @@ USER_LEVEL_API = "https://www.douyu.com/japi/interactnc/web/userLevel/userLevelD
def check_douyu_cookie(cookie: str) -> dict:
"""检测斗鱼 Cookie 有效性,鱼丸与等级接口均通过才算有效。"""
checked_at = datetime.now(timezone.utc)
checked_at = datetime.now(UTC)
base = {
"checked_at": checked_at,
"fish_ball": None,
+8 -4
View File
@@ -7,17 +7,21 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
from sqlalchemy.orm import joinedload
from core.douyu import DouyuActivityError
from ..database import SessionLocal
from ..models import DouyuTask
from .cookie_check_service import check_douyu_cookie
from .douyu_service import latest_success_login_task, update_account_profile_from_cookie
from .douyu_runner_core import DouyuBatchRunnerCore, douyu_batch_registry # noqa: F401 (douyu_batch_registry 供 routers 重导出)
from .douyu_runner_bind import BindMixin
from .douyu_runner_manual import ManualMixin
from .douyu_runner_gold import GoldMixin
from .douyu_runner_core import ( # noqa: F401 (douyu_batch_registry 供 routers 重导出)
DouyuBatchRunnerCore,
douyu_batch_registry,
)
from .douyu_runner_donate import DonateMixin
from .douyu_runner_gold import GoldMixin
from .douyu_runner_goods import GoodsMixin
from .douyu_runner_manual import ManualMixin
from .douyu_runner_xpd import XpdMixin
from .douyu_service import latest_success_login_task, update_account_profile_from_cookie
class DouyuBatchRunner(
+17 -17
View File
@@ -1,16 +1,16 @@
"""斗鱼任务执行器:角色绑定(由 douyu_runner.py 按功能域拆分)。"""
from __future__ import annotations
import time
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from sqlalchemy.orm import Session
from core.douyu import DouyuActivityClient, DouyuActivityError
from ..models import Account, DouyuTask
if TYPE_CHECKING:
from .douyu_runner import DouyuBatchRunner
from ..models import Account, DouyuTask
DOUYU_LEGACY_BIND_ACT_ALIAS = "20250213NQCYX"
DOUYU_BIND_ROLE_POLL_SECONDS = 65
@@ -126,7 +126,7 @@ class BindMixin:
if str(bind_info.get("api_version") or "") == "esports":
can_change_time = cls._to_int(bind_info.get("can_change_time"))
if can_change_time is not None:
return can_change_time <= int(datetime.now(timezone.utc).timestamp())
return can_change_time <= int(datetime.now(UTC).timestamp())
wait_time = cls._to_int(bind_info.get("change_role_wait_time"))
if wait_time is not None and wait_time > 0:
return False
@@ -236,7 +236,7 @@ class BindMixin:
bind_info.get("change_role_wait_time")
)
account.bind_status = status
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
def _apply_esports_bind_info_to_account(
self, account: Account, bind_info: dict, status: str
@@ -252,7 +252,7 @@ class BindMixin:
)
account.esports_can_change_time = self._to_int(bind_info.get("can_change_time"))
account.esports_bind_status = status
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
def _esports_bind_state(
self,
@@ -503,7 +503,7 @@ class BindMixin:
role_name = snapshot["role_name"]
# 待确认角色只回传前端展示,不写入账号表,避免“未换绑成功但角色信息已变新”
account.bind_status = "game_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._push_log(
"success", f"识别到待确认角色: {role_name} (act={query_alias})"
)
@@ -688,7 +688,7 @@ class BindMixin:
"bind_polling": True,
}
account.bind_status = "bind_qr_generated"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
# 关键:先把二维码 progress 出去,前端 running 期间即可弹窗扫码。
self._update_task_progress(
db, task, "running", "已生成绑定二维码,等待扫码绑定", result
@@ -822,7 +822,7 @@ class BindMixin:
self._apply_bind_info_to_account(account, before_bound, "game_queried")
else:
account.bind_status = "game_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -882,7 +882,7 @@ class BindMixin:
)
else:
account.bind_status = "game_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -917,7 +917,7 @@ class BindMixin:
)
else:
account.bind_status = "bind_confirmed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -1027,7 +1027,7 @@ class BindMixin:
"bind_phase": "switching_role",
}
account.esports_bind_status = "esports_role_switching"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -1052,7 +1052,7 @@ class BindMixin:
return
account.esports_bind_status = "esports_bind_confirming"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._update_task_progress(
db,
task,
@@ -1191,7 +1191,7 @@ class BindMixin:
self._apply_bind_info_to_account(account, bind_info, "game_queried")
else:
account.bind_status = "game_queried" if has_pending else "game_not_bound"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
result = {
"act_alias": source_alias,
"query_act_alias": source_alias,
@@ -1281,7 +1281,7 @@ class BindMixin:
wait_time = snapshot["change_role_wait_time"]
account.change_role_wait_time = wait_time
account.bind_status = "change_time_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
source_alias = str(bind_info.get("act_alias") or "")
wait_text = self._format_wait_time(wait_time)
can_change = snapshot["can_change_role"]
+13 -10
View File
@@ -5,27 +5,30 @@ from __future__ import annotations
import asyncio
import threading
import time
from datetime import datetime, timezone
from typing import Optional, TYPE_CHECKING
from datetime import UTC, datetime
from typing import TYPE_CHECKING
from loguru import logger
from sqlalchemy.orm import Session
from core.douyu import DouyuActivityClient
from core.douyu.proxy_fetcher import ProxyFetcher
from ..models import (
Account,
DouyuEsportsGoodsSnapshot,
DouyuGoodsSnapshot,
DouyuTask,
DouyuXpdGoodsSnapshot,
)
from ..models import (
ProxyConfig as ProxyConfigModel,
)
from .douyu_service import (
DOUYU_CONFIG_FIELDS,
douyu_config_value,
ensure_douyu_config,
douyu_task_payload,
ensure_douyu_config,
)
if TYPE_CHECKING:
@@ -62,9 +65,9 @@ class DouyuBatchRunnerCore:
db: Session,
batch_id: str,
task_type: str,
payload: Optional[dict] = None,
log_queue: Optional[asyncio.Queue] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
payload: dict | None = None,
log_queue: asyncio.Queue | None = None,
loop: asyncio.AbstractEventLoop | None = None,
concurrency: int = 3,
):
self.db = db
@@ -212,7 +215,7 @@ class DouyuBatchRunnerCore:
task.message = message[:512]
if result is not None:
task.result = result
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
db.commit()
self._push_task_event(task)
@@ -232,7 +235,7 @@ class DouyuBatchRunnerCore:
self._push_task_event(task)
def _upsert_goods(self, db: Session, goods: list[dict]) -> None:
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
for raw in goods:
commodity_id = str(raw.get("commodityId") or raw.get("commodity_id") or "")
if not commodity_id:
@@ -254,7 +257,7 @@ class DouyuBatchRunnerCore:
db.commit()
def _upsert_esports_goods(self, db: Session, goods: list[dict]) -> None:
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
for raw in goods:
commodity_id = str(raw.get("commodityId") or raw.get("commodity_id") or "")
if not commodity_id:
@@ -276,7 +279,7 @@ class DouyuBatchRunnerCore:
def _upsert_xpd_goods(self, db: Session, goods: list[dict]) -> None:
"""同步和平小店商品快照,移除上一次热门抢购等遗留商品。"""
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
commodity_ids = {
str(raw.get("commodity_id") or raw.get("iGoodsId") or "") for raw in goods
}
+7 -7
View File
@@ -1,15 +1,15 @@
"""斗鱼任务执行器:送礼(由 douyu_runner.py 按功能域拆分)。"""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING
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
if TYPE_CHECKING:
from .douyu_runner import DouyuBatchRunner
from ..models import Account, DouyuTask
DOUYU_GIFT_POINTS_REFRESH_TIMES = 3
DOUYU_GIFT_POINTS_REFRESH_INTERVAL = 2
@@ -177,7 +177,7 @@ class DonateMixin:
result["refresh_errors"] = refresh_errors
account.esports_bind_status = "esports_gift_donated"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
message = f"赠送{gift_name}成功: {gift_count}"
if account.gold_balance is not None:
message += f",鱼翅余额: {account.gold_balance}"
@@ -273,7 +273,7 @@ class DonateMixin:
if refresh_errors:
result["refresh_errors"] = refresh_errors
account.bind_status = "gift_donated"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
message = f"赠送精英令成功: {gift_count}"
if account.gold_balance is not None:
message += f",鱼翅余额: {account.gold_balance}"
+13 -13
View File
@@ -1,11 +1,13 @@
"""斗鱼任务执行器:鱼翅充值(由 douyu_runner.py 按功能域拆分)。"""
from __future__ import annotations
import re
import time
from datetime import UTC, datetime
from decimal import Decimal
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING, cast
from typing import TYPE_CHECKING, Any, cast
from sqlalchemy.orm import Session
from core.douyu import (
@@ -14,12 +16,10 @@ from core.douyu import (
FishFinRechargeConfig,
FishFinRechargeError,
)
from ..models import Account, DouyuTask
from .douyu_service import update_account_profile_from_cookie
from .douyu_runner_core import DOUYU_PAYMENT_POLL_INTERVAL, DOUYU_PAYMENT_POLL_SECONDS
if TYPE_CHECKING:
from .douyu_runner import DouyuBatchRunner
from ..models import Account, DouyuTask
from .douyu_runner_core import DOUYU_PAYMENT_POLL_INTERVAL, DOUYU_PAYMENT_POLL_SECONDS
from .douyu_service import update_account_profile_from_cookie
class GoldMixin:
@@ -45,7 +45,7 @@ class GoldMixin:
exchange = client.exchange_balance()
account.gold_balance = self._to_int(gold.get("gold"))
account.exchange_balance = self._to_int(exchange.get("count"))
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
return {
"gold_balance": account.gold_balance,
"exchange_balance": account.exchange_balance,
@@ -266,7 +266,7 @@ class GoldMixin:
amount=amount, pay_type=int(config["gold_pay_type"])
)
account.bind_status = "gold_qr_created"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._update_task_progress(
db, task, "running", f"鱼翅 {amount} 元支付码已生成,等待到账", result
)
@@ -278,7 +278,7 @@ class GoldMixin:
return
if recharged:
account.bind_status = "gold_recharged"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -414,7 +414,7 @@ class GoldMixin:
)
return
account.bind_status = "gold_api_order_created"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._update_task_progress(
db, task, "running", "供应商直充订单已创建,等待到账", result
)
@@ -425,7 +425,7 @@ class GoldMixin:
return
if status == 2:
account.bind_status = "gold_recharged"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(db, task, "success", "供应商直充成功", result)
return
if status in {3, 4}:
@@ -439,7 +439,7 @@ class GoldMixin:
client = self._client(cookie)
result = self._refresh_account_gold_balance(client, account)
account.bind_status = "gold_balance_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
+16 -16
View File
@@ -1,17 +1,17 @@
"""斗鱼任务执行器:商城兑换(由 douyu_runner.py 按功能域拆分)。"""
from __future__ import annotations
import random
import time
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from sqlalchemy.orm import Session
from core.douyu import DouyuActivityClient, DouyuActivityError
from ..models import Account, DouyuEsportsGoodsSnapshot, DouyuGoodsSnapshot, DouyuTask
if TYPE_CHECKING:
from .douyu_runner import DouyuBatchRunner
from ..models import Account, DouyuEsportsGoodsSnapshot, DouyuGoodsSnapshot, DouyuTask
# 兑换节奏与重试(对齐 8.30 浏览器抓包:锁单->支付间隔约 2.2~4.2s;火爆类错误要长退避而不是秒级连打)
DOUYU_EXCHANGE_PRE_CREATE_JITTER = (0.3, 1.2)
@@ -65,7 +65,7 @@ class GoodsMixin:
goods = result["goods"]
self._upsert_goods(db, goods)
account.bind_status = account.bind_status or "active"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -91,7 +91,7 @@ class GoodsMixin:
goods = result["goods"]
self._upsert_esports_goods(db, goods)
account.esports_bind_status = "esports_goods_refreshed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -131,7 +131,7 @@ class GoodsMixin:
.first()
)
account.bind_status = "goods_locked"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
expire_seconds = result.get("expire_seconds")
expire_text = f"{expire_seconds} 秒内有效" if expire_seconds else ""
self._mark_task(
@@ -181,7 +181,7 @@ class GoodsMixin:
.first()
)
account.bind_status = "goods_exchanged"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
result = {
"commodity_id": commodity_id,
"order_id": order_id,
@@ -346,7 +346,7 @@ class GoodsMixin:
finish_failed(f"预约到货失败: {exc}")
return
account.bind_status = "goods_subscribed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -383,7 +383,7 @@ class GoodsMixin:
finish_failed(f"预兑失败: {exc}")
return
account.bind_status = "goods_pre_exchanged"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -485,7 +485,7 @@ class GoodsMixin:
}
)
account.bind_status = "goods_exchanged"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
# 兑换成功后自动刷新积分,更新账号最新积分信息(失败不阻断兑换成功)
points_refresh = None
try:
@@ -575,7 +575,7 @@ class GoodsMixin:
result["esports_points_refresh_error"] = str(exc)
account.esports_bind_status = "esports_goods_exchanged"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
name = (goods.name if goods else "") or commodity_id
message = f"兑换电竞皮肤成功: {name}"
if quantity > 1:
@@ -608,7 +608,7 @@ class GoodsMixin:
else f"限兑 {len(names)} 个: {', '.join(names[:5])}"
)
account.bind_status = "limited_goods_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -624,7 +624,7 @@ class GoodsMixin:
result = client.exchange_records(manual_id=str(config["manual_id"]))
records = result["records"]
account.bind_status = "exchange_records_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -639,7 +639,7 @@ class GoodsMixin:
client = self._client(cookie)
token = client.csrf_token()
account.bind_status = "csrf_token_ready"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
+16 -16
View File
@@ -1,18 +1,18 @@
"""斗鱼任务执行器:手册开通与积分(由 douyu_runner.py 按功能域拆分)。"""
from __future__ import annotations
import time
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from sqlalchemy.orm import Session
from core.douyu import DouyuActivityClient, DouyuActivityError
from ..models import Account, DouyuTask
from .douyu_service import account_uid, update_account_profile_from_cookie
from .douyu_runner_core import DOUYU_PAYMENT_POLL_INTERVAL, DOUYU_PAYMENT_POLL_SECONDS
if TYPE_CHECKING:
from .douyu_runner import DouyuBatchRunner
from ..models import Account, DouyuTask
from .douyu_runner_core import DOUYU_PAYMENT_POLL_INTERVAL, DOUYU_PAYMENT_POLL_SECONDS
from .douyu_service import account_uid, update_account_profile_from_cookie
class ManualMixin:
@@ -54,7 +54,7 @@ class ManualMixin:
account.uid = uid
account.points = points
update_account_profile_from_cookie(account, cookie)
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
return {"points": points, "points_query": result}
def _wait_points_after_payment(
@@ -121,7 +121,7 @@ class ManualMixin:
manual_type = self._to_int(result.get("manual_type"))
manual_score = self._to_int(result.get("manual_score"))
account.esports_points = manual_score
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
return {
"esports_manual_type": manual_type,
"esports_manual_score": manual_score,
@@ -220,7 +220,7 @@ class ManualMixin:
room_id=str(config["room_id"]),
)
account.bind_status = "elite_qr_created"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._update_task_progress(
db, task, "running", "精英宝典支付码已生成,等待开通到账", result
)
@@ -232,7 +232,7 @@ class ManualMixin:
return
if opened:
account.bind_status = "elite_opened"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db, task, "success", f"精英宝典已开通,积分: {account.points}", result
)
@@ -281,7 +281,7 @@ class ManualMixin:
if baseline_manual_type is not None and baseline_manual_type >= 1:
account.esports_bind_status = "esports_opened"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -299,7 +299,7 @@ class ManualMixin:
)
result.update(baseline_result)
account.esports_bind_status = "esports_qr_created"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._update_task_progress(
db, task, "running", "电竞手册支付码已生成,等待开通到账", result
)
@@ -318,7 +318,7 @@ class ManualMixin:
return
if opened:
account.esports_bind_status = "esports_opened"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(
db,
task,
@@ -353,7 +353,7 @@ class ManualMixin:
client = self._client(cookie)
result = self._refresh_esports_handbook(client, account, manual_id=manual_id)
account.esports_bind_status = "esports_points_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
points = result["esports_points"]
self._mark_task(
db,
@@ -370,7 +370,7 @@ class ManualMixin:
ctn = client.acf_ccn(refresh_subscribe=False)
result = self._refresh_account_points(client, account, cookie, ctn=ctn)
account.bind_status = "points_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
points = result["points"]
self._mark_task(
db,
+14 -14
View File
@@ -1,16 +1,16 @@
"""斗鱼任务执行器:和平小店(由 douyu_runner.py 按功能域拆分)。"""
from __future__ import annotations
import time
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING, cast
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any, cast
from sqlalchemy.orm import Session
from core.douyu import DouyuActivityClient, DouyuActivityError
from ..models import Account, DouyuTask, DouyuXpdGoodsSnapshot
if TYPE_CHECKING:
from .douyu_runner import DouyuBatchRunner
from ..models import Account, DouyuTask, DouyuXpdGoodsSnapshot
DOUYU_XPD_BIND_POLL_SECONDS = 300
DOUYU_XPD_BIND_POLL_INTERVAL = 5
@@ -72,7 +72,7 @@ class XpdMixin:
account.xpd_role_id = str(role.get("role_id") or "") or account.xpd_role_id
account.xpd_plat_id = self._to_int(role.get("plat_id"))
account.xpd_area_id = area_id
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
def _execute_query_xpd_role(
self,
@@ -126,7 +126,7 @@ class XpdMixin:
return
result = client.xpd_bind_qr(act_alias=act_alias)
account.xpd_bind_status = "xpd_bind_qr_ready"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
# 记录绑定前状态:已绑定账号生成二维码后必须等扫码换绑,不能立即成功
try:
@@ -242,7 +242,7 @@ class XpdMixin:
role_name = str(result.get("role_name") or "")
if not result.get("bind_role") or not role_name:
account.xpd_bind_status = "xpd_not_bound"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
self._mark_task(
db, task, "failed", "尚未检测到小店绑定角色,请先扫码绑定", result
@@ -258,12 +258,12 @@ class XpdMixin:
)
else:
account.xpd_game_name = role_name
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
except Exception:
account.xpd_game_name = role_name
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
account.xpd_bind_status = "xpd_bound"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
result["xpd_pending_confirm"] = False
result["xpd_bound"] = True
@@ -427,7 +427,7 @@ class XpdMixin:
fragments = result.get("fragments")
account.xpd_fragments = fragments
account.xpd_bind_status = "xpd_fragments_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
if fragments is None:
self._mark_task(db, task, "failed", "未获取到小店扭蛋碎片数量")
@@ -459,7 +459,7 @@ class XpdMixin:
act_id=str(config["xpd_act_id"]),
)
account.xpd_bind_status = "xpd_purchase_records_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
total = result.get("total") or len(result.get("records") or [])
self._mark_task(
db,
@@ -561,7 +561,7 @@ class XpdMixin:
if pay_type == 5 and result.get("new_balance") is not None:
account.xpd_fragments = result["new_balance"]
account.xpd_bind_status = "xpd_goods_exchanged"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
currency = "点券" if pay_type == 1 else "扭蛋碎片"
+3 -4
View File
@@ -3,7 +3,7 @@
from __future__ import annotations
import uuid
from datetime import datetime, timezone
from datetime import UTC, datetime
from sqlalchemy import select
from sqlalchemy.orm import Session
@@ -13,7 +13,6 @@ from core.douyu.cookie_utils import cookie_value
from ..models import Account, DouyuConfig, DouyuTask, LoginTask
SUPPORTED_DOUYU_TASK_TYPES = {
"get_bind_qr": "获取绑定二维码",
"confirm_bind": "确认绑定",
@@ -302,7 +301,7 @@ def cleanup_orphan_douyu_tasks(
tasks = query.all()
if not tasks:
return 0
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
for task in tasks:
task.status = "stopped"
task.message = message
@@ -426,7 +425,7 @@ def douyu_task_payload(task: DouyuTask, *, include_detail: bool = False) -> dict
if value is None:
return None
if value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
value = value.replace(tzinfo=UTC)
return value.isoformat()
return {
+11 -7
View File
@@ -6,8 +6,7 @@ import threading
import uuid
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Optional
from datetime import UTC, datetime
from core.douyu.proxy_fetcher import ProxyFetcher
from core.huya.auto_register import HuyaAutoRegisterResult, register_huya_with_sms_line
@@ -17,13 +16,18 @@ from core.sms_provider import SmsLine
from ..database import SessionLocal
from ..models import (
HuyaRegisterBatch as HuyaRegisterBatchModel,
)
from ..models import (
HuyaRegisterItem as HuyaRegisterItemModel,
)
from ..models import (
HuyaRegisterSuccessLog,
)
from ..models import (
ProxyConfig as ProxyConfigModel,
)
from .huya_service import upsert_huya_cookie
# 运行中状态:服务中断后视为未完成,可继续
RUNNING_ITEM_STATUSES = frozenset({"sending", "waiting", "changing", "logging"})
# 续跑(continue):只跑「还没跑完」的,不重试已明确失败(error)、不重跑成功(success)
@@ -37,7 +41,7 @@ TERMINAL_BATCH_STATUSES = frozenset({"finished", "stopped", "error", "interrupte
def _now() -> datetime:
return datetime.now(timezone.utc)
return datetime.now(UTC)
def _cookie_preview(cookie: str) -> str:
@@ -409,7 +413,7 @@ class HuyaRegisterRunner:
self,
batch: HuyaRegisterBatch,
sms_lines: list[SmsLine],
proxy_config: Optional[ProxyConfigModel] = None,
proxy_config: ProxyConfigModel | None = None,
*,
item_indices: list[int] | None = None,
):
@@ -812,7 +816,7 @@ class HuyaRegisterRegistry:
password_prefix: str = "hy",
fixed_password: str = "",
use_proxy: bool = False,
proxy_config: Optional[ProxyConfigModel] = None,
proxy_config: ProxyConfigModel | None = None,
) -> HuyaRegisterRunner:
batch_id = uuid.uuid4().hex[:12]
concurrency = max(1, min(int(concurrency or 1), 5))
@@ -918,7 +922,7 @@ class HuyaRegisterRegistry:
def retry(
self,
batch_id: str,
proxy_config: Optional[ProxyConfigModel] = None,
proxy_config: ProxyConfigModel | None = None,
*,
mode: str = "continue",
concurrency: int | None = None,
+10 -7
View File
@@ -3,19 +3,22 @@
from __future__ import annotations
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import UTC, datetime
from sqlalchemy.orm import joinedload
from ..database import SessionLocal
from datetime import datetime, timezone
from ..models import HuyaAccount, HuyaTask
from core.huya.cookie_utils import normalize_huya_cookie
from .huya_service import HUYA_CONFIG_FIELDS, ensure_huya_config, huya_config_value
from .huya_runner_core import HuyaBatchRunnerCore, huya_batch_registry # noqa: F401 (huya_batch_registry 供 routers 重导出)
from ..database import SessionLocal
from ..models import HuyaAccount, HuyaTask
from .huya_runner_bind import BindMixin
from .huya_runner_core import ( # noqa: F401 (huya_batch_registry 供 routers 重导出)
HuyaBatchRunnerCore,
huya_batch_registry,
)
from .huya_runner_goods import GoodsMixin
from .huya_runner_recharge import RechargeMixin
from .huya_service import HUYA_CONFIG_FIELDS, ensure_huya_config, huya_config_value
class HuyaBatchRunner(
@@ -148,7 +151,7 @@ class HuyaBatchRunner(
if not account:
task.status = "error"
task.message = "账号不存在"
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
continue
task.status = "pending"
task.message = "等待执行"
+8 -11
View File
@@ -1,20 +1,17 @@
"""虎牙任务执行器:角色绑定(由 huya_runner.py 按功能域拆分)。"""
from __future__ import annotations
import time
from datetime import datetime, timezone
from typing import Any
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from sqlalchemy.orm import Session
from core.huya import HuyaHttpClient
from ..models import HuyaAccount, HuyaTask
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .huya_runner import HuyaBatchRunner
HUYA_BIND_ROLE_POLL_SECONDS = 180
HUYA_BIND_ROLE_POLL_INTERVAL = 3
HUYA_BIND_ZT_UUID = "b02faae1"
@@ -77,7 +74,7 @@ class BindMixin:
account_data = bind_status.accountData
is_bound = bool(account_data.isBindAcount and account_data.isBindRole)
change_time = int(account_data.changBindTime or 0)
now = int(datetime.now(timezone.utc).timestamp())
now = int(datetime.now(UTC).timestamp())
can_change = not is_bound or not change_time or change_time <= now
return {
"is_bound": is_bound,
@@ -150,7 +147,7 @@ class BindMixin:
account.status = status
account.game_name = role_name or account.game_name
account.game_channel = self._role_channel(bind_status) or account.game_channel
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
@staticmethod
def _bind_redirect_url(config_info: dict) -> str:
@@ -439,7 +436,7 @@ class BindMixin:
account.game_name = role_info["role_name"] or account.game_name
account.game_channel = self._role_channel(bind_status) or account.game_channel
account.nickname = profile_nick or account.nickname
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
# 生成二维码后自动轮询扫码/角色,避免用户必须手动点「查询角色」。
role_name, result = self._wait_bind_role_result(
@@ -545,7 +542,7 @@ class BindMixin:
return
account.status = "game_not_bound"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(worker_db, task, "success", "未绑定游戏角色", result)
def _execute_confirm_bind(
+8 -8
View File
@@ -5,11 +5,11 @@ from __future__ import annotations
import asyncio
import threading
import time
from datetime import datetime, timezone
from typing import Optional
from datetime import UTC, datetime
from loguru import logger
from sqlalchemy.orm import Session
from ..models import HuyaTask
from .huya_service import cookie_value
@@ -29,9 +29,9 @@ class HuyaBatchRunnerCore:
db: Session,
batch_id: str,
task_type: str,
payload: Optional[dict] = None,
log_queue: Optional[asyncio.Queue] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
payload: dict | None = None,
log_queue: asyncio.Queue | None = None,
loop: asyncio.AbstractEventLoop | None = None,
concurrency: int = 3,
):
self.db = db
@@ -123,12 +123,12 @@ class HuyaBatchRunnerCore:
task: HuyaTask,
status: str,
message: str,
result: Optional[dict] = None,
result: dict | None = None,
):
task.status = status
task.message = message
task.result = result
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
worker_db.commit()
def _update_task_progress(
@@ -137,7 +137,7 @@ class HuyaBatchRunnerCore:
task: HuyaTask,
status: str,
message: str,
result: Optional[dict] = None,
result: dict | None = None,
):
task.status = status
task.message = message
+9 -10
View File
@@ -1,17 +1,16 @@
"""虎牙任务执行器:积分与商城(由 huya_runner.py 按功能域拆分)。"""
from __future__ import annotations
import time
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from sqlalchemy.orm import Session
from core.huya import HuyaHttpClient
from ..models import HuyaAccount, HuyaGoodsSnapshot, HuyaTask
if TYPE_CHECKING:
from .huya_runner import HuyaBatchRunner
from ..models import HuyaAccount, HuyaGoodsSnapshot, HuyaTask
class GoodsMixin:
@@ -83,7 +82,7 @@ class GoodsMixin:
points = response.available_score
account.points = points
account.status = "points_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
self._mark_task(worker_db, task, "success", f"积分: {points}", result)
def _execute_query_exchange_records(
@@ -144,7 +143,7 @@ class GoodsMixin:
item["score_text"] = f"{int(item.get('score') or 0)}积分"
account.status = "exchange_records_queried"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
count = len(records)
message = f"兑换记录 {count}" if count else "暂无兑换记录"
self._mark_task(worker_db, task, "success", message, result)
@@ -202,7 +201,7 @@ class GoodsMixin:
for item in result.get("goods", [])
if item.get("product_id") and item.get("name")
]
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
worker_db.query(HuyaGoodsSnapshot).delete(synchronize_session=False)
for item in goods:
worker_db.add(
@@ -290,7 +289,7 @@ class GoodsMixin:
"product_id": str(product_id),
"product_name": product_name,
"scheduled_at": scheduled_at.isoformat() if scheduled_at else "",
"executed_at": datetime.now(timezone.utc).isoformat(),
"executed_at": datetime.now(UTC).isoformat(),
"goods": snapshot.raw if snapshot else None,
}
)
@@ -305,6 +304,6 @@ class GoodsMixin:
return
account.status = "goods_exchanged"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
message = response.msg or f"兑换成功: {product_name}"
self._mark_task(worker_db, task, "success", message, result)
+8 -9
View File
@@ -1,13 +1,15 @@
"""虎牙任务执行器:充值(由 huya_runner.py 按功能域拆分)。"""
from __future__ import annotations
import time
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any
from sqlalchemy.orm import Session
from core.huya import HuyaHttpClient
from ..models import HuyaAccount, HuyaRechargeGoodsSnapshot, HuyaTask
HUYA_RECHARGE_ACT_ID = 25135
@@ -25,9 +27,6 @@ HUYA_RECHARGE_EXTRA_PRODUCTS = [
]
from .huya_runner_core import HUYA_RECHARGE_SOURCE_ID
if TYPE_CHECKING:
from .huya_runner import HuyaBatchRunner
class RechargeMixin:
"""充值域:充值商品、下单与到账轮询。"""
@@ -103,7 +102,7 @@ class RechargeMixin:
status=0,
timeout=10.0,
)
checked_at = datetime.now(timezone.utc).isoformat()
checked_at = datetime.now(UTC).isoformat()
if resp is not None and getattr(resp, "orders", None):
for order in resp.orders:
if str(getattr(order, "orderId", "")) != order_id_text:
@@ -248,7 +247,7 @@ class RechargeMixin:
)
return
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
goods: list[dict] = []
failed: list[dict] = []
@@ -501,7 +500,7 @@ class RechargeMixin:
"order": order_result,
}
account.status = "recharge_order_created"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
message = f"{product_name} x{count} {self._pay_channel_label(pay_channel)} {result['amount_text']}"
self._update_task_progress(
worker_db, task, "running", f"{message},等待扫码支付", result
@@ -518,7 +517,7 @@ class RechargeMixin:
order_id=order_resp.orderId,
result=result,
)
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
if payment_status == "paid":
account.status = "recharge_paid"
paid_message = f"支付成功: {product_name} x{count} {result['amount_text']}"
+5 -6
View File
@@ -3,7 +3,7 @@
import csv
import uuid
from dataclasses import dataclass
from datetime import datetime, timezone
from datetime import UTC, datetime
from urllib.parse import urlparse
from sqlalchemy.orm import Session
@@ -13,7 +13,6 @@ from core.huya.cookie_utils import cookie_value, normalize_huya_cookie
from ..huya_defaults import HUYA_CONFIG_DEFAULTS, HUYA_CONFIG_FIELDS
from ..models import HuyaAccount, HuyaConfig, HuyaTask
SUPPORTED_TASK_TYPES = {
"get_bind_qr": "获取绑定二维码",
"query_points": "一键查询积分",
@@ -204,7 +203,7 @@ def import_huya_password_accounts(
account.tag = tag
if not account.cookie:
account.status = "password_imported"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
created_or_updated += 1
if created_or_updated:
@@ -247,7 +246,7 @@ def _upsert_huya_account(
if tag:
account.tag = tag
account.status = status or "updated"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
return account
@@ -276,7 +275,7 @@ def save_huya_login_cookie_to_account(
if tag:
account.tag = tag
account.status = "login_success"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
db.refresh(account)
return account
@@ -358,7 +357,7 @@ def cleanup_orphan_huya_tasks(
if not tasks:
return 0
now = datetime.now(timezone.utc)
now = datetime.now(UTC)
for task in tasks:
task.status = "stopped"
task.message = message
+23 -20
View File
@@ -5,18 +5,21 @@ import os
import threading
import time
import uuid
from types import SimpleNamespace
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from typing import Optional, cast
from datetime import UTC, datetime
from types import SimpleNamespace
from typing import cast
from sqlalchemy.orm import Session
from loguru import logger
from sqlalchemy.orm import Session
from core.douyu import DouyuLogin, WgapiLoginAPI, IframeLoginAPI
from core.douyu import DouyuLogin, IframeLoginAPI, WgapiLoginAPI
from core.douyu.login import AccountLike
from core.douyu.proxy_fetcher import ProxyFetcher
from ..models import Account as AccountModel, LoginTask, ProxyConfig as ProxyConfigModel
from ..models import Account as AccountModel
from ..models import LoginTask
from ..models import ProxyConfig as ProxyConfigModel
from .cookie_check_service import check_douyu_cookie
@@ -62,8 +65,8 @@ def get_relogin_limits() -> tuple[int, int]:
def _snapshot_proxy_config(
proxy_config: Optional[ProxyConfigModel],
) -> Optional[ProxyConfigModel]:
proxy_config: ProxyConfigModel | None,
) -> ProxyConfigModel | None:
"""复制代理配置,避免后台线程访问已关闭会话中的 ORM 对象。"""
if proxy_config is None:
return None
@@ -97,13 +100,13 @@ class LoginBatchRunner:
creator_permissions: list[str],
max_login_retries: int = 0,
max_total_time: float = 0,
proxy_config: Optional[ProxyConfigModel] = None,
log_queue: Optional[asyncio.Queue] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
proxy_config: ProxyConfigModel | None = None,
log_queue: asyncio.Queue | None = None,
loop: asyncio.AbstractEventLoop | None = None,
concurrency: int = 3,
api_strategy: str = "wgapi",
mode: str = "login",
relogin_task_ids: Optional[list[int]] = None,
relogin_task_ids: list[int] | None = None,
):
self.db = db
self.account_ids = account_ids
@@ -179,7 +182,7 @@ class LoginBatchRunner:
if task and task.status in ("relogin_pending", "relogin_running"):
task.status = "relogin_failed"
task.message = message
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
worker_db.commit()
finally:
worker_db.close()
@@ -199,7 +202,7 @@ class LoginBatchRunner:
self.loop,
)
def _resolve_static_proxy(self) -> tuple[Optional[dict], str]:
def _resolve_static_proxy(self) -> tuple[dict | None, str]:
"""解析静态代理配置。"""
if not self.proxy_config or not self.proxy_config.enabled:
return None, ""
@@ -270,7 +273,7 @@ class LoginBatchRunner:
else:
task.status = "error"
task.message = "代理不可用: 未配置代理"
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
worker_db.commit()
self._push_log(
"error", f"[{current}] {acc_info['username']} 代理不可用"
@@ -384,7 +387,7 @@ class LoginBatchRunner:
f"[{current}] {acc_info['username']} {action_name}异常: {e}",
)
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
worker_db.commit()
finally:
@@ -581,10 +584,10 @@ class BatchRegistry:
def register(
self,
batch_id: str,
log_queue: Optional[asyncio.Queue],
loop: Optional[asyncio.AbstractEventLoop],
log_queue: asyncio.Queue | None,
loop: asyncio.AbstractEventLoop | None,
runner: LoginBatchRunner,
owner_id: Optional[int] = None,
owner_id: int | None = None,
):
self._batches[batch_id] = {
"log_queue": log_queue,
@@ -630,7 +633,7 @@ def cleanup_orphan_relogin_tasks(
if not tasks:
return 0
finished_at = datetime.now(timezone.utc)
finished_at = datetime.now(UTC)
for task in tasks:
task.status = "relogin_failed"
task.message = message
+6 -6
View File
@@ -1,21 +1,21 @@
"""代理 & 白名单服务层:封装 core/ 代理/白名单逻辑,供路由调用。"""
import asyncio
import re
import threading
import uuid
from typing import Optional
from sqlalchemy.orm import Session
from core.douyu.proxy import (
parse_proxy_response,
resolve_working_proxy,
verify_proxy_url,
parse_proxy_response,
)
from core.douyu.proxy_platforms import create_adapter, get_platform_labels
from core.douyu.proxy_platforms import create_adapter
from core.douyu.proxy_platforms.base import _get_local_exit_ip
from ..models import ProxyConfig as ProxyConfigModel, AuditLog
from ..models import AuditLog
from ..models import ProxyConfig as ProxyConfigModel
def _build_whitelist_params(cfg: ProxyConfigModel) -> dict:
@@ -128,7 +128,7 @@ class ProxyService:
self._active_tests[test_id] = {"log_queue": log_queue, "loop": loop}
return test_id, log_queue, loop
def get_test(self, test_id: str) -> Optional[dict]:
def get_test(self, test_id: str) -> dict | None:
"""获取测试任务信息。"""
with self._active_tests_lock:
return self._active_tests.get(test_id)
+5 -5
View File
@@ -2,7 +2,7 @@
from __future__ import annotations
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import Any
from sqlalchemy.orm import Session
@@ -12,12 +12,12 @@ from .yyb_worker_client import YybWorkerClient
def _utcnow():
return datetime.now(timezone.utc)
return datetime.now(UTC)
def _from_ts(value) -> datetime | None:
try:
return datetime.fromtimestamp(int(value), tz=timezone.utc) if value else None
return datetime.fromtimestamp(int(value), tz=UTC) if value else None
except (TypeError, ValueError, OSError):
return None
@@ -27,8 +27,8 @@ def _as_utc(value: datetime | None) -> datetime | None:
if value is None:
return None
if value.tzinfo is None:
return value.replace(tzinfo=timezone.utc)
return value.astimezone(timezone.utc)
return value.replace(tzinfo=UTC)
return value.astimezone(UTC)
def cleanup_orphan_yyb_tasks(db: Session, message: str) -> int: