From aaa0c080eae8a7097417f89ae14c512cfda525a9 Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Sun, 19 Apr 2026 17:58:00 +0800 Subject: [PATCH] =?UTF-8?q?LibVIO=E8=A7=A3=E6=9E=90=E7=BD=91=E7=9B=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- py/libvio.py | 46 ++++++++++++++++++++++++++-- py/tests/test_libvio.py | 68 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 103 insertions(+), 11 deletions(-) diff --git a/py/libvio.py b/py/libvio.py index 568212c..3aaa8d8 100644 --- a/py/libvio.py +++ b/py/libvio.py @@ -12,7 +12,7 @@ sys.path.append("..") class Spider(BaseSpider): def __init__(self): self.name = "LibVIO" - self.host = "https://libvio.site" + self.host = "https://www.libvio.la" self.headers = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " @@ -181,6 +181,13 @@ class Spider(BaseSpider): seen.add(play_id) episodes.append(f"{name}${play_id}") + netdisk_groups = self._extract_netdisk_sources(html) + play_from = ["LibVIO"] if episodes else [] + play_url = ["#".join(episodes)] if episodes else [] + for group in netdisk_groups: + play_from.append(group["from"]) + play_url.append(group["urls"]) + vod = { "vod_id": vod_id, "path": self._build_detail_request_url(vod_id), @@ -189,8 +196,8 @@ class Spider(BaseSpider): "vod_tag": "", "vod_time": "", "vod_remarks": "", - "vod_play_from": "LibVIO", - "vod_play_url": "#".join(episodes), + "vod_play_from": "$$$".join(play_from), + "vod_play_url": "$$$".join(play_url), "type_name": self._extract_detail_field(detail_root, "类型"), "vod_content": self._extract_detail_field(detail_root, "简介"), "vod_year": self._extract_detail_field(detail_root, "年份"), @@ -201,6 +208,33 @@ class Spider(BaseSpider): } return {"list": [vod]} + def _extract_netdisk_sources(self, html): + root = self.html(html) + if root is None: + return [] + + groups = [] + for panel in root.xpath("//*[contains(@class,'netdisk-panel')]"): + heading = self._clean_text("".join(panel.xpath(".//h3[1]//text()"))) + matched = re.search(r"\(([^()]+)\)", heading) + line_name = self._clean_text(matched.group(1) if matched else heading.replace("视频下载", "")) or "网盘资源" + + entries = [] + seen_links = set() + for anchor in panel.xpath(".//a[contains(@class,'netdisk-item')]"): + href = ((anchor.xpath("./@href") or [""])[0]).strip() + url_text = self._clean_text("".join(anchor.xpath(".//*[contains(@class,'netdisk-url')][1]//text()"))) + link = url_text or href + if not link or link in seen_links: + continue + seen_links.add(link) + title = self._clean_text("".join(anchor.xpath(".//*[contains(@class,'netdisk-name')][1]//text()"))) or line_name + entries.append(f"{title}${link}") + + if entries: + groups.append({"from": line_name, "urls": "#".join(entries)}) + return groups + def homeVideoContent(self): html = self._request_html("/", expect_xpath="//*[contains(@class,'stui-vodlist__box')]") return {"list": self._parse_list_cards(html)} @@ -256,6 +290,12 @@ class Spider(BaseSpider): return self._request_html(f"/static/player/{source}.js", referer=self.host + "/") def playerContent(self, flag, id, vipFlags): + raw_id = str(id or "").strip() + if raw_id.startswith(("https://", "http://")) and any( + token in raw_id for token in ("drive.uc.cn", "pan.quark.cn", "pan.baidu.com", "alipan.com", "aliyundrive.com") + ): + return {"parse": 0, "playUrl": "", "url": raw_id} + play_page_url = self._build_play_request_url(id) detail_html = self._request_html(play_page_url, referer=self.host + "/") config = self._parse_player_config(detail_html) diff --git a/py/tests/test_libvio.py b/py/tests/test_libvio.py index 050ab1b..e350e85 100644 --- a/py/tests/test_libvio.py +++ b/py/tests/test_libvio.py @@ -33,7 +33,7 @@ class TestLibVioSpider(unittest.TestCase): { "vod_id": "456", "vod_name": "示例影片", - "vod_pic": "https://libvio.site/cover.jpg", + "vod_pic": "https://www.libvio.la/cover.jpg", "vod_remarks": "更新至10集", } ], @@ -75,7 +75,7 @@ class TestLibVioSpider(unittest.TestCase): self.assertEqual(result["list"][0]["vod_id"], "333") self.assertEqual(result["list"][0]["vod_name"], "搜索影片") - def test_parse_detail_page_extracts_fields_and_filters_pan_sources(self): + def test_parse_detail_page_extracts_fields_and_preserves_pan_sources(self): html = """
@@ -98,11 +98,24 @@ class TestLibVioSpider(unittest.TestCase): +
+
+
+

