refactor(async): music_api 异步化 + 全项目 lint 收敛

music_api.py:
- 22 个方法改 async(HTTPClient.post_request*/NeteaseAPI.*/QRLoginManager.*),
  33 处 requests.* → await core.aiohttp_compat.*
- 16 个模块级同步包装改走 run_sync 桥(main.py 等 99 个同步调用点零改动)
- async 内 time.sleep → asyncio.sleep(修 7 处阻塞事件循环)

musica_downloader/qr_login:
- 单曲三请求 ThreadPoolExecutor → asyncio.gather(语义等价,失败处理保留)
- interactive_login 改 async,CLI 入口用 asyncio.run

真实缺陷修复(lint 发现):
- podcast_api._to_int 未定义(2 处调用会 NameError)→ 补安全转换 helper
- main.py VALID_QUALITY_LEVELS 重复定义(覆盖 core.config 导入值)→ 移除
- 移除死代码变量(salt×2/device_id×3/file_path/zipped_files)

lint 收敛:524→0(ruff All checks passed);pyright 0 errors。
pyproject 记录「有意保留」项(B904/RUF012/S305 上游协议等)及理由。

验证:冒烟 10/10;pytest 16/16;真实上游 get_toplist 异步直调与桥接均得 63 条。
This commit is contained in:
snowy-yang committed 2026-09-26 00:32:45 +08:00
1 parent 4962542e78
commit ce7ea66d6a
8 files changed
+520 -490

No files matched your search

