From 0aa6292d3feba6e81e230883c0dac6f770a9cfaa Mon Sep 17 00:00:00 2001 From: Harold <8866033@gmail.com> Date: Sun, 26 Apr 2026 08:52:47 +0800 Subject: [PATCH] feat: add feikuai player parsing --- py/tests/test_飞快TV.py | 27 ++++++++++++++++++++++++++ py/飞快TV.py | 42 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/py/tests/test_飞快TV.py b/py/tests/test_飞快TV.py index 2aede7e..c5b3929 100644 --- a/py/tests/test_飞快TV.py +++ b/py/tests/test_飞快TV.py @@ -116,3 +116,30 @@ class TestFeikuaiSpider(unittest.TestCase): vod["vod_play_url"], "第1集$/vodplay/1-1-1.html#第2集$/vodplay/1-1-2.html$$$第1集$/vodplay/1-2-1.html$$$夸克资源$https://pan.quark.cn/s/demo1$$$百度合集$https://pan.baidu.com/s/demo2", ) + + def test_base64decode_decodes_fixture(self): + self.assertEqual( + self.spider._base64decode("aHR0cHM6Ly9jZG4uZXhhbXBsZS5jb20vdjIubTN1OA=="), + "https://cdn.example.com/v2.m3u8", + ) + + @patch.object(Spider, "_request_html") + def test_player_content_supports_encrypt_1_and_encrypt_2(self, mock_request_html): + mock_request_html.side_effect = [ + '', + '', + ] + direct = self.spider.playerContent("feikuai", "/vodplay/1-1-1.html", {}) + encoded = self.spider.playerContent("feikuai", "/vodplay/1-1-2.html", {}) + self.assertEqual(direct["parse"], 0) + self.assertEqual(direct["url"], "https://cdn.example.com/v1.m3u8") + self.assertEqual(encoded["parse"], 0) + self.assertEqual(encoded["url"], "https://cdn.example.com/v2.m3u8") + + @patch.object(Spider, "_request_html") + def test_player_content_falls_back_when_script_missing(self, mock_request_html): + mock_request_html.return_value = "empty" + result = self.spider.playerContent("feikuai", "/vodplay/1-1-3.html", {}) + self.assertEqual(result["parse"], 1) + self.assertEqual(result["jx"], 1) + self.assertEqual(result["url"], "https://feikuai.tv/vodplay/1-1-3.html") diff --git a/py/飞快TV.py b/py/飞快TV.py index 76e10fb..38d2585 100644 --- a/py/飞快TV.py +++ b/py/飞快TV.py @@ -1,7 +1,9 @@ # coding=utf-8 +import base64 +import json import re import sys -from urllib.parse import quote, urljoin +from urllib.parse import quote, unquote, urljoin from base.spider import Spider as BaseSpider @@ -134,6 +136,21 @@ class Spider(BaseSpider): def _join_group_urls(self, groups): return "$$$".join("#".join(group) for group in groups if group) + def _base64decode(self, value): + try: + return base64.b64decode(str(value or "")).decode("utf-8") + except Exception: + return "" + + def _extract_player_data(self, html): + matched = re.search(r"player_aaaa\s*=\s*(\{[\s\S]*?\})", str(html or "")) + if not matched: + return {} + try: + return json.loads(matched.group(1)) + except Exception: + return {} + def categoryContent(self, tid, pg, filter, extend): page = max(1, int(pg)) url = self.host + f"/vodshow/{tid}--------{page}---.html" @@ -213,3 +230,26 @@ class Spider(BaseSpider): "vod_play_url": self._join_group_urls(play_urls), } return {"list": [vod]} + + def playerContent(self, flag, id, vipFlags): + raw_id = str(id or "") + if raw_id.startswith(("http://", "https://")) and any( + raw_id.lower().split("?")[0].endswith(ext) for ext in [".m3u8", ".mp4", ".flv"] + ): + return {"parse": 0, "jx": 0, "url": raw_id} + + target = self.host + raw_id + data = self._extract_player_data(self._request_html(target)) + raw_url = str(data.get("url") or "") + encrypt = str(data.get("encrypt") or "") + media_url = "" + if encrypt == "1": + media_url = unquote(raw_url) + elif encrypt == "2": + media_url = unquote(self._base64decode(raw_url)) + elif raw_url: + media_url = raw_url + + if media_url.startswith("http"): + return {"parse": 0, "jx": 0, "url": media_url} + return {"parse": 1, "jx": 1, "url": target}