人人视频
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
import base64
|
||||
import json
|
||||
import unittest
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MODULE = SourceFileLoader("rrmj_spider", str(ROOT / "人人视频.py")).load_module()
|
||||
Spider = MODULE.Spider
|
||||
|
||||
|
||||
def encrypt_ecb_payload(payload):
|
||||
cipher = AES.new(MODULE.AES_KEY.encode("utf-8"), AES.MODE_ECB)
|
||||
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
||||
return base64.b64encode(cipher.encrypt(pad(body, AES.block_size))).decode("utf-8")
|
||||
|
||||
|
||||
def encrypt_cbc_payload(value, new_sign):
|
||||
key = new_sign[4:20].encode("utf-8")
|
||||
cipher = AES.new(key, AES.MODE_CBC, MODULE.AES_IV.encode("utf-8"))
|
||||
return base64.b64encode(cipher.encrypt(pad(value.encode("utf-8"), AES.block_size))).decode("utf-8")
|
||||
|
||||
|
||||
class TestRenRenShiPinSpider(unittest.TestCase):
|
||||
def setUp(self):
|
||||
Spider._instance = None
|
||||
self.spider = Spider()
|
||||
self.spider.init()
|
||||
|
||||
def test_module_uses_pycryptodome_and_hmac(self):
|
||||
source = (ROOT / "人人视频.py").read_text(encoding="utf-8")
|
||||
self.assertIn("from Crypto.Cipher import AES", source)
|
||||
self.assertIn("from Crypto.Util.Padding import pad, unpad", source)
|
||||
self.assertIn("import hmac", source)
|
||||
|
||||
def test_home_video_content_returns_empty_list(self):
|
||||
self.assertEqual(self.spider.homeVideoContent(), {"list": []})
|
||||
|
||||
@patch("rrmj_spider.time.time", return_value=1713523200.123)
|
||||
def test_create_headers_signs_sorted_params(self, mock_time):
|
||||
headers = self.spider._create_headers(
|
||||
"/m-station/drama/drama_filter_search",
|
||||
{
|
||||
"sort": "hot",
|
||||
"dramaType": "TV",
|
||||
"year": "",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
self.assertEqual(headers["t"], "1713523200123")
|
||||
self.assertEqual(headers["aliId"], MODULE.KY_ID)
|
||||
self.assertEqual(
|
||||
headers["x-ca-sign"],
|
||||
"wmstQtQaO9LiPx+xbWpi7LncPdyFnf6eVrVTNgoDDBU=",
|
||||
)
|
||||
|
||||
@patch.object(Spider, "fetch")
|
||||
def test_request_api_decrypts_ecb_response(self, mock_fetch):
|
||||
mock_fetch.return_value = SimpleNamespace(
|
||||
status_code=200,
|
||||
text=encrypt_ecb_payload({"code": "0000", "data": {"ok": True}}),
|
||||
)
|
||||
result = self.spider._request_api("/m-station/drama/get_drama_filter")
|
||||
self.assertEqual(result, {"code": "0000", "data": {"ok": True}})
|
||||
self.assertEqual(
|
||||
mock_fetch.call_args.args[0],
|
||||
"https://api.rrmj.plus/m-station/drama/get_drama_filter",
|
||||
)
|
||||
|
||||
@patch.object(Spider, "_request_api")
|
||||
def test_home_content_maps_expected_classes_and_filters(self, mock_request_api):
|
||||
mock_request_api.return_value = {
|
||||
"code": "0000",
|
||||
"data": [
|
||||
{
|
||||
"filterType": "sort",
|
||||
"dramaFilterItemList": [
|
||||
{"displayName": "最热", "value": "hot"},
|
||||
{"displayName": "最新", "value": "new"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"filterType": "dramaType",
|
||||
"dramaFilterItemList": [
|
||||
{"displayName": "全部", "value": ""},
|
||||
{"displayName": "电视剧", "value": "TV"},
|
||||
{"displayName": "电影", "value": "MOVIE"},
|
||||
{"displayName": "综艺", "value": "VARIETY"},
|
||||
{"displayName": "纪录片", "value": "DOCUMENTARY"},
|
||||
{"displayName": "动漫", "value": "COMIC"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"filterType": "area",
|
||||
"dramaFilterItemList": [
|
||||
{"displayName": "全部", "value": ""},
|
||||
{"displayName": "内地", "value": "中国大陆"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"filterType": "plotType",
|
||||
"dramaFilterItemList": [
|
||||
{"displayName": "全部", "value": ""},
|
||||
{"displayName": "悬疑", "value": "悬疑"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"filterType": "year",
|
||||
"dramaFilterItemList": [
|
||||
{"displayName": "全部", "value": ""},
|
||||
{"displayName": "2026", "value": "2026"},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
result = self.spider.homeContent(False)
|
||||
self.assertEqual(
|
||||
result["class"],
|
||||
[
|
||||
{"type_id": "TV", "type_name": "剧集"},
|
||||
{"type_id": "MOVIE", "type_name": "电影"},
|
||||
{"type_id": "VARIETY", "type_name": "综艺"},
|
||||
{"type_id": "COMIC", "type_name": "动漫"},
|
||||
],
|
||||
)
|
||||
self.assertEqual([item["key"] for item in result["filters"]["TV"]], ["area", "class", "year", "by"])
|
||||
self.assertEqual(result["filters"]["TV"][0]["value"][1], {"n": "内地", "v": "中国大陆"})
|
||||
self.assertEqual(result["filters"]["TV"][1]["value"][1], {"n": "悬疑", "v": "悬疑"})
|
||||
self.assertEqual(result["filters"]["TV"][3]["value"][1], {"n": "最新", "v": "new"})
|
||||
|
||||
@patch.object(Spider, "_request_api")
|
||||
def test_category_content_posts_expected_payload_and_omits_pagecount(self, mock_request_api):
|
||||
mock_request_api.return_value = {
|
||||
"code": "0000",
|
||||
"data": [
|
||||
{
|
||||
"dramaId": 56433,
|
||||
"title": "神与律师事务所",
|
||||
"coverUrl": "https://img.example/poster.jpg",
|
||||
"year": "2026",
|
||||
"subtitle": "柳演锡x李絮x金景南",
|
||||
}
|
||||
],
|
||||
}
|
||||
result = self.spider.categoryContent(
|
||||
"TV",
|
||||
"2",
|
||||
False,
|
||||
{"area": "韩国", "year": "2026", "class": "悬疑", "by": "new"},
|
||||
)
|
||||
self.assertEqual(
|
||||
mock_request_api.call_args.args,
|
||||
(
|
||||
"/m-station/drama/drama_filter_search",
|
||||
{
|
||||
"area": "韩国",
|
||||
"sort": "new",
|
||||
"year": "2026",
|
||||
"dramaType": "TV",
|
||||
"plotType": "悬疑",
|
||||
"contentLabel": "",
|
||||
"page": 2,
|
||||
"rows": 30,
|
||||
},
|
||||
),
|
||||
)
|
||||
self.assertEqual(result["page"], 2)
|
||||
self.assertEqual(result["list"][0]["vod_id"], "56433")
|
||||
self.assertEqual(result["list"][0]["vod_remarks"], "2026 柳演锡x李絮x金景南")
|
||||
self.assertNotIn("pagecount", result)
|
||||
|
||||
@patch.object(Spider, "_request_api")
|
||||
def test_detail_content_maps_metadata_and_episode_ids(self, mock_request_api):
|
||||
mock_request_api.return_value = {
|
||||
"code": "0000",
|
||||
"data": {
|
||||
"dramaInfo": {
|
||||
"dramaId": 56433,
|
||||
"title": "神与律师事务所",
|
||||
"cover": "https://img.example/detail.jpg",
|
||||
"description": "一段剧情简介",
|
||||
"year": 2026,
|
||||
"area": "韩国",
|
||||
"plotType": "剧情/悬疑",
|
||||
"subtitle": "柳演锡x李絮x金景南",
|
||||
"playStatus": "更新至12集",
|
||||
},
|
||||
"episodeList": [
|
||||
{"id": 380772, "episodeNo": 1, "title": ""},
|
||||
{"id": 380810, "episodeNo": 2, "title": "特别篇"},
|
||||
],
|
||||
},
|
||||
}
|
||||
result = self.spider.detailContent(["56433"])
|
||||
vod = result["list"][0]
|
||||
self.assertEqual(vod["vod_id"], "56433")
|
||||
self.assertEqual(vod["vod_name"], "神与律师事务所")
|
||||
self.assertEqual(vod["vod_pic"], "https://img.example/detail.jpg")
|
||||
self.assertEqual(vod["vod_content"], "一段剧情简介")
|
||||
self.assertEqual(vod["vod_year"], "2026")
|
||||
self.assertEqual(vod["vod_area"], "韩国")
|
||||
self.assertEqual(vod["type_name"], "剧情/悬疑")
|
||||
self.assertEqual(vod["vod_actor"], "柳演锡x李絮x金景南")
|
||||
self.assertEqual(vod["vod_play_from"], "人人专线")
|
||||
self.assertEqual(vod["vod_play_url"], "第1集$56433@380772#特别篇$56433@380810")
|
||||
|
||||
@patch.object(Spider, "fetch")
|
||||
@patch.object(Spider, "_request_api")
|
||||
def test_player_content_decrypts_play_url_and_uses_redirect_location(self, mock_request_api, mock_fetch):
|
||||
new_sign = "ngXN+eiB3og7T+Zf3iojv6zn6GFDjCi+9COuBBGHO6Y="
|
||||
encrypted_url = encrypt_cbc_payload("https://video.example/decrypted.mp4", new_sign)
|
||||
mock_request_api.return_value = {
|
||||
"code": "0000",
|
||||
"data": {
|
||||
"m3u8": {"url": encrypted_url},
|
||||
"newSign": new_sign,
|
||||
},
|
||||
}
|
||||
mock_fetch.return_value = SimpleNamespace(status_code=302, headers={"location": "https://video.example/final.mp4"})
|
||||
result = self.spider.playerContent("人人专线", "56433@380772", {})
|
||||
self.assertEqual(result["parse"], 0)
|
||||
self.assertEqual(result["jx"], 0)
|
||||
self.assertEqual(result["url"], "https://video.example/final.mp4")
|
||||
self.assertEqual(result["header"]["Referer"], MODULE.HOST)
|
||||
|
||||
@patch.object(Spider, "_request_api")
|
||||
def test_search_content_prefers_precise_results_and_omits_pagecount(self, mock_request_api):
|
||||
mock_request_api.return_value = {
|
||||
"code": "0000",
|
||||
"data": {
|
||||
"seasonList": [
|
||||
{
|
||||
"id": "56433",
|
||||
"title": "神与律师事务所",
|
||||
"cover": "https://img.example/cover.jpg",
|
||||
"year": "2026",
|
||||
"classify": "电视剧",
|
||||
}
|
||||
],
|
||||
"fuzzySeasonList": [
|
||||
{
|
||||
"id": "56694",
|
||||
"title": "山田轰律师事务所",
|
||||
"cover": "https://img.example/fuzzy.jpg",
|
||||
"year": "2026",
|
||||
"classify": "电视剧",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
result = self.spider.searchContent("律师事务所", False, "1")
|
||||
self.assertEqual(
|
||||
result["list"],
|
||||
[
|
||||
{
|
||||
"vod_id": "56433",
|
||||
"vod_name": "神与律师事务所",
|
||||
"vod_pic": "https://img.example/cover.jpg",
|
||||
"vod_remarks": "2026 电视剧",
|
||||
}
|
||||
],
|
||||
)
|
||||
self.assertNotIn("pagecount", result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
# coding=utf-8
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
|
||||
from base.spider import Spider as BaseSpider
|
||||
|
||||
sys.path.append("..")
|
||||
|
||||
|
||||
HOST = "https://m.yichengwlkj.com"
|
||||
API_URL = "https://api.rrmj.plus"
|
||||
UA = (
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0"
|
||||
)
|
||||
KY_ID = "BA21A0F5-7C57-41BA-8665-B7164A131832"
|
||||
APP_SECRET = "ES513W0B1CsdUrR13Qk5EgDAKPeeKZY"
|
||||
AES_KEY = "3b744389882a4067"
|
||||
AES_IV = "b1da7878016e4e2b"
|
||||
|
||||
|
||||
class Spider(BaseSpider):
|
||||
def __init__(self):
|
||||
self.name = "人人视频"
|
||||
self.class_name_map = {
|
||||
"TV": "剧集",
|
||||
"MOVIE": "电影",
|
||||
"VARIETY": "综艺",
|
||||
"COMIC": "动漫",
|
||||
}
|
||||
|
||||
def init(self, extend=""):
|
||||
return None
|
||||
|
||||
def getName(self):
|
||||
return self.name
|
||||
|
||||
def homeVideoContent(self):
|
||||
return {"list": []}
|
||||
|
||||
def _stringify(self, value):
|
||||
return "" if value is None else str(value)
|
||||
|
||||
def _build_api_url(self, path):
|
||||
return f"{API_URL}{str(path or '').strip()}"
|
||||
|
||||
def _join_values(self, *values):
|
||||
return " ".join([self._stringify(value).strip() for value in values if self._stringify(value).strip()]).strip()
|
||||
|
||||
def _generate_signature(self, text):
|
||||
digest = hmac.new(APP_SECRET.encode("utf-8"), text.encode("utf-8"), hashlib.sha256).digest()
|
||||
return base64.b64encode(digest).decode("utf-8")
|
||||
|
||||
def _create_headers(self, url_path, params=None, method="GET"):
|
||||
payload = {
|
||||
self._stringify(key): self._stringify(value)
|
||||
for key, value in dict(params or {}).items()
|
||||
}
|
||||
timestamp = str(int(time.time() * 1000))
|
||||
sorted_params = "&".join([f"{key}={payload[key]}" for key in sorted(payload)])
|
||||
full_url = f"{url_path}?{sorted_params}" if sorted_params else url_path
|
||||
string_to_sign = "\n".join(
|
||||
[
|
||||
method.upper(),
|
||||
f"aliId:{KY_ID}",
|
||||
"ct:web_pc",
|
||||
"cv:1.0.0",
|
||||
f"t:{timestamp}",
|
||||
full_url,
|
||||
]
|
||||
)
|
||||
headers = {
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"User-Agent": UA,
|
||||
"Origin": HOST,
|
||||
"Referer": HOST,
|
||||
"aliId": KY_ID,
|
||||
"clientType": "web_pc",
|
||||
"clientVersion": "1.0.0",
|
||||
"ct": "web_pc",
|
||||
"cv": "1.0.0",
|
||||
"deviceId": KY_ID,
|
||||
"t": timestamp,
|
||||
"token": "",
|
||||
"uet": "9",
|
||||
"umid": KY_ID,
|
||||
"x-ca-sign": self._generate_signature(string_to_sign),
|
||||
}
|
||||
if method.upper() == "POST":
|
||||
headers["Content-Type"] = "application/json;charset=UTF-8"
|
||||
return headers
|
||||
|
||||
def _decrypt_aes_ecb(self, encrypted_data):
|
||||
text = self._stringify(encrypted_data).strip()
|
||||
if not text:
|
||||
return None
|
||||
if text.startswith("{") or text.startswith("["):
|
||||
try:
|
||||
return json.loads(text)
|
||||
except Exception:
|
||||
return None
|
||||
try:
|
||||
clean = "".join(text.split())
|
||||
cipher = AES.new(AES_KEY.encode("utf-8"), AES.MODE_ECB)
|
||||
raw = base64.b64decode(clean)
|
||||
decoded = unpad(cipher.decrypt(raw), AES.block_size).decode("utf-8")
|
||||
return json.loads(decoded)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _decrypt_aes_cbc(self, encrypted_text, new_sign):
|
||||
text = self._stringify(encrypted_text).strip()
|
||||
sign = self._stringify(new_sign).strip()
|
||||
if not text or len(sign) < 20:
|
||||
return ""
|
||||
try:
|
||||
key = sign[4:20].encode("utf-8")
|
||||
cipher = AES.new(key, AES.MODE_CBC, AES_IV.encode("utf-8"))
|
||||
decoded = unpad(cipher.decrypt(base64.b64decode(text)), AES.block_size).decode("utf-8")
|
||||
return decoded
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _request_api(self, url_path, params=None, method="GET"):
|
||||
payload = dict(params or {})
|
||||
headers = self._create_headers(url_path, payload, method=method)
|
||||
try:
|
||||
if method.upper() == "POST":
|
||||
response = self.post(
|
||||
self._build_api_url(url_path),
|
||||
headers=headers,
|
||||
json=payload,
|
||||
timeout=10,
|
||||
verify=False,
|
||||
)
|
||||
else:
|
||||
response = self.fetch(
|
||||
self._build_api_url(url_path),
|
||||
params=payload,
|
||||
headers=headers,
|
||||
timeout=10,
|
||||
verify=False,
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
if getattr(response, "status_code", 0) != 200:
|
||||
return None
|
||||
return self._decrypt_aes_ecb(getattr(response, "text", ""))
|
||||
|
||||
def _build_filter_values(self, items):
|
||||
values = []
|
||||
for item in items or []:
|
||||
name = self._stringify(item.get("displayName"))
|
||||
value = self._stringify(item.get("value"))
|
||||
if not name:
|
||||
continue
|
||||
values.append({"n": name, "v": value})
|
||||
return values
|
||||
|
||||
def _page_result(self, items, pg):
|
||||
page = int(pg)
|
||||
return {"page": page, "limit": len(items), "total": len(items), "list": items}
|
||||
|
||||
def homeContent(self, filter):
|
||||
payload = self._request_api("/m-station/drama/get_drama_filter") or {}
|
||||
groups = {
|
||||
self._stringify(item.get("filterType")): list(item.get("dramaFilterItemList") or [])
|
||||
for item in payload.get("data", [])
|
||||
}
|
||||
classes = []
|
||||
filters = {}
|
||||
for item in groups.get("dramaType", []):
|
||||
type_id = self._stringify(item.get("value"))
|
||||
if type_id not in self.class_name_map:
|
||||
continue
|
||||
classes.append({"type_id": type_id, "type_name": self.class_name_map[type_id]})
|
||||
filters[type_id] = [
|
||||
{"key": "area", "name": "地区", "value": self._build_filter_values(groups.get("area", []))},
|
||||
{"key": "class", "name": "类型", "value": self._build_filter_values(groups.get("plotType", []))},
|
||||
{"key": "year", "name": "年份", "value": self._build_filter_values(groups.get("year", []))},
|
||||
{"key": "by", "name": "排序", "value": self._build_filter_values(groups.get("sort", []))},
|
||||
]
|
||||
return {"class": classes, "filters": filters}
|
||||
|
||||
def categoryContent(self, tid, pg, filter, extend):
|
||||
params = {
|
||||
"area": self._stringify((extend or {}).get("area")),
|
||||
"sort": self._stringify((extend or {}).get("by") or "hot"),
|
||||
"year": self._stringify((extend or {}).get("year")),
|
||||
"dramaType": self._stringify(tid),
|
||||
"plotType": self._stringify((extend or {}).get("class")),
|
||||
"contentLabel": "",
|
||||
"page": int(pg),
|
||||
"rows": 30,
|
||||
}
|
||||
payload = self._request_api("/m-station/drama/drama_filter_search", params, method="POST") or {}
|
||||
items = []
|
||||
for vod in payload.get("data", []) or []:
|
||||
items.append(
|
||||
{
|
||||
"vod_id": self._stringify(vod.get("dramaId") or vod.get("id")),
|
||||
"vod_name": self._stringify(vod.get("title") or vod.get("name")),
|
||||
"vod_pic": self._stringify(vod.get("coverUrl") or vod.get("cover")),
|
||||
"vod_remarks": self._join_values(vod.get("year"), vod.get("subtitle")),
|
||||
}
|
||||
)
|
||||
return self._page_result(items, pg)
|
||||
|
||||
def _episode_name(self, episode):
|
||||
title = self._stringify(episode.get("title")).strip()
|
||||
if title:
|
||||
return title
|
||||
text = self._stringify(episode.get("text")).strip()
|
||||
if text:
|
||||
return text
|
||||
number = self._stringify(episode.get("episodeNo")).strip()
|
||||
return f"第{number}集" if number else "正片"
|
||||
|
||||
def detailContent(self, ids):
|
||||
items = []
|
||||
for raw_id in ids or []:
|
||||
drama_id = self._stringify(raw_id).strip()
|
||||
if not drama_id:
|
||||
continue
|
||||
payload = self._request_api(
|
||||
"/m-station/drama/page",
|
||||
{
|
||||
"hsdrOpen": "0",
|
||||
"isAgeLimit": "0",
|
||||
"dramaId": drama_id,
|
||||
"quality": "AI4K",
|
||||
"hevcOpen": "0",
|
||||
"tria4k": "1",
|
||||
},
|
||||
) or {}
|
||||
data = payload.get("data") or {}
|
||||
info = data.get("dramaInfo") or {}
|
||||
play_urls = []
|
||||
for episode in data.get("episodeList", []) or []:
|
||||
episode_id = self._stringify(episode.get("sid") or episode.get("id")).strip()
|
||||
if not episode_id:
|
||||
continue
|
||||
play_urls.append(f"{self._episode_name(episode)}${drama_id}@{episode_id}")
|
||||
items.append(
|
||||
{
|
||||
"vod_id": drama_id,
|
||||
"vod_name": self._stringify(info.get("title")),
|
||||
"vod_pic": self._stringify(info.get("coverUrl") or info.get("cover") or info.get("cover3Url")),
|
||||
"vod_remarks": self._stringify(info.get("playStatus")),
|
||||
"vod_year": self._stringify(info.get("year")),
|
||||
"vod_area": self._stringify(info.get("area")),
|
||||
"vod_actor": self._stringify(info.get("actor") or info.get("subtitle")),
|
||||
"vod_director": self._stringify(info.get("director")),
|
||||
"vod_content": self._stringify(
|
||||
info.get("introduction") or info.get("description") or info.get("subtitle")
|
||||
),
|
||||
"type_name": self._stringify(info.get("plotType") or self.class_name_map.get(info.get("dramaType"), "")),
|
||||
"vod_play_from": "人人专线" if play_urls else "",
|
||||
"vod_play_url": "#".join(play_urls),
|
||||
}
|
||||
)
|
||||
return {"list": items}
|
||||
|
||||
def playerContent(self, flag, id, vipFlags):
|
||||
raw = self._stringify(id).strip()
|
||||
if "@" not in raw:
|
||||
return {"parse": 1, "playUrl": "", "url": raw, "jx": 1}
|
||||
drama_id, episode_sid = raw.split("@", 1)
|
||||
payload = self._request_api(
|
||||
"/m-station/drama/play",
|
||||
{
|
||||
"dramaId": drama_id,
|
||||
"episodeSid": episode_sid,
|
||||
"hevcOpen": "0",
|
||||
"hsdrOpen": "0",
|
||||
"quality": "AI4K",
|
||||
"tria4k": "1",
|
||||
},
|
||||
) or {}
|
||||
data = payload.get("data") or {}
|
||||
encrypted_url = self._stringify((data.get("m3u8") or {}).get("url") or (data.get("tria4kPlayInfo") or {}).get("url"))
|
||||
final_url = self._decrypt_aes_cbc(encrypted_url, data.get("newSign"))
|
||||
if not final_url:
|
||||
return {"parse": 1, "playUrl": "", "url": "", "jx": 1}
|
||||
try:
|
||||
response = self.fetch(
|
||||
final_url,
|
||||
headers={"User-Agent": UA},
|
||||
timeout=10,
|
||||
verify=False,
|
||||
allow_redirects=False,
|
||||
)
|
||||
final_url = self._stringify(getattr(response, "headers", {}).get("location") or final_url)
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
"parse": 0,
|
||||
"playUrl": "",
|
||||
"url": final_url,
|
||||
"jx": 0,
|
||||
"header": {"User-Agent": UA, "Referer": HOST},
|
||||
}
|
||||
|
||||
def searchContent(self, key, quick, pg="1"):
|
||||
keyword = self._stringify(key).strip()
|
||||
if not keyword:
|
||||
return self._page_result([], pg)
|
||||
payload = self._request_api(
|
||||
"/search/comprehensive/precise-mixed",
|
||||
{"keywords": keyword, "size": "20", "searchAfter": ""},
|
||||
) or {}
|
||||
data = payload.get("data") or {}
|
||||
source = list(data.get("seasonList") or []) or list(data.get("fuzzySeasonList") or [])
|
||||
items = []
|
||||
seen = set()
|
||||
for vod in source:
|
||||
vod_id = self._stringify(vod.get("id") or vod.get("dramaId"))
|
||||
if not vod_id or vod_id in seen:
|
||||
continue
|
||||
seen.add(vod_id)
|
||||
items.append(
|
||||
{
|
||||
"vod_id": vod_id,
|
||||
"vod_name": self._stringify(vod.get("title") or vod.get("name")),
|
||||
"vod_pic": self._stringify(vod.get("cover") or vod.get("coverUrl")),
|
||||
"vod_remarks": self._join_values(vod.get("year"), vod.get("classify") or vod.get("cat")),
|
||||
}
|
||||
)
|
||||
return self._page_result(items, pg)
|
||||
Reference in New Issue
Block a user