+33 -32
View File
@@ -10,14 +10,14 @@
数据存储:SQLite(data/site.db),会话:Flask 签名 Cookie(secret_key 持久化)
"""
import base64
import hashlib
import shutil
import json
import math
import base64
import os
import re
import secrets
import shutil
import smtplib
import sqlite3
import ssl
@@ -29,9 +29,9 @@ from email.mime.text import MIMEText
from email.utils import formataddr
from functools import wraps
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from typing import Any
from flask import request, session, g
from flask import g, request, session
# ============ 路径与常量 ============
DATA_DIR = Path('data')
@@ -59,7 +59,7 @@ def get_conn() -> sqlite3.Connection:
return conn
def _q(sql: str, params: Tuple = ()) -> List[sqlite3.Row]:
def _q(sql: str, params: tuple = ()) -> list[sqlite3.Row]:
with _DB_LOCK:
conn = get_conn()
try:
@@ -68,7 +68,7 @@ def _q(sql: str, params: Tuple = ()) -> List[sqlite3.Row]:
conn.close()
def _x(sql: str, params: Tuple = ()) -> int:
def _x(sql: str, params: tuple = ()) -> int:
"""执行写操作,返回 lastrowid / rowcount"""
with _DB_LOCK:
conn = get_conn()
@@ -209,7 +209,6 @@ def init_db() -> None:
row = conn.execute("SELECT id FROM users WHERE role = 'admin' LIMIT 1").fetchone()
if not row:
initial_pass = secrets.token_urlsafe(12)
salt = secrets.token_hex(8)
conn.execute(
'INSERT INTO users (username, password_hash, salt, role, duration_seconds, created_at) '
'VALUES (?, ?, ?, ?, -1, ?)',
@@ -270,7 +269,7 @@ def _hash_new(password: str, salt: str = '', iterations: int = _PBKDF2_ITERS) ->
return f'pbkdf2${iterations}${salt}${digest}'
def _verify_password(stored: str, password: str, salt: str) -> Tuple[bool, bool]:
def _verify_password(stored: str, password: str, salt: str) -> tuple[bool, bool]:
"""校验口令。返回 (是否正确, 是否需要升级到 PBKDF2)"""
if stored.startswith('pbkdf2$'):
try:
@@ -287,7 +286,7 @@ def _verify_password(stored: str, password: str, salt: str) -> Tuple[bool, bool]
def create_user(username: str, password: str, email: str = '', credits: int = 0,
role: str = 'user', device_id: str = '', ip: str = '', ua: str = '',
register_ip_limit: int = 0, register_ip_window: int = 86400,
register_device_once: bool = False) -> Dict[str, Any]:
register_device_once: bool = False) -> dict[str, Any]:
# F77:余额只接受 -1(不限)或 >= 0;非法负值一律按 0 处理,禁止出现 -2 这类"假不限"
try:
credits = int(credits)
@@ -298,7 +297,6 @@ def create_user(username: str, password: str, email: str = '', credits: int = 0,
# R5(第 79 条):注册入口传 register_ip_limit=3 —— 同 IP 24h 注册数检查挪进
# _DB_LOCK 事务内与 INSERT 原子完成,堵住"锁外检查+并发插入"的 TOCTOU 刷号;
# 管理建号与不限制场景传 0 关闭。超限抛 ValueError 由调用方转 400/429。
salt = secrets.token_hex(8)
now = int(time.time())
with _DB_LOCK:
conn = get_conn()
@@ -328,12 +326,12 @@ def create_user(username: str, password: str, email: str = '', credits: int = 0,
return get_user_by_name(username)
def get_user_by_name(username: str) -> Optional[sqlite3.Row]:
def get_user_by_name(username: str) -> sqlite3.Row | None:
rows = _q('SELECT * FROM users WHERE username = ?', (username,))
return rows[0] if rows else None
def get_user_by_email(email: str) -> Optional[sqlite3.Row]:
def get_user_by_email(email: str) -> sqlite3.Row | None:
"""按邮箱查用户(P3-19:注册/建号邮箱查重的友好提示;库侧另有唯一索引兜底并发)"""
email = _normalize_email(email)
if not email:
@@ -367,12 +365,12 @@ def register_ip_exceeded(ip: str, limit: int = 3, window: int = 86400) -> bool:
return bool(rows and rows[0]['n'] >= limit)
def get_user_by_id(uid: int) -> Optional[sqlite3.Row]:
def get_user_by_id(uid: int) -> sqlite3.Row | None:
rows = _q('SELECT * FROM users WHERE id = ?', (uid,))
return rows[0] if rows else None
def verify_login(username: str, password: str) -> Optional[sqlite3.Row]:
def verify_login(username: str, password: str) -> sqlite3.Row | None:
"""校验用户名密码。密码正确即返回用户行(含被禁用/封禁账号——
状态语义由登录路由判断:禁用显示"已被禁用"、封禁显示封禁原因。
原实现把禁用账号提前过滤成 None,路由的"账号已被禁用"分支永不可达)"""
@@ -388,7 +386,7 @@ def verify_login(username: str, password: str) -> Optional[sqlite3.Row]:
return None
def list_users() -> List[Dict[str, Any]]:
def list_users() -> list[dict[str, Any]]:
rows = _q('SELECT * FROM users ORDER BY id DESC')
return [_user_public(r, full=True) for r in rows]
@@ -449,7 +447,7 @@ def unban_user(uid: int) -> bool:
return bool(_x('UPDATE users SET status = 1, ban_reason = \'\', recent_provinces = \'[]\' WHERE id = ?', (uid,)))
_PROVINCE_CACHE: Dict[str, str] = {}
_PROVINCE_CACHE: dict[str, str] = {}
def _is_private_ip(ip: str) -> bool:
@@ -484,8 +482,9 @@ def query_ip_province(ip: str) -> str:
return _PROVINCE_CACHE[ip]
province = ''
try:
import requests as _rq
import urllib.parse as _ul
import requests as _rq
r = _rq.get('http://ip-api.com/json/' + _ul.quote(ip),
params={'lang': 'zh-CN', 'fields': 'status,regionName'},
timeout=5)
@@ -560,7 +559,7 @@ def set_user_netease_cookie(username: str, cookie: str) -> None:
_x('UPDATE users SET netease_cookie = ? WHERE username = ?', (cookie, username))
def _user_public(r: sqlite3.Row, full: bool = False) -> Dict[str, Any]:
def _user_public(r: sqlite3.Row, full: bool = False) -> dict[str, Any]:
d = {
'id': r['id'],
'username': r['username'],
@@ -636,7 +635,7 @@ def logout_session() -> None:
pass
def current_user() -> Optional[sqlite3.Row]:
def current_user() -> sqlite3.Row | None:
"""当前登录用户(每请求只查一次库:结果缓存到 flask.g。
一次下载流程会调用本函数 5-6 次——原实现每次都新建 SQLite 连接,公网下是纯浪费。
缓存含 None:未登录同样只查一次。请求内登录态不会变化(登录/注册路由不走本函数)。)"""
@@ -648,7 +647,7 @@ def current_user() -> Optional[sqlite3.Row]:
return user
def _load_current_user() -> Optional[sqlite3.Row]:
def _load_current_user() -> sqlite3.Row | None:
uid = session.get(SESSION_KEY)
if not uid:
return None
@@ -848,7 +847,7 @@ def ledger_open(task_id: str, user_id: int, reserved: int) -> None:
conn.close()
def reserve_and_open(uid: int, count: int, ledger_key: str) -> Optional[int]:
def reserve_and_open(uid: int, count: int, ledger_key: str) -> int | None:
"""A2/B8:预扣与开账同一事务(原两步之间存在崩溃丢账窗口)。
足额:扣减+开账并返回剩余;不限/管理员:开账(reserved=0)返回 -1;不足:返回 None(不开账)。"""
count = max(1, int(count))
@@ -941,7 +940,7 @@ def ledger_supplement(task_id: str, extra: int) -> bool:
conn.close()
def ledger_get(task_id: str) -> Optional[Dict[str, Any]]:
def ledger_get(task_id: str) -> dict[str, Any] | None:
rows = _q('SELECT * FROM billing_ledger WHERE task_id = ?', (str(task_id),))
return dict(rows[0]) if rows else None
@@ -1016,7 +1015,7 @@ def ledger_cleanup_settled(max_age_seconds: int = 90 * 86400) -> int:
# ============ 装饰器 ============
def _deny(message: str, code: int = 401, error_code: str = 'unauthorized') -> Tuple[Dict[str, Any], int]:
def _deny(message: str, code: int = 401, error_code: str = 'unauthorized') -> tuple[dict[str, Any], int]:
return ({'status': code, 'success': False, 'message': message, 'error_code': error_code}, code)
@@ -1174,8 +1173,9 @@ _SMTP_ENC_PREFIX = 'enc1:' # 第 95 条:信封加密标记前缀(存量明
def _smtp_password_encrypt(plaintext: str) -> str:
"""用 data/secret.key 派生 Fernet 密钥加密 SMTP 授权码(第 95 条 P2-7:
原明文落库+随备份导出=泄露半径扩大;注释编号笔误已按第 97 条纠正)。"""
from cryptography.fernet import Fernet
import hashlib as _hl
from cryptography.fernet import Fernet
key = _hl.sha256(get_secret_key().encode('utf-8')).digest()
f = Fernet(base64.urlsafe_b64encode(key))
return _SMTP_ENC_PREFIX + f.encrypt(plaintext.encode('utf-8')).decode('utf-8')
@@ -1184,14 +1184,15 @@ def _smtp_password_encrypt(plaintext: str) -> str:
def _smtp_password_decrypt(ciphertext: str) -> str:
if not ciphertext.startswith(_SMTP_ENC_PREFIX):
return ciphertext # 存量明文:透明兼容,下次保存时升级为密文
from cryptography.fernet import Fernet
import hashlib as _hl
from cryptography.fernet import Fernet
key = _hl.sha256(get_secret_key().encode('utf-8')).digest()
f = Fernet(base64.urlsafe_b64encode(key))
return f.decrypt(ciphertext[len(_SMTP_ENC_PREFIX):].encode('utf-8')).decode('utf-8')
def get_smtp_config() -> Dict[str, Any]:
def get_smtp_config() -> dict[str, Any]:
rows = _q('SELECT * FROM smtp_config WHERE id = 1')
cfg = dict(rows[0]) if rows else {'host': '', 'port': 587, 'sender': '', 'password': ''}
# 第 95 条:读取时解密密码——调用方(SMTP 连接/后台展示)拿到的始终是明文
@@ -1399,7 +1400,7 @@ def restore_verify_code(email: str, code: str, ttl_seconds: int, purpose: str =
def generate_cards(count: int, credits: int, created_by: str = '',
prefix: str = '', note: str = '', valid_days: int = 0) -> List[str]:
prefix: str = '', note: str = '', valid_days: int = 0) -> list[str]:
"""生成次数卡密(credits = 每张卡可下载次数)
prefix: 自定义卡密前缀(默认 VIP-)
note: 备注信息
@@ -1425,7 +1426,7 @@ def generate_cards(count: int, credits: int, created_by: str = '',
return codes
def list_cards(limit: int = 100, offset: int = 0) -> List[Dict[str, Any]]:
def list_cards(limit: int = 100, offset: int = 0) -> list[dict[str, Any]]:
"""卡密列表(F85:支持分页 limit/offset,管理路由透传)"""
limit = max(1, min(int(limit), 500))
offset = max(0, int(offset))
@@ -1485,7 +1486,7 @@ def delete_card(code: str) -> None:
conn.close()
def redeem_card(code: str, username: str) -> Tuple[int, int]:
def redeem_card(code: str, username: str) -> tuple[int, int]:
"""兑换卡密,返回 (卡密次数, 新剩余次数)(F25:标记已用与加余额同事务,失败整体回滚不吞卡)"""
code = (code or '').strip().upper()
with _DB_LOCK:
@@ -1533,7 +1534,7 @@ def redeem_card(code: str, username: str) -> Tuple[int, int]:
# ============ 公告 ============
def get_announcement() -> Dict[str, Any]:
def get_announcement() -> dict[str, Any]:
rows = _q('SELECT * FROM announcements WHERE id = 1')
return dict(rows[0]) if rows else {'title': '', 'content': '', 'popup_mode': 'off'}
@@ -1606,7 +1607,7 @@ def create_backup() -> str:
conn.close()
def list_backups() -> List[Dict[str, Any]]:
def list_backups() -> list[dict[str, Any]]:
"""备份列表(F44:按文件修改时间倒序——按名字排序会让 upload_ 永远排前,误删新自动备份)"""
if not BACKUP_DIR.exists():
return []
@@ -1622,7 +1623,7 @@ def list_backups() -> List[Dict[str, Any]]:
return out
def backup_path(name: str) -> Optional[Path]:
def backup_path(name: str) -> Path | None:
name = Path(name).name # 防目录穿越
p = BACKUP_DIR / name
return p if p.exists() and p.suffix == '.db' else None