style: 统一 Ruff 代码格式
This commit is contained in:
@@ -8,27 +8,31 @@ from sqlalchemy.orm import Session
|
||||
from ..models import Account, LoginTask
|
||||
|
||||
|
||||
EMAIL_PATTERN = re.compile(r'^[^\s@|]+@[^\s@|]+\.[^\s@|]+$')
|
||||
EMAIL_PATTERN = re.compile(r"^[^\s@|]+@[^\s@|]+\.[^\s@|]+$")
|
||||
|
||||
|
||||
def split_account_line(line: str) -> list[str]:
|
||||
"""拆分一行账号文本,支持 |、tab、逗号、空格分隔。"""
|
||||
if '|' in line:
|
||||
return line.split('|')
|
||||
if '\t' in line:
|
||||
return line.split('\t')
|
||||
if ',' in line:
|
||||
if "|" in line:
|
||||
return line.split("|")
|
||||
if "\t" in line:
|
||||
return line.split("\t")
|
||||
if "," in line:
|
||||
return next(csv.reader([line]))
|
||||
return line.split()
|
||||
|
||||
|
||||
def cookie_account_ids_query(db: Session):
|
||||
"""返回有成功登录记录(cookie非空)的账号ID子查询。"""
|
||||
return db.query(LoginTask.account_id).filter(
|
||||
LoginTask.status == 'success',
|
||||
LoginTask.cookie != '',
|
||||
LoginTask.cookie.isnot(None),
|
||||
).distinct()
|
||||
return (
|
||||
db.query(LoginTask.account_id)
|
||||
.filter(
|
||||
LoginTask.status == "success",
|
||||
LoginTask.cookie != "",
|
||||
LoginTask.cookie.isnot(None),
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
|
||||
def parse_and_build_accounts(
|
||||
@@ -61,9 +65,9 @@ def parse_and_build_accounts(
|
||||
skipped = 0
|
||||
duplicated = 0
|
||||
seen_in_batch: set[str] = set()
|
||||
for line in text.strip().split('\n'):
|
||||
for line in text.strip().split("\n"):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
parts = split_account_line(line)
|
||||
if len(parts) < 4:
|
||||
@@ -88,14 +92,16 @@ def parse_and_build_accounts(
|
||||
seen_in_batch.add(username_key)
|
||||
|
||||
email_cfg = get_email_config_for_account(email)
|
||||
accounts.append(Account(
|
||||
username=username,
|
||||
password=password,
|
||||
email=email,
|
||||
email_password=email_password,
|
||||
email_imap_server=email_cfg['server'],
|
||||
email_imap_port=email_cfg.get('port', 993),
|
||||
email_imap_ssl=email_cfg.get('ssl', True),
|
||||
tag=tag,
|
||||
))
|
||||
accounts.append(
|
||||
Account(
|
||||
username=username,
|
||||
password=password,
|
||||
email=email,
|
||||
email_password=email_password,
|
||||
email_imap_server=email_cfg["server"],
|
||||
email_imap_port=email_cfg.get("port", 993),
|
||||
email_imap_ssl=email_cfg.get("ssl", True),
|
||||
tag=tag,
|
||||
)
|
||||
)
|
||||
return accounts, skipped, duplicated
|
||||
|
||||
@@ -12,8 +12,17 @@ from ..models import AuditLog, User
|
||||
|
||||
|
||||
_SENSITIVE_KEY_PARTS = (
|
||||
"cookie", "token", "password", "passwd", "secret", "signature", "sign",
|
||||
"qr_data", "authorization", "credential", "private_key",
|
||||
"cookie",
|
||||
"token",
|
||||
"password",
|
||||
"passwd",
|
||||
"secret",
|
||||
"signature",
|
||||
"sign",
|
||||
"qr_data",
|
||||
"authorization",
|
||||
"credential",
|
||||
"private_key",
|
||||
)
|
||||
|
||||
|
||||
@@ -42,7 +51,9 @@ def record_audit(
|
||||
) -> AuditLog:
|
||||
"""加入一条审计记录;调用方负责与业务变更一起提交事务。"""
|
||||
if isinstance(detail, Mapping):
|
||||
detail_text = json.dumps(_safe_value(detail), ensure_ascii=False, separators=(",", ":"))
|
||||
detail_text = json.dumps(
|
||||
_safe_value(detail), ensure_ascii=False, separators=(",", ":")
|
||||
)
|
||||
elif detail is None:
|
||||
detail_text = ""
|
||||
else:
|
||||
|
||||
@@ -200,7 +200,9 @@ def snapshot_from_batch(batch: HuyaRegisterBatch) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def load_batch_snapshot(batch_id: str, *, recover_interrupted: bool = True) -> dict | None:
|
||||
def load_batch_snapshot(
|
||||
batch_id: str, *, recover_interrupted: bool = True
|
||||
) -> dict | None:
|
||||
"""从数据库加载批次详情;若服务中断则标记为 interrupted。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
@@ -246,7 +248,9 @@ def load_batch_snapshot(batch_id: str, *, recover_interrupted: bool = True) -> d
|
||||
db.close()
|
||||
|
||||
|
||||
def list_batch_summaries(limit: int = 50, live_batch_ids: set[str] | None = None) -> list[dict]:
|
||||
def list_batch_summaries(
|
||||
limit: int = 50, live_batch_ids: set[str] | None = None
|
||||
) -> list[dict]:
|
||||
"""列出最近的注册批次摘要。live_batch_ids 中的 running 保持运行中。"""
|
||||
live = live_batch_ids or set()
|
||||
db = SessionLocal()
|
||||
@@ -269,42 +273,51 @@ def list_batch_summaries(limit: int = 50, live_batch_ids: set[str] | None = None
|
||||
if status == "running":
|
||||
running_count = max(
|
||||
0,
|
||||
int(row.total or 0) - int(row.success_count or 0) - int(row.failed_count or 0) - int(row.stopped_count or 0),
|
||||
int(row.total or 0)
|
||||
- int(row.success_count or 0)
|
||||
- int(row.failed_count or 0)
|
||||
- int(row.stopped_count or 0),
|
||||
)
|
||||
result.append({
|
||||
"batch_id": row.batch_id,
|
||||
"status": status,
|
||||
"message": message,
|
||||
"tag": row.tag or "",
|
||||
"created_by": row.created_by,
|
||||
"concurrency": int(row.concurrency or 1),
|
||||
"wait_seconds": float(row.wait_seconds or 180),
|
||||
"poll_interval": float(row.poll_interval or 5),
|
||||
"password_prefix": row.password_prefix or "hy",
|
||||
"use_proxy": bool(row.use_proxy),
|
||||
"total": int(row.total or 0),
|
||||
"success_count": int(row.success_count or 0),
|
||||
"failed_count": int(row.failed_count or 0),
|
||||
"stopped_count": int(row.stopped_count or 0),
|
||||
"running_count": running_count,
|
||||
"created_at": row.created_at,
|
||||
"started_at": row.started_at,
|
||||
"finished_at": row.finished_at,
|
||||
"items": [],
|
||||
})
|
||||
result.append(
|
||||
{
|
||||
"batch_id": row.batch_id,
|
||||
"status": status,
|
||||
"message": message,
|
||||
"tag": row.tag or "",
|
||||
"created_by": row.created_by,
|
||||
"concurrency": int(row.concurrency or 1),
|
||||
"wait_seconds": float(row.wait_seconds or 180),
|
||||
"poll_interval": float(row.poll_interval or 5),
|
||||
"password_prefix": row.password_prefix or "hy",
|
||||
"use_proxy": bool(row.use_proxy),
|
||||
"total": int(row.total or 0),
|
||||
"success_count": int(row.success_count or 0),
|
||||
"failed_count": int(row.failed_count or 0),
|
||||
"stopped_count": int(row.stopped_count or 0),
|
||||
"running_count": running_count,
|
||||
"created_at": row.created_at,
|
||||
"started_at": row.started_at,
|
||||
"finished_at": row.finished_at,
|
||||
"items": [],
|
||||
}
|
||||
)
|
||||
return result
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def _refresh_batch_counts(batch_row: HuyaRegisterBatchModel, items: list[HuyaRegisterItemModel]):
|
||||
def _refresh_batch_counts(
|
||||
batch_row: HuyaRegisterBatchModel, items: list[HuyaRegisterItemModel]
|
||||
):
|
||||
batch_row.total = len(items)
|
||||
batch_row.success_count = sum(1 for item in items if item.status == "success")
|
||||
batch_row.failed_count = sum(1 for item in items if item.status == "error")
|
||||
batch_row.stopped_count = sum(1 for item in items if item.status == "stopped")
|
||||
|
||||
|
||||
def format_success_export_line(username: str, uid: str, password: str, phone: str, sms_url: str) -> str:
|
||||
def format_success_export_line(
|
||||
username: str, uid: str, password: str, phone: str, sms_url: str
|
||||
) -> str:
|
||||
"""统一成功导出格式。"""
|
||||
account = (username or uid or "").strip()
|
||||
return f"{account}----{password or ''}----{phone or ''}----{sms_url or ''}"
|
||||
@@ -319,7 +332,9 @@ def export_success_logs_text(
|
||||
"""从成功流水表导出 txt。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
query = db.query(HuyaRegisterSuccessLog).order_by(HuyaRegisterSuccessLog.id.asc())
|
||||
query = db.query(HuyaRegisterSuccessLog).order_by(
|
||||
HuyaRegisterSuccessLog.id.asc()
|
||||
)
|
||||
if batch_id:
|
||||
query = query.filter(HuyaRegisterSuccessLog.batch_id == batch_id)
|
||||
if tag:
|
||||
@@ -350,7 +365,9 @@ def list_success_logs(
|
||||
"""列出成功流水(含密码,供管理端展示/导出)。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
query = db.query(HuyaRegisterSuccessLog).order_by(HuyaRegisterSuccessLog.id.desc())
|
||||
query = db.query(HuyaRegisterSuccessLog).order_by(
|
||||
HuyaRegisterSuccessLog.id.desc()
|
||||
)
|
||||
if batch_id:
|
||||
query = query.filter(HuyaRegisterSuccessLog.batch_id == batch_id)
|
||||
if tag:
|
||||
@@ -406,7 +423,11 @@ class HuyaRegisterRunner:
|
||||
|
||||
def _create_proxy_fetcher(self) -> ProxyFetcher | None:
|
||||
"""按需创建 API 代理获取器。"""
|
||||
if not self.batch.use_proxy or not self.proxy_config or not self.proxy_config.enabled:
|
||||
if (
|
||||
not self.batch.use_proxy
|
||||
or not self.proxy_config
|
||||
or not self.proxy_config.enabled
|
||||
):
|
||||
return None
|
||||
if not self.proxy_config.api_url:
|
||||
return None
|
||||
@@ -414,9 +435,15 @@ class HuyaRegisterRunner:
|
||||
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,
|
||||
@@ -446,7 +473,11 @@ class HuyaRegisterRunner:
|
||||
return
|
||||
db = SessionLocal()
|
||||
try:
|
||||
row = db.query(HuyaRegisterBatchModel).filter(HuyaRegisterBatchModel.id == self.batch.db_id).first()
|
||||
row = (
|
||||
db.query(HuyaRegisterBatchModel)
|
||||
.filter(HuyaRegisterBatchModel.id == self.batch.db_id)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
return
|
||||
row.status = self.batch.status
|
||||
@@ -485,7 +516,11 @@ class HuyaRegisterRunner:
|
||||
return
|
||||
db = SessionLocal()
|
||||
try:
|
||||
row = db.query(HuyaRegisterItemModel).filter(HuyaRegisterItemModel.id == item.db_id).first()
|
||||
row = (
|
||||
db.query(HuyaRegisterItemModel)
|
||||
.filter(HuyaRegisterItemModel.id == item.db_id)
|
||||
.first()
|
||||
)
|
||||
if not row:
|
||||
return
|
||||
row.status = item.status
|
||||
@@ -518,12 +553,16 @@ class HuyaRegisterRunner:
|
||||
setattr(item, key, value)
|
||||
self._persist_item(index)
|
||||
|
||||
def _save_success(self, index: int, result: HuyaAutoRegisterResult) -> tuple[int | None, str, str]:
|
||||
def _save_success(
|
||||
self, index: int, result: HuyaAutoRegisterResult
|
||||
) -> tuple[int | None, str, str]:
|
||||
"""成功时:写账号 + 成功流水(成功一个写一条,立即可导出)。"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# upsert_huya_cookie 内部会 commit 一次
|
||||
account = upsert_huya_cookie(db, result.cookie, tag=self.batch.tag, username_hint="")
|
||||
account = upsert_huya_cookie(
|
||||
db, result.cookie, tag=self.batch.tag, username_hint=""
|
||||
)
|
||||
account.game_phone = result.phone or account.game_phone or ""
|
||||
if result.username:
|
||||
account.username = result.username
|
||||
@@ -537,23 +576,29 @@ class HuyaRegisterRunner:
|
||||
account.updated_at = _now()
|
||||
|
||||
item = self.batch.items[index]
|
||||
db.add(HuyaRegisterSuccessLog(
|
||||
batch_id=self.batch.batch_id,
|
||||
item_id=item.db_id,
|
||||
account_id=account.id,
|
||||
phone=result.phone or item.phone,
|
||||
username=result.username or account.username or "",
|
||||
uid=result.uid or account.uid or account.yyuid or "",
|
||||
password=result.password or "",
|
||||
sms_url=sms_url,
|
||||
tag=self.batch.tag,
|
||||
provider=result.provider or item.provider,
|
||||
created_by=self.batch.created_by,
|
||||
created_at=_now(),
|
||||
))
|
||||
db.add(
|
||||
HuyaRegisterSuccessLog(
|
||||
batch_id=self.batch.batch_id,
|
||||
item_id=item.db_id,
|
||||
account_id=account.id,
|
||||
phone=result.phone or item.phone,
|
||||
username=result.username or account.username or "",
|
||||
uid=result.uid or account.uid or account.yyuid or "",
|
||||
password=result.password or "",
|
||||
sms_url=sms_url,
|
||||
tag=self.batch.tag,
|
||||
provider=result.provider or item.provider,
|
||||
created_by=self.batch.created_by,
|
||||
created_at=_now(),
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(account)
|
||||
return account.id, account.username or "", account.uid or account.yyuid or ""
|
||||
return (
|
||||
account.id,
|
||||
account.username or "",
|
||||
account.uid or account.yyuid or "",
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -578,12 +623,16 @@ class HuyaRegisterRunner:
|
||||
|
||||
def _run_one(self, index: int, item: SmsLine):
|
||||
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
|
||||
|
||||
proxies, proxy_error = self._resolve_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(
|
||||
@@ -714,7 +763,12 @@ class HuyaRegisterRunner:
|
||||
futures = []
|
||||
for index in indices:
|
||||
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
|
||||
item = self.sms_lines[index]
|
||||
futures.append(executor.submit(self._run_one, index, item))
|
||||
@@ -841,7 +895,11 @@ class HuyaRegisterRegistry:
|
||||
# DB 同步为 running,避免返回 pending 导致前端误判
|
||||
db = SessionLocal()
|
||||
try:
|
||||
row = db.query(HuyaRegisterBatchModel).filter(HuyaRegisterBatchModel.id == db_id).first()
|
||||
row = (
|
||||
db.query(HuyaRegisterBatchModel)
|
||||
.filter(HuyaRegisterBatchModel.id == db_id)
|
||||
.first()
|
||||
)
|
||||
if row:
|
||||
row.status = "running"
|
||||
row.message = "批次运行中"
|
||||
@@ -850,7 +908,9 @@ class HuyaRegisterRegistry:
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
runner = HuyaRegisterRunner(batch=batch, sms_lines=sms_lines, proxy_config=proxy_config)
|
||||
runner = HuyaRegisterRunner(
|
||||
batch=batch, sms_lines=sms_lines, proxy_config=proxy_config
|
||||
)
|
||||
with self._lock:
|
||||
self._runners[batch_id] = runner
|
||||
return runner
|
||||
@@ -930,7 +990,9 @@ class HuyaRegisterRegistry:
|
||||
if poll_interval is not None:
|
||||
batch_row.poll_interval = int(max(1.0, float(poll_interval)))
|
||||
if password_prefix is not None:
|
||||
batch_row.password_prefix = (password_prefix or "hy").strip()[:8] or "hy"
|
||||
batch_row.password_prefix = (password_prefix or "hy").strip()[
|
||||
:8
|
||||
] or "hy"
|
||||
if fixed_password is not None:
|
||||
batch_row.fixed_password = (fixed_password or "").strip()
|
||||
if use_proxy is not None:
|
||||
@@ -965,7 +1027,12 @@ class HuyaRegisterRegistry:
|
||||
|
||||
batch = _batch_from_db(batch_row, item_rows)
|
||||
sms_lines = [
|
||||
SmsLine(phone=item.phone, url=item.sms_url, provider=item.provider, raw=f"{item.phone}----{item.sms_url}")
|
||||
SmsLine(
|
||||
phone=item.phone,
|
||||
url=item.sms_url,
|
||||
provider=item.provider,
|
||||
raw=f"{item.phone}----{item.sms_url}",
|
||||
)
|
||||
for item in batch.items
|
||||
]
|
||||
finally:
|
||||
|
||||
@@ -19,15 +19,24 @@ from .huya_runner_recharge import RechargeMixin
|
||||
|
||||
|
||||
class HuyaBatchRunner(
|
||||
HuyaBatchRunnerCore, BindMixin, GoodsMixin, RechargeMixin,
|
||||
HuyaBatchRunnerCore,
|
||||
BindMixin,
|
||||
GoodsMixin,
|
||||
RechargeMixin,
|
||||
):
|
||||
"""批量执行虎牙任务(功能域 Mixin 聚合 + 批次调度)。"""
|
||||
|
||||
def _execute_one(self, task_id: int, account_info: dict, config_info: dict, total: int):
|
||||
def _execute_one(
|
||||
self, task_id: int, account_info: dict, config_info: dict, total: int
|
||||
):
|
||||
worker_db = SessionLocal()
|
||||
try:
|
||||
task = worker_db.query(HuyaTask).filter(HuyaTask.id == task_id).first()
|
||||
account = worker_db.query(HuyaAccount).filter(HuyaAccount.id == account_info["account_id"]).first()
|
||||
account = (
|
||||
worker_db.query(HuyaAccount)
|
||||
.filter(HuyaAccount.id == account_info["account_id"])
|
||||
.first()
|
||||
)
|
||||
if not task or not account:
|
||||
return
|
||||
|
||||
@@ -59,28 +68,48 @@ class HuyaBatchRunner(
|
||||
"create_recharge_order",
|
||||
}:
|
||||
self._mark_task(worker_db, task, "failed", "该虎牙任务执行器暂未实现")
|
||||
self._push_log("warning", f"[{current}] {name} 暂未实现: {self.task_type}")
|
||||
self._push_log(
|
||||
"warning", f"[{current}] {name} 暂未实现: {self.task_type}"
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
if self.task_type == "query_points":
|
||||
self._execute_query_points(worker_db, task, account, account_info, config_info)
|
||||
self._execute_query_points(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "refresh_goods":
|
||||
self._execute_refresh_goods(worker_db, task, account, account_info, config_info)
|
||||
self._execute_refresh_goods(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "refresh_recharge_goods":
|
||||
self._execute_refresh_recharge_goods(worker_db, task, account, account_info, config_info)
|
||||
self._execute_refresh_recharge_goods(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "exchange_goods":
|
||||
self._execute_exchange_goods(worker_db, task, account, account_info, config_info)
|
||||
self._execute_exchange_goods(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "create_recharge_order":
|
||||
self._execute_create_recharge_order(worker_db, task, account, account_info, config_info)
|
||||
self._execute_create_recharge_order(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "get_bind_qr":
|
||||
self._execute_get_bind_qr(worker_db, task, account, account_info, config_info)
|
||||
self._execute_get_bind_qr(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "confirm_bind":
|
||||
self._execute_confirm_bind(worker_db, task, account, account_info, config_info)
|
||||
self._execute_confirm_bind(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "query_game_name":
|
||||
self._execute_query_game_name(worker_db, task, account, account_info, config_info)
|
||||
self._execute_query_game_name(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
elif self.task_type == "query_exchange_records":
|
||||
self._execute_query_exchange_records(worker_db, task, account, account_info, config_info)
|
||||
self._execute_query_exchange_records(
|
||||
worker_db, task, account, account_info, config_info
|
||||
)
|
||||
worker_db.refresh(task)
|
||||
if task.status == "success":
|
||||
self._push_log("success", f"[{current}] {name} {task.message}")
|
||||
@@ -124,17 +153,19 @@ class HuyaBatchRunner(
|
||||
task.status = "pending"
|
||||
task.message = "等待执行"
|
||||
task.finished_at = None
|
||||
task_infos.append({
|
||||
"task_id": task.id,
|
||||
"account_info": {
|
||||
"account_id": account.id,
|
||||
"uid": account.uid or "",
|
||||
"yyuid": account.yyuid or "",
|
||||
"username": account.username or "",
|
||||
"nickname": account.nickname or "",
|
||||
"cookie": normalize_huya_cookie(account.cookie or ""),
|
||||
},
|
||||
})
|
||||
task_infos.append(
|
||||
{
|
||||
"task_id": task.id,
|
||||
"account_info": {
|
||||
"account_id": account.id,
|
||||
"uid": account.uid or "",
|
||||
"yyuid": account.yyuid or "",
|
||||
"username": account.username or "",
|
||||
"nickname": account.nickname or "",
|
||||
"cookie": normalize_huya_cookie(account.cookie or ""),
|
||||
},
|
||||
}
|
||||
)
|
||||
self.db.commit()
|
||||
|
||||
total = len(task_infos)
|
||||
@@ -149,13 +180,15 @@ class HuyaBatchRunner(
|
||||
if self._stop.is_set():
|
||||
self._push_log("warning", "任务已停止,跳过剩余账号")
|
||||
break
|
||||
futures.append(executor.submit(
|
||||
self._execute_one,
|
||||
item["task_id"],
|
||||
item["account_info"],
|
||||
config_info,
|
||||
total,
|
||||
))
|
||||
futures.append(
|
||||
executor.submit(
|
||||
self._execute_one,
|
||||
item["task_id"],
|
||||
item["account_info"],
|
||||
config_info,
|
||||
total,
|
||||
)
|
||||
)
|
||||
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
|
||||
@@ -20,8 +20,10 @@ from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from .huya_runner import HuyaBatchRunner
|
||||
|
||||
|
||||
class HuyaBatchRunnerCore:
|
||||
"""虎牙任务执行器公共基础:批次状态、日志、任务落库。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
db: Session,
|
||||
@@ -156,7 +158,8 @@ class HuyaBatchRegistry:
|
||||
expired = [
|
||||
batch_id
|
||||
for batch_id, batch in self._batches.items()
|
||||
if batch.get("finished") and now - float(batch.get("finished_at") or now) > ttl_seconds
|
||||
if batch.get("finished")
|
||||
and now - float(batch.get("finished_at") or now) > ttl_seconds
|
||||
]
|
||||
for batch_id in expired:
|
||||
self._batches.pop(batch_id, None)
|
||||
@@ -206,4 +209,3 @@ class HuyaBatchRegistry:
|
||||
|
||||
|
||||
huya_batch_registry = HuyaBatchRegistry()
|
||||
|
||||
|
||||
@@ -37,7 +37,10 @@ def apply_huya_config_defaults(config: HuyaConfig) -> bool:
|
||||
"""补齐虎牙配置默认值,返回是否发生变更。"""
|
||||
changed = False
|
||||
for field in HUYA_CONFIG_FIELDS:
|
||||
if field == "bind_act_id" and str(getattr(config, field, "") or "").strip() == "17096":
|
||||
if (
|
||||
field == "bind_act_id"
|
||||
and str(getattr(config, field, "") or "").strip() == "17096"
|
||||
):
|
||||
setattr(config, field, HUYA_CONFIG_DEFAULTS[field])
|
||||
changed = True
|
||||
continue
|
||||
@@ -160,7 +163,9 @@ def split_huya_password_line(line: str) -> HuyaPasswordLine | None:
|
||||
)
|
||||
|
||||
|
||||
def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tuple[int, int]:
|
||||
def import_huya_password_accounts(
|
||||
db: Session, text: str, tag: str = ""
|
||||
) -> tuple[int, int]:
|
||||
"""导入虎牙账号密码,返回 (导入/更新数, 跳过数)。"""
|
||||
created_or_updated = 0
|
||||
skipped = 0
|
||||
@@ -207,13 +212,17 @@ def import_huya_password_accounts(db: Session, text: str, tag: str = "") -> tupl
|
||||
return created_or_updated, skipped
|
||||
|
||||
|
||||
def _upsert_huya_account(db: Session, parsed: dict, tag: str = "", status: str | None = None) -> HuyaAccount:
|
||||
def _upsert_huya_account(
|
||||
db: Session, parsed: dict, tag: str = "", status: str | None = None
|
||||
) -> HuyaAccount:
|
||||
"""按 uid/yyuid 新增或更新虎牙账号。"""
|
||||
account = None
|
||||
if parsed["uid"]:
|
||||
account = db.query(HuyaAccount).filter(HuyaAccount.uid == parsed["uid"]).first()
|
||||
if account is None and parsed["yyuid"]:
|
||||
account = db.query(HuyaAccount).filter(HuyaAccount.yyuid == parsed["yyuid"]).first()
|
||||
account = (
|
||||
db.query(HuyaAccount).filter(HuyaAccount.yyuid == parsed["yyuid"]).first()
|
||||
)
|
||||
|
||||
if account is None:
|
||||
account = HuyaAccount(
|
||||
@@ -273,13 +282,17 @@ def save_huya_login_cookie_to_account(
|
||||
return account
|
||||
|
||||
|
||||
def upsert_huya_cookie(db: Session, cookie: str, tag: str = "", username_hint: str = "") -> HuyaAccount:
|
||||
def upsert_huya_cookie(
|
||||
db: Session, cookie: str, tag: str = "", username_hint: str = ""
|
||||
) -> HuyaAccount:
|
||||
"""保存单条登录得到的虎牙 Cookie。"""
|
||||
line = f"{username_hint}----{cookie}" if username_hint else cookie
|
||||
parsed = parse_huya_cookie_line(line)
|
||||
if not parsed:
|
||||
raise ValueError("登录成功但 Cookie 中没有识别到虎牙 uid")
|
||||
account = _upsert_huya_account(db, parsed, tag=(tag or "").strip(), status="login_success")
|
||||
account = _upsert_huya_account(
|
||||
db, parsed, tag=(tag or "").strip(), status="login_success"
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(account)
|
||||
return account
|
||||
@@ -368,18 +381,24 @@ def create_planned_tasks(
|
||||
batch_id = uuid.uuid4().hex[:12]
|
||||
payload = payload or {}
|
||||
accounts = db.query(HuyaAccount).filter(HuyaAccount.id.in_(account_ids)).all()
|
||||
if task_type in {"refresh_goods", "refresh_recharge_goods", "create_recharge_order"} and accounts:
|
||||
if (
|
||||
task_type
|
||||
in {"refresh_goods", "refresh_recharge_goods", "create_recharge_order"}
|
||||
and accounts
|
||||
):
|
||||
# 全局快照和单笔支付二维码使用一个选中的 CK 即可;兑换商品需要保留多账号批量任务。
|
||||
accounts = accounts[:1]
|
||||
for account in accounts:
|
||||
db.add(HuyaTask(
|
||||
batch_id=batch_id,
|
||||
account_id=account.id,
|
||||
task_type=task_type,
|
||||
status="planned",
|
||||
message="任务已创建,等待执行",
|
||||
result={"payload": payload} if payload else None,
|
||||
created_by=created_by,
|
||||
))
|
||||
db.add(
|
||||
HuyaTask(
|
||||
batch_id=batch_id,
|
||||
account_id=account.id,
|
||||
task_type=task_type,
|
||||
status="planned",
|
||||
message="任务已创建,等待执行",
|
||||
result={"payload": payload} if payload else None,
|
||||
created_by=created_by,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return batch_id, len(accounts)
|
||||
|
||||
@@ -8,7 +8,11 @@ from typing import Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.douyu.proxy import resolve_working_proxy, verify_proxy_url, parse_proxy_response
|
||||
from core.douyu.proxy import (
|
||||
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.base import _get_local_exit_ip
|
||||
from ..models import ProxyConfig as ProxyConfigModel, AuditLog
|
||||
@@ -20,8 +24,8 @@ def _build_whitelist_params(cfg: ProxyConfigModel) -> dict:
|
||||
Returns:
|
||||
{"whitelist_platform": str, "whitelist_credentials": dict|None}
|
||||
"""
|
||||
platform = getattr(cfg, 'whitelist_platform', None) or "xiequ"
|
||||
credentials = getattr(cfg, 'whitelist_credentials', None)
|
||||
platform = getattr(cfg, "whitelist_platform", None) or "xiequ"
|
||||
credentials = getattr(cfg, "whitelist_credentials", None)
|
||||
|
||||
# 向后兼容:旧字段有值但新字段为空时,自动迁移
|
||||
if not credentials and cfg.whitelist_uid and cfg.whitelist_ukey:
|
||||
@@ -57,7 +61,10 @@ class ProxyService:
|
||||
# 应用层自动迁移:旧字段有值但新字段为空时,填充新字段
|
||||
if cfg.whitelist_uid and cfg.whitelist_ukey and not cfg.whitelist_credentials:
|
||||
cfg.whitelist_platform = "xiequ"
|
||||
cfg.whitelist_credentials = {"uid": cfg.whitelist_uid, "ukey": cfg.whitelist_ukey}
|
||||
cfg.whitelist_credentials = {
|
||||
"uid": cfg.whitelist_uid,
|
||||
"ukey": cfg.whitelist_ukey,
|
||||
}
|
||||
db.commit()
|
||||
|
||||
return cfg
|
||||
@@ -65,7 +72,10 @@ class ProxyService:
|
||||
@staticmethod
|
||||
def update_config(
|
||||
db: Session,
|
||||
enabled, api_url, http, https,
|
||||
enabled,
|
||||
api_url,
|
||||
http,
|
||||
https,
|
||||
whitelist_enabled,
|
||||
whitelist_platform="xiequ",
|
||||
whitelist_credentials=None,
|
||||
@@ -96,12 +106,14 @@ class ProxyService:
|
||||
db.refresh(cfg)
|
||||
|
||||
if current_user:
|
||||
db.add(AuditLog(
|
||||
user_id=current_user.id,
|
||||
username=current_user.username,
|
||||
action="proxy:update",
|
||||
target="proxy_config",
|
||||
))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current_user.id,
|
||||
username=current_user.username,
|
||||
action="proxy:update",
|
||||
target="proxy_config",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return cfg
|
||||
|
||||
@@ -258,18 +270,21 @@ class ProxyService:
|
||||
|
||||
# 3. 检查并同步白名单
|
||||
records = adapter.get_whitelist()
|
||||
in_list = any(r.get('ip') == local_ip for r in records)
|
||||
in_list = any(r.get("ip") == local_ip for r in records)
|
||||
|
||||
if records:
|
||||
push("info", f"白名单共 {len(records)} 条记录")
|
||||
|
||||
if in_list:
|
||||
record = next((r for r in records if r.get('ip') == local_ip), {})
|
||||
memo = record.get('memo', '')
|
||||
record = next((r for r in records if r.get("ip") == local_ip), {})
|
||||
memo = record.get("memo", "")
|
||||
if memo == adapter.memo:
|
||||
push("success", f"本机IP {local_ip} 已在白名单中 (备注正确)")
|
||||
else:
|
||||
push("warning", f'本机IP {local_ip} 备注不匹配 (当前: "{memo}"),更新中...')
|
||||
push(
|
||||
"warning",
|
||||
f'本机IP {local_ip} 备注不匹配 (当前: "{memo}"),更新中...',
|
||||
)
|
||||
sync_ok, sync_msg = adapter.sync_ip(local_ip)
|
||||
push("success" if sync_ok else "error", f"白名单更新: {sync_msg}")
|
||||
else:
|
||||
|
||||
@@ -32,9 +32,22 @@ def _as_utc(value: datetime | None) -> datetime | None:
|
||||
|
||||
|
||||
def cleanup_orphan_yyb_tasks(db: Session, message: str) -> int:
|
||||
rows = db.query(YybRechargeTask).filter(
|
||||
YybRechargeTask.status.in_(["created", "waiting_login", "ready", "running", "ordering", "waiting_payment"])
|
||||
).all()
|
||||
rows = (
|
||||
db.query(YybRechargeTask)
|
||||
.filter(
|
||||
YybRechargeTask.status.in_(
|
||||
[
|
||||
"created",
|
||||
"waiting_login",
|
||||
"ready",
|
||||
"running",
|
||||
"ordering",
|
||||
"waiting_payment",
|
||||
]
|
||||
)
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for task in rows:
|
||||
if task.status == "ordering":
|
||||
task.status = "waiting_payment"
|
||||
@@ -50,7 +63,9 @@ def cleanup_orphan_yyb_tasks(db: Session, message: str) -> int:
|
||||
return len(rows)
|
||||
|
||||
|
||||
def sync_task(db: Session, task: YybRechargeTask, worker: YybWorkerClient) -> YybRechargeTask:
|
||||
def sync_task(
|
||||
db: Session, task: YybRechargeTask, worker: YybWorkerClient
|
||||
) -> YybRechargeTask:
|
||||
data = worker.get_job(task.worker_job_id)
|
||||
task.status = str(data.get("status", task.status))
|
||||
task.phase = str(data.get("phase", task.phase))
|
||||
@@ -59,10 +74,16 @@ def sync_task(db: Session, task: YybRechargeTask, worker: YybWorkerClient) -> Yy
|
||||
task.provider = str(data["provider"])
|
||||
if data.get("qr_data"):
|
||||
task.login_qr_data = str(data["qr_data"])
|
||||
task.result = {**(task.result or {}), "login_qr_mime_type": data.get("qr_mime_type", "image/jpeg")}
|
||||
task.result = {
|
||||
**(task.result or {}),
|
||||
"login_qr_mime_type": data.get("qr_mime_type", "image/jpeg"),
|
||||
}
|
||||
if data.get("payment_qr_data"):
|
||||
task.payment_qr_data = str(data["payment_qr_data"])
|
||||
task.result = {**(task.result or {}), "payment_qr_mime_type": data.get("payment_qr_mime_type", "image/png")}
|
||||
task.result = {
|
||||
**(task.result or {}),
|
||||
"payment_qr_mime_type": data.get("payment_qr_mime_type", "image/png"),
|
||||
}
|
||||
task.result = {
|
||||
**(task.result or {}),
|
||||
"logs": data.get("logs", []),
|
||||
@@ -76,32 +97,53 @@ def sync_task(db: Session, task: YybRechargeTask, worker: YybWorkerClient) -> Yy
|
||||
task.payment_last_checked_at = last_checked_at
|
||||
if task.status in {"success", "failed"} and task.finished_at is None:
|
||||
task.finished_at = _utcnow()
|
||||
if task.status not in {"success", "failed", "stopped"} and task.finished_at is not None:
|
||||
if (
|
||||
task.status not in {"success", "failed", "stopped"}
|
||||
and task.finished_at is not None
|
||||
):
|
||||
task.finished_at = None
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
return task
|
||||
|
||||
|
||||
def public_task(task: YybRechargeTask, include_qr: bool = True,
|
||||
include_payment_qr: bool | None = None,
|
||||
creator_username: str = "") -> dict[str, Any]:
|
||||
def public_task(
|
||||
task: YybRechargeTask,
|
||||
include_qr: bool = True,
|
||||
include_payment_qr: bool | None = None,
|
||||
creator_username: str = "",
|
||||
) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {
|
||||
"id": task.id, "task_id": task.task_id,
|
||||
"provider": task.provider, "platform": task.platform, "points": task.points,
|
||||
"id": task.id,
|
||||
"task_id": task.task_id,
|
||||
"provider": task.provider,
|
||||
"platform": task.platform,
|
||||
"points": task.points,
|
||||
"price_fen": task.price_fen,
|
||||
"product_id": task.product_id, "zone_id": task.zone_id, "zone_name": task.zone_name,
|
||||
"role_id": task.role_id, "role_name": task.role_name, "status": task.status,
|
||||
"phase": task.phase, "message": task.message, "result": task.result,
|
||||
"created_by": task.created_by, "created_by_username": creator_username,
|
||||
"created_at": _as_utc(task.created_at), "finished_at": _as_utc(task.finished_at),
|
||||
"product_id": task.product_id,
|
||||
"zone_id": task.zone_id,
|
||||
"zone_name": task.zone_name,
|
||||
"role_id": task.role_id,
|
||||
"role_name": task.role_name,
|
||||
"status": task.status,
|
||||
"phase": task.phase,
|
||||
"message": task.message,
|
||||
"result": task.result,
|
||||
"created_by": task.created_by,
|
||||
"created_by_username": creator_username,
|
||||
"created_at": _as_utc(task.created_at),
|
||||
"finished_at": _as_utc(task.finished_at),
|
||||
"payment_started_at": _as_utc(task.payment_started_at),
|
||||
"payment_qr_created_at": _as_utc(task.payment_qr_created_at),
|
||||
"payment_last_checked_at": _as_utc(task.payment_last_checked_at),
|
||||
}
|
||||
if task.result:
|
||||
result["login_qr_mime_type"] = task.result.get("login_qr_mime_type", "image/jpeg")
|
||||
result["payment_qr_mime_type"] = task.result.get("payment_qr_mime_type", "image/png")
|
||||
result["login_qr_mime_type"] = task.result.get(
|
||||
"login_qr_mime_type", "image/jpeg"
|
||||
)
|
||||
result["payment_qr_mime_type"] = task.result.get(
|
||||
"payment_qr_mime_type", "image/png"
|
||||
)
|
||||
if include_payment_qr is None:
|
||||
include_payment_qr = include_qr
|
||||
if include_qr:
|
||||
|
||||
@@ -18,13 +18,20 @@ class YybWorkerClient:
|
||||
self.key = os.getenv("YYB_WORKER_KEY", "")
|
||||
self.timeout = float(os.getenv("YYB_WORKER_TIMEOUT", "30"))
|
||||
|
||||
def _request(self, method: str, path: str, payload: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
def _request(
|
||||
self, method: str, path: str, payload: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
headers = {"Accept": "application/json"}
|
||||
if self.key:
|
||||
headers["Authorization"] = f"Bearer {self.key}"
|
||||
try:
|
||||
response = requests.request(method, self.base_url + path, json=payload,
|
||||
headers=headers, timeout=self.timeout)
|
||||
response = requests.request(
|
||||
method,
|
||||
self.base_url + path,
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
data = response.json()
|
||||
except (requests.RequestException, ValueError) as exc:
|
||||
raise YybWorkerError(f"应用宝 Worker 不可用: {exc}") from exc
|
||||
@@ -35,19 +42,34 @@ class YybWorkerClient:
|
||||
def create_job(self) -> dict[str, Any]:
|
||||
return self._request("POST", "/v1/jobs")
|
||||
|
||||
def login(self, worker_job_id: str, provider: str, timeout: int = 600) -> dict[str, Any]:
|
||||
return self._request("POST", f"/v1/jobs/{worker_job_id}/login",
|
||||
{"provider": provider, "timeout": timeout})
|
||||
def login(
|
||||
self, worker_job_id: str, provider: str, timeout: int = 600
|
||||
) -> dict[str, Any]:
|
||||
return self._request(
|
||||
"POST",
|
||||
f"/v1/jobs/{worker_job_id}/login",
|
||||
{"provider": provider, "timeout": timeout},
|
||||
)
|
||||
|
||||
def get_job(self, worker_job_id: str) -> dict[str, Any]:
|
||||
return self._request("GET", f"/v1/jobs/{worker_job_id}")
|
||||
|
||||
def selection_options(self, worker_job_id: str, platform: str,
|
||||
points: int | None = None, zone_id: str | None = None) -> dict[str, Any]:
|
||||
return self._request("POST", f"/v1/jobs/{worker_job_id}/selection-options",
|
||||
{"platform": platform, "points": points, "zone_id": zone_id})
|
||||
def selection_options(
|
||||
self,
|
||||
worker_job_id: str,
|
||||
platform: str,
|
||||
points: int | None = None,
|
||||
zone_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return self._request(
|
||||
"POST",
|
||||
f"/v1/jobs/{worker_job_id}/selection-options",
|
||||
{"platform": platform, "points": points, "zone_id": zone_id},
|
||||
)
|
||||
|
||||
def selection(self, worker_job_id: str, selection: dict[str, Any]) -> dict[str, Any]:
|
||||
def selection(
|
||||
self, worker_job_id: str, selection: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
return self._request("POST", f"/v1/jobs/{worker_job_id}/selection", selection)
|
||||
|
||||
def payment(self, worker_job_id: str) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user