Merge branch 'heimao-app-spider'
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MODULE = SourceFileLoader("heimao_spider", str(ROOT / "黑猫APP.py")).load_module()
|
||||
Spider = MODULE.Spider
|
||||
|
||||
|
||||
class TestHeiMaoAppSpider(unittest.TestCase):
|
||||
def setUp(self):
|
||||
Spider._instance = None
|
||||
self.spider = Spider()
|
||||
self.spider.init()
|
||||
|
||||
def test_home_video_content_returns_empty_list(self):
|
||||
self.assertEqual(self.spider.homeVideoContent(), {"list": []})
|
||||
|
||||
def test_build_api_url_uses_default_getappapi_path(self):
|
||||
self.assertEqual(
|
||||
self.spider._build_api_url("initV119"),
|
||||
"http://app1-0-0.87333.cc/api.php/getappapi.index/initV119",
|
||||
)
|
||||
|
||||
def test_aes_encrypt_and_decrypt_round_trip(self):
|
||||
encrypted = self.spider._aes_encrypt('{"msg":"ok"}')
|
||||
self.assertNotEqual(encrypted, '{"msg":"ok"}')
|
||||
self.assertEqual(self.spider._aes_decrypt(encrypted), '{"msg":"ok"}')
|
||||
|
||||
def test_replace_code_normalizes_common_ocr_misreads(self):
|
||||
self.assertEqual(self.spider._replace_code("5y6口"), "5960")
|
||||
|
||||
@patch.object(Spider, "_api_post")
|
||||
def test_home_content_filters_categories_merges_area_and_inserts_current_year(self, mock_api_post):
|
||||
mock_api_post.return_value = {
|
||||
"type_list": [
|
||||
{
|
||||
"type_id": "6",
|
||||
"type_name": "伦理",
|
||||
"recommend_list": [],
|
||||
"filter_type_list": [],
|
||||
},
|
||||
{
|
||||
"type_id": "3",
|
||||
"type_name": "综艺",
|
||||
"recommend_list": [{"vod_id": "z1"}],
|
||||
"filter_type_list": [
|
||||
{"name": "area", "list": ["全部", "中国大陆", "香港"]},
|
||||
{"name": "year", "list": ["全部", "2025", "2024"]},
|
||||
{"name": "sort", "list": ["最新", "最热"]},
|
||||
],
|
||||
},
|
||||
{
|
||||
"type_id": "1",
|
||||
"type_name": "电影",
|
||||
"recommend_list": [{"vod_id": "m1"}],
|
||||
"filter_type_list": [],
|
||||
},
|
||||
],
|
||||
"config": {},
|
||||
}
|
||||
content = self.spider.homeContent(False)
|
||||
self.assertEqual([item["type_name"] for item in content["class"]], ["电影", "综艺"])
|
||||
self.assertEqual(content["list"], [{"vod_id": "z1"}, {"vod_id": "m1"}])
|
||||
area_values = content["filters"]["3"][0]["value"]
|
||||
year_values = content["filters"]["3"][1]["value"]
|
||||
self.assertEqual(area_values[1], {"n": "大陆", "v": "大陆"})
|
||||
self.assertIn({"n": str(datetime.now().year), "v": str(datetime.now().year)}, year_values)
|
||||
self.assertEqual(content["filters"]["3"][2]["key"], "by")
|
||||
|
||||
@patch.object(Spider, "_api_post")
|
||||
def test_category_content_maps_by_to_sort_and_omits_pagecount(self, mock_api_post):
|
||||
mock_api_post.return_value = {
|
||||
"recommend_list": [{"vod_id": "movie-1", "vod_name": "分类片"}]
|
||||
}
|
||||
result = self.spider.categoryContent(
|
||||
"1",
|
||||
"2",
|
||||
False,
|
||||
{"area": "香港", "year": "2025", "by": "最热", "lang": "粤语", "class": "动作"},
|
||||
)
|
||||
self.assertEqual(
|
||||
mock_api_post.call_args.args,
|
||||
(
|
||||
"typeFilterVodList",
|
||||
{
|
||||
"type_id": "1",
|
||||
"page": "2",
|
||||
"area": "香港",
|
||||
"year": "2025",
|
||||
"sort": "最热",
|
||||
"lang": "粤语",
|
||||
"class": "动作",
|
||||
},
|
||||
),
|
||||
)
|
||||
self.assertEqual(result["page"], 2)
|
||||
self.assertEqual(result["list"][0]["vod_id"], "movie-1")
|
||||
self.assertNotIn("pagecount", result)
|
||||
|
||||
@patch.object(Spider, "_api_post")
|
||||
def test_category_content_merges_mainland_results_and_deduplicates(self, mock_api_post):
|
||||
mock_api_post.side_effect = [
|
||||
{"recommend_list": [{"vod_id": "a"}, {"vod_id": "b"}]},
|
||||
{"recommend_list": [{"vod_id": "b"}, {"vod_id": "c"}]},
|
||||
{"recommend_list": [{"vod_id": "c"}, {"vod_id": "d"}]},
|
||||
]
|
||||
result = self.spider.categoryContent("1", "1", False, {"area": "大陆"})
|
||||
self.assertEqual([item["vod_id"] for item in result["list"]], ["a", "b", "c", "d"])
|
||||
self.assertEqual(
|
||||
[call.args[1]["area"] for call in mock_api_post.call_args_list],
|
||||
["中国大陆", "大陆", "内地"],
|
||||
)
|
||||
|
||||
@patch.object(Spider, "_api_post")
|
||||
def test_search_content_filters_ethics_and_non_matching_items(self, mock_api_post):
|
||||
mock_api_post.return_value = {
|
||||
"search_list": [
|
||||
{"vod_id": "1", "vod_name": "繁花", "vod_pic": "p1", "vod_year": "2024", "vod_class": "剧情"},
|
||||
{"vod_id": "2", "vod_name": "别的片", "vod_pic": "p2", "vod_year": "2024", "vod_class": "伦理"},
|
||||
{"vod_id": "3", "vod_name": "完全无关", "vod_pic": "p3", "vod_year": "2023", "vod_class": "动作"},
|
||||
]
|
||||
}
|
||||
result = self.spider.searchContent("繁花", False, "1")
|
||||
self.assertEqual(
|
||||
result["list"],
|
||||
[{"vod_id": "1", "vod_name": "繁花", "vod_pic": "p1", "vod_remarks": "2024 剧情"}],
|
||||
)
|
||||
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",
|
||||
)
|
||||
|
||||
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__":
|
||||
unittest.main()
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
# coding=utf-8
|
||||
import base64
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from urllib.parse import unquote
|
||||
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives.padding import PKCS7
|
||||
|
||||
from base.spider import Spider as BaseSpider
|
||||
|
||||
sys.path.append("..")
|
||||
|
||||
|
||||
class Spider(BaseSpider):
|
||||
def __init__(self):
|
||||
self.name = "黑猫APP"
|
||||
self.host = "http://app1-0-0.87333.cc"
|
||||
self.api_path = "/api.php/getappapi"
|
||||
self.user_agent = "okhttp/3.10.0"
|
||||
self.aes_key = "VwsHxkCViDXEExWa"
|
||||
self.aes_iv = "VwsHxkCViDXEExWa"
|
||||
self.category_config = {
|
||||
"blockedNames": ["伦理"],
|
||||
"renameMap": {},
|
||||
"forceOrder": ["电影", "连续剧", "综艺", "动漫", "短剧", "直播"],
|
||||
}
|
||||
self.area_merge_config = {
|
||||
"enabled": True,
|
||||
"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"
|
||||
self.init_api = "initV119"
|
||||
self.search_verify = False
|
||||
return None
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def _headers(self):
|
||||
return {
|
||||
"User-Agent": self.user_agent,
|
||||
"Accept-Encoding": "gzip",
|
||||
}
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {"list": []}
|
||||
|
||||
def _build_api_url(self, endpoint):
|
||||
path = str(endpoint or "").lstrip("/")
|
||||
return f"{self.host}{self.api_path}.index/{path}"
|
||||
|
||||
def _api_post(self, endpoint, payload=None):
|
||||
data = {} if payload is None else dict(payload)
|
||||
response = self.post(
|
||||
self._build_api_url(endpoint),
|
||||
data=data,
|
||||
headers=self._headers(),
|
||||
timeout=10,
|
||||
verify=False,
|
||||
)
|
||||
body = json.loads(response.text or "{}")
|
||||
if not body.get("data"):
|
||||
return None
|
||||
return json.loads(self._aes_decrypt(body["data"]))
|
||||
|
||||
def _aes_encrypt(self, text):
|
||||
padder = PKCS7(128).padder()
|
||||
data = padder.update(str(text).encode("utf-8")) + padder.finalize()
|
||||
cipher = Cipher(algorithms.AES(self.aes_key.encode("utf-8")), modes.CBC(self.aes_iv.encode("utf-8")))
|
||||
encryptor = cipher.encryptor()
|
||||
return base64.b64encode(encryptor.update(data) + encryptor.finalize()).decode("utf-8")
|
||||
|
||||
def _aes_decrypt(self, text):
|
||||
data = base64.b64decode(str(text or ""))
|
||||
cipher = Cipher(algorithms.AES(self.aes_key.encode("utf-8")), modes.CBC(self.aes_iv.encode("utf-8")))
|
||||
decryptor = cipher.decryptor()
|
||||
padded = decryptor.update(data) + decryptor.finalize()
|
||||
unpadder = PKCS7(128).unpadder()
|
||||
return (unpadder.update(padded) + unpadder.finalize()).decode("utf-8")
|
||||
|
||||
def _replace_code(self, text):
|
||||
replacements = {
|
||||
"y": "9",
|
||||
"口": "0",
|
||||
"q": "0",
|
||||
"u": "0",
|
||||
"o": "0",
|
||||
">": "1",
|
||||
"d": "0",
|
||||
"b": "8",
|
||||
"已": "2",
|
||||
"D": "0",
|
||||
"五": "5",
|
||||
}
|
||||
return "".join(replacements.get(char, char) for char in str(text or ""))
|
||||
|
||||
def _process_classes(self, type_list):
|
||||
blocked = set(self.category_config.get("blockedNames", []))
|
||||
rename_map = self.category_config.get("renameMap", {})
|
||||
order_map = {name: index for index, name in enumerate(self.category_config.get("forceOrder", []))}
|
||||
classes = [
|
||||
{
|
||||
"type_id": item.get("type_id", ""),
|
||||
"type_name": rename_map.get(item.get("type_name", ""), item.get("type_name", "")),
|
||||
}
|
||||
for item in type_list
|
||||
if item.get("type_name") not in blocked
|
||||
]
|
||||
classes.sort(key=lambda item: order_map.get(item["type_name"], 999))
|
||||
return classes
|
||||
|
||||
def _merge_area_filter(self, values):
|
||||
merge_values = set(self.area_merge_config.get("mergeList", []))
|
||||
raw_values = list(values)
|
||||
if not self.area_merge_config.get("enabled") or not any(value in merge_values for value in raw_values):
|
||||
return raw_values
|
||||
result = [value for value in raw_values if value not in merge_values]
|
||||
index = result.index("全部") + 1 if "全部" in result else 0
|
||||
result.insert(index, self.area_merge_config.get("displayName", "大陆"))
|
||||
return result
|
||||
|
||||
def _convert_filters(self, type_list):
|
||||
name_map = {"class": "类型", "area": "地区", "lang": "语言", "year": "年份", "sort": "排序"}
|
||||
current_year = str(datetime.now().year)
|
||||
filters = {}
|
||||
for item in type_list:
|
||||
entries = []
|
||||
for config in item.get("filter_type_list", []):
|
||||
raw_name = config.get("name", "")
|
||||
values = list(config.get("list", []))
|
||||
if raw_name == "area":
|
||||
values = self._merge_area_filter(values)
|
||||
if raw_name == "year" and current_year not in values:
|
||||
index = values.index("全部") + 1 if "全部" in values else 0
|
||||
values.insert(index, current_year)
|
||||
entries.append(
|
||||
{
|
||||
"key": "by" if raw_name == "sort" else raw_name,
|
||||
"name": name_map.get(raw_name, raw_name),
|
||||
"value": [{"n": value, "v": value} for value in values],
|
||||
}
|
||||
)
|
||||
filters[item.get("type_id", "")] = entries
|
||||
return filters
|
||||
|
||||
def homeContent(self, filter):
|
||||
init_data = self._api_post(self.init_api) or {"type_list": [], "config": {}}
|
||||
self.search_verify = bool(init_data.get("config", {}).get("system_search_verify_status"))
|
||||
return {
|
||||
"class": self._process_classes(init_data.get("type_list", [])),
|
||||
"filters": self._convert_filters(init_data.get("type_list", [])),
|
||||
"list": [vod for item in init_data.get("type_list", []) for vod in item.get("recommend_list", [])],
|
||||
}
|
||||
|
||||
def _page_result(self, items, pg, limit=90, total=None):
|
||||
page = int(pg)
|
||||
return {
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total": len(items) if total is None else total,
|
||||
"list": items,
|
||||
}
|
||||
|
||||
def _merge_area_search(self, tid, pg, extend):
|
||||
seen = set()
|
||||
merged = []
|
||||
for area in self.area_merge_config.get("mergeList", []):
|
||||
payload = {
|
||||
"type_id": str(tid),
|
||||
"page": str(pg),
|
||||
"area": area,
|
||||
"year": extend.get("year", "全部"),
|
||||
"sort": extend.get("by", "最新"),
|
||||
"lang": extend.get("lang", "全部"),
|
||||
"class": extend.get("class", "全部"),
|
||||
}
|
||||
result = self._api_post("typeFilterVodList", payload) or {}
|
||||
for item in result.get("recommend_list", []):
|
||||
vod_id = item.get("vod_id")
|
||||
if vod_id in seen:
|
||||
continue
|
||||
seen.add(vod_id)
|
||||
merged.append(item)
|
||||
return merged
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
options = dict(extend or {})
|
||||
if options.get("area") == self.area_merge_config.get("displayName"):
|
||||
return self._page_result(self._merge_area_search(tid, pg, options), pg)
|
||||
payload = {
|
||||
"type_id": str(tid),
|
||||
"page": str(pg),
|
||||
"area": options.get("area", "全部"),
|
||||
"year": options.get("year", "全部"),
|
||||
"sort": options.get("by", "最新"),
|
||||
"lang": options.get("lang", "全部"),
|
||||
"class": options.get("class", "全部"),
|
||||
}
|
||||
result = self._api_post("typeFilterVodList", payload) or {}
|
||||
return self._page_result(result.get("recommend_list", []), pg)
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
payload = {
|
||||
"keywords": str(key or ""),
|
||||
"type_id": "0",
|
||||
"page": str(pg),
|
||||
}
|
||||
result = self._api_post(self.search_api, payload) or {}
|
||||
keyword = str(key or "").strip().lower()
|
||||
items = []
|
||||
for item in result.get("search_list", []):
|
||||
vod_class = str(item.get("vod_class", ""))
|
||||
text = " ".join(
|
||||
[
|
||||
str(item.get("vod_name", "")),
|
||||
str(item.get("vod_remarks", "")),
|
||||
vod_class,
|
||||
]
|
||||
).lower()
|
||||
if "伦理" in vod_class:
|
||||
continue
|
||||
if keyword and keyword not in text:
|
||||
continue
|
||||
items.append(
|
||||
{
|
||||
"vod_id": item.get("vod_id", ""),
|
||||
"vod_name": item.get("vod_name", ""),
|
||||
"vod_pic": item.get("vod_pic", ""),
|
||||
"vod_remarks": " ".join(
|
||||
value for value in [str(item.get("vod_year", "")), vod_class] if value
|
||||
).strip(),
|
||||
}
|
||||
)
|
||||
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 _player_headers(self):
|
||||
return {"User-Agent": "Dalvik/2.1.0 (Linux; Android 14)"}
|
||||
|
||||
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}
|
||||
|
||||
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": {}}
|
||||
Reference in New Issue
Block a user