feat: add heimao player parsing

This commit is contained in:
Harold
2026-04-20 11:52:53 +08:00
parent eda3ef2ada
commit 5cab55eba6
2 changed files with 103 additions and 0 deletions
+60
View File
@@ -2,6 +2,7 @@ import unittest
from datetime import datetime from datetime import datetime
from importlib.machinery import SourceFileLoader from importlib.machinery import SourceFileLoader
from pathlib import Path from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch from unittest.mock import patch
@@ -190,6 +191,65 @@ class TestHeiMaoAppSpider(unittest.TestCase):
"$$$蓝光$高清线路12@@direct@@https://parser-c/?url=,https%3A%2F%2Fplay-c,token+tk-c,2,2", "$$$蓝光$高清线路12@@direct@@https://parser-c/?url=,https%3A%2F%2Fplay-c,token+tk-c,2,2",
) )
def test_player_content_returns_direct_url_for_parse_type_zero(self):
result = self.spider.playerContent(
"🐈‍⬛高清线路",
"高清线路1@@direct@@https://parser/?url=,https%3A%2F%2Fvideo.example%2Fraw.m3u8,token+abc,1,0",
"",
)
self.assertEqual(
result,
{
"parse": 0,
"jx": 0,
"url": "https://video.example/raw.m3u8",
"header": {"User-Agent": "Dalvik/2.1.0 (Linux; Android 14)"},
},
)
def test_player_content_returns_parser_url_for_parse_type_two(self):
result = self.spider.playerContent(
"🐈‍⬛高清线路",
"高清线路1@@direct@@https://parser/?url=,https%3A%2F%2Fvideo.example%2Fneed-jx.m3u8,token+abc,1,2",
"",
)
self.assertEqual(
result,
{
"parse": 1,
"jx": 1,
"url": "https://parser/?url=https://video.example/need-jx.m3u8",
"header": {"User-Agent": "Dalvik/2.1.0 (Linux; Android 14)"},
},
)
@patch.object(Spider, "fetch")
def test_player_content_uses_player_parse_type_two_direct_json(self, mock_fetch):
mock_fetch.return_value = SimpleNamespace(status_code=200, text='{"url":"https://video.example/direct.m3u8"}')
result = self.spider.playerContent(
"🐈‍⬛高清线路",
"高清线路1@@direct@@https://parser/?url=,https%3A%2F%2Fvideo.example%2Fwrapped,token+abc,2,1",
"",
)
self.assertEqual(result["parse"], 0)
self.assertEqual(result["jx"], 0)
self.assertEqual(result["url"], "https://video.example/direct.m3u8")
@patch.object(Spider, "_api_post")
def test_player_content_falls_back_to_vod_parse(self, mock_api_post):
mock_api_post.return_value = {"json": '{"url":"https://video.example/final.m3u8"}'}
result = self.spider.playerContent(
"🐈‍⬛高清线路",
"高清线路1@@direct@@https://parser/?url=,https%3A%2F%2Fvideo.example%2Fencrypted,token+abc,1,1",
"",
)
self.assertEqual(result["parse"], 0)
self.assertEqual(result["jx"], 0)
self.assertEqual(result["url"], "https://video.example/final.m3u8")
self.assertEqual(mock_api_post.call_args.args[0], "vodParse")
self.assertEqual(mock_api_post.call_args.args[1]["parse_api"], "https://parser/?url=")
self.assertEqual(mock_api_post.call_args.args[1]["token"], "abc")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+43
View File
@@ -3,6 +3,7 @@ import base64
import json import json
import sys import sys
from datetime import datetime from datetime import datetime
from urllib.parse import unquote
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.padding import PKCS7 from cryptography.hazmat.primitives.padding import PKCS7
@@ -261,6 +262,9 @@ class Spider(BaseSpider):
config = self._get_line_config(name) config = self._get_line_config(name)
return bool(config) and config.get("enabled") is False return bool(config) and config.get("enabled") is False
def _player_headers(self):
return {"User-Agent": "Dalvik/2.1.0 (Linux; Android 14)"}
def detailContent(self, ids): def detailContent(self, ids):
items = [] items = []
for vod_id in ids: for vod_id in ids:
@@ -319,3 +323,42 @@ class Spider(BaseSpider):
} }
) )
return {"list": items} return {"list": items}
def playerContent(self, flag, id, vipFlags):
parts = str(id or "").split("@@", 2)
line_name = parts[0] if len(parts) > 0 else ""
mode = parts[1] if len(parts) > 1 else "direct"
payload = parts[2] if len(parts) > 2 else ""
if self._line_blocked(line_name):
return {"parse": 0, "jx": 0, "url": "", "header": {}}
if mode == "auto":
return {"parse": 0, "jx": 0, "url": "", "header": {}}
fields = payload.split(",", 4)
while len(fields) < 5:
fields.append("")
parse_api, play_url, token_with_prefix, player_parse_type, parse_type = fields
real_url = unquote(play_url)
token = token_with_prefix.replace("token+", "", 1)
if parse_type == "0":
return {"parse": 0, "jx": 0, "url": real_url, "header": self._player_headers()}
if parse_type == "2":
return {"parse": 1, "jx": 1, "url": parse_api + real_url, "header": self._player_headers()}
if player_parse_type == "2":
response = self.fetch(parse_api + real_url, headers=self._headers(), timeout=10, verify=False)
data = json.loads(response.text or "{}")
if data.get("url"):
return {"parse": 0, "jx": 0, "url": data.get("url", ""), "header": {}}
result = self._api_post(
"vodParse",
{
"parse_api": parse_api,
"url": self._aes_encrypt(real_url),
"player_parse_type": player_parse_type,
"token": token,
},
) or {}
try:
inner = json.loads(result.get("json", "{}"))
except Exception:
inner = {}
return {"parse": 0, "jx": 0, "url": inner.get("url", ""), "header": {}}