执行 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
+1 -2
View File
@@ -10,11 +10,10 @@ from dataclasses import dataclass
from functools import lru_cache
from Crypto.Cipher import AES
from loguru import logger
from sqlalchemy import text
from sqlalchemy.engine import Engine
from sqlalchemy.types import Text, TypeDecorator
from loguru import logger
_PREFIX = "enc:v1:"
_FALLBACK_SECRET = "douyu-login-py-dev-storage-key-change-me"
+2 -1
View File
@@ -5,7 +5,7 @@ from pathlib import Path
from urllib.parse import quote_plus
from sqlalchemy import create_engine, event
from sqlalchemy.orm import sessionmaker, declarative_base
from sqlalchemy.orm import declarative_base, sessionmaker
PROJECT_ROOT = Path(__file__).resolve().parents[2]
@@ -147,6 +147,7 @@ def _seed():
def _encrypt_existing_sensitive_data():
"""启动时把历史明文敏感数据迁移为密文。"""
from loguru import logger
from .crypto_storage import encrypt_existing_sensitive_data
changed = encrypt_existing_sensitive_data(engine)
+9 -9
View File
@@ -1,15 +1,15 @@
"""FastAPI 依赖注入"""
from typing import Optional
from fastapi import Depends, HTTPException, Request, status, WebSocket
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from jose import JWTError
from .database import get_db, SessionLocal
from .security import decode_access_token
from fastapi import Depends, HTTPException, Request, WebSocket, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError
from sqlalchemy.orm import Session
from .database import SessionLocal, get_db
from .models import User
from .permissions import get_user_permissions
from .security import decode_access_token
# auto_error=False: 允许 token 为空(后续从 cookie 读取)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=False)
@@ -17,7 +17,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login", auto_error=Fals
def get_current_user(
request: Request,
token: Optional[str] = Depends(oauth2_scheme),
token: str | None = Depends(oauth2_scheme),
db: Session = Depends(get_db),
) -> User:
credentials_exc = HTTPException(
@@ -61,7 +61,7 @@ def require_permission(permission: str):
return checker
def authenticate_websocket(websocket: WebSocket) -> Optional[User]:
def authenticate_websocket(websocket: WebSocket) -> User | None:
"""WebSocket 认证:从 cookie 或 query param token 中验证用户身份。
Returns:
+15 -13
View File
@@ -1,34 +1,36 @@
"""FastAPI 入口"""
import os
from pathlib import Path
import uvicorn
from contextlib import asynccontextmanager
from pathlib import Path
import uvicorn
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
from utils import setup_logger
from .database import init_db
from .routers import (
auth,
users,
accounts,
account_check,
accounts,
audit,
auth,
cookies,
dashboard,
douyu,
huya,
login,
proxy,
cookies,
huya,
douyu,
users,
yyb,
audit,
)
from .schemas import AppInfo
from .version import get_app_version
from utils import setup_logger
@asynccontextmanager
@@ -45,9 +47,9 @@ async def lifespan(app: FastAPI):
from loguru import logger
from .database import SessionLocal
from .services.login_service import cleanup_orphan_relogin_tasks
from .services.huya_service import cleanup_orphan_huya_tasks
from .services.douyu_service import cleanup_orphan_douyu_tasks
from .services.huya_service import cleanup_orphan_huya_tasks
from .services.login_service import cleanup_orphan_relogin_tasks
db = SessionLocal()
try:
+4 -4
View File
@@ -1,9 +1,9 @@
"""Alembic 迁移环境。"""
from logging.config import fileConfig
from pathlib import Path
import os
import sys
from logging.config import fileConfig
from pathlib import Path
from alembic import context
from sqlalchemy import engine_from_config, pool
@@ -12,8 +12,8 @@ ROOT_DIR = Path(__file__).resolve().parents[3]
if str(ROOT_DIR) not in sys.path:
sys.path.insert(0, str(ROOT_DIR))
from web.backend.database import Base, DATABASE_URL # noqa: E402
from web.backend import models # noqa: F401,E402
from web.backend import models # noqa: F401
from web.backend.database import DATABASE_URL, Base
config = context.config
config.set_main_option("sqlalchemy.url", DATABASE_URL)
@@ -5,15 +5,15 @@ Revises:
Create Date: 2026-06-23
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260623_0001"
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = None
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _has_table(bind, table_name: str) -> bool:
@@ -5,16 +5,15 @@ Revises: 20260623_0001
Create Date: 2026-06-24
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260624_0002"
down_revision: Union[str, None] = "20260623_0001"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260623_0001"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260624_0002
Create Date: 2026-06-24
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260624_0003"
down_revision: Union[str, None] = "20260624_0002"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260624_0002"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260624_0003
Create Date: 2026-07-04
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260704_0004"
down_revision: Union[str, None] = "20260624_0003"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260624_0003"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _has_table(bind, table_name: str) -> bool:
@@ -5,16 +5,15 @@ Revises: 20260704_0004
Create Date: 2026-07-04
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260704_0005"
down_revision: Union[str, None] = "20260704_0004"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260704_0004"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _has_table(bind, table_name: str) -> bool:
@@ -5,16 +5,15 @@ Revises: 20260704_0005
Create Date: 2026-07-05
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260705_0006"
down_revision: Union[str, None] = "20260704_0005"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260704_0005"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _has_table(bind, table_name: str) -> bool:
@@ -5,16 +5,15 @@ Revises: 20260705_0006
Create Date: 2026-07-12
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260712_0007"
down_revision: Union[str, None] = "20260705_0006"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260705_0006"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _has_table(bind, table_name: str) -> bool:
@@ -5,16 +5,15 @@ Revises: 20260712_0007
Create Date: 2026-07-24
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260724_0008"
down_revision: Union[str, None] = "20260712_0007"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260712_0007"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _has_table(bind, table_name: str) -> bool:
@@ -5,16 +5,15 @@ Revises: 20260724_0008
Create Date: 2026-07-25
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260725_0009"
down_revision: Union[str, None] = "20260724_0008"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260724_0008"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _has_table(bind, table_name: str) -> bool:
@@ -5,16 +5,15 @@ Revises: 20260725_0009
Create Date: 2026-07-28
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260728_0010"
down_revision: Union[str, None] = "20260725_0009"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260725_0009"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _columns(bind, table_name: str) -> set[str]:
@@ -5,16 +5,15 @@ Revises: 20260728_0010
Create Date: 2026-07-28
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260728_0011"
down_revision: Union[str, None] = "20260728_0010"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260728_0010"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _columns(bind, table_name: str) -> set[str]:
@@ -5,16 +5,15 @@ Revises: 20260728_0011
Create Date: 2026-07-28
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260728_0012"
down_revision: Union[str, None] = "20260728_0011"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260728_0011"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260728_0012
Create Date: 2026-07-28
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260728_0013"
down_revision: Union[str, None] = "20260728_0012"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260728_0012"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _columns(bind, table_name: str) -> set[str]:
@@ -5,16 +5,15 @@ Revises: 20260728_0013
Create Date: 2026-07-28
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260728_0014"
down_revision: Union[str, None] = "20260728_0013"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260728_0013"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260728_0014
Create Date: 2026-08-05
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260805_0015"
down_revision: Union[str, None] = "20260728_0014"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260728_0014"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
INDEXES = [
@@ -5,16 +5,15 @@ Revises: 20260805_0015
Create Date: 2026-08-06
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260806_0016"
down_revision: Union[str, None] = "20260805_0015"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260805_0015"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _columns(bind, table_name: str) -> set[str]:
@@ -5,16 +5,15 @@ Revises: 20260806_0016
Create Date: 2026-08-07
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260807_0017"
down_revision: Union[str, None] = "20260806_0016"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260806_0016"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260807_0017
Create Date: 2026-08-07
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260807_0018"
down_revision: Union[str, None] = "20260807_0017"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260807_0017"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260807_0018
Create Date: 2026-08-08
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260808_0019"
down_revision: Union[str, None] = "20260807_0018"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260807_0018"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -1,13 +1,14 @@
"""增加应用宝和平精英充值任务"""
from typing import Sequence, Union
from alembic import op
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20260812_0020"
down_revision: Union[str, None] = "20260808_0019"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260808_0019"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -1,15 +1,13 @@
"""扩展应用宝二维码密文字段容量"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
revision: str = "20260812_0021"
down_revision: Union[str, None] = "20260812_0020"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260812_0020"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _is_mysql() -> bool:
@@ -1,15 +1,14 @@
"""应用宝支付元数据字段:金额与支付时间线"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260812_0022"
down_revision: Union[str, None] = "20260812_0021"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260812_0021"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -1,15 +1,14 @@
"""支持用户软删除并保留历史任务归属"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260813_0023"
down_revision: Union[str, None] = "20260812_0022"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260812_0022"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _columns(bind, table_name: str) -> set[str]:
@@ -5,16 +5,15 @@ Revises: 20260813_0023
Create Date: 2026-08-13
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260813_0024"
down_revision: Union[str, None] = "20260813_0023"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260813_0023"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _columns(bind, table_name: str) -> set[str]:
@@ -5,16 +5,15 @@ Revises: 20260813_0024
Create Date: 2026-08-13
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260813_0025"
down_revision: Union[str, None] = "20260813_0024"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260813_0024"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260813_0025
Create Date: 2026-08-13
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260813_0026"
down_revision: Union[str, None] = "20260813_0025"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260813_0025"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def _columns(bind) -> set[str]:
@@ -5,16 +5,15 @@ Revises: 20260813_0026
Create Date: 2026-08-13
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260813_0027"
down_revision: Union[str, None] = "20260813_0026"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260813_0026"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260813_0027
Create Date: 2026-08-14
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260814_0028"
down_revision: Union[str, None] = "20260813_0027"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260813_0027"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260814_0028
Create Date: 2026-08-14
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260814_0029"
down_revision: Union[str, None] = "20260814_0028"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260814_0028"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260814_0029
Create Date: 2026-08-29
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260829_0030"
down_revision: Union[str, None] = "20260814_0029"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260814_0029"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
@@ -5,16 +5,15 @@ Revises: 20260829_0030
Create Date: 2026-08-30
"""
from typing import Sequence, Union
from collections.abc import Sequence
from alembic import op
import sqlalchemy as sa
from alembic import op
revision: str = "20260830_0031"
down_revision: Union[str, None] = "20260829_0030"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
down_revision: str | None = "20260829_0030"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
INDEXES = (
+9 -8
View File
@@ -2,21 +2,22 @@
from __future__ import annotations
from datetime import datetime, timezone
from datetime import UTC, datetime
from sqlalchemy import (
Column,
Integer,
String,
JSON,
Boolean,
Text,
DateTime,
ForeignKey,
JSON,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .database import Base
from .crypto_storage import EncryptedText
from .database import Base
from .huya_defaults import (
HUYA_DEFAULT_BIND_ACT_ID,
HUYA_DEFAULT_OUTER_ACT_ID,
@@ -28,7 +29,7 @@ from .huya_defaults import (
def _utcnow():
"""返回时区感知的 UTC 当前时间,替代 _utcnow()。"""
return datetime.now(timezone.utc)
return datetime.now(UTC)
class User(Base):
+2 -2
View File
@@ -10,7 +10,8 @@ from sqlalchemy.orm import Session
from ..database import get_db
from ..deps import get_current_user
from ..models import ProxyConfig as ProxyConfigModel, User
from ..models import ProxyConfig as ProxyConfigModel
from ..models import User
from ..permissions import user_has_permission
from ..schemas import AccountCheckBatchOut, AccountCheckBatchRequest
from ..services.account_check_service import (
@@ -18,7 +19,6 @@ from ..services.account_check_service import (
parse_account_check_lines,
)
router = APIRouter(prefix="/api/account-check", tags=["账号检测"])
+6 -6
View File
@@ -5,25 +5,25 @@ from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session, defer, joinedload
from ..database import get_db
from ..deps import get_current_user, require_permission
from ..models import (
User,
Account,
AuditLog,
LoginTask,
DouyuTask,
DouyuWorkbenchAccount,
LoginTask,
User,
)
from ..permissions import user_has_permission
from ..schemas import (
AccountAssign,
AccountBulkSelection,
AccountBulkTag,
AccountImport,
AccountAssign,
AccountTag,
AccountOut,
AccountTag,
BatchAssign,
)
from ..deps import get_current_user, require_permission
from ..permissions import user_has_permission
from ..services.account_service import (
cookie_account_ids_query,
parse_and_build_accounts,
-1
View File
@@ -12,7 +12,6 @@ from ..database import get_db
from ..deps import get_current_user
from ..models import Account, AuditLog, DouyuTask, User
router = APIRouter(prefix="/api/audit-logs", tags=["审计日志"])
+5 -5
View File
@@ -1,16 +1,16 @@
"""认证路由"""
import os
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Response
from sqlalchemy.orm import Session
from ..database import get_db
from ..models import User, AuditLog
from ..security import verify_password, create_access_token, ACCESS_TOKEN_EXPIRE_HOURS
from ..permissions import get_user_permissions, ROLE_LABELS
from ..schemas import LoginRequest, TokenResponse
from ..deps import get_current_user
from ..models import AuditLog, User
from ..permissions import ROLE_LABELS, get_user_permissions
from ..schemas import LoginRequest, TokenResponse
from ..security import ACCESS_TOKEN_EXPIRE_HOURS, create_access_token, verify_password
router = APIRouter(prefix="/api/auth", tags=["认证"])
+13 -11
View File
@@ -1,22 +1,24 @@
"""Cookie 管理路由"""
import csv
import io
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from datetime import UTC, datetime
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse
from sqlalchemy import case, func, or_
from sqlalchemy.orm import Session, defer, joinedload
import io
import csv
from ..database import get_db, SessionLocal
from ..models import User, LoginTask, Account, AuditLog, ProxyConfig as ProxyConfigModel
from ..database import SessionLocal, get_db
from ..deps import get_current_user, require_permission
from ..permissions import user_has_permission, get_user_permissions
from ..models import Account, AuditLog, LoginTask, User
from ..models import ProxyConfig as ProxyConfigModel
from ..permissions import get_user_permissions, user_has_permission
from ..schemas import CookieReloginRequest
from ..services.login_service import BatchRegistry, LoginBatchRunner, get_relogin_limits
from ..services.cookie_check_service import check_douyu_cookie
from ..services.login_service import BatchRegistry, LoginBatchRunner, get_relogin_limits
def _fmt_dt(dt) -> str | None:
@@ -24,7 +26,7 @@ def _fmt_dt(dt) -> str | None:
if dt is None:
return None
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
dt = dt.replace(tzinfo=UTC)
return dt.isoformat()
@@ -491,7 +493,7 @@ def _check_cookies(ids: str, db: Session, current: User, *, detailed: bool) -> d
"fish_ball": None,
"nickname": None,
"level": None,
"checked_at": datetime.now(timezone.utc).isoformat(),
"checked_at": datetime.now(UTC).isoformat(),
}
)
results.sort(key=lambda item: item["id"])
@@ -509,7 +511,7 @@ def _check_cookies(ids: str, db: Session, current: User, *, detailed: bool) -> d
"level": item.get("level"),
"message": item.get("message", ""),
}
task.ck_checked_at = datetime.now(timezone.utc)
task.ck_checked_at = datetime.now(UTC)
db.add(
AuditLog(
user_id=current.id,
@@ -571,7 +573,7 @@ def _start_relogin_tasks(
if not acc.password or not acc.email:
task.status = "relogin_failed"
task.message = "重新登录失败: 账号缺少密码或邮箱,旧 Cookie 已保留"
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
skipped.append(task.id)
continue
if task.status in {"relogin_pending", "relogin_running"}:
-1
View File
@@ -17,7 +17,6 @@ from ..models import (
)
from ..permissions import user_has_permission
router = APIRouter(prefix="/api/dashboard", tags=["首页概览"])
+15 -16
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
import asyncio
import threading
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import Any, cast
from fastapi import (
@@ -18,7 +18,13 @@ from fastapi import (
)
from loguru import logger
from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session, joinedload, load_only
from sqlalchemy.orm import Session, joinedload
from core.douyu import (
FishFinRechargeClient,
FishFinRechargeConfig,
FishFinRechargeConfigError,
)
from ..database import SessionLocal, get_db
from ..deps import authenticate_websocket, get_current_user, require_permission
@@ -45,26 +51,19 @@ from ..schemas import (
DouyuWorkbenchAccountsUpdate,
DouyuXpdGoodsOut,
)
from ..services.douyu_runner import DouyuBatchRunner, douyu_batch_registry
from ..services.audit_service import record_audit
from ..services.douyu_runner import DouyuBatchRunner, douyu_batch_registry
from ..services.douyu_service import (
DOUYU_CONFIG_FIELDS,
DOUYU_HANDBOOK_TASK_TYPES,
SUPPORTED_DOUYU_TASK_TYPES,
apply_douyu_config_defaults,
cleanup_orphan_douyu_tasks,
cookie_account_ids_query,
create_douyu_planned_tasks,
douyu_config_value,
douyu_task_payload,
ensure_douyu_config,
)
from core.douyu import (
FishFinRechargeClient,
FishFinRechargeConfig,
FishFinRechargeConfigError,
)
router = APIRouter(prefix="/api/douyu", tags=["斗鱼活动"])
@@ -132,10 +131,10 @@ async def supplier_recharge_callback(request: Request, db: Session = Depends(get
if status == 2:
task.status = "success"
task.message = "供应商直充成功(异步通知)"
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
if task.account:
task.account.bind_status = "gold_recharged"
task.account.updated_at = datetime.now(timezone.utc)
task.account.updated_at = datetime.now(UTC)
elif status in {3, 4}:
reason = str(
_supplier_response_value(payload, "fail_reason", "message", "msg")
@@ -143,7 +142,7 @@ async def supplier_recharge_callback(request: Request, db: Session = Depends(get
)
task.status = "failed"
task.message = reason[:512]
task.finished_at = datetime.now(timezone.utc)
task.finished_at = datetime.now(UTC)
else:
task.status = "running"
task.message = (
@@ -466,7 +465,7 @@ def update_workbench_accounts(
if workbench is None:
db.add(DouyuWorkbench(user_id=current.id, handbook_scope=req.handbook_scope))
else:
workbench.updated_at = datetime.now(timezone.utc)
workbench.updated_at = datetime.now(UTC)
db.query(DouyuWorkbenchAccount).filter(
DouyuWorkbenchAccount.user_id == current.id,
DouyuWorkbenchAccount.handbook_scope == req.handbook_scope,
@@ -525,7 +524,7 @@ def update_config(
setattr(config, field, value.strip() if isinstance(value, str) else value)
changed_fields.append(field)
apply_douyu_config_defaults(config)
config.updated_at = datetime.now(timezone.utc)
config.updated_at = datetime.now(UTC)
recharge_fields = [field for field in changed_fields if field.startswith("gold_")]
if recharge_fields:
record_audit(
@@ -853,7 +852,7 @@ async def ws_douyu_logs(websocket: WebSocket, batch_id: str):
if msg.get("level") == "result":
await asyncio.sleep(0.1)
break
except asyncio.TimeoutError:
except TimeoutError:
await websocket.send_json({"level": "heartbeat", "message": ""})
except WebSocketDisconnect:
pass
+23 -25
View File
@@ -3,14 +3,11 @@
import asyncio
import csv
import io
import json
import shutil
import threading
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import cast
from pathlib import Path
from fastapi import (
APIRouter,
Depends,
@@ -37,6 +34,8 @@ from core.huya.cookie_utils import normalize_huya_cookie
from core.huya.device_fingerprint import account_state_dir as _fp_state_dir
from core.huya.device_profile import (
_load_db as _load_profile_db,
)
from core.huya.device_profile import (
_save_db as _save_profile_db,
)
from core.sms_provider import parse_sms_lines
@@ -62,6 +61,7 @@ from ..schemas import (
AccountTag,
BatchAssign,
HuyaAccountOut,
HuyaAppPasswordLoginRequest,
HuyaAutoRegisterBatchOut,
HuyaAutoRegisterRequest,
HuyaAutoRegisterRetryRequest,
@@ -72,7 +72,6 @@ from ..schemas import (
HuyaGoodsOut,
HuyaPasswordAccountImport,
HuyaPasswordLoginRequest,
HuyaAppPasswordLoginRequest,
HuyaPasswordLoginSelectedRequest,
HuyaRechargeGoodsOut,
HuyaRegisterSuccessLogOut,
@@ -81,6 +80,13 @@ from ..schemas import (
HuyaTaskBatchRequest,
HuyaTaskOut,
)
from ..services.audit_service import record_audit
from ..services.huya_register_runner import (
export_success_logs_text,
huya_register_registry,
list_success_logs,
)
from ..services.huya_runner import HuyaBatchRunner, huya_batch_registry
from ..services.huya_service import (
HUYA_CONFIG_FIELDS,
SUPPORTED_TASK_TYPES,
@@ -94,14 +100,6 @@ from ..services.huya_service import (
save_huya_login_cookie_to_account,
upsert_huya_cookie,
)
from ..services.huya_runner import HuyaBatchRunner, huya_batch_registry
from ..services.audit_service import record_audit
from ..services.huya_register_runner import (
export_success_logs_text,
huya_register_registry,
list_success_logs,
)
router = APIRouter(prefix="/api/huya", tags=["虎牙"])
@@ -760,7 +758,7 @@ def sms_login_account(
phone = (req.phone or "").strip()
if phone:
account.game_phone = phone
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
db.refresh(account)
except ValueError as exc:
@@ -1011,7 +1009,7 @@ def password_login_selected_accounts(
)
if not result.success or not result.cookie:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1051,7 +1049,7 @@ def password_login_selected_accounts(
)
except HuyaCredentialError as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1065,7 +1063,7 @@ def password_login_selected_accounts(
)
except (HuyaLoginError, ValueError) as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1079,7 +1077,7 @@ def password_login_selected_accounts(
)
except Exception as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1163,7 +1161,7 @@ def app_password_login_selected_accounts(
)
if not result.success or not result.cookie:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1201,7 +1199,7 @@ def app_password_login_selected_accounts(
)
except HuyaAppQrAuthRequiredError as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1215,7 +1213,7 @@ def app_password_login_selected_accounts(
)
except HuyaCredentialError as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1229,7 +1227,7 @@ def app_password_login_selected_accounts(
)
except (HuyaAppLoginError, HuyaLoginError, ValueError) as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1243,7 +1241,7 @@ def app_password_login_selected_accounts(
)
except Exception as exc:
account.status = "login_failed"
account.updated_at = datetime.now(timezone.utc)
account.updated_at = datetime.now(UTC)
db.commit()
failed_count += 1
results.append(
@@ -1729,7 +1727,7 @@ def update_config(
setattr(config, field, value.strip())
changed_fields.append(field)
apply_huya_config_defaults(config)
config.updated_at = datetime.now(timezone.utc)
config.updated_at = datetime.now(UTC)
if changed_fields:
record_audit(
db,
@@ -1995,7 +1993,7 @@ async def ws_huya_logs(websocket: WebSocket, batch_id: str):
if msg.get("level") == "result":
await asyncio.sleep(0.1)
break
except asyncio.TimeoutError:
except TimeoutError:
await websocket.send_json({"level": "heartbeat", "message": ""})
except WebSocketDisconnect:
pass
+7 -6
View File
@@ -2,16 +2,17 @@
import asyncio
import threading
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect
from sqlalchemy import func
from sqlalchemy.orm import Session, defer, load_only
from ..database import get_db, SessionLocal
from ..models import User, Account, LoginTask, ProxyConfig as ProxyConfigModel
from ..database import SessionLocal, get_db
from ..deps import authenticate_websocket, get_current_user, require_permission
from ..models import Account, LoginTask, User
from ..models import ProxyConfig as ProxyConfigModel
from ..permissions import get_user_permissions, user_has_permission
from ..schemas import LoginBatchRequest, LoginTaskOut
from ..deps import get_current_user, require_permission, authenticate_websocket
from ..permissions import user_has_permission, get_user_permissions
from ..services.login_service import LoginBatchRunner, batch_registry
router = APIRouter(prefix="/api/login", tags=["登录任务"])
@@ -259,7 +260,7 @@ async def ws_login_logs(websocket: WebSocket, batch_id: str):
if msg.get("level") == "result":
await asyncio.sleep(0.1)
break
except asyncio.TimeoutError:
except TimeoutError:
await websocket.send_json({"level": "heartbeat", "message": ""})
except WebSocketDisconnect:
pass
+10 -8
View File
@@ -2,20 +2,22 @@
import asyncio
import threading
from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect
from sqlalchemy.orm import Session
from ..database import get_db
from ..models import User
from ..schemas import ProxyConfigOut, ProxyConfigUpdate, PlatformInfo, PlatformFieldDef
from ..deps import require_permission, authenticate_websocket
from ..services.proxy_service import proxy_service
from core.douyu.proxy_platforms import (
get_platform_names,
get_platform_labels,
get_credential_fields,
get_platform_labels,
get_platform_names,
)
from ..database import get_db
from ..deps import authenticate_websocket, require_permission
from ..models import User
from ..schemas import PlatformFieldDef, PlatformInfo, ProxyConfigOut, ProxyConfigUpdate
from ..services.proxy_service import proxy_service
router = APIRouter(prefix="/api/proxy", tags=["代理与白名单"])
@@ -97,7 +99,7 @@ async def ws_test_logs(websocket: WebSocket, test_id: str):
if msg.get("level") == "result":
await asyncio.sleep(0.1)
break
except asyncio.TimeoutError:
except TimeoutError:
await websocket.send_json({"level": "heartbeat", "message": ""})
except WebSocketDisconnect:
pass
+6 -6
View File
@@ -1,6 +1,6 @@
"""用户管理路由(超管)"""
from datetime import datetime, timezone
from datetime import UTC, datetime
from uuid import uuid4
from fastapi import APIRouter, Depends, HTTPException
@@ -8,11 +8,11 @@ from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from ..database import get_db
from ..models import Account, AuditLog, HuyaAccount, User
from ..security import hash_password
from ..permissions import PERMISSIONS, get_user_permissions
from ..schemas import UserCreate, UserRename, UserUpdate, UserInfo
from ..deps import require_permission
from ..models import Account, AuditLog, HuyaAccount, User
from ..permissions import PERMISSIONS, get_user_permissions
from ..schemas import UserCreate, UserInfo, UserRename, UserUpdate
from ..security import hash_password
router = APIRouter(prefix="/api/users", tags=["用户管理"])
@@ -221,7 +221,7 @@ def delete_user(
)
)
user.is_active = False
user.deleted_at = datetime.now(timezone.utc)
user.deleted_at = datetime.now(UTC)
user.deleted_username = deleted_username
# 释放用户名供新用户使用,同时不改变历史任务通过 user.id 保存的归属。
user.username = f"deleted-{user.id}-{uuid4().hex[:12]}"
+1 -2
View File
@@ -12,10 +12,9 @@ from ..deps import get_current_user, require_permission
from ..models import User, YybRechargeTask
from ..permissions import user_has_permission
from ..schemas import YybLoginRequest, YybSelectionRequest, YybTaskCreateRequest
from ..services.audit_service import record_audit
from ..services.yyb_service import _utcnow, public_task, sync_task
from ..services.yyb_worker_client import YybWorkerClient, YybWorkerError
from ..services.audit_service import record_audit
router = APIRouter(prefix="/api/yyb", tags=["应用宝充值"])
+127 -126
View File
@@ -1,8 +1,9 @@
"""Pydantic 请求/响应模型"""
from datetime import datetime, timezone
from typing import Optional, Any
from pydantic import BaseModel, Field, ConfigDict, model_serializer
from datetime import UTC, datetime
from typing import Any
from pydantic import BaseModel, ConfigDict, Field, model_serializer
from .huya_defaults import (
HUYA_DEFAULT_BIND_ACT_ID,
@@ -32,16 +33,16 @@ class YybSelectionRequest(BaseModel):
role_name: str = Field(..., min_length=1, max_length=128)
def _ensure_tz(dt: Optional[datetime]) -> Optional[datetime]:
def _ensure_tz(dt: datetime | None) -> datetime | None:
"""确保 datetime 带有 UTC 时区信息,无时区的视为 UTC。"""
if dt is None:
return None
if dt.tzinfo is None:
return dt.replace(tzinfo=timezone.utc)
return dt.replace(tzinfo=UTC)
return dt
def _isoformat(dt: Optional[datetime]) -> str | None:
def _isoformat(dt: datetime | None) -> str | None:
normalized = _ensure_tz(dt)
return normalized.isoformat() if normalized is not None else None
@@ -66,9 +67,9 @@ class UserInfo(BaseModel):
role: str
is_active: bool
remark: str = ""
created_at: Optional[datetime] = None
created_at: datetime | None = None
permissions: list[str] = []
custom_permissions: Optional[list[str]] = None
custom_permissions: list[str] | None = None
model_config = ConfigDict(from_attributes=True)
@@ -95,11 +96,11 @@ class UserCreate(BaseModel):
class UserUpdate(BaseModel):
password: Optional[str] = None
role: Optional[str] = None
is_active: Optional[bool] = None
remark: Optional[str] = None
custom_permissions: Optional[list[str]] = None
password: str | None = None
role: str | None = None
is_active: bool | None = None
remark: str | None = None
custom_permissions: list[str] | None = None
class UserRename(BaseModel):
@@ -119,12 +120,12 @@ class AccountImport(BaseModel):
class AccountAssign(BaseModel):
assigned_to: Optional[int] = None
assigned_to: int | None = None
class BatchAssign(BaseModel):
account_ids: list[int]
assigned_to: Optional[int] = None # None=取消分配
assigned_to: int | None = None # None=取消分配
class AccountBulkSelection(BaseModel):
@@ -143,22 +144,22 @@ class AccountBulkTag(AccountBulkSelection):
class AccountTag(BaseModel):
tag: Optional[str] = None
account_ids: Optional[list[int]] = None
tag: str | None = None
account_ids: list[int] | None = None
class AccountOut(BaseModel):
id: int
username: str
# 敏感字段根据角色决定是否返回
password: Optional[str] = None
email: Optional[str] = None
email_password: Optional[str] = None
password: str | None = None
email: str | None = None
email_password: str | None = None
tag: str = ""
assigned_to: Optional[int] = None
assigned_username: Optional[str] = None
assigned_to: int | None = None
assigned_username: str | None = None
remark: str = ""
created_at: Optional[datetime] = None
created_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -203,8 +204,8 @@ class LoginTaskOut(BaseModel):
cookie: str = ""
message: str = ""
created_by: int
created_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
created_at: datetime | None = None
finished_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -239,8 +240,8 @@ class AccountCheckItemOut(BaseModel):
email: str
status: str
message: str
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
started_at: datetime | None = None
finished_at: datetime | None = None
class AccountCheckBatchOut(BaseModel):
@@ -256,9 +257,9 @@ class AccountCheckBatchOut(BaseModel):
finished_count: int
running_count: int
status_counts: dict[str, int]
created_at: Optional[datetime] = None
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
created_at: datetime | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
items: list[AccountCheckItemOut]
@@ -327,12 +328,12 @@ class HuyaAutoRegisterRetryRequest(BaseModel):
"""
mode: str = Field("continue", max_length=32)
concurrency: Optional[int] = Field(None, ge=1, le=5)
wait_seconds: Optional[float] = Field(None, ge=15, le=600)
poll_interval: Optional[float] = Field(None, ge=1, le=30)
password_prefix: Optional[str] = Field(None, max_length=8)
fixed_password: Optional[str] = Field(None, max_length=64)
use_proxy: Optional[bool] = None
concurrency: int | None = Field(None, ge=1, le=5)
wait_seconds: float | None = Field(None, ge=15, le=600)
poll_interval: float | None = Field(None, ge=1, le=30)
password_prefix: str | None = Field(None, max_length=8)
fixed_password: str | None = Field(None, max_length=64)
use_proxy: bool | None = None
class HuyaAutoRegisterItemOut(BaseModel):
@@ -346,15 +347,15 @@ class HuyaAutoRegisterItemOut(BaseModel):
change_code: str = ""
attempts: int = 0
change_attempts: int = 0
account_id: Optional[int] = None
account_id: int | None = None
username: str = ""
uid: str = ""
password: str = ""
password_changed: bool = False
cookie: str = ""
cookie_preview: str = ""
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
started_at: datetime | None = None
finished_at: datetime | None = None
class HuyaAutoRegisterBatchOut(BaseModel):
@@ -373,17 +374,17 @@ class HuyaAutoRegisterBatchOut(BaseModel):
failed_count: int
stopped_count: int
running_count: int
created_at: Optional[datetime] = None
started_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
created_at: datetime | None = None
started_at: datetime | None = None
finished_at: datetime | None = None
items: list[HuyaAutoRegisterItemOut] = []
class HuyaRegisterSuccessLogOut(BaseModel):
id: int
batch_id: str = ""
item_id: Optional[int] = None
account_id: Optional[int] = None
item_id: int | None = None
account_id: int | None = None
phone: str = ""
username: str = ""
uid: str = ""
@@ -391,8 +392,8 @@ class HuyaRegisterSuccessLogOut(BaseModel):
sms_url: str = ""
tag: str = ""
provider: str = ""
created_by: Optional[int] = None
created_at: Optional[datetime] = None
created_by: int | None = None
created_at: datetime | None = None
export_line: str = ""
@@ -435,14 +436,14 @@ class HuyaAccountOut(BaseModel):
status: str = ""
# 上次成功登录渠道: web / app / sms (空 = 仅导入 Cookie, 从未协议登录)
login_channel: str = ""
points: Optional[int] = None
points: int | None = None
game_name: str = ""
game_channel: str = ""
game_phone: str = ""
assigned_to: Optional[int] = None
assigned_username: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
assigned_to: int | None = None
assigned_username: str | None = None
created_at: datetime | None = None
updated_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -477,7 +478,7 @@ class HuyaConfigOut(BaseModel):
outer_act_id: str = HUYA_DEFAULT_OUTER_ACT_ID
bind_act_id: str = HUYA_DEFAULT_BIND_ACT_ID
pay_channel: str = HUYA_DEFAULT_PAY_CHANNEL
updated_at: Optional[datetime] = None
updated_at: datetime | None = None
@model_serializer
def _serialize(self) -> dict[str, Any]:
@@ -492,11 +493,11 @@ class HuyaConfigOut(BaseModel):
class HuyaConfigUpdate(BaseModel):
room_pid: Optional[str] = None
sid: Optional[str] = None
outer_act_id: Optional[str] = None
bind_act_id: Optional[str] = None
pay_channel: Optional[str] = None
room_pid: str | None = None
sid: str | None = None
outer_act_id: str | None = None
bind_act_id: str | None = None
pay_channel: str | None = None
class HuyaTaskBatchRequest(BaseModel):
@@ -515,10 +516,10 @@ class HuyaTaskOut(BaseModel):
task_type: str
status: str
message: str = ""
result: Optional[dict[str, Any]] = None
result: dict[str, Any] | None = None
created_by: int
created_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
created_at: datetime | None = None
finished_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -544,10 +545,10 @@ class HuyaGoodsOut(BaseModel):
id: int
product_id: str
name: str = ""
price: Optional[int] = None
price: int | None = None
remain_text: str = ""
raw: Optional[dict[str, Any]] = None
updated_at: Optional[datetime] = None
raw: dict[str, Any] | None = None
updated_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -569,15 +570,15 @@ class HuyaRechargeGoodsOut(BaseModel):
spu_id: str
sku_id: str = ""
name: str = ""
price: Optional[int] = None
stock: Optional[int] = None
buy_limit: Optional[int] = None
price: int | None = None
stock: int | None = None
buy_limit: int | None = None
icon: str = ""
description: str = ""
task_id: str = ""
task_name: str = ""
raw: Optional[dict[str, Any]] = None
updated_at: Optional[datetime] = None
raw: dict[str, Any] | None = None
updated_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -625,7 +626,7 @@ class DouyuConfigOut(BaseModel):
gold_api_account_template_name: str = "斗鱼昵称"
gift_id: str = "23643"
skin_id: str = "2942"
updated_at: Optional[datetime] = None
updated_at: datetime | None = None
@model_serializer
def _serialize(self) -> dict[str, Any]:
@@ -658,31 +659,31 @@ class DouyuConfigOut(BaseModel):
class DouyuConfigUpdate(BaseModel):
manual_id: Optional[str] = None
rid: Optional[str] = None
bind_act_alias: Optional[str] = None
confirm_act_alias: Optional[str] = None
legacy_act_alias: Optional[str] = None
room_id: Optional[str] = None
elite_amount: Optional[int] = Field(None, ge=1)
esports_manual_id: Optional[str] = None
esports_act_alias: Optional[str] = None
esports_amount: Optional[int] = Field(None, ge=1)
esports_chicken_gift_id: Optional[str] = None
esports_chicken_skin_id: Optional[str] = None
esports_firework_gift_id: Optional[str] = None
esports_firework_skin_id: Optional[str] = None
xpd_act_alias: Optional[str] = None
xpd_act_id: Optional[str] = None
xpd_rid: Optional[str] = None
gold_pay_type: Optional[int] = Field(None, ge=1, le=9)
gold_recharge_channel: Optional[str] = Field(
manual_id: str | None = None
rid: str | None = None
bind_act_alias: str | None = None
confirm_act_alias: str | None = None
legacy_act_alias: str | None = None
room_id: str | None = None
elite_amount: int | None = Field(None, ge=1)
esports_manual_id: str | None = None
esports_act_alias: str | None = None
esports_amount: int | None = Field(None, ge=1)
esports_chicken_gift_id: str | None = None
esports_chicken_skin_id: str | None = None
esports_firework_gift_id: str | None = None
esports_firework_skin_id: str | None = None
xpd_act_alias: str | None = None
xpd_act_id: str | None = None
xpd_rid: str | None = None
gold_pay_type: int | None = Field(None, ge=1, le=9)
gold_recharge_channel: str | None = Field(
None, pattern="^(wechat_qr|supplier_api)$"
)
gold_api_product_id: Optional[str] = Field(None, max_length=128)
gold_api_account_template_name: Optional[str] = Field(None, max_length=64)
gift_id: Optional[str] = None
skin_id: Optional[str] = None
gold_api_product_id: str | None = Field(None, max_length=128)
gold_api_account_template_name: str | None = Field(None, max_length=64)
gift_id: str | None = None
skin_id: str | None = None
class DouyuTaskBatchRequest(BaseModel):
@@ -709,10 +710,10 @@ class DouyuTaskOut(BaseModel):
handbook_scope: str = "legacy"
status: str
message: str = ""
result: Optional[dict[str, Any]] = None
result: dict[str, Any] | None = None
created_by: int
created_at: Optional[datetime] = None
finished_at: Optional[datetime] = None
created_at: datetime | None = None
finished_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -740,10 +741,10 @@ class DouyuGoodsOut(BaseModel):
id: int
commodity_id: str
name: str = ""
score: Optional[int] = None
score: int | None = None
status: str = ""
raw: Optional[dict[str, Any]] = None
updated_at: Optional[datetime] = None
raw: dict[str, Any] | None = None
updated_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -766,41 +767,41 @@ class DouyuTaskAccountOut(BaseModel):
uid: str = ""
nickname: str = ""
tag: str = ""
points: Optional[int] = None
points: int | None = None
game_name: str = ""
game_channel: str = ""
gold_balance: Optional[int] = None
exchange_balance: Optional[int] = None
gold_balance: int | None = None
exchange_balance: int | None = None
bind_status: str = ""
change_role_wait_time: Optional[int] = None
esports_points: Optional[int] = None
change_role_wait_time: int | None = None
esports_points: int | None = None
esports_game_name: str = ""
esports_game_channel: str = ""
esports_bind_status: str = ""
esports_change_role_wait_time: Optional[int] = None
esports_can_change_time: Optional[int] = None
esports_change_role_wait_time: int | None = None
esports_can_change_time: int | None = None
xpd_game_name: str = ""
xpd_openid: str = ""
xpd_role_id: str = ""
xpd_plat_id: Optional[int] = None
xpd_area_id: Optional[int] = None
xpd_balance: Optional[int] = None
xpd_fragments: Optional[int] = None
xpd_plat_id: int | None = None
xpd_area_id: int | None = None
xpd_balance: int | None = None
xpd_fragments: int | None = None
xpd_bind_status: str = ""
assigned_to: Optional[int] = None
assigned_username: Optional[str] = None
assigned_to: int | None = None
assigned_username: str | None = None
class DouyuXpdGoodsOut(BaseModel):
id: int
commodity_id: str
name: str = ""
price: Optional[int] = None
org_price: Optional[int] = None
price: int | None = None
org_price: int | None = None
category: str = ""
goods_left: Optional[int] = None
raw: Optional[dict[str, Any]] = None
updated_at: Optional[datetime] = None
goods_left: int | None = None
raw: dict[str, Any] | None = None
updated_at: datetime | None = None
model_config = ConfigDict(from_attributes=True)
@@ -827,23 +828,23 @@ class ProxyConfigOut(BaseModel):
https: str = ""
whitelist_enabled: bool = False
whitelist_platform: str = "xiequ"
whitelist_credentials: Optional[dict] = None
whitelist_credentials: dict | None = None
# 旧字段保留(向后兼容)
whitelist_uid: str = ""
whitelist_ukey: str = ""
class ProxyConfigUpdate(BaseModel):
enabled: Optional[bool] = None
api_url: Optional[str] = None
http: Optional[str] = None
https: Optional[str] = None
whitelist_enabled: Optional[bool] = None
whitelist_platform: Optional[str] = "xiequ"
whitelist_credentials: Optional[dict] = None
enabled: bool | None = None
api_url: str | None = None
http: str | None = None
https: str | None = None
whitelist_enabled: bool | None = None
whitelist_platform: str | None = "xiequ"
whitelist_credentials: dict | None = None
# 旧字段保留(向后兼容)
whitelist_uid: Optional[str] = None
whitelist_ukey: Optional[str] = None
whitelist_uid: str | None = None
whitelist_ukey: str | None = None
# ---- 代理平台元信息 ----
+5 -5
View File
@@ -3,10 +3,10 @@
import os
import secrets
import warnings
from datetime import datetime, timedelta, timezone
from typing import Optional
from datetime import UTC, datetime, timedelta
import bcrypt
from jose import jwt, JWTError
from jose import JWTError, jwt
# JWT 密钥:生产环境必须通过环境变量 JWT_SECRET_KEY 设置
SECRET_KEY = os.getenv("JWT_SECRET_KEY")
@@ -41,12 +41,12 @@ def create_access_token(
data: dict, expires_hours: int = ACCESS_TOKEN_EXPIRE_HOURS
) -> str:
to_encode = data.copy()
expire = datetime.now(timezone.utc) + timedelta(hours=expires_hours)
expire = datetime.now(UTC) + timedelta(hours=expires_hours)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY_TYPED, algorithm=ALGORITHM)
def decode_access_token(token: str) -> Optional[dict]:
def decode_access_token(token: str) -> dict | None:
try:
return jwt.decode(token, SECRET_KEY_TYPED, algorithms=[ALGORITHM])
except JWTError:
@@ -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:
+4 -3
View File
@@ -1,9 +1,10 @@
"""应用版本信息。"""
from functools import lru_cache
from importlib.metadata import PackageNotFoundError, version as package_version
from pathlib import Path
import tomllib
from functools import lru_cache
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as package_version
from pathlib import Path
@lru_cache(maxsize=1)