Files
MoviePilot/app/modules/indexer/parser/tnode.py
freeman f3ad81b39e style: apply safe ruff auto-fixes and exempt startup late imports
Run `ruff check app tests scripts --fix` with the configured rule set
(E4/E7/E9/F/I): import sorting (I001), f-strings without placeholders
(F541), lambda/variable/unused-import cleanups that ruff marks safe.
Nine files whose only ruff debt sits next to pre-existing pylint
import-resolution errors (lazy exports, sys.path script imports) or
whose "unused" imports are reached through attribute access by tests are
left untouched so the changed-file pylint gate and the benchmark tests
stay green; their diagnostics remain in the baseline.

app/startup/lifecycle/__init__.py and app/main.py import business
modules after cache composition, import-path and stdio setup on purpose
(the source comments document the contract), so declare E402
per-file-ignores for them instead of adding per-line noqa.

Ruff ratchet watermark drops from 498 to 100; sync the baseline fixture
and the optimization checklist figure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-09 13:29:09 +08:00

121 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
import json
import re
from typing import Optional
from app.foundation import temporal as time_tools
from app.modules.indexer.parser import SiteParserBase, SiteSchema
from app.runtime.log import logger
class TNodeSiteUserInfo(SiteParserBase):
"""通过当前会话 API 获取 TNode 站点用户资料与做种信息。"""
schema = SiteSchema.TNode
def _parse_site_page(self, html_text: str):
"""提取 CSRF 令牌,并配置用于取得用户 ID 的当前会话接口。"""
html_text = self._prepare_html_text(html_text)
# <meta name="x-csrf-token" content="fd169876a7b4846f3a7a16fcd5cccf8d">
csrf_token = re.search(r'<meta name="x-csrf-token" content="(.+?)">', html_text)
if csrf_token:
self._addition_headers = {'X-CSRF-TOKEN': csrf_token.group(1)}
# 当前用户接口依赖 Cookie 会话,不需要预先知道用户 ID。
self._user_detail_page = "api/user/getMainInfo"
self._user_detail_requires_userid = False
self._torrent_seeding_page = "api/user/listTorrentActivity?id=&type=seeding&page=1&size=20000"
def _parse_logged_in(self, html_text):
"""
判断是否登录成功, 通过判断是否存在用户信息
暂时跳过检测,待后续优化
:param html_text:
:return:
"""
return True
def _parse_user_base_info(self, html_text: str):
"""首页不提供用户 ID,身份信息需在详情接口返回后补齐。"""
self.username = self.userid
def _parse_user_traffic_info(self, html_text: str):
"""流量信息已由当前用户接口返回,无需单独解析。"""
pass
def _parse_user_detail_info(self, html_text: str):
"""从当前用户接口一次解析身份、资料、流量及未读消息数量。"""
try:
detail = json.loads(html_text)
except json.JSONDecodeError:
return
if detail.get("status") != 200:
return
user_info = detail.get("data", {})
self.userid = user_info.get("id")
self.username = user_info.get("username")
self.user_level = user_info.get("class", {}).get("name")
self.join_at = user_info.get("regTime", 0)
self.join_at = time_tools.normalize_datetime(str(self.join_at))
self.upload = user_info.get("upload")
self.download = user_info.get("download")
self.ratio = 0 if self.download <= 0 else round(self.upload / self.download, 3)
self.bonus = user_info.get("bonus")
self.message_unread = user_info.get("unreadAdmin", 0) + user_info.get("unreadInbox", 0) + user_info.get(
"unreadSystem", 0)
pass
def _parse_user_torrent_seeding_info(self, html_text: str, multi_page: Optional[bool] = False) -> Optional[str]:
"""
解析用户做种信息
"""
try:
seeding_info = json.loads(html_text)
except json.JSONDecodeError as e:
logger.warning(f"{self._site_name}: Failed to decode seeding info JSON: {e}")
return None
if not isinstance(seeding_info, dict):
logger.warning(f"{self._site_name}: Seeding info payload is not a dictionary")
return None
if seeding_info.get("status") != 200:
return None
torrents = seeding_info.get("data", {}).get("torrents", [])
page_seeding_size = 0
page_seeding_info = []
for torrent in torrents:
size = torrent.get("size", 0)
seeders = torrent.get("seeding", 0)
page_seeding_size += size
page_seeding_info.append([seeders, size])
self.seeding += len(torrents)
self.seeding_size += page_seeding_size
self.seeding_info.extend(page_seeding_info)
# 是否存在下页数据
next_page = None
return next_page
def _parse_message_unread_links(self, html_text: str, msg_links: list) -> Optional[str]:
"""当前仅支持 API 返回的未读数量,不解析消息链接。"""
return None
def _parse_message_content(self, html_text):
"""
系统信息 api/message/listSystem?page=1&size=20
收件箱信息 api/message/listInbox?page=1&size=20
管理员信息 api/message/listAdmin?page=1&size=20
:param html_text:
:return:
"""
return None, None, None