Files
MoviePilot/tests/test_nexus_audiences_parser.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

708 lines
23 KiB
Python
Raw Permalink 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.
"""
验证Nexus Audience 站点页面的种子信息解析。
"""
# -*- coding: utf-8 -*-
import pytest
from app.foundation.size import parse_size
from app.modules.indexer.parser.nexus_audiences import NexusAudiencesSiteUserInfo
def test_audiences_userbar_metrics_override_generic_nexus_regex():
"""
Audiences 专用用户栏指标优先于通用 Nexus 正则结果。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<div
data-uploader-label="jxxghp"
data-uploader-url="userdetails.php?id=18978"
data-uploader-badge="(江湖儿女)Elite User"
data-uploader-stats='[
{"label":"上传量:","value":"10.150 TB","tone":"uploaded"},
{"label":"爆米花:","value":"1,973,896.2","tone":"bonus"},
{"label":"下载量:","value":"3.624 TB","tone":"downloaded"},
{"label":"活跃","value":"↑ 355 / ↓ 7","tone":"active"}
]'>
</div>
<span class="site-userbar__compact-metric site-userbar__compact-metric--ratio">
<i></i><span>2.801</span>
</span>
</body>
</html>
"""
# Audiences 新版用户栏把流量数据放在 data 属性中,通用 NexusPHP 正则无法稳定识别。
parser._parse_user_traffic_info(html_text)
assert parser.userid == "18978"
assert parser.username == "jxxghp"
assert parser.user_level == "Elite User"
assert parser.upload == parse_size("10.150 TB")
assert parser.download == parse_size("3.624 TB")
assert parser.ratio == 2.801
assert parser.bonus == 1973896.2
assert parser.seeding == 355
assert parser.leeching == 7
@pytest.mark.parametrize("parse_method", ["_parse_user_traffic_info", "_parse_user_detail_info"])
@pytest.mark.parametrize("badge, expected", [
("(小鬼当家)User", "User"),
("(江湖儿女)Elite User", "Elite User"),
(" (江湖儿女) Elite User ", "Elite User"),
("Elite User", "Elite User"),
(" User ", "User"),
("(小鬼当家)", "Power User"),
(" ", "Power User"),
])
def test_audiences_user_level_omits_site_group(parse_method, badge, expected):
"""
解析用户等级时去除站点分组前缀,保留实际等级名称。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
parser.user_level = "Power User"
html_text = f"""
<html><body>
<a data-uploader-label="testuser" data-uploader-url="userdetails.php?id=123"
data-uploader-badge="{badge}">testuser</a>
</body></html>
"""
getattr(parser, parse_method)(html_text)
assert parser.user_level == expected
def test_audiences_inbox_total_unread_badge_uses_unread_part():
"""
收件箱同时展示总数与未读数时,只提取未读部分。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<div class="site-userbar__compact-actions">
<a class="site-userbar__compact-tool site-userbar__compact-tool--has-unread"
href="messages.php"
title="收件箱 1749/172"
aria-label="收件箱 1749/172">
<i class="fas fa-inbox" aria-hidden="true"></i>
<strong>收件箱</strong>
<span class="site-userbar__compact-tool-badge site-userbar__compact-tool-badge--unread">1749/172</span>
</a>
<a class="site-userbar__compact-tool"
href="messages.php?action=viewmailbox&amp;box=-1"
title="发件箱 0"
aria-label="发件箱 0">
<strong>发件箱</strong>
<span class="site-userbar__compact-tool-badge">0</span>
</a>
</div>
</body>
</html>
"""
parser._parse_message_unread(html_text)
assert parser.message_unread == 172
def test_audiences_inbox_total_only_is_not_unread_count():
"""
只有收件箱总数且没有未读标记时,不将总数误报为未读数。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<div class="site-userbar__compact-actions">
<a class="site-userbar__compact-tool"
href="messages.php"
title="收件箱 1749"
aria-label="收件箱 1749">
<strong>收件箱</strong>
<span class="site-userbar__compact-tool-badge">1749</span>
</a>
</div>
</body>
</html>
"""
parser._parse_message_unread(html_text)
assert parser.message_unread == 0
def test_audiences_unread_badge_plain_count_is_unread_count():
"""
明确带未读标记的纯数字徽章可直接作为未读数量。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<div class="site-userbar__compact-actions">
<a class="site-userbar__compact-tool site-userbar__compact-tool--has-unread"
href="messages.php"
title="收件箱 1749"
aria-label="收件箱 1749">
<strong>收件箱</strong>
<span class="site-userbar__compact-tool-badge site-userbar__compact-tool-badge--unread">172</span>
</a>
</div>
</body>
</html>
"""
parser._parse_message_unread(html_text)
assert parser.message_unread == 172
def test_audiences_unread_marker_without_count_uses_unknown_count():
"""
只有未读标记而没有数量时保留数量未知的状态。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<div class="site-userbar__compact-actions">
<a class="site-userbar__compact-tool site-userbar__compact-tool--has-unread"
href="messages.php"
title="收件箱"
aria-label="收件箱">
<strong>收件箱</strong>
</a>
</div>
</body>
</html>
"""
parser._parse_message_unread(html_text)
assert parser.message_unread == 99999
def test_audiences_table_unread_links_ignore_content_rows():
"""
从消息表格提取未读链接时排除消息正文行,避免重复计数。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<table>
<tr>
<td class="rowfollow" align="center">
<img class="unreadpm" src="pic/trans.gif" alt="Unread" title="未读">
</td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4318225">种子被删除</a>
</td>
<td class="rowfollow" align="left">系统</td>
<td class="rowfollow" nowrap=""><span title="2026-05-07 23:01:58">8天17时前</span></td>
<td class="rowfollow"><input class="checkbox" type="checkbox" name="messages[]" value="4318225"></td>
</tr>
<tr>
<td colspan="5" style="padding: 8px;">消息摘要内容</td>
</tr>
<tr>
<td class="rowfollow" align="center">
<img class="readpm" src="pic/trans.gif" alt="Read" title="已读">
</td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4318000">已读消息</a>
</td>
<td class="rowfollow" align="left">系统</td>
<td class="rowfollow" nowrap=""><span title="2026-05-07 23:01:58">8天17时前</span></td>
<td class="rowfollow"><input class="checkbox" type="checkbox" name="messages[]" value="4318000"></td>
</tr>
<tr>
<td class="rowfollow" align="center">
<img class="readpm" src="pic/trans.gif" title="已读">
</td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4317999">无英文 alt 的已读消息</a>
</td>
<td class="rowfollow" align="left">系统</td>
<td class="rowfollow" nowrap=""><span title="2026-05-07 23:01:58">8天17时前</span></td>
<td class="rowfollow"><input class="checkbox" type="checkbox" name="messages[]" value="4317999"></td>
</tr>
<tr>
<td class="rowfollow" align="center"></td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4317998">无状态图标消息</a>
</td>
<td class="rowfollow" align="left">系统</td>
<td class="rowfollow" nowrap=""><span title="2026-05-07 23:01:58">8天17时前</span></td>
<td class="rowfollow"><input class="checkbox" type="checkbox" name="messages[]" value="4317998"></td>
</tr>
</table>
</body>
</html>
"""
msg_links = []
next_page = parser._parse_message_unread_links(html_text, msg_links)
assert msg_links == ["messages.php?action=viewmessage&id=4318225"]
assert next_page == "messages.php?action=viewmailbox&box=1&unread=yes&page=1"
def test_audiences_readpm_row_is_not_unread_message():
"""
带已读图标的表格行不能计入未读消息。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<table>
<tr>
<td class="rowfollow" align="center">
<img class="readpm" src="pic/trans.gif" alt="Read" title="已读">
</td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4318000">已读消息</a>
</td>
</tr>
</table>
</body>
</html>
"""
msg_links = []
parser._parse_message_unread_links(html_text, msg_links)
assert msg_links == []
def test_audiences_pm_item_unread_links_use_list_preview_when_detail_empty():
"""
Audiences 新版 div 私信列表应能识别未读行,并在详情页不可解析时使用列表预览通知。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
parser.message_unread = 7
unread_rows = "".join(
f"""
<div class="pm-item-row is-unread">
<div class="pm-item">
<input class="pm-item__check" type="checkbox" name="messages[]" value="{4495900 + index}">
<span class="pm-item__status pm-item__status--unread" title="未读"></span>
<a class="pm-item__subject"
href="messages.php?action=viewmessage&amp;id={4495900 + index}">种子被删除</a>
<span class="pm-item__user"><i class="fas fa-user" aria-hidden="true"></i>系统</span>
<span class="pm-item__time">2026-06-22 22:32:11</span>
</div>
<div class="pm-item__preview">
你下载的种子'Wonder Wall S01E{index:02d} 2026 1080p WEB-DL H265 AAC-ADWeb'被管理员删除。
</div>
</div>
"""
for index in range(1, 8)
)
list_html = f"""
<html>
<body>
<form action="messages.php" method="post">
<div class="pm-list">
{unread_rows}
<div class="pm-item-row">
<div class="pm-item">
<span class="pm-item__status" title="已读"></span>
<a class="pm-item__subject"
href="messages.php?action=viewmessage&amp;id=4419171">已读消息</a>
<span class="pm-item__time">2026-06-07 14:27:45</span>
</div>
</div>
</div>
</form>
</body>
</html>
"""
requested_urls = []
def fake_get_page_content(url, params=None, headers=None):
"""
模拟新版列表页可读,但详情页结构暂不兼容导致解析为空。
"""
requested_urls.append(url)
return "<html></html>" if "viewmessage" in url else list_html
parser._get_page_content = fake_get_page_content
parser._pase_unread_msgs()
detail_requests = [url for url in requested_urls if "viewmessage" in url]
assert len(detail_requests) == 7
assert len(parser.message_unread_contents) == 7
assert parser.message_unread_contents[0] == (
"种子被删除",
"2026-06-22 22:32:11",
"你下载的种子'Wonder Wall S01E01 2026 1080p WEB-DL H265 AAC-ADWeb'被管理员删除。",
)
assert "已读消息" not in [item[0] for item in parser.message_unread_contents]
def test_audiences_pm_view_message_content_is_parsed():
"""
Audiences 新版短消息详情页应解析 pm-view 中的标题、日期和正文。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<td class="embedded">
<div class="pm-page">
<div class="pm-hero">
<div class="pm-hero__text">
<h1 class="pm-hero__title">种子被删除</h1>
<p class="pm-hero__sub">自 系统</p>
</div>
</div>
<article class="pm-view">
<header class="pm-view__head">
<div class="pm-view__meta">
<span class="pm-view__label">自</span>
<span class="pm-view__value">系统</span>
</div>
<div class="pm-view__meta">
<span class="pm-view__label">日期</span>
<span class="pm-view__value">2026-06-22 22:32:11 </span>
</div>
</header>
<div class="pm-view__body">
你下载的种子'Wonder Wall S01E20 2026 1080p WEB-DL H265 AAC-ADWeb'被管理员删除。原因:已完结剧集,清理单集。
</div>
</article>
</div>
</td>
</body>
</html>
"""
head, date, content = parser._parse_message_content(html_text)
assert head == "种子被删除"
assert date == "2026-06-22 22:32:11"
assert content == "你下载的种子'Wonder Wall S01E20 2026 1080p WEB-DL H265 AAC-ADWeb'被管理员删除。原因:已完结剧集,清理单集。"
def test_audiences_unread_mailbox_only_uses_user_box():
"""
Audiences 只使用用户消息箱,首页不传 page,page=1 实际表示第二页。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
assert parser._user_mail_unread_page == "messages.php?action=viewmailbox&box=1&unread=yes"
assert parser._sys_mail_unread_page is None
def test_audiences_unread_links_increment_page_until_empty():
"""
Audiences 每页固定 10 条,有未读行时按 page 参数自增继续翻页。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
html_text = """
<html>
<body>
<table>
<tr>
<td class="rowfollow" align="center">
<img class="unreadpm" src="pic/trans.gif" alt="Unread" title="未读">
</td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4318225">种子被删除</a>
</td>
</tr>
</table>
</body>
</html>
"""
next_html_text = html_text.replace("4318225", "4318226").replace("种子被删除", "系统通知")
msg_links = []
next_page = parser._parse_message_unread_links(html_text, msg_links)
next_next_page = parser._parse_message_unread_links(next_html_text, msg_links)
stop_page = parser._parse_message_unread_links("<html><body><table></table></body></html>", msg_links)
assert msg_links == [
"messages.php?action=viewmessage&id=4318225",
"messages.php?action=viewmessage&id=4318226",
]
assert next_page == "messages.php?action=viewmailbox&box=1&unread=yes&page=1"
assert next_next_page == "messages.php?action=viewmailbox&box=1&unread=yes&page=2"
assert stop_page is None
def test_audiences_unread_messages_stop_when_pages_repeat():
"""
Audiences 异常分页重复返回同一批消息时,应停止翻页并只通知一次。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
parser.message_unread = 172
list_html = """
<html>
<body>
<table>
<tr>
<td class="rowfollow" align="center">
<img class="unreadpm" src="pic/trans.gif" alt="Unread" title="未读">
</td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4318225">种子被删除</a>
</td>
</tr>
</table>
</body>
</html>
"""
requested_urls = []
def fake_get_page_content(url, params=None, headers=None):
"""
模拟观众分页异常:每个未读列表页都返回同一个消息链接。
"""
requested_urls.append(url)
return "<html></html>" if "viewmessage" in url else list_html
def fake_parse_message_content(_):
"""
返回可识别的消息详情,便于验证重复链接没有被重复通知。
"""
return "种子被删除", "2026-05-07 23:01:58", "消息摘要内容"
parser._get_page_content = fake_get_page_content
parser._parse_message_content = fake_parse_message_content
parser._pase_unread_msgs()
mailbox_requests = [url for url in requested_urls if "viewmailbox" in url]
detail_requests = [url for url in requested_urls if "viewmessage" in url]
assert mailbox_requests == [
"https://audiences.me/messages.php?action=viewmailbox&box=1&unread=yes",
"https://audiences.me/messages.php?action=viewmailbox&box=1&unread=yes&page=1",
]
assert detail_requests == [
"https://audiences.me/messages.php?action=viewmessage&id=4318225"
]
assert parser.message_unread_contents == [
("种子被删除", "2026-05-07 23:01:58", "消息摘要内容")
]
def test_audiences_unread_messages_skip_empty_detail():
"""
详情页解析失败时,不应把全 None 的消息写入通知列表。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
parser.message_unread = 1
list_html = """
<html>
<body>
<table>
<tr>
<td class="rowfollow" align="center">
<img class="unreadpm" src="pic/trans.gif" alt="Unread" title="未读">
</td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4318225">种子被删除</a>
</td>
</tr>
</table>
</body>
</html>
"""
def fake_get_page_content(url, params=None, headers=None):
"""
未读列表正常返回链接,消息详情页返回无法解析的空页面。
"""
return "<html></html>" if "viewmessage" in url else list_html
def fake_parse_message_content(_):
"""
模拟详情页解析不到标题、时间和内容。
"""
return None, None, None
parser._get_page_content = fake_get_page_content
parser._parse_message_content = fake_parse_message_content
parser._pase_unread_msgs()
assert parser.message_unread_contents == []
def test_audiences_unknown_unread_count_updates_from_collected_links():
"""
只有未读状态没有可靠数量时,最终用实际抓到的未读链接数回填。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
parser.message_unread = 99999
first_list_html = """
<html>
<body>
<table>
<tr>
<td class="rowfollow" align="center">
<img class="unreadpm" src="pic/trans.gif" alt="Unread" title="未读">
</td>
<td class="rowfollow" align="left">
<a href="messages.php?action=viewmessage&amp;id=4318225">种子被删除</a>
</td>
</tr>
</table>
</body>
</html>
"""
second_list_html = first_list_html.replace("4318225", "4318226").replace("种子被删除", "系统通知")
def fake_get_page_content(url, params=None, headers=None):
"""
模拟未读数量未知时正常翻到空页为止。
"""
if "viewmessage" in url:
return "<html></html>"
if "page=1" in url:
return second_list_html
if "page=2" in url:
return "<html><body><table></table></body></html>"
return first_list_html
def fake_parse_message_content(html_text):
"""
返回固定消息详情,测试重点是未知数量回填。
"""
return "标题", "2026-05-07 23:01:58", "内容"
parser._get_page_content = fake_get_page_content
parser._parse_message_content = fake_parse_message_content
parser._pase_unread_msgs()
assert parser.message_unread == 2
assert len(parser.message_unread_contents) == 2
def test_audiences_unknown_unread_count_resets_when_no_links():
"""
未知未读数量但列表为空时,不保留 99999 作为通知数量。
"""
parser = NexusAudiencesSiteUserInfo(
site_name="Audiences",
url="https://audiences.me/",
site_cookie="",
apikey=None,
token=None,
)
parser.message_unread = 99999
def fake_get_page_content(url, params=None, headers=None):
"""
模拟站点标记有未读但未读列表为空。
"""
return "<html><body><table></table></body></html>"
parser._get_page_content = fake_get_page_content
parser._pase_unread_msgs()
assert parser.message_unread == 0
assert parser.message_unread_contents == []