From eda3ef2adaa9b977e4a11128d31d48bb2b21b21e Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Mon, 20 Apr 2026 11:51:41 +0800 Subject: [PATCH] feat: add heimao detail parsing --- py/tests/test_黑猫APP.py | 59 ++++++++++++++++++++++++++++ py/黑猫APP.py | 83 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/py/tests/test_黑猫APP.py b/py/tests/test_黑猫APP.py index 18edcec..c95a626 100644 --- a/py/tests/test_黑猫APP.py +++ b/py/tests/test_黑猫APP.py @@ -131,6 +131,65 @@ class TestHeiMaoAppSpider(unittest.TestCase): ) self.assertNotIn("pagecount", result) + @patch.object(Spider, "_api_post") + def test_detail_content_falls_back_to_second_endpoint_and_sorts_lines(self, mock_api_post): + mock_api_post.side_effect = [ + None, + { + "vod": { + "vod_name": "示例影片", + "vod_pic": "poster.jpg", + "vod_remarks": "更新至10集", + "vod_content": "一段剧情", + "vod_actor": "演员甲/演员乙", + "vod_director": "导演甲", + "vod_year": "2025", + "vod_area": "中国大陆", + }, + "vod_play_list": [ + { + "player_info": { + "show": "高清线路1", + "parse": "https://parser-a/?url=", + "player_parse_type": "1", + "parse_type": "1", + }, + "urls": [{"name": "正片", "url": "https%3A%2F%2Fplay-a", "token": "tk-a"}], + }, + { + "player_info": { + "show": "防走丢加群", + "parse": "https://parser-b/?url=", + "player_parse_type": "1", + "parse_type": "0", + }, + "urls": [{"name": "备用", "url": "https%3A%2F%2Fplay-b", "token": "tk-b"}], + }, + { + "player_info": { + "show": "高清线路1", + "parse": "https://parser-c/?url=", + "player_parse_type": "2", + "parse_type": "2", + }, + "urls": [{"name": "蓝光", "url": "https%3A%2F%2Fplay-c", "token": "tk-c"}], + }, + ], + }, + ] + result = self.spider.detailContent(["123"]) + vod = result["list"][0] + self.assertEqual(vod["vod_id"], "123") + self.assertEqual(vod["vod_name"], "示例影片") + self.assertEqual(vod["vod_year"], "2025年") + self.assertEqual(vod["vod_play_from"], "🐈‍⬛高清线路$$$1线$$$高清线路12") + self.assertEqual( + vod["vod_play_url"], + "正片$高清线路1@@direct@@https://parser-a/?url=,https%3A%2F%2Fplay-a,token+tk-a,1,1" + "$$$备用$1线@@direct@@https://parser-b/?url=,https%3A%2F%2Fplay-b,token+tk-b,1,0" + "$$$蓝光$高清线路12@@direct@@https://parser-c/?url=,https%3A%2F%2Fplay-c,token+tk-c,2,2", + ) + if __name__ == "__main__": unittest.main() diff --git a/py/黑猫APP.py b/py/黑猫APP.py index 50d3e33..9b4d1b1 100644 --- a/py/黑猫APP.py +++ b/py/黑猫APP.py @@ -30,6 +30,11 @@ class Spider(BaseSpider): "displayName": "大陆", "mergeList": ["中国大陆", "大陆", "内地"], } + self.line_config = { + "高清线路1": {"displayName": "🐈‍⬛高清线路", "order": 301, "mode": "direct", "enabled": True}, + "标清线路2": {"displayName": "🐈‍⬛标清线路1", "order": 302, "mode": "direct", "enabled": True}, + "标清线路3": {"displayName": "🐈‍⬛标清线路2", "order": 303, "mode": "direct", "enabled": True}, + } def init(self, extend=""): self.search_api = "searchList" @@ -236,3 +241,81 @@ class Spider(BaseSpider): } ) return self._page_result(items, pg) + + def _get_line_config(self, name): + return self.line_config.get(str(name or "")) + + def _line_display(self, name): + config = self._get_line_config(name) + return config.get("displayName", name) if config else name + + def _line_order(self, name): + config = self._get_line_config(name) + return config.get("order", 999) if config else 999 + + def _line_mode(self, name): + config = self._get_line_config(name) + return config.get("mode", "direct") if config else "direct" + + def _line_blocked(self, name): + config = self._get_line_config(name) + return bool(config) and config.get("enabled") is False + + def detailContent(self, ids): + items = [] + for vod_id in ids: + payload = {"vod_id": str(vod_id)} + data = self._api_post("vodDetail", payload) or self._api_post("vodDetail2", payload) + if not data: + continue + lines = [] + name_count = {} + fallback_index = 1 + for line in data.get("vod_play_list", []): + player_info = line.get("player_info", {}) + raw_name = str(player_info.get("show", "")) + if any(keyword in raw_name for keyword in ["防走丢", "群", "防失群", "官网"]): + raw_name = f"{fallback_index}线" + fallback_index += 1 + name_count[raw_name] = name_count.get(raw_name, 0) + 1 + line_name = raw_name if name_count[raw_name] == 1 else f"{raw_name}{name_count[raw_name]}" + if self._line_blocked(line_name): + continue + urls = [] + for vod in line.get("urls", []): + payload_text = ",".join( + [ + str(player_info.get("parse", "")), + str(vod.get("url", "")), + "token+" + str(vod.get("token", "")), + str(player_info.get("player_parse_type", "")), + str(player_info.get("parse_type", "")), + ] + ) + urls.append(f"{vod.get('name', '')}${line_name}@@{self._line_mode(line_name)}@@{payload_text}") + if urls: + lines.append( + { + "display": self._line_display(line_name), + "order": self._line_order(line_name), + "urls": "#".join(urls), + } + ) + lines.sort(key=lambda item: item["order"]) + vod = data.get("vod", {}) + items.append( + { + "vod_id": str(vod_id), + "vod_name": vod.get("vod_name", ""), + "vod_pic": vod.get("vod_pic", ""), + "vod_remarks": vod.get("vod_remarks", ""), + "vod_content": vod.get("vod_content", ""), + "vod_actor": str(vod.get("vod_actor", "")).replace("演员", ""), + "vod_director": str(vod.get("vod_director", "")).replace("导演", ""), + "vod_year": f"{vod.get('vod_year', '')}年" if vod.get("vod_year") else "", + "vod_area": vod.get("vod_area", ""), + "vod_play_from": "$$$".join(line["display"] for line in lines), + "vod_play_url": "$$$".join(line["urls"] for line in lines), + } + ) + return {"list": items}