diff --git a/app/modules/indexer/parser/nexus_php.py b/app/modules/indexer/parser/nexus_php.py index 97ff1054f..ba18cd9da 100644 --- a/app/modules/indexer/parser/nexus_php.py +++ b/app/modules/indexer/parser/nexus_php.py @@ -76,6 +76,10 @@ class NexusPhpSiteUserInfo(SiteParserBase): if not DomUtils.has_child_elements(html): return + user_level = self._parse_user_level_marker(html.xpath("string(.)"), self.userid) + if user_level: + self.user_level = user_level + ret = html.xpath(f'//a[contains(@href, "userdetails") and contains(@href, "{self.userid}")]//b//text()') if ret: self.username = str(ret[0]) @@ -125,12 +129,24 @@ class NexusPhpSiteUserInfo(SiteParserBase): 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::*') + for bonus_container in bonus_containers: + bonus_text = bonus_container.xpath("string(.)") + 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)) + return bonus_match = re.search(r"mybonus.[\[\]::<>/a-zA-Z_\-=\"'\s#;.(使用魔力值豆]+\s*([\d,.]+)[<()&\s]", html_text) try: if bonus_match and bonus_match.group(1).strip(): self.bonus = text_tools.parse_float(bonus_match.group(1)) return - bonus_match = re.search(r"[魔力值|\]][\[\]::<>/a-zA-Z_\-=\"'\s#;]+\s*([\d,.]+|\"[\d,.]+\")[<>()&\s]", + bonus_match = re.search(r"(?:魔力值|\])[\[\]::<>/a-zA-Z_\-=\"'\s#;]+\s*([\d,.]+|\"[\d,.]+\")[<>()&\s]", html_text, flags=re.S) if bonus_match and bonus_match.group(1).strip(): @@ -365,8 +381,24 @@ class NexusPhpSiteUserInfo(SiteParserBase): # if seeding_url_text: # self._torrent_seeding_page = seeding_url_text + @staticmethod + def _parse_user_level_marker(page_text: str, userid: Optional[str]) -> Optional[str]: + """ + 解析 PTT-NP 首页中的 UID 与用户等级标记。 + """ + user_id_pattern = re.escape(str(userid)) if userid else r"\d+" + user_level_match = re.search( + rf"\[\s*UID\s*=\s*{user_id_pattern}\s*\]\s*\[\(\s*([^\)\]]+?)\s*\)", + page_text, + flags=re.IGNORECASE, + ) + return user_level_match.group(1).strip() if user_level_match else None + def _get_user_level(self, html): - # 等级 获取同一行等级数据,图片格式等级,取title信息,否则取文本信息 + """ + 从用户详情页或首页中解析用户等级。 + """ + # 等级获取同一行等级数据,图片格式等级取 title 信息,否则取文本信息。 user_levels_text = html.xpath('//tr/td[text()="等級" or text()="等级" or *[text()="等级"]]/' 'following-sibling::td[1]/img[1]/@title') if user_levels_text: @@ -395,6 +427,10 @@ class NexusPhpSiteUserInfo(SiteParserBase): self.user_level = user_level_match.group(1).strip() break + user_level = self._parse_user_level_marker(html.xpath("string(.)"), self.userid) + if user_level: + self.user_level = user_level + def _parse_message_unread_links(self, html_text: str, msg_links: list) -> Optional[str]: html = etree.HTML(html_text) try: diff --git a/tests/test_nexus_php_parser.py b/tests/test_nexus_php_parser.py index 6ce165284..aa8e26a4e 100644 --- a/tests/test_nexus_php_parser.py +++ b/tests/test_nexus_php_parser.py @@ -61,3 +61,42 @@ def test_nexus_php_seeding_next_page_checks_userid_parameter_name(): assert query_params["page"] == ["12"] assert query_params["type"] == ["seeding"] assert query_params["userid"] == ["12"] + + +def test_nexus_php_pttime_homepage_parses_bonus_and_user_level(): + """ + PTT-NP 首页应从魔力值容器和 UID 标记中解析魔力值、用户等级。 + """ + parser = _build_parser() + html_text = """ + + + + Opportunity + [UID=67537][(初中)Elite User] + + 魔力值(96.67魔力/小时) + [使用&说明]:14419.2 + + + + """ + + parser._parse_site_page(html_text) + parser._parse_user_base_info(html_text) + + assert parser.bonus == 14419.2 + assert parser.user_level == "初中" + + +def test_nexus_php_bonus_fallback_ignores_unrelated_script_numbers(): + """ + 魔力值兜底解析不得把页面脚本中的无关数字当作积分。 + """ + parser = _build_parser() + + parser._parse_user_traffic_info( + "" + ) + + assert parser.bonus == 0