Archived
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:
1 parent
4962542e78
commit
ce7ea66d6a
8 files changed
+520
-490
No files matched your search
+33
-32
@@ -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
|
||||
|
||||
Reference in new issue
Block a user