type: 收敛测试 schemas 与协议层类型
This commit is contained in:
@@ -10,9 +10,10 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from types import SimpleNamespace
|
||||
from typing import Optional
|
||||
from typing import Optional, cast
|
||||
|
||||
from core.douyu import DouyuLogin, WgapiLoginAPI
|
||||
from core.douyu.login import AccountLike
|
||||
from core.douyu.proxy_fetcher import ProxyFetcher
|
||||
|
||||
from ..models import ProxyConfig as ProxyConfigModel
|
||||
@@ -118,13 +119,15 @@ def parse_account_check_lines(text: str) -> list[AccountCheckInput]:
|
||||
f"或 账号|密码|邮箱|邮箱密码"
|
||||
)
|
||||
|
||||
accounts.append(AccountCheckInput(
|
||||
line=line_no,
|
||||
username=parts[0],
|
||||
password=parts[1],
|
||||
email=parts[2],
|
||||
email_password=parts[3],
|
||||
))
|
||||
accounts.append(
|
||||
AccountCheckInput(
|
||||
line=line_no,
|
||||
username=parts[0],
|
||||
password=parts[1],
|
||||
email=parts[2],
|
||||
email_password=parts[3],
|
||||
)
|
||||
)
|
||||
|
||||
return accounts
|
||||
|
||||
@@ -158,9 +161,15 @@ class AccountCheckRunner:
|
||||
wl_platform = "xiequ"
|
||||
wl_credentials = None
|
||||
if self.proxy_config.whitelist_enabled:
|
||||
wl_platform = getattr(self.proxy_config, "whitelist_platform", None) or "xiequ"
|
||||
wl_platform = (
|
||||
getattr(self.proxy_config, "whitelist_platform", None) or "xiequ"
|
||||
)
|
||||
wl_credentials = getattr(self.proxy_config, "whitelist_credentials", None)
|
||||
if not wl_credentials and self.proxy_config.whitelist_uid and self.proxy_config.whitelist_ukey:
|
||||
if (
|
||||
not wl_credentials
|
||||
and self.proxy_config.whitelist_uid
|
||||
and self.proxy_config.whitelist_ukey
|
||||
):
|
||||
wl_credentials = {
|
||||
"uid": self.proxy_config.whitelist_uid,
|
||||
"ukey": self.proxy_config.whitelist_ukey,
|
||||
@@ -209,25 +218,38 @@ class AccountCheckRunner:
|
||||
|
||||
def _run_one(self, index: int, account: AccountCheckInput):
|
||||
if self._stop.is_set():
|
||||
self._set_item(index, status="stopped", message="已停止", finished_at=_now())
|
||||
self._set_item(
|
||||
index, status="stopped", message="已停止", finished_at=_now()
|
||||
)
|
||||
return
|
||||
|
||||
proxy_dict, proxy_error = self._resolve_static_proxy()
|
||||
if proxy_error:
|
||||
self._set_item(index, status="error", message=proxy_error, finished_at=_now())
|
||||
self._set_item(
|
||||
index, status="error", message=proxy_error, finished_at=_now()
|
||||
)
|
||||
return
|
||||
|
||||
self._set_item(index, status="running", message="检测中", started_at=_now(), finished_at=None)
|
||||
self._set_item(
|
||||
index,
|
||||
status="running",
|
||||
message="检测中",
|
||||
started_at=_now(),
|
||||
finished_at=None,
|
||||
)
|
||||
try:
|
||||
result = DouyuLogin(
|
||||
SimpleNamespace(
|
||||
username=account.username,
|
||||
password=account.password,
|
||||
email=account.email,
|
||||
email_password=account.email_password,
|
||||
email_imap_server="",
|
||||
email_imap_port=993,
|
||||
email_imap_ssl=True,
|
||||
cast(
|
||||
AccountLike,
|
||||
SimpleNamespace(
|
||||
username=account.username,
|
||||
password=account.password,
|
||||
email=account.email,
|
||||
email_password=account.email_password,
|
||||
email_imap_server="",
|
||||
email_imap_port=993,
|
||||
email_imap_ssl=True,
|
||||
),
|
||||
),
|
||||
proxy=proxy_dict,
|
||||
max_login_retries=self.batch.max_login_retries,
|
||||
@@ -237,15 +259,24 @@ class AccountCheckRunner:
|
||||
api_strategy=WgapiLoginAPI(),
|
||||
).check_account()
|
||||
except Exception as exc:
|
||||
self._set_item(index, status="error", message=f"检测异常: {exc}", finished_at=_now())
|
||||
self._set_item(
|
||||
index, status="error", message=f"检测异常: {exc}", finished_at=_now()
|
||||
)
|
||||
return
|
||||
|
||||
if self._stop.is_set() and not result.success:
|
||||
self._set_item(index, status="stopped", message=result.message or "已停止", finished_at=_now())
|
||||
self._set_item(
|
||||
index,
|
||||
status="stopped",
|
||||
message=result.message or "已停止",
|
||||
finished_at=_now(),
|
||||
)
|
||||
return
|
||||
|
||||
if result.success:
|
||||
status = result.code if result.code in STATUS_LABELS else "account_auth_unknown"
|
||||
status = (
|
||||
result.code if result.code in STATUS_LABELS else "account_auth_unknown"
|
||||
)
|
||||
message = result.message or STATUS_LABELS.get(status, "认证状态未知")
|
||||
else:
|
||||
status = "error"
|
||||
@@ -260,7 +291,9 @@ class AccountCheckRunner:
|
||||
status: sum(1 for item in self.batch.items if item.status == status)
|
||||
for status in STATUS_LABELS
|
||||
}
|
||||
running_count = sum(1 for item in self.batch.items if item.status in {"pending", "running"})
|
||||
running_count = sum(
|
||||
1 for item in self.batch.items if item.status in {"pending", "running"}
|
||||
)
|
||||
finished_count = len(self.batch.items) - running_count
|
||||
return {
|
||||
"batch_id": self.batch.batch_id,
|
||||
@@ -296,7 +329,10 @@ class AccountCheckRunner:
|
||||
if item.status != status:
|
||||
continue
|
||||
line = item.export_text
|
||||
if status in {"error", "stopped", "account_auth_unknown"} and item.message:
|
||||
if (
|
||||
status in {"error", "stopped", "account_auth_unknown"}
|
||||
and item.message
|
||||
):
|
||||
line = f"{line}----{item.message}"
|
||||
lines.append(line)
|
||||
content = "\n".join(lines)
|
||||
@@ -309,7 +345,9 @@ class AccountCheckRunner:
|
||||
|
||||
def run(self):
|
||||
"""线程入口。"""
|
||||
self._set_batch(status="running", message="批次运行中", started_at=_now(), finished_at=None)
|
||||
self._set_batch(
|
||||
status="running", message="批次运行中", started_at=_now(), finished_at=None
|
||||
)
|
||||
try:
|
||||
if self._shared_proxy_fetcher:
|
||||
self._shared_proxy_fetcher.warmup_whitelist()
|
||||
@@ -318,14 +356,21 @@ class AccountCheckRunner:
|
||||
futures = []
|
||||
for index, account in enumerate(self.accounts):
|
||||
if self._stop.is_set():
|
||||
self._set_item(index, status="stopped", message="已停止", finished_at=_now())
|
||||
self._set_item(
|
||||
index,
|
||||
status="stopped",
|
||||
message="已停止",
|
||||
finished_at=_now(),
|
||||
)
|
||||
continue
|
||||
futures.append(executor.submit(self._run_one, index, account))
|
||||
|
||||
for future in as_completed(futures):
|
||||
future.result()
|
||||
except Exception as exc:
|
||||
self._set_batch(status="error", message=f"批次执行异常: {exc}", finished_at=_now())
|
||||
self._set_batch(
|
||||
status="error", message=f"批次执行异常: {exc}", finished_at=_now()
|
||||
)
|
||||
return
|
||||
|
||||
if self._stop.is_set():
|
||||
@@ -370,7 +415,9 @@ class AccountCheckRegistry:
|
||||
for account in accounts
|
||||
],
|
||||
)
|
||||
runner = AccountCheckRunner(batch=batch, accounts=accounts, proxy_config=proxy_config)
|
||||
runner = AccountCheckRunner(
|
||||
batch=batch, accounts=accounts, proxy_config=proxy_config
|
||||
)
|
||||
with self._lock:
|
||||
self._runners[batch_id] = runner
|
||||
return runner
|
||||
|
||||
@@ -42,9 +42,17 @@ def check_douyu_cookie(cookie: str) -> dict:
|
||||
).json()
|
||||
if isinstance(fish_data, dict) and fish_data.get("error") in (0, "0"):
|
||||
fish_ok = True
|
||||
fish_ball = (fish_data.get("data") or {}).get("count") if isinstance(fish_data.get("data"), dict) else None
|
||||
fish_ball = (
|
||||
(fish_data.get("data") or {}).get("count")
|
||||
if isinstance(fish_data.get("data"), dict)
|
||||
else None
|
||||
)
|
||||
else:
|
||||
fish_msg = str(fish_data.get("msg") or fish_data.get("error") or "响应异常") if isinstance(fish_data, dict) else "响应异常"
|
||||
fish_msg = (
|
||||
str(fish_data.get("msg") or fish_data.get("error") or "响应异常")
|
||||
if isinstance(fish_data, dict)
|
||||
else "响应异常"
|
||||
)
|
||||
except Exception as exc:
|
||||
fish_msg = f"请求失败: {exc}"
|
||||
|
||||
@@ -66,11 +74,16 @@ def check_douyu_cookie(cookie: str) -> dict:
|
||||
).json()
|
||||
if isinstance(level_data, dict) and level_data.get("error") in (0, "0"):
|
||||
level_ok = True
|
||||
info = level_data.get("data") if isinstance(level_data.get("data"), dict) else {}
|
||||
info_raw = level_data.get("data")
|
||||
info = info_raw if isinstance(info_raw, dict) else {}
|
||||
nickname = str(info.get("nn") or "") or None
|
||||
level = info.get("lv")
|
||||
else:
|
||||
level_msg = str(level_data.get("msg") or level_data.get("error") or "响应异常") if isinstance(level_data, dict) else "响应异常"
|
||||
level_msg = (
|
||||
str(level_data.get("msg") or level_data.get("error") or "响应异常")
|
||||
if isinstance(level_data, dict)
|
||||
else "响应异常"
|
||||
)
|
||||
except Exception as exc:
|
||||
level_msg = f"请求失败: {exc}"
|
||||
|
||||
@@ -78,10 +91,12 @@ def check_douyu_cookie(cookie: str) -> dict:
|
||||
if valid:
|
||||
message = "有效"
|
||||
else:
|
||||
message = ";".join([
|
||||
f"鱼丸接口: {'ok' if fish_ok else (fish_msg or '失败')}",
|
||||
f"等级接口: {'ok' if level_ok else (level_msg or '失败')}",
|
||||
])
|
||||
message = ";".join(
|
||||
[
|
||||
f"鱼丸接口: {'ok' if fish_ok else (fish_msg or '失败')}",
|
||||
f"等级接口: {'ok' if level_ok else (level_msg or '失败')}",
|
||||
]
|
||||
)
|
||||
return {
|
||||
**base,
|
||||
"valid": valid,
|
||||
|
||||
@@ -21,7 +21,13 @@ from .douyu_runner_xpd import XpdMixin
|
||||
|
||||
|
||||
class DouyuBatchRunner(
|
||||
DouyuBatchRunnerCore, BindMixin, ManualMixin, GoldMixin, DonateMixin, GoodsMixin, XpdMixin,
|
||||
DouyuBatchRunnerCore,
|
||||
BindMixin,
|
||||
ManualMixin,
|
||||
GoldMixin,
|
||||
DonateMixin,
|
||||
GoodsMixin,
|
||||
XpdMixin,
|
||||
):
|
||||
"""批量执行斗鱼活动任务(功能域 Mixin 聚合 + 批次调度)。"""
|
||||
|
||||
@@ -43,13 +49,18 @@ class DouyuBatchRunner(
|
||||
self._started += 1
|
||||
current = self._started
|
||||
|
||||
self._push_log("info", f"[{current}/{total}] 开始: {self._account_name(account)}")
|
||||
self._push_log(
|
||||
"info", f"[{current}/{total}] 开始: {self._account_name(account)}"
|
||||
)
|
||||
login_task = latest_success_login_task(worker_db, account.id)
|
||||
cookie = login_task.cookie if login_task else ""
|
||||
if not cookie:
|
||||
self._mark_task(worker_db, task, "failed", "账号没有成功登录 Cookie")
|
||||
self._push_log("warning", f"[{current}] {self._account_name(account)} 无 Cookie")
|
||||
self._push_log(
|
||||
"warning", f"[{current}] {self._account_name(account)} 无 Cookie"
|
||||
)
|
||||
return
|
||||
assert login_task is not None
|
||||
|
||||
cookie_check = check_douyu_cookie(cookie)
|
||||
login_task.ck_check_status = "valid" if cookie_check["valid"] else "invalid"
|
||||
@@ -62,7 +73,9 @@ class DouyuBatchRunner(
|
||||
if not cookie_check["valid"]:
|
||||
message = f"Cookie 已失效,请重新登录:{cookie_check['message']}"
|
||||
self._mark_task(worker_db, task, "failed", message)
|
||||
self._push_log("warning", f"[{current}] {self._account_name(account)} {message}")
|
||||
self._push_log(
|
||||
"warning", f"[{current}] {self._account_name(account)} {message}"
|
||||
)
|
||||
return
|
||||
|
||||
update_account_profile_from_cookie(account, cookie)
|
||||
@@ -109,7 +122,9 @@ class DouyuBatchRunner(
|
||||
return
|
||||
|
||||
handler(worker_db, task, account, cookie, config)
|
||||
self._push_log("success", f"[{current}] {self._account_name(account)} {task.message}")
|
||||
self._push_log(
|
||||
"success", f"[{current}] {self._account_name(account)} {task.message}"
|
||||
)
|
||||
except DouyuActivityError as exc:
|
||||
if "task" in locals() and task:
|
||||
self._mark_task(worker_db, task, "failed", str(exc))
|
||||
@@ -128,7 +143,9 @@ class DouyuBatchRunner(
|
||||
config = self._config_info(self.db)
|
||||
tasks = (
|
||||
self.db.query(DouyuTask)
|
||||
.filter(DouyuTask.batch_id == self.batch_id, DouyuTask.status == "planned")
|
||||
.filter(
|
||||
DouyuTask.batch_id == self.batch_id, DouyuTask.status == "planned"
|
||||
)
|
||||
.order_by(DouyuTask.id.asc())
|
||||
.all()
|
||||
)
|
||||
@@ -150,7 +167,9 @@ class DouyuBatchRunner(
|
||||
for task in tasks:
|
||||
if self._stop.is_set():
|
||||
break
|
||||
futures.append(executor.submit(self._execute_one, task.id, config, total))
|
||||
futures.append(
|
||||
executor.submit(self._execute_one, task.id, config, total)
|
||||
)
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
future.result()
|
||||
|
||||
@@ -21,7 +21,12 @@ from ..models import (
|
||||
DouyuXpdGoodsSnapshot,
|
||||
ProxyConfig as ProxyConfigModel,
|
||||
)
|
||||
from .douyu_service import DOUYU_CONFIG_FIELDS, douyu_config_value, ensure_douyu_config, douyu_task_payload
|
||||
from .douyu_service import (
|
||||
DOUYU_CONFIG_FIELDS,
|
||||
douyu_config_value,
|
||||
ensure_douyu_config,
|
||||
douyu_task_payload,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .douyu_runner import DouyuBatchRunner
|
||||
@@ -78,7 +83,9 @@ class DouyuBatchRunnerCore:
|
||||
self._proxy_fetcher = self._create_proxy_fetcher()
|
||||
self._static_proxies = self._resolve_static_proxies()
|
||||
if self._static_proxies:
|
||||
logger.info(f"[douyu] 写操作任务将走静态代理: {self._static_proxies.get('https', '')}")
|
||||
logger.info(
|
||||
f"[douyu] 写操作任务将走静态代理: {self._static_proxies.get('https', '')}"
|
||||
)
|
||||
elif self._proxy_fetcher:
|
||||
logger.info("[douyu] 写操作任务将按任务从代理 API 取新代理")
|
||||
|
||||
@@ -89,7 +96,11 @@ class DouyuBatchRunnerCore:
|
||||
return None
|
||||
wl_platform = getattr(cfg, "whitelist_platform", None) or "xiequ"
|
||||
wl_credentials = getattr(cfg, "whitelist_credentials", None)
|
||||
if not wl_credentials and getattr(cfg, "whitelist_uid", "") and getattr(cfg, "whitelist_ukey", ""):
|
||||
if (
|
||||
not wl_credentials
|
||||
and getattr(cfg, "whitelist_uid", "")
|
||||
and getattr(cfg, "whitelist_ukey", "")
|
||||
):
|
||||
wl_credentials = {"uid": cfg.whitelist_uid, "ukey": cfg.whitelist_ukey}
|
||||
return ProxyFetcher(
|
||||
api_url=cfg.api_url,
|
||||
@@ -267,8 +278,7 @@ class DouyuBatchRunnerCore:
|
||||
"""同步和平小店商品快照,移除上一次热门抢购等遗留商品。"""
|
||||
now = datetime.now(timezone.utc)
|
||||
commodity_ids = {
|
||||
str(raw.get("commodity_id") or raw.get("iGoodsId") or "")
|
||||
for raw in goods
|
||||
str(raw.get("commodity_id") or raw.get("iGoodsId") or "") for raw in goods
|
||||
}
|
||||
commodity_ids.discard("")
|
||||
query = db.query(DouyuXpdGoodsSnapshot)
|
||||
@@ -304,11 +314,15 @@ class DouyuBatchRunnerCore:
|
||||
|
||||
def _config_info(self, db: Session) -> dict:
|
||||
config = ensure_douyu_config(db)
|
||||
return {field: douyu_config_value(field, getattr(config, field, None)) for field in DOUYU_CONFIG_FIELDS}
|
||||
return {
|
||||
field: douyu_config_value(field, getattr(config, field, None))
|
||||
for field in DOUYU_CONFIG_FIELDS
|
||||
}
|
||||
|
||||
def _task_payload(self, task: DouyuTask) -> dict:
|
||||
result = task.result if isinstance(task.result, dict) else {}
|
||||
payload = result.get("payload") if isinstance(result.get("payload"), dict) else {}
|
||||
payload_raw = result.get("payload")
|
||||
payload = payload_raw if isinstance(payload_raw, dict) else {}
|
||||
return {**payload, **self.payload}
|
||||
|
||||
def _client(self, cookie: str) -> DouyuActivityClient:
|
||||
@@ -338,8 +352,13 @@ class DouyuBatchRegistry:
|
||||
def __init__(self):
|
||||
self._batches: dict[str, dict] = {}
|
||||
|
||||
def register(self, batch_id: str, log_queue: asyncio.Queue,
|
||||
loop: asyncio.AbstractEventLoop, runner: DouyuBatchRunner):
|
||||
def register(
|
||||
self,
|
||||
batch_id: str,
|
||||
log_queue: asyncio.Queue,
|
||||
loop: asyncio.AbstractEventLoop,
|
||||
runner: DouyuBatchRunner,
|
||||
):
|
||||
self._batches[batch_id] = {
|
||||
"log_queue": log_queue,
|
||||
"loop": loop,
|
||||
@@ -368,4 +387,3 @@ class DouyuBatchRegistry:
|
||||
|
||||
|
||||
douyu_batch_registry = DouyuBatchRegistry()
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.douyu.activity_client import DouyuActivityClient
|
||||
@@ -13,7 +14,8 @@ from core.douyu.cookie_utils import cookie_value
|
||||
from ..models import Account, DouyuConfig, DouyuTask, LoginTask
|
||||
|
||||
|
||||
SUPPORTED_DOUYU_TASK_TYPES = { "get_bind_qr": "获取绑定二维码",
|
||||
SUPPORTED_DOUYU_TASK_TYPES = {
|
||||
"get_bind_qr": "获取绑定二维码",
|
||||
"confirm_bind": "确认绑定",
|
||||
"create_elite_qr": "开通精英宝典30",
|
||||
"prepare_esports_bind": "绑定电竞手册角色",
|
||||
@@ -53,26 +55,55 @@ SUPPORTED_DOUYU_TASK_TYPES = { "get_bind_qr": "获取绑定二维码",
|
||||
DOUYU_HANDBOOK_SCOPES = {"elite", "esports", "peace"}
|
||||
DOUYU_HANDBOOK_TASK_TYPES = {
|
||||
"elite": {
|
||||
"get_bind_qr", "confirm_bind", "create_elite_qr", "create_gold_qr", "donate_elite_gift",
|
||||
"query_points", "lock_goods", "pay_locked_order", "exchange_goods", "query_game_name", "query_change_bind_time",
|
||||
"query_limited_goods", "query_gold_balance", "refresh_goods", "query_exchange_records",
|
||||
"get_bind_qr",
|
||||
"confirm_bind",
|
||||
"create_elite_qr",
|
||||
"create_gold_qr",
|
||||
"donate_elite_gift",
|
||||
"query_points",
|
||||
"lock_goods",
|
||||
"pay_locked_order",
|
||||
"exchange_goods",
|
||||
"query_game_name",
|
||||
"query_change_bind_time",
|
||||
"query_limited_goods",
|
||||
"query_gold_balance",
|
||||
"refresh_goods",
|
||||
"query_exchange_records",
|
||||
"prefetch_csrf_token",
|
||||
},
|
||||
"esports": {
|
||||
"prepare_esports_bind", "get_esports_bind_qr", "query_esports_game_name", "confirm_esports_bind",
|
||||
"create_esports_qr", "query_esports_points", "query_gold_balance", "query_change_bind_time",
|
||||
"query_limited_goods", "refresh_esports_goods", "exchange_esports_goods", "create_gold_qr",
|
||||
"donate_esports_chicken_gift", "donate_esports_firework_gift",
|
||||
"prepare_esports_bind",
|
||||
"get_esports_bind_qr",
|
||||
"query_esports_game_name",
|
||||
"confirm_esports_bind",
|
||||
"create_esports_qr",
|
||||
"query_esports_points",
|
||||
"query_gold_balance",
|
||||
"query_change_bind_time",
|
||||
"query_limited_goods",
|
||||
"refresh_esports_goods",
|
||||
"exchange_esports_goods",
|
||||
"create_gold_qr",
|
||||
"donate_esports_chicken_gift",
|
||||
"donate_esports_firework_gift",
|
||||
},
|
||||
"peace": {
|
||||
"get_xpd_bind_qr", "query_xpd_bind_info", "confirm_xpd_bind", "query_xpd_role",
|
||||
"refresh_xpd_goods", "query_xpd_balance", "query_xpd_fragments",
|
||||
"query_xpd_purchase_records", "exchange_xpd_goods",
|
||||
"get_xpd_bind_qr",
|
||||
"query_xpd_bind_info",
|
||||
"confirm_xpd_bind",
|
||||
"query_xpd_role",
|
||||
"refresh_xpd_goods",
|
||||
"query_xpd_balance",
|
||||
"query_xpd_fragments",
|
||||
"query_xpd_purchase_records",
|
||||
"exchange_xpd_goods",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
DOUYU_CONFIG_DEFAULTS = { "manual_id": "G4KA4Qnz4LDp7",
|
||||
DOUYU_CONFIG_DEFAULTS = {
|
||||
"manual_id": "G4KA4Qnz4LDp7",
|
||||
"rid": "9263298",
|
||||
"bind_act_alias": "20260120QYOOB",
|
||||
"confirm_act_alias": "20260120QYOOB",
|
||||
@@ -117,12 +148,18 @@ def apply_douyu_config_defaults(config: DouyuConfig) -> bool:
|
||||
"""补齐斗鱼配置默认值,返回是否发生变更。"""
|
||||
changed = False
|
||||
for field in DOUYU_CONFIG_FIELDS:
|
||||
if field == "bind_act_alias" and str(getattr(config, field, "") or "").strip() == "20250213NQCYX":
|
||||
if (
|
||||
field == "bind_act_alias"
|
||||
and str(getattr(config, field, "") or "").strip() == "20250213NQCYX"
|
||||
):
|
||||
setattr(config, field, DOUYU_CONFIG_DEFAULTS[field])
|
||||
changed = True
|
||||
continue
|
||||
normalized = douyu_config_value(field, getattr(config, field, None))
|
||||
if field == "gold_recharge_channel" and normalized not in {"wechat_qr", "supplier_api"}:
|
||||
if field == "gold_recharge_channel" and normalized not in {
|
||||
"wechat_qr",
|
||||
"supplier_api",
|
||||
}:
|
||||
normalized = DOUYU_CONFIG_DEFAULTS[field]
|
||||
if getattr(config, field, None) != normalized:
|
||||
setattr(config, field, normalized)
|
||||
@@ -178,7 +215,11 @@ def visible_douyu_task_accounts(db: Session, account_ids: list[int]) -> list[Acc
|
||||
"""只保留存在成功 Cookie 的斗鱼账号。"""
|
||||
if not account_ids:
|
||||
return []
|
||||
cookie_ids = cookie_account_ids_query(db).subquery()
|
||||
cookie_ids = select(LoginTask.account_id).where(
|
||||
LoginTask.status == "success",
|
||||
LoginTask.cookie != "",
|
||||
LoginTask.cookie.isnot(None),
|
||||
)
|
||||
return (
|
||||
db.query(Account)
|
||||
.filter(Account.id.in_(account_ids), Account.id.in_(cookie_ids))
|
||||
@@ -217,23 +258,28 @@ def create_douyu_planned_tasks(
|
||||
raise ValueError("该任务不属于当前工作台")
|
||||
|
||||
accounts = visible_douyu_task_accounts(db, account_ids)
|
||||
if task_type in {"refresh_goods", "refresh_esports_goods", "refresh_xpd_goods"} and accounts:
|
||||
if (
|
||||
task_type in {"refresh_goods", "refresh_esports_goods", "refresh_xpd_goods"}
|
||||
and accounts
|
||||
):
|
||||
# 商品快照是全局数据,一个可用 CK 足够;没有 CK 时前端无法选账号创建任务。
|
||||
accounts = accounts[:1]
|
||||
|
||||
batch_id = uuid.uuid4().hex[:12]
|
||||
payload = payload or {}
|
||||
for account in accounts:
|
||||
db.add(DouyuTask(
|
||||
batch_id=batch_id,
|
||||
account_id=account.id,
|
||||
task_type=task_type,
|
||||
handbook_scope=handbook_scope,
|
||||
status="planned",
|
||||
message="任务已创建,等待执行",
|
||||
result={"payload": payload} if payload else None,
|
||||
created_by=created_by,
|
||||
))
|
||||
db.add(
|
||||
DouyuTask(
|
||||
batch_id=batch_id,
|
||||
account_id=account.id,
|
||||
task_type=task_type,
|
||||
handbook_scope=handbook_scope,
|
||||
status="planned",
|
||||
message="任务已创建,等待执行",
|
||||
result={"payload": payload} if payload else None,
|
||||
created_by=created_by,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return batch_id, len(accounts)
|
||||
|
||||
@@ -272,7 +318,17 @@ def slim_douyu_goods(goods: object) -> object:
|
||||
return {
|
||||
key: value
|
||||
for key, value in goods.items()
|
||||
if key in {"commodityId", "commodity_id", "commodityName", "name", "webPic", "pic", "score", "status"}
|
||||
if key
|
||||
in {
|
||||
"commodityId",
|
||||
"commodity_id",
|
||||
"commodityName",
|
||||
"name",
|
||||
"webPic",
|
||||
"pic",
|
||||
"score",
|
||||
"status",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -284,11 +340,13 @@ def slim_douyu_limited_goods(goods: object) -> list[dict[str, object]]:
|
||||
for item in goods[:5]:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
result.append({
|
||||
key: value
|
||||
for key, value in item.items()
|
||||
if key in {"commodityId", "commodity_id", "commodityName", "name"}
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
key: value
|
||||
for key, value in item.items()
|
||||
if key in {"commodityId", "commodity_id", "commodityName", "name"}
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -305,7 +363,9 @@ def strip_douyu_raw_snapshots(value: object) -> object:
|
||||
return value
|
||||
|
||||
|
||||
def sanitize_douyu_task_result(result: dict | None, task_type: str, *, include_detail: bool = False) -> dict | None:
|
||||
def sanitize_douyu_task_result(
|
||||
result: dict | None, task_type: str, *, include_detail: bool = False
|
||||
) -> dict | None:
|
||||
"""列表/实时推送接口剥离原始快照与大数组;详情接口保留完整 result。"""
|
||||
if not isinstance(result, dict):
|
||||
return result
|
||||
@@ -346,7 +406,12 @@ def sanitize_douyu_task_result(result: dict | None, task_type: str, *, include_d
|
||||
if "limited_goods" in data:
|
||||
data["limited_goods"] = slim_douyu_limited_goods(data.get("limited_goods"))
|
||||
|
||||
if task_type not in {"get_bind_qr", "prepare_esports_bind", "get_esports_bind_qr", "get_xpd_bind_qr"}:
|
||||
if task_type not in {
|
||||
"get_bind_qr",
|
||||
"prepare_esports_bind",
|
||||
"get_esports_bind_qr",
|
||||
"get_xpd_bind_qr",
|
||||
}:
|
||||
data.pop("url", None)
|
||||
if task_type not in {"create_elite_qr", "create_esports_qr", "create_gold_qr"}:
|
||||
data.pop("pay_url", None)
|
||||
|
||||
@@ -8,12 +8,13 @@ import uuid
|
||||
from types import SimpleNamespace
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
from typing import Optional, cast
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from loguru import logger
|
||||
|
||||
from core.douyu import DouyuLogin, WgapiLoginAPI, IframeLoginAPI
|
||||
from core.douyu.login import AccountLike
|
||||
from core.douyu.proxy_fetcher import ProxyFetcher
|
||||
from ..models import Account as AccountModel, LoginTask, ProxyConfig as ProxyConfigModel
|
||||
from .cookie_check_service import check_douyu_cookie
|
||||
@@ -60,21 +61,28 @@ def get_relogin_limits() -> tuple[int, int]:
|
||||
)
|
||||
|
||||
|
||||
def _snapshot_proxy_config(proxy_config: Optional[ProxyConfigModel]) -> Optional[SimpleNamespace]:
|
||||
def _snapshot_proxy_config(
|
||||
proxy_config: Optional[ProxyConfigModel],
|
||||
) -> Optional[ProxyConfigModel]:
|
||||
"""复制代理配置,避免后台线程访问已关闭会话中的 ORM 对象。"""
|
||||
if proxy_config is None:
|
||||
return None
|
||||
credentials = getattr(proxy_config, "whitelist_credentials", None)
|
||||
return SimpleNamespace(
|
||||
enabled=bool(getattr(proxy_config, "enabled", False)),
|
||||
http=getattr(proxy_config, "http", "") or "",
|
||||
https=getattr(proxy_config, "https", "") or "",
|
||||
api_url=getattr(proxy_config, "api_url", "") or "",
|
||||
whitelist_enabled=bool(getattr(proxy_config, "whitelist_enabled", False)),
|
||||
whitelist_platform=getattr(proxy_config, "whitelist_platform", None),
|
||||
whitelist_credentials=dict(credentials) if isinstance(credentials, dict) else credentials,
|
||||
whitelist_uid=getattr(proxy_config, "whitelist_uid", "") or "",
|
||||
whitelist_ukey=getattr(proxy_config, "whitelist_ukey", "") or "",
|
||||
return cast(
|
||||
ProxyConfigModel,
|
||||
SimpleNamespace(
|
||||
enabled=bool(getattr(proxy_config, "enabled", False)),
|
||||
http=getattr(proxy_config, "http", "") or "",
|
||||
https=getattr(proxy_config, "https", "") or "",
|
||||
api_url=getattr(proxy_config, "api_url", "") or "",
|
||||
whitelist_enabled=bool(getattr(proxy_config, "whitelist_enabled", False)),
|
||||
whitelist_platform=getattr(proxy_config, "whitelist_platform", None),
|
||||
whitelist_credentials=dict(credentials)
|
||||
if isinstance(credentials, dict)
|
||||
else credentials,
|
||||
whitelist_uid=getattr(proxy_config, "whitelist_uid", "") or "",
|
||||
whitelist_ukey=getattr(proxy_config, "whitelist_ukey", "") or "",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -124,12 +132,21 @@ class LoginBatchRunner:
|
||||
wl_platform = "xiequ"
|
||||
wl_credentials = None
|
||||
if proxy_config.whitelist_enabled:
|
||||
wl_platform = getattr(proxy_config, 'whitelist_platform', None) or "xiequ"
|
||||
wl_credentials = getattr(proxy_config, 'whitelist_credentials', None)
|
||||
wl_platform = (
|
||||
getattr(proxy_config, "whitelist_platform", None) or "xiequ"
|
||||
)
|
||||
wl_credentials = getattr(proxy_config, "whitelist_credentials", None)
|
||||
# 向后兼容
|
||||
if not wl_credentials and proxy_config.whitelist_uid and proxy_config.whitelist_ukey:
|
||||
if (
|
||||
not wl_credentials
|
||||
and proxy_config.whitelist_uid
|
||||
and proxy_config.whitelist_ukey
|
||||
):
|
||||
wl_platform = "xiequ"
|
||||
wl_credentials = {"uid": proxy_config.whitelist_uid, "ukey": proxy_config.whitelist_ukey}
|
||||
wl_credentials = {
|
||||
"uid": proxy_config.whitelist_uid,
|
||||
"ukey": proxy_config.whitelist_ukey,
|
||||
}
|
||||
|
||||
self._shared_proxy_fetcher = ProxyFetcher(
|
||||
api_url=proxy_config.api_url,
|
||||
@@ -170,7 +187,11 @@ class LoginBatchRunner:
|
||||
def _push_log(self, level: str, message: str):
|
||||
# 即使没有页面实时日志,也要保留批次进度到 app.log,便于排查卡点。
|
||||
if message:
|
||||
log_level = level if level in {"debug", "info", "warning", "error", "success"} else "debug"
|
||||
log_level = (
|
||||
level
|
||||
if level in {"debug", "info", "warning", "error", "success"}
|
||||
else "debug"
|
||||
)
|
||||
getattr(logger, log_level)(f"[登录批次 {self.batch_id}] {message}")
|
||||
if self.log_queue and self.loop:
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
@@ -181,15 +202,15 @@ class LoginBatchRunner:
|
||||
def _resolve_static_proxy(self) -> tuple[Optional[dict], str]:
|
||||
"""解析静态代理配置。"""
|
||||
if not self.proxy_config or not self.proxy_config.enabled:
|
||||
return None, ''
|
||||
return None, ""
|
||||
|
||||
# 静态代理
|
||||
if self.proxy_config.http or self.proxy_config.https:
|
||||
proxy_url = self.proxy_config.http or self.proxy_config.https
|
||||
return {'http': proxy_url, 'https': proxy_url}, f'使用静态代理: {proxy_url}'
|
||||
return {"http": proxy_url, "https": proxy_url}, f"使用静态代理: {proxy_url}"
|
||||
|
||||
# API代理:由 DouyuLogin 通过 proxy_fetcher 内部管理
|
||||
return None, ''
|
||||
return None, ""
|
||||
|
||||
def _execute_one(self, task_id: int, acc_info: dict, total: int):
|
||||
"""在独立线程中执行单个账号登录,使用独立的 DB 会话。"""
|
||||
@@ -216,8 +237,16 @@ class LoginBatchRunner:
|
||||
self._completed += 1
|
||||
current = self._completed
|
||||
|
||||
action_name = "检测" if self.mode == "check" else "重新登录" if self.mode == "relogin" else "登录"
|
||||
self._push_log("info", f"[{current}/{total}] 开始{action_name}: {acc_info['username']}")
|
||||
action_name = (
|
||||
"检测"
|
||||
if self.mode == "check"
|
||||
else "重新登录"
|
||||
if self.mode == "relogin"
|
||||
else "登录"
|
||||
)
|
||||
self._push_log(
|
||||
"info", f"[{current}/{total}] 开始{action_name}: {acc_info['username']}"
|
||||
)
|
||||
|
||||
try:
|
||||
# 代理配置也可能异常,必须由当前任务的失败处理收敛状态。
|
||||
@@ -226,26 +255,39 @@ class LoginBatchRunner:
|
||||
self._push_log("info", f"[{current}] {proxy_msg}")
|
||||
|
||||
# 静态代理启用但配置为空 → 不可用
|
||||
if self.proxy_config and self.proxy_config.enabled and not (self.proxy_config.http or self.proxy_config.https) and not self._shared_proxy_fetcher and not proxy_dict:
|
||||
if (
|
||||
self.proxy_config
|
||||
and self.proxy_config.enabled
|
||||
and not (self.proxy_config.http or self.proxy_config.https)
|
||||
and not self._shared_proxy_fetcher
|
||||
and not proxy_dict
|
||||
):
|
||||
if self.mode == "relogin":
|
||||
task.status = "relogin_failed"
|
||||
task.message = "重新登录失败: 代理不可用: 未配置代理(旧 Cookie 已保留)"
|
||||
task.message = (
|
||||
"重新登录失败: 代理不可用: 未配置代理(旧 Cookie 已保留)"
|
||||
)
|
||||
else:
|
||||
task.status = "error"
|
||||
task.message = "代理不可用: 未配置代理"
|
||||
task.finished_at = datetime.now(timezone.utc)
|
||||
worker_db.commit()
|
||||
self._push_log("error", f"[{current}] {acc_info['username']} 代理不可用")
|
||||
self._push_log(
|
||||
"error", f"[{current}] {acc_info['username']} 代理不可用"
|
||||
)
|
||||
return
|
||||
|
||||
account = SimpleNamespace(
|
||||
username=acc_info["username"],
|
||||
password=acc_info["password"],
|
||||
email=acc_info["email"],
|
||||
email_password=acc_info["email_password"],
|
||||
email_imap_server=acc_info["email_imap_server"] or "",
|
||||
email_imap_port=acc_info["email_imap_port"] or 993,
|
||||
email_imap_ssl=acc_info["email_imap_ssl"],
|
||||
account = cast(
|
||||
AccountLike,
|
||||
SimpleNamespace(
|
||||
username=acc_info["username"],
|
||||
password=acc_info["password"],
|
||||
email=acc_info["email"],
|
||||
email_password=acc_info["email_password"],
|
||||
email_imap_server=acc_info["email_imap_server"] or "",
|
||||
email_imap_port=acc_info["email_imap_port"] or 993,
|
||||
email_imap_ssl=acc_info["email_imap_ssl"],
|
||||
),
|
||||
)
|
||||
|
||||
loginer = DouyuLogin(
|
||||
@@ -257,22 +299,33 @@ class LoginBatchRunner:
|
||||
stop_event=self._stop,
|
||||
api_strategy=self.api_strategy,
|
||||
)
|
||||
result = loginer.check_account() if self.mode == "check" else loginer.login()
|
||||
result = (
|
||||
loginer.check_account() if self.mode == "check" else loginer.login()
|
||||
)
|
||||
|
||||
if self.mode == "check" and result.success:
|
||||
status = result.code if result.code in CHECK_STATUS_MESSAGES else "account_auth_unknown"
|
||||
status = (
|
||||
result.code
|
||||
if result.code in CHECK_STATUS_MESSAGES
|
||||
else "account_auth_unknown"
|
||||
)
|
||||
task.status = status
|
||||
task.cookie = ""
|
||||
task.message = result.message or CHECK_STATUS_MESSAGES[status]
|
||||
level = CHECK_STATUS_LOG_LEVELS.get(status, "info")
|
||||
self._push_log(level, f"[{current}] {acc_info['username']} 检测结果: {task.message}")
|
||||
self._push_log(
|
||||
level,
|
||||
f"[{current}] {acc_info['username']} 检测结果: {task.message}",
|
||||
)
|
||||
elif result.success:
|
||||
task.status = "success"
|
||||
task.cookie = result.cookie
|
||||
task.message = result.message or "登录成功"
|
||||
if self.mode == "relogin":
|
||||
check_result = check_douyu_cookie(result.cookie)
|
||||
task.ck_check_status = "valid" if check_result["valid"] else "invalid"
|
||||
task.ck_check_status = (
|
||||
"valid" if check_result["valid"] else "invalid"
|
||||
)
|
||||
task.ck_check_result = {
|
||||
"fish_ball": check_result["fish_ball"],
|
||||
"nickname": check_result["nickname"],
|
||||
@@ -282,32 +335,54 @@ class LoginBatchRunner:
|
||||
task.ck_checked_at = check_result["checked_at"]
|
||||
if check_result["valid"]:
|
||||
task.message = "重新登录成功,Cookie 有效"
|
||||
self._push_log("success", f"[{current}] {acc_info['username']} 重新登录成功,Cookie 已替换并验证有效")
|
||||
self._push_log(
|
||||
"success",
|
||||
f"[{current}] {acc_info['username']} 重新登录成功,Cookie 已替换并验证有效",
|
||||
)
|
||||
else:
|
||||
task.message = f"重新登录成功,但 Cookie 有效性检测失败: {check_result['message']}"
|
||||
self._push_log("warning", f"[{current}] {acc_info['username']} 重新登录成功,但 Cookie 有效性检测失败: {check_result['message']}")
|
||||
self._push_log(
|
||||
"warning",
|
||||
f"[{current}] {acc_info['username']} 重新登录成功,但 Cookie 有效性检测失败: {check_result['message']}",
|
||||
)
|
||||
else:
|
||||
self._push_log("success", f"[{current}] {acc_info['username']} {task.message}")
|
||||
self._push_log(
|
||||
"success",
|
||||
f"[{current}] {acc_info['username']} {task.message}",
|
||||
)
|
||||
else:
|
||||
if self.mode == "relogin":
|
||||
# 重新登录失败时保留旧 Cookie 与成功状态,仅记录失败原因,行不消失
|
||||
task.status = "relogin_failed"
|
||||
task.message = f"重新登录失败: {result.message}(旧 Cookie 已保留)"
|
||||
self._push_log("error", f"[{current}] {acc_info['username']} 重新登录失败: {result.message}")
|
||||
task.message = (
|
||||
f"重新登录失败: {result.message}(旧 Cookie 已保留)"
|
||||
)
|
||||
self._push_log(
|
||||
"error",
|
||||
f"[{current}] {acc_info['username']} 重新登录失败: {result.message}",
|
||||
)
|
||||
else:
|
||||
task.status = "failed"
|
||||
task.message = result.message
|
||||
self._push_log("error", f"[{current}] {acc_info['username']} {action_name}失败: {result.message}")
|
||||
self._push_log(
|
||||
"error",
|
||||
f"[{current}] {acc_info['username']} {action_name}失败: {result.message}",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
if self.mode == "relogin":
|
||||
task.status = "relogin_failed"
|
||||
task.message = f"重新登录异常: {e}(旧 Cookie 已保留)"
|
||||
self._push_log("error", f"[{current}] {acc_info['username']} 重新登录异常: {e}")
|
||||
self._push_log(
|
||||
"error", f"[{current}] {acc_info['username']} 重新登录异常: {e}"
|
||||
)
|
||||
else:
|
||||
task.status = "error"
|
||||
task.message = str(e)
|
||||
self._push_log("error", f"[{current}] {acc_info['username']} {action_name}异常: {e}")
|
||||
self._push_log(
|
||||
"error",
|
||||
f"[{current}] {acc_info['username']} {action_name}异常: {e}",
|
||||
)
|
||||
|
||||
task.finished_at = datetime.now(timezone.utc)
|
||||
worker_db.commit()
|
||||
@@ -319,8 +394,17 @@ class LoginBatchRunner:
|
||||
"""在线程中执行批量登录。"""
|
||||
batch_id = self.batch_id
|
||||
concurrency = self.concurrency
|
||||
action_name = "账号检测" if self.mode == "check" else "重新登录" if self.mode == "relogin" else "登录"
|
||||
self._push_log("info", f"批量{action_name}任务 {batch_id} 开始,共 {len(self.account_ids or self.relogin_task_ids)} 个账号,并发数: {concurrency}")
|
||||
action_name = (
|
||||
"账号检测"
|
||||
if self.mode == "check"
|
||||
else "重新登录"
|
||||
if self.mode == "relogin"
|
||||
else "登录"
|
||||
)
|
||||
self._push_log(
|
||||
"info",
|
||||
f"批量{action_name}任务 {batch_id} 开始,共 {len(self.account_ids or self.relogin_task_ids)} 个账号,并发数: {concurrency}",
|
||||
)
|
||||
|
||||
# 批次开始前同步一次出口 IP 到白名单,后续 fetch_new_proxy 不再主动同步
|
||||
if self._shared_proxy_fetcher:
|
||||
@@ -339,18 +423,22 @@ class LoginBatchRunner:
|
||||
task.message = ""
|
||||
task.finished_at = None
|
||||
self.db.flush()
|
||||
task_infos.append({
|
||||
"task_id": task.id,
|
||||
"acc_info": {
|
||||
"username": acc.username,
|
||||
"password": acc.password,
|
||||
"email": acc.email,
|
||||
"email_password": acc.email_password,
|
||||
"email_imap_server": acc.email_imap_server or "",
|
||||
"email_imap_port": acc.email_imap_port or 993,
|
||||
"email_imap_ssl": acc.email_imap_ssl if acc.email_imap_ssl is not None else True,
|
||||
},
|
||||
})
|
||||
task_infos.append(
|
||||
{
|
||||
"task_id": task.id,
|
||||
"acc_info": {
|
||||
"username": acc.username,
|
||||
"password": acc.password,
|
||||
"email": acc.email,
|
||||
"email_password": acc.email_password,
|
||||
"email_imap_server": acc.email_imap_server or "",
|
||||
"email_imap_port": acc.email_imap_port or 993,
|
||||
"email_imap_ssl": acc.email_imap_ssl
|
||||
if acc.email_imap_ssl is not None
|
||||
else True,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
# 创建或复用任务记录(顺序执行,线程安全)
|
||||
@@ -358,10 +446,16 @@ class LoginBatchRunner:
|
||||
if self.relogin_task_ids:
|
||||
# 重新登录模式:复用指定 Cookie 记录,登录成功后原地替换 Cookie
|
||||
for task_id in self.relogin_task_ids:
|
||||
task = self.db.query(LoginTask).filter(LoginTask.id == task_id).first()
|
||||
task = (
|
||||
self.db.query(LoginTask).filter(LoginTask.id == task_id).first()
|
||||
)
|
||||
if not task:
|
||||
continue
|
||||
acc = self.db.query(AccountModel).filter(AccountModel.id == task.account_id).first()
|
||||
acc = (
|
||||
self.db.query(AccountModel)
|
||||
.filter(AccountModel.id == task.account_id)
|
||||
.first()
|
||||
)
|
||||
if not acc:
|
||||
self._push_log("warning", f"跳过无账号的任务 #{task_id}")
|
||||
continue
|
||||
@@ -393,7 +487,9 @@ class LoginBatchRunner:
|
||||
# 一个斗鱼账号只保留一条成功 CK:再次普通登录时更新最新成功记录。
|
||||
latest_success_task = (
|
||||
self.db.query(LoginTask)
|
||||
.filter(LoginTask.account_id == aid, LoginTask.status == "success")
|
||||
.filter(
|
||||
LoginTask.account_id == aid, LoginTask.status == "success"
|
||||
)
|
||||
.order_by(LoginTask.finished_at.desc(), LoginTask.id.desc())
|
||||
.first()
|
||||
)
|
||||
@@ -414,7 +510,10 @@ class LoginBatchRunner:
|
||||
# 复用该账号最近一条失败任务记录,避免重复产生多条失败历史。
|
||||
existing_task = (
|
||||
self.db.query(LoginTask)
|
||||
.filter(LoginTask.account_id == aid, LoginTask.status.in_(["failed", "error"]))
|
||||
.filter(
|
||||
LoginTask.account_id == aid,
|
||||
LoginTask.status.in_(["failed", "error"]),
|
||||
)
|
||||
.order_by(LoginTask.id.desc())
|
||||
.first()
|
||||
)
|
||||
@@ -479,9 +578,14 @@ class BatchRegistry:
|
||||
def __init__(self):
|
||||
self._batches: dict[str, dict] = {}
|
||||
|
||||
def register(self, batch_id: str, log_queue: Optional[asyncio.Queue],
|
||||
loop: Optional[asyncio.AbstractEventLoop], runner: LoginBatchRunner,
|
||||
owner_id: Optional[int] = None):
|
||||
def register(
|
||||
self,
|
||||
batch_id: str,
|
||||
log_queue: Optional[asyncio.Queue],
|
||||
loop: Optional[asyncio.AbstractEventLoop],
|
||||
runner: LoginBatchRunner,
|
||||
owner_id: Optional[int] = None,
|
||||
):
|
||||
self._batches[batch_id] = {
|
||||
"log_queue": log_queue,
|
||||
"loop": loop,
|
||||
|
||||
Reference in New Issue
Block a user