mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-02 05:17:23 +08:00
- NexusPHP v1.10+ 站点以 userdetails.php?uuid= 标识用户,解析 UUID 用户标识, 做种列表接口改用 useruuid 参数请求 - 憨憨等站点的魔力值嵌在 mybonus 链接内部,优先解析链接文本与链接后文本, 兜底容器限制为临近且文本紧凑,避免误取公告等大段文本中的无关数字
This commit is contained in:
@@ -22,11 +22,12 @@ class NexusPhpSiteUserInfo(SiteParserBase):
|
||||
def _parse_site_page(self, html_text: str):
|
||||
html_text = self._prepare_html_text(html_text)
|
||||
|
||||
user_detail = re.search(r"userdetails.php\?id=(\d+)", html_text)
|
||||
if user_detail and user_detail.group().strip():
|
||||
# NexusPHP v1.10+ 使用 UUID 标识用户,详情链接参数为 uuid
|
||||
user_detail = re.search(r"userdetails\.php\?(?:id|uuid)=([0-9A-Za-z_-]+)", html_text)
|
||||
if user_detail and user_detail.group(1).strip():
|
||||
self._user_detail_page = user_detail.group().strip().lstrip('/')
|
||||
self.userid = user_detail.group(1)
|
||||
self._torrent_seeding_page = f"getusertorrentlistajax.php?userid={self.userid}&type=seeding"
|
||||
self.userid = user_detail.group(1).strip()
|
||||
self._torrent_seeding_page = self._build_torrent_seeding_page()
|
||||
else:
|
||||
user_detail = re.search(r"(userdetails)", html_text)
|
||||
if user_detail and user_detail.group().strip():
|
||||
@@ -34,6 +35,29 @@ class NexusPhpSiteUserInfo(SiteParserBase):
|
||||
self.userid = None
|
||||
self._torrent_seeding_page = None
|
||||
|
||||
@staticmethod
|
||||
def _is_uuid(user_id: Optional[str]) -> bool:
|
||||
"""
|
||||
判断用户标识是否为 UUID 形式。
|
||||
|
||||
使用 UUID 的 NexusPHP 站点将做种列表接口参数改名为 useruuid。
|
||||
"""
|
||||
return bool(re.fullmatch(
|
||||
r"[0-9a-fA-F]{8}(?:-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}",
|
||||
str(user_id or "").strip()
|
||||
))
|
||||
|
||||
def _build_torrent_seeding_page(self) -> str:
|
||||
"""
|
||||
构造做种列表接口地址。
|
||||
|
||||
:return: 做种列表地址,无有效用户标识时返回空字符串
|
||||
"""
|
||||
if not self._has_valid_userid():
|
||||
return ""
|
||||
user_id_param = "useruuid" if self._is_uuid(self.userid) else "userid"
|
||||
return f"getusertorrentlistajax.php?{user_id_param}={self.userid}&type=seeding"
|
||||
|
||||
def _parse_message_unread(self, html_text):
|
||||
"""
|
||||
解析未读短消息数量
|
||||
@@ -123,21 +147,33 @@ class NexusPhpSiteUserInfo(SiteParserBase):
|
||||
has_ucoin, self.bonus = self._parse_ucoin(html)
|
||||
if has_ucoin:
|
||||
return
|
||||
tmps = html.xpath('//a[contains(@href,"mybonus")]/text()') if html is not None else None
|
||||
if tmps:
|
||||
bonus_text = str(tmps[0]).strip()
|
||||
bonus_match = re.search(r"([\d,.]+)", bonus_text)
|
||||
bonus_links = html.xpath('//a[contains(@href,"mybonus")]') if html is not None else []
|
||||
for bonus_link in bonus_links:
|
||||
# 魔力值直接写在链接文本中
|
||||
for bonus_text in bonus_link.xpath('text()'):
|
||||
bonus_match = re.search(r"([\d,.]+)", str(bonus_text).strip())
|
||||
if bonus_match and bonus_match.group(1).strip():
|
||||
self.bonus = text_tools.parse_float(bonus_match.group(1))
|
||||
return
|
||||
# 数值嵌在链接内部的元素中(憨憨:<a href="mybonus.php"><div>13,853,582</div></a>)
|
||||
bonus_text = bonus_link.xpath("string(.)").strip()
|
||||
bonus_match = re.fullmatch(r"[\s\[((【]*([\d][\d,.]*)[\s\]))】]*", bonus_text)
|
||||
if bonus_match and bonus_match.group(1).strip():
|
||||
self.bonus = text_tools.parse_float(bonus_match.group(1))
|
||||
return
|
||||
# PTT-NP 等站点的链接文本只有“使用&说明”,数值紧跟在链接之后
|
||||
bonus_match = re.search(r"[::]\s*([\d,.]+)", bonus_link.tail or "")
|
||||
if bonus_match and bonus_match.group(1).strip():
|
||||
self.bonus = text_tools.parse_float(bonus_match.group(1))
|
||||
return
|
||||
# PTT-NP 的 mybonus 链接文本只有“使用&说明”,数值位于同一容器的文本中。
|
||||
bonus_links = html.xpath('//a[contains(@href,"mybonus")]') if html is not None else []
|
||||
for bonus_link in bonus_links:
|
||||
bonus_containers = bonus_link.xpath('ancestor::*[contains(., "魔力值")][1]')
|
||||
if not bonus_containers:
|
||||
bonus_containers = bonus_link.xpath('parent::*')
|
||||
# 兜底解析链接附近容器中的魔力值,容器必须临近且文本紧凑,
|
||||
# 避免把公告、新闻等大段文本里的无关数字当作魔力值
|
||||
bonus_containers = bonus_link.xpath('ancestor::*[position() <= 4][contains(., "魔力值")]')
|
||||
for bonus_container in bonus_containers:
|
||||
bonus_text = bonus_container.xpath("string(.)")
|
||||
if len(bonus_text) > 200:
|
||||
continue
|
||||
bonus_match = re.search(r"魔力值.*?[::]\s*([\d,.]+)", bonus_text, flags=re.S)
|
||||
if bonus_match and bonus_match.group(1).strip():
|
||||
self.bonus = text_tools.parse_float(bonus_match.group(1))
|
||||
@@ -264,8 +300,7 @@ class NexusPhpSiteUserInfo(SiteParserBase):
|
||||
|
||||
return next_page
|
||||
|
||||
@staticmethod
|
||||
def _fixup_next_page_url(next_page: str, userid: Optional[str]) -> Optional[str]:
|
||||
def _fixup_next_page_url(self, next_page: str, userid: Optional[str]) -> Optional[str]:
|
||||
"""
|
||||
修正做种下一页地址,无法补齐用户 ID 时停止翻页。
|
||||
|
||||
@@ -276,12 +311,12 @@ class NexusPhpSiteUserInfo(SiteParserBase):
|
||||
parsed_url = urlsplit(next_page)
|
||||
query_params = dict(parse_qsl(parsed_url.query, keep_blank_values=True))
|
||||
|
||||
if query_params.get("userid"):
|
||||
if any(query_params.get(key) for key in ("userid", "useruuid")):
|
||||
return next_page
|
||||
if not userid:
|
||||
return None
|
||||
|
||||
query_params["userid"] = userid
|
||||
query_params["useruuid" if self._is_uuid(userid) else "userid"] = userid
|
||||
query_params.setdefault("type", "seeding")
|
||||
return urlunsplit(parsed_url._replace(query=urlencode(query_params)))
|
||||
|
||||
@@ -365,10 +400,10 @@ class NexusPhpSiteUserInfo(SiteParserBase):
|
||||
'and contains(@href,"seeding")]/@href')
|
||||
csrf_text = html.xpath('//meta[@name="x-csrf"]/@content')
|
||||
if not self._torrent_seeding_page and seeding_url_text:
|
||||
user_js = re.search(r"javascript: getusertorrentlistajax\(\s*'(\d+)", seeding_url_text[0])
|
||||
user_js = re.search(r"javascript:\s*getusertorrentlistajax\(\s*'([^']+)'", seeding_url_text[0])
|
||||
if user_js and user_js.group(1).strip():
|
||||
self.userid = user_js.group(1).strip()
|
||||
self._torrent_seeding_page = f"getusertorrentlistajax.php?userid={self.userid}&type=seeding"
|
||||
self._torrent_seeding_page = self._build_torrent_seeding_page()
|
||||
elif seeding_url_text and csrf_text:
|
||||
if csrf_text[0].strip():
|
||||
self._torrent_seeding_page \
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from urllib.parse import parse_qs, urlsplit
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from app.modules.indexer.parser.nexus_php import NexusPhpSiteUserInfo
|
||||
|
||||
|
||||
@@ -108,3 +110,119 @@ def test_nexus_php_bonus_fallback_ignores_unrelated_script_numbers():
|
||||
)
|
||||
|
||||
assert parser.bonus == 0
|
||||
|
||||
|
||||
def test_nexus_php_bonus_reads_value_nested_in_mybonus_link():
|
||||
"""
|
||||
憨憨等站点将魔力值嵌在 mybonus 链接内的元素中,且页面公告里存在无关的“魔力值…:数字”文本。
|
||||
"""
|
||||
parser = _build_parser()
|
||||
html_text = """
|
||||
<html>
|
||||
<body>
|
||||
<div>
|
||||
视工作情况会给予魔力值、邀请码等奖励。具体处理措施如下:
|
||||
1、补偿所有用户100000憨豆;(已经发放完成)
|
||||
</div>
|
||||
<div class="flex flex-row items-center">
|
||||
<img src="styles/HHan/icons/icon-bean.svg" alt="憨豆">
|
||||
<a href="mybonus.php">
|
||||
<div class="text-sm flex flex-wrap break-all">13,853,582</div>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
parser._parse_user_traffic_info(html_text)
|
||||
|
||||
assert parser.bonus == 13853582.0
|
||||
|
||||
|
||||
def test_nexus_php_uuid_user_details_link_parses_uuid_user():
|
||||
"""
|
||||
NexusPHP v1.10+ 使用 UUID 标识用户的站点应解析用户详情链接,并使用 useruuid 请求做种列表。
|
||||
"""
|
||||
parser = _build_parser()
|
||||
html_text = """
|
||||
<html>
|
||||
<body>
|
||||
<table id="info_block"><tr><td>
|
||||
欢迎回来, <span class="nowrap"><a href="https://mua.xloli.cc/userdetails.php?uuid=53d25816-f7b7-4372-b58d-6f445acfe090" class='User_Name'><b>Andreas</b></a></span>
|
||||
<font class='color_bonus'>魔力值 </font>[<a href="mybonus.php">使用</a>]: 105.5
|
||||
</td></tr></table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
parser._parse_site_page(html_text)
|
||||
parser._parse_user_base_info(html_text)
|
||||
|
||||
assert parser.userid == "53d25816-f7b7-4372-b58d-6f445acfe090"
|
||||
assert parser._user_detail_page == "userdetails.php?uuid=53d25816-f7b7-4372-b58d-6f445acfe090"
|
||||
assert parser._torrent_seeding_page == (
|
||||
"getusertorrentlistajax.php?useruuid=53d25816-f7b7-4372-b58d-6f445acfe090&type=seeding"
|
||||
)
|
||||
assert parser.username == "Andreas"
|
||||
assert parser.bonus == 105.5
|
||||
|
||||
|
||||
def test_nexus_php_numeric_user_details_link_keeps_userid_param():
|
||||
"""
|
||||
传统数字用户 ID 的站点应继续使用 userid 请求做种列表。
|
||||
"""
|
||||
parser = _build_parser()
|
||||
html_text = '<html><body><a href="userdetails.php?id=67537"><b>Opportunity</b></a></body></html>'
|
||||
|
||||
parser._parse_site_page(html_text)
|
||||
|
||||
assert parser.userid == "67537"
|
||||
assert parser._torrent_seeding_page == "getusertorrentlistajax.php?userid=67537&type=seeding"
|
||||
|
||||
|
||||
def test_nexus_php_uuid_seeding_js_link_uses_useruuid():
|
||||
"""
|
||||
用户详情页的做种 JS 调用携带 UUID 时,应改用 useruuid 参数请求做种列表。
|
||||
"""
|
||||
parser = _build_parser()
|
||||
# 首页未解析到用户链接时,做种地址为空,需要从用户详情页的 JS 调用中补齐
|
||||
parser._torrent_seeding_page = None
|
||||
html_text = """
|
||||
<html>
|
||||
<body>
|
||||
<a href="javascript: getusertorrentlistajax('53d25816-f7b7-4372-b58d-6f445acfe090', 'seeding', 'ka1'); klappe_news('a1')">[显示/隐藏]</a>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
parser._fixup_torrent_seeding_page(etree.HTML(html_text))
|
||||
|
||||
assert parser.userid == "53d25816-f7b7-4372-b58d-6f445acfe090"
|
||||
assert parser._torrent_seeding_page == (
|
||||
"getusertorrentlistajax.php?useruuid=53d25816-f7b7-4372-b58d-6f445acfe090&type=seeding"
|
||||
)
|
||||
|
||||
|
||||
def test_nexus_php_uuid_seeding_next_page_uses_useruuid():
|
||||
"""
|
||||
UUID 站点的做种下一页地址缺少用户标识时,应补齐 useruuid 而不是 userid。
|
||||
"""
|
||||
parser = _build_parser()
|
||||
parser.userid = "53d25816-f7b7-4372-b58d-6f445acfe090"
|
||||
html_text = """
|
||||
<html>
|
||||
<body>
|
||||
<table class="torrents">
|
||||
<tr><td>标题</td><td>大小</td><td>在做种</td></tr>
|
||||
</table>
|
||||
<a href="getusertorrentlistajax.php?page=2&type=seeding">下一页</a>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
next_page = parser._parse_user_torrent_seeding_info(html_text, multi_page=True)
|
||||
query_params = parse_qs(urlsplit(next_page).query)
|
||||
|
||||
assert query_params["useruuid"] == ["53d25816-f7b7-4372-b58d-6f445acfe090"]
|
||||
assert "userid" not in query_params
|
||||
assert query_params["type"] == ["seeding"]
|
||||
|
||||
Reference in New Issue
Block a user