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
+39
-32
@@ -19,8 +19,7 @@
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from random import randrange
|
||||
from typing import Dict, List, Optional, Any
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
@@ -54,7 +53,7 @@ class PodcastConstants:
|
||||
_MAX_UPSTREAM_JSON_BYTES = 8 * 1024 * 1024
|
||||
|
||||
|
||||
def _post_json(url: str, data: Dict[str, Any], cookies: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||
def _post_json(url: str, data: dict[str, Any], cookies: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
"""发送表单 POST 请求并返回 JSON"""
|
||||
headers = {
|
||||
'User-Agent': APIConstants.USER_AGENT,
|
||||
@@ -78,7 +77,15 @@ def _post_json(url: str, data: Dict[str, Any], cookies: Optional[Dict[str, str]]
|
||||
return result
|
||||
|
||||
|
||||
def _ts_to_date(ts: Optional[int]) -> str:
|
||||
def _to_int(v: Any, default: int | None = None) -> int | None:
|
||||
"""安全转 int:垃圾值/None 回退默认值(上游字段常为 null/字符串)。"""
|
||||
try:
|
||||
return int(v)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def _ts_to_date(ts: int | None) -> str:
|
||||
"""毫秒时间戳 → 日期字符串"""
|
||||
try:
|
||||
if not ts:
|
||||
@@ -88,7 +95,7 @@ def _ts_to_date(ts: Optional[int]) -> str:
|
||||
return ''
|
||||
|
||||
|
||||
def _fmt_duration(ms: Optional[int]) -> str:
|
||||
def _fmt_duration(ms: int | None) -> str:
|
||||
"""毫秒 → mm:ss / hh:mm:ss"""
|
||||
try:
|
||||
sec = int((ms or 0) / 1000)
|
||||
@@ -101,7 +108,7 @@ def _fmt_duration(ms: Optional[int]) -> str:
|
||||
return f"{h}:{m:02d}:{s:02d}" if h else f"{m:02d}:{s:02d}"
|
||||
|
||||
|
||||
def _fmt_count(n: Optional[int]) -> str:
|
||||
def _fmt_count(n: int | None) -> str:
|
||||
"""数量 → 万单位简写"""
|
||||
try:
|
||||
n = int(n or 0)
|
||||
@@ -118,7 +125,7 @@ def _fmt_count(n: Optional[int]) -> str:
|
||||
|
||||
# ============ 数据规范化 ============
|
||||
|
||||
def _norm_radio(raw: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _norm_radio(raw: dict[str, Any]) -> dict[str, Any]:
|
||||
"""规范化电台(播客)信息"""
|
||||
dj = raw.get('dj') or {}
|
||||
return {
|
||||
@@ -139,7 +146,7 @@ def _norm_radio(raw: Dict[str, Any]) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _norm_program(raw: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _norm_program(raw: dict[str, Any]) -> dict[str, Any]:
|
||||
"""规范化单集(program)信息"""
|
||||
radio = raw.get('radio') or {}
|
||||
dj = raw.get('dj') or radio.get('dj') or {}
|
||||
@@ -168,7 +175,7 @@ def _norm_program(raw: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
# ============ 链接 / ID 解析 ============
|
||||
|
||||
_SHORTLINK_CACHE: Dict[str, str] = {}
|
||||
_SHORTLINK_CACHE: dict[str, str] = {}
|
||||
|
||||
|
||||
def _resolve_short_link(url: str) -> str:
|
||||
@@ -201,7 +208,7 @@ def _resolve_short_link(url: str) -> str:
|
||||
return real
|
||||
|
||||
|
||||
def extract_podcast_ref(raw: str) -> Dict[str, str]:
|
||||
def extract_podcast_ref(raw: str) -> dict[str, str]:
|
||||
"""从用户输入中提取播客引用(T4:正规 URL 解析,替代子串匹配与非数字剔除)
|
||||
|
||||
Returns:
|
||||
@@ -210,7 +217,7 @@ def extract_podcast_ref(raw: str) -> Dict[str, str]:
|
||||
- program: program?id= / sound?id= 链接
|
||||
- unknown: 纯数字 ID(需依次尝试单集/电台)
|
||||
"""
|
||||
from urllib.parse import urlsplit, parse_qs
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
text = str(raw or '').strip()
|
||||
# 短链先还原(仅当整串就是一个短链 URL)
|
||||
if '163cn.tv' in text.lower():
|
||||
@@ -247,9 +254,9 @@ def extract_podcast_ref(raw: str) -> Dict[str, str]:
|
||||
|
||||
# ============ 核心 API ============
|
||||
|
||||
def search_podcast(keywords: str, cookies: Optional[Dict[str, str]] = None,
|
||||
def search_podcast(keywords: str, cookies: dict[str, str] | None = None,
|
||||
limit: int = 20, offset: int = 0,
|
||||
search_type: str = 'all') -> Dict[str, Any]:
|
||||
search_type: str = 'all') -> dict[str, Any]:
|
||||
"""搜索播客
|
||||
|
||||
Args:
|
||||
@@ -267,9 +274,9 @@ def search_podcast(keywords: str, cookies: Optional[Dict[str, str]] = None,
|
||||
raise APIException('搜索关键词不能为空')
|
||||
limit = max(1, min(int(limit or 20), 50))
|
||||
|
||||
result: Dict[str, Any] = {'radios': [], 'voices': []}
|
||||
result: dict[str, Any] = {'radios': [], 'voices': []}
|
||||
|
||||
def _do_search(stype: int) -> Dict[str, Any]:
|
||||
def _do_search(stype: int) -> dict[str, Any]:
|
||||
return _post_json(PodcastConstants.SEARCH_API,
|
||||
{'s': keywords, 'type': stype, 'limit': limit, 'offset': offset},
|
||||
cookies)
|
||||
@@ -288,7 +295,7 @@ def search_podcast(keywords: str, cookies: Optional[Dict[str, str]] = None,
|
||||
return result
|
||||
|
||||
|
||||
def get_radio_detail(radio_id: Any, cookies: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||
def get_radio_detail(radio_id: Any, cookies: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
"""获取播客电台(djradio)详情"""
|
||||
try:
|
||||
rid = int(str(radio_id).strip())
|
||||
@@ -302,8 +309,8 @@ def get_radio_detail(radio_id: Any, cookies: Optional[Dict[str, str]] = None) ->
|
||||
return _norm_radio(data)
|
||||
|
||||
|
||||
def get_radio_programs(radio_id: Any, cookies: Optional[Dict[str, str]] = None,
|
||||
limit: int = 30, offset: int = 0, asc: bool = True) -> Dict[str, Any]:
|
||||
def get_radio_programs(radio_id: Any, cookies: dict[str, str] | None = None,
|
||||
limit: int = 30, offset: int = 0, asc: bool = True) -> dict[str, Any]:
|
||||
"""获取电台下的单集列表(分页)
|
||||
|
||||
Args:
|
||||
@@ -334,7 +341,7 @@ def get_radio_programs(radio_id: Any, cookies: Optional[Dict[str, str]] = None,
|
||||
}
|
||||
|
||||
|
||||
def get_program_detail(program_id: Any, cookies: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
|
||||
def get_program_detail(program_id: Any, cookies: dict[str, str] | None = None) -> dict[str, Any]:
|
||||
"""获取单集(program)详情"""
|
||||
try:
|
||||
pid = int(str(program_id).strip())
|
||||
@@ -347,7 +354,7 @@ def get_program_detail(program_id: Any, cookies: Optional[Dict[str, str]] = None
|
||||
return _norm_program(program)
|
||||
|
||||
|
||||
def get_categories() -> List[Dict[str, Any]]:
|
||||
def get_categories() -> list[dict[str, Any]]:
|
||||
"""获取播客分类列表(GET 接口)
|
||||
|
||||
Returns:
|
||||
@@ -387,9 +394,9 @@ def get_categories() -> List[Dict[str, Any]]:
|
||||
return cats
|
||||
|
||||
|
||||
def get_hot_radios(cookies: Optional[Dict[str, str]] = None,
|
||||
def get_hot_radios(cookies: dict[str, str] | None = None,
|
||||
limit: int = 30, offset: int = 0,
|
||||
cate_id: Optional[int] = None) -> List[Dict[str, Any]]:
|
||||
cate_id: int | None = None) -> list[dict[str, Any]]:
|
||||
"""获取分类下热门播客
|
||||
|
||||
Args:
|
||||
@@ -400,7 +407,7 @@ def get_hot_radios(cookies: Optional[Dict[str, str]] = None,
|
||||
电台列表(已规范化)
|
||||
"""
|
||||
limit = max(1, min(int(limit or 30), 100))
|
||||
cate_ids: List[int] = []
|
||||
cate_ids: list[int] = []
|
||||
user_cate = False
|
||||
if cate_id:
|
||||
try:
|
||||
@@ -425,8 +432,8 @@ def get_hot_radios(cookies: Optional[Dict[str, str]] = None,
|
||||
return []
|
||||
|
||||
|
||||
def resolve_episode_url(song_id: Any, cookies: Optional[Dict[str, str]] = None,
|
||||
level: str = 'auto') -> Optional[Dict[str, Any]]:
|
||||
def resolve_episode_url(song_id: Any, cookies: dict[str, str] | None = None,
|
||||
level: str = 'auto') -> dict[str, Any] | None:
|
||||
"""解析单集音频直链(带音质降级)
|
||||
|
||||
Args:
|
||||
@@ -439,7 +446,7 @@ def resolve_episode_url(song_id: Any, cookies: Optional[Dict[str, str]] = None,
|
||||
"""
|
||||
# F83:降级阶梯只向下不向上——原实现指定 standard 会回退到更高的 exhigh
|
||||
_FALLDOWN_LADDER = ['hires', 'lossless', 'exhigh', 'standard']
|
||||
order: List[str] = []
|
||||
order: list[str] = []
|
||||
level = str(level or 'auto').strip()
|
||||
if level and level != 'auto':
|
||||
order.append(level)
|
||||
@@ -454,7 +461,7 @@ def resolve_episode_url(song_id: Any, cookies: Optional[Dict[str, str]] = None,
|
||||
else:
|
||||
order.extend(PodcastConstants.QUALITY_LADDER)
|
||||
# 去重保序
|
||||
dedup: List[str] = []
|
||||
dedup: list[str] = []
|
||||
for lv in order:
|
||||
if lv not in dedup:
|
||||
dedup.append(lv)
|
||||
@@ -487,8 +494,8 @@ def resolve_episode_url(song_id: Any, cookies: Optional[Dict[str, str]] = None,
|
||||
|
||||
# ============ 组合能力 ============
|
||||
|
||||
def parse_podcast_ref(raw: str, cookies: Optional[Dict[str, str]] = None,
|
||||
page_size: int = 30) -> Dict[str, Any]:
|
||||
def parse_podcast_ref(raw: str, cookies: dict[str, str] | None = None,
|
||||
page_size: int = 30) -> dict[str, Any]:
|
||||
"""智能解析用户输入:电台链接 → 电台详情+单集列表;单集链接 → 单集详情
|
||||
|
||||
Returns:
|
||||
@@ -515,15 +522,15 @@ def parse_podcast_ref(raw: str, cookies: Optional[Dict[str, str]] = None,
|
||||
return {'kind': 'radio', 'radio': radio, **page}
|
||||
|
||||
|
||||
def list_radio_all_program_ids(radio_id: Any, cookies: Optional[Dict[str, str]] = None,
|
||||
max_count: int = 200) -> List[Dict[str, Any]]:
|
||||
def list_radio_all_program_ids(radio_id: Any, cookies: dict[str, str] | None = None,
|
||||
max_count: int = 200) -> list[dict[str, Any]]:
|
||||
"""拉取电台全部单集(用于整台打包,正序),返回 [{'id','name','main_song_id','serial_num','cover_url'}]
|
||||
|
||||
最多拉取 max_count 集,防止超大电台拖垮磁盘/任务。
|
||||
F98:按节目 ID 去重;一页没有任何新增(短页/重复页/列表变动)即有界停止,
|
||||
不再固定 offset+=100 盲目翻页。
|
||||
"""
|
||||
out: List[Dict[str, Any]] = []
|
||||
out: list[dict[str, Any]] = []
|
||||
seen_ids = set()
|
||||
offset = 0
|
||||
page_size = 100
|
||||
|
||||
Reference in new issue
Block a user