视频下载(UC)

+
+
+ +
""" result = self.spider._parse_detail_page(html, "999") vod = result["list"][0] self.assertEqual(vod["vod_id"], "999") - self.assertEqual(vod["path"], "https://libvio.site/detail/999.html") + self.assertEqual(vod["path"], "https://www.libvio.la/detail/999.html") self.assertEqual(vod["vod_name"], "示例剧") self.assertEqual(vod["type_name"], "剧情") self.assertEqual(vod["vod_area"], "大陆") @@ -110,8 +123,36 @@ class TestLibVioSpider(unittest.TestCase): self.assertEqual(vod["vod_director"], "张三") self.assertEqual(vod["vod_actor"], "李四,王五") self.assertEqual(vod["vod_content"], "一段剧情简介") - self.assertEqual(vod["vod_play_from"], "LibVIO") - self.assertEqual(vod["vod_play_url"], "第1集$999-1-1#第2集$999-1-2") + self.assertEqual(vod["vod_play_from"], "LibVIO$$$UC") + self.assertEqual( + vod["vod_play_url"], + "第1集$999-1-1#第2集$999-1-2$$$合集$https://drive.uc.cn/s/e1532998c2bf4?public=1", + ) + + def test_extract_netdisk_sources_deduplicates_same_link(self): + html = """ +
+
+
+

视频下载(UC)

+
+
+ +
+ """ + self.assertEqual( + self.spider._extract_netdisk_sources(html), + [{"from": "UC", "urls": "合集$https://drive.uc.cn/s/e1532998c2bf4?public=1"}], + ) @patch.object(Spider, "_request_html") def test_detail_content_builds_detail_request_url_from_vod_id(self, mock_request_html): @@ -122,7 +163,7 @@ class TestLibVioSpider(unittest.TestCase): """ result = self.spider.detailContent(["123"]) - self.assertEqual(mock_request_html.call_args.args[0], "https://libvio.site/detail/123.html") + self.assertEqual(mock_request_html.call_args.args[0], "https://www.libvio.la/detail/123.html") self.assertEqual(result["list"][0]["vod_id"], "123") def test_extract_player_config_reads_json_assignment(self): @@ -132,7 +173,7 @@ class TestLibVioSpider(unittest.TestCase): def test_extract_play_api_base_reads_player_js(self): body = 'var player={}; src="/player/api.php?url=";' - self.assertEqual(self.spider._extract_play_api_base(body), "https://libvio.site/player/api.php?url=") + self.assertEqual(self.spider._extract_play_api_base(body), "https://www.libvio.la/player/api.php?url=") @patch.object(Spider, "_request_html") def test_player_content_resolves_direct_api_url(self, mock_request_html): @@ -144,13 +185,24 @@ class TestLibVioSpider(unittest.TestCase): result = self.spider.playerContent("LibVIO", "999-1-1", {}) self.assertEqual(result["parse"], 0) self.assertEqual(result["url"], "https://video.example/final.m3u8") - self.assertEqual(result["header"]["Referer"], "https://libvio.site/") + self.assertEqual(result["header"]["Referer"], "https://www.libvio.la/") @patch.object(Spider, "_request_html") def test_player_content_returns_empty_for_pan_source(self, mock_request_html): mock_request_html.return_value = '' self.assertEqual(self.spider.playerContent("LibVIO", "999-1-1", {}), {"parse": 0, "playUrl": "", "url": ""}) + def test_player_content_returns_direct_netdisk_link(self): + result = self.spider.playerContent("UC", "https://drive.uc.cn/s/e1532998c2bf4?public=1", {}) + self.assertEqual( + result, + { + "parse": 0, + "playUrl": "", + "url": "https://drive.uc.cn/s/e1532998c2bf4?public=1", + }, + ) + if __name__ == "__main__": unittest.main()