feat: add bubuyingshi spider

This commit is contained in:
Harold
2026-04-20 08:46:16 +08:00
parent 3cc3543be7
commit 3c1597f429
2 changed files with 720 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
import unittest
from importlib.machinery import SourceFileLoader
from pathlib import Path
from unittest.mock import patch
ROOT = Path(__file__).resolve().parents[1]
MODULE = SourceFileLoader("bbys_spider", str(ROOT / "步步影视.py")).load_module()
Spider = MODULE.Spider
class TestBuBuYingShiSpider(unittest.TestCase):
def setUp(self):
Spider._instance = None
self.spider = Spider()
self.spider.init()
def test_home_content_exposes_expected_classes_and_filters(self):
content = self.spider.homeContent(False)
self.assertEqual(
[item["type_id"] for item in content["class"]],
["1", "2", "3", "4"],
)
self.assertEqual(
[item["key"] for item in content["filters"]["1"]],
["class", "area", "year", "by"],
)
@patch("bbys_spider.time.time", return_value=1713523200)
@patch("bbys_spider.random.randint", side_effect=[8, 37])
def test_build_app_headers_includes_signed_fields(self, mock_randint, mock_time):
headers = self.spider._get_app_headers()
self.assertEqual(headers["x-aid"], "com.sunshine.tv")
self.assertEqual(headers["x-ave"], "4")
self.assertEqual(headers["x-time"], "1713523200")
self.assertEqual(headers["x-nonc"], "837")
self.assertEqual(len(headers["x-sign"]), 64)
@patch("bbys_spider.time.time", return_value=1713523200)
@patch("bbys_spider.random.randint", side_effect=[8, 37])
def test_build_web_headers_adds_web_specific_fields(self, mock_randint, mock_time):
headers = self.spider._get_web_headers()
self.assertEqual(headers["web-sign"], "f65f3a83d6d9ad6f")
self.assertEqual(headers["X-Client"], "8f3d2a1c7b6e5d4c9a0b1f2e3d4c5b6a")
def test_generate_years_for_movie_starts_with_current_year_and_includes_ranges(self):
years = self.spider._generate_years("电影")
self.assertEqual(years[0], {"n": "全部", "v": ""})
self.assertEqual(years[1]["n"], str(self.spider.current_year))
self.assertIn({"n": "2015-2011", "v": "2015-2011"}, years)
def test_convert_json_to_vods_maps_common_fields(self):
items = self.spider._convert_json_to_vods(
[
{
"vod_id": 11,
"vod_name": "首页片",
"vod_pic": "https://img.example/a.jpg",
"vod_duration": "更新至2集",
"vod_class": ["剧情", "悬疑"],
"vod_year": 2026,
"vod_area": "大陆",
}
]
)
self.assertEqual(items[0]["vod_id"], "11")
self.assertEqual(items[0]["type_name"], "剧情/悬疑")
self.assertEqual(items[0]["vod_year"], "2026")
@patch.object(Spider, "_request_json")
def test_home_video_content_reads_home_categories(self, mock_request_json):
mock_request_json.return_value = {
"data": {
"categories": [
{
"type_id": "1",
"type_name": "电影",
"videos": [{"vod_id": 7, "vod_name": "首页影片", "vod_pic": "/a.jpg"}],
}
]
}
}
result = self.spider.homeVideoContent()
self.assertEqual(result["list"][0]["vod_id"], "7")
self.assertEqual(result["list"][0]["vod_name"], "首页影片")
@patch.object(Spider, "_request_json")
@patch.object(Spider, "_get_class_list")
def test_home_video_content_falls_back_to_main_categories(self, mock_get_class_list, mock_request_json):
mock_request_json.return_value = {"data": {"categories": []}}
mock_get_class_list.side_effect = [
{"list": [{"vod_id": "1", "vod_name": "电影片"}]},
{"list": [{"vod_id": "2", "vod_name": "剧集片"}]},
{"list": []},
{"list": []},
]
result = self.spider.homeVideoContent()
self.assertEqual([item["vod_id"] for item in result["list"]], ["1", "2"])
@patch.object(Spider, "_request_json")
def test_category_content_builds_expected_web_request_and_page_shape(self, mock_request_json):
mock_request_json.return_value = {
"data": [{"vod_id": 9, "vod_name": "分类片", "vod_pic": "/cate.jpg", "vod_remarks": "HD"}]
}
result = self.spider.categoryContent("电影", "2", False, {"area": "大陆", "year": "2025", "by": "score"})
kwargs = mock_request_json.call_args.kwargs
self.assertEqual(kwargs["headers"]["web-sign"], "f65f3a83d6d9ad6f")
self.assertEqual(kwargs["params"]["type_name"], "电影")
self.assertEqual(kwargs["params"]["page"], 2)
self.assertEqual(kwargs["params"]["sort"], "score")
self.assertEqual(result["page"], 2)
self.assertNotIn("pagecount", result)
self.assertEqual(result["list"][0]["vod_id"], "9")
@patch.object(Spider, "_get_class_list")
def test_category_content_unknown_type_falls_back_to_hot_aggregate(self, mock_get_class_list):
mock_get_class_list.side_effect = [
{"list": [{"vod_id": "1", "vod_name": "电影片"}]},
{"list": [{"vod_id": "2", "vod_name": "剧集片"}]},
{"list": [{"vod_id": "3", "vod_name": "综艺片"}]},
{"list": [{"vod_id": "4", "vod_name": "动漫片"}]},
]
result = self.spider.categoryContent("步步影视", "1", False, {})
self.assertEqual([item["vod_id"] for item in result["list"]], ["1", "2", "3", "4"])
@patch.object(Spider, "_request_json")
def test_search_content_skips_blank_keyword_and_builds_app_request(self, mock_request_json):
blank = self.spider.searchContent("", False, "1")
self.assertEqual(blank, {"page": 1, "limit": 0, "total": 0, "list": []})
mock_request_json.assert_not_called()
mock_request_json.return_value = {
"data": [{"vod_id": 13, "vod_name": "搜索片", "vod_pic": "/search.jpg"}]
}
result = self.spider.searchContent("繁花", False, "3")
kwargs = mock_request_json.call_args.kwargs
self.assertEqual(kwargs["params"]["wd"], "繁花")
self.assertEqual(kwargs["params"]["page"], 3)
self.assertEqual(kwargs["params"]["limit"], 15)
self.assertEqual(result["list"][0]["vod_id"], "13")
def test_clean_html_content_strips_tags_and_keeps_line_breaks(self):
text = self.spider._clean_html_content("<p>第一段</p><p>第二段<br>第三行</p>")
self.assertEqual(text, "第一段\n第二段\n第三行")
def test_build_play_data_packs_group_names_and_urls(self):
data = self.spider._build_play_data(
"线路一$$$线路二",
"第1集$/play/1#第2集$/play/2$$$正片$/movie/main",
)
self.assertEqual(data["vod_play_from"], "线路一(2)$$$线路二(1)")
self.assertIn("第1集$线路一@1@/play/1", data["vod_play_url"])
self.assertIn("正片$线路二@1@/movie/main", data["vod_play_url"])
@patch.object(Spider, "_request_json")
def test_detail_content_handles_array_payload_and_packs_play_urls(self, mock_request_json):
mock_request_json.return_value = {
"data": [
{
"vod_id": 18,
"vod_name": "详情片",
"vod_pic": "/detail.jpg",
"vod_remarks": "更新至2集",
"vod_year": "2026",
"vod_area": "大陆",
"vod_actor": "甲/乙",
"vod_director": "",
"vod_content": "<p>一段简介</p>",
"vod_class": "剧情",
"vod_play_from": "线路一",
"vod_play_url": "第1集$/play/1",
}
]
}
result = self.spider.detailContent(["18"])
detail = result["list"][0]
self.assertEqual(detail["vod_id"], "18")
self.assertEqual(detail["vod_content"], "一段简介")
self.assertEqual(detail["type_name"], "剧情")
self.assertEqual(detail["vod_play_from"], "线路一(1)")
self.assertEqual(detail["vod_play_url"], "第1集$线路一@1@/play/1")
def test_extract_decode_url_accepts_multiple_shapes(self):
self.assertEqual(self.spider._extract_decode_url({"data": "https://a.example/m3u8"}), "https://a.example/m3u8")
self.assertEqual(
self.spider._extract_decode_url({"data": {"url": "https://b.example/m3u8"}}),
"https://b.example/m3u8",
)
self.assertEqual(self.spider._extract_decode_url({"url": "https://c.example/m3u8"}), "https://c.example/m3u8")
@patch.object(Spider, "_request_json")
def test_player_content_decodes_encoded_play_id(self, mock_request_json):
mock_request_json.return_value = {"data": {"url": "https://cdn.example/real.m3u8"}}
result = self.spider.playerContent("线路一", "线路一@1@/play/1", {})
kwargs = mock_request_json.call_args.kwargs
self.assertEqual(kwargs["params"]["url"], "/play/1")
self.assertEqual(kwargs["params"]["vodFrom"], "线路一")
self.assertEqual(result["parse"], 0)
self.assertEqual(result["playUrl"], "")
self.assertEqual(result["url"], "https://cdn.example/real.m3u8")
@patch.object(Spider, "_request_json")
def test_player_content_sets_jx_for_major_video_sites(self, mock_request_json):
mock_request_json.return_value = {"data": {"url": "https://v.qq.com/x/cover/demo.html"}}
result = self.spider.playerContent("线路一", "线路一@1@/play/1", {})
self.assertEqual(result["jx"], 1)
def test_player_content_passthrough_for_direct_url(self):
result = self.spider.playerContent("直连", "https://cdn.example/direct.m3u8", {})
self.assertEqual(result["url"], "https://cdn.example/direct.m3u8")
self.assertEqual(result["jx"], 0)
def test_home_content_keeps_reference_filter_values(self):
movie_filters = self.spider.homeContent(False)["filters"]["1"]
self.assertIn({"n": "动作", "v": "动作"}, movie_filters[0]["value"])
self.assertIn({"n": "大陆", "v": "大陆"}, movie_filters[1]["value"])
self.assertIn({"n": "评分", "v": "score"}, movie_filters[3]["value"])
@patch.object(Spider, "_request_json")
def test_detail_content_returns_empty_list_when_payload_missing(self, mock_request_json):
mock_request_json.return_value = {"data": []}
self.assertEqual(self.spider.detailContent(["404"]), {"list": []})
@patch.object(Spider, "_request_json")
def test_player_content_falls_back_to_raw_input_when_decode_fails(self, mock_request_json):
mock_request_json.return_value = {}
result = self.spider.playerContent("线路一", "线路一@1@/play/404", {})
self.assertEqual(result["url"], "/play/404")
if __name__ == "__main__":
unittest.main()
+488
View File
@@ -0,0 +1,488 @@
# coding=utf-8
import hashlib
import random
import re
import sys
import time
from base.spider import Spider as BaseSpider
sys.path.append("..")
class Spider(BaseSpider):
def __init__(self):
self.name = "步步影视"
self.host = "https://bbys.app"
self.pkg = "com.sunshine.tv"
self.ver = "4"
self.finger = "SF-C3B2B41F6EFFFF9869176CF68F6790E8F07506FC88632C94B4F5F0430D5498CA"
self.sk = "SK-thanks"
self.web_sign = "f65f3a83d6d9ad6f"
self.x_client = "8f3d2a1c7b6e5d4c9a0b1f2e3d4c5b6a"
self.current_year = time.localtime().tm_year
self.headers = {
"User-Agent": "okhttp/4.12.0",
"Accept": "application/json",
}
self.classes = [
{"type_id": "1", "type_name": "电影"},
{"type_id": "2", "type_name": "剧集"},
{"type_id": "3", "type_name": "动漫"},
{"type_id": "4", "type_name": "综艺"},
]
self.filters = {
"1": [
{
"key": "class",
"name": "类型",
"value": [
{"n": "全部", "v": ""},
{"n": "动作", "v": "动作"},
{"n": "喜剧", "v": "喜剧"},
{"n": "爱情", "v": "爱情"},
{"n": "科幻", "v": "科幻"},
{"n": "恐怖", "v": "恐怖"},
{"n": "悬疑", "v": "悬疑"},
{"n": "犯罪", "v": "犯罪"},
{"n": "战争", "v": "战争"},
{"n": "动画", "v": "动画"},
{"n": "冒险", "v": "冒险"},
{"n": "历史", "v": "历史"},
{"n": "灾难", "v": "灾难"},
{"n": "纪录", "v": "纪录"},
{"n": "剧情", "v": "剧情"},
],
},
{
"key": "area",
"name": "地区",
"value": [
{"n": "全部", "v": ""},
{"n": "大陆", "v": "大陆"},
{"n": "香港", "v": "香港"},
{"n": "台湾", "v": "台湾"},
{"n": "美国", "v": "美国"},
{"n": "日本", "v": "日本"},
{"n": "韩国", "v": "韩国"},
{"n": "泰国", "v": "泰国"},
{"n": "印度", "v": "印度"},
{"n": "英国", "v": "英国"},
{"n": "法国", "v": "法国"},
{"n": "德国", "v": "德国"},
{"n": "加拿大", "v": "加拿大"},
{"n": "西班牙", "v": "西班牙"},
{"n": "意大利", "v": "意大利"},
{"n": "澳大利亚", "v": "澳大利亚"},
],
},
{"key": "year", "name": "年份", "value": self._generate_years("电影")},
{
"key": "by",
"name": "排序",
"value": [
{"n": "最热", "v": "hits"},
{"n": "最新", "v": "time"},
{"n": "评分", "v": "score"},
],
},
],
"2": [
{
"key": "class",
"name": "类型",
"value": [
{"n": "全部", "v": ""},
{"n": "爱情", "v": "爱情"},
{"n": "古装", "v": "古装"},
{"n": "武侠", "v": "武侠"},
{"n": "历史", "v": "历史"},
{"n": "家庭", "v": "家庭"},
{"n": "喜剧", "v": "喜剧"},
{"n": "悬疑", "v": "悬疑"},
{"n": "犯罪", "v": "犯罪"},
{"n": "战争", "v": "战争"},
{"n": "奇幻", "v": "奇幻"},
{"n": "科幻", "v": "科幻"},
{"n": "恐怖", "v": "恐怖"},
],
},
{
"key": "area",
"name": "地区",
"value": [
{"n": "全部", "v": ""},
{"n": "大陆", "v": "大陆"},
{"n": "香港", "v": "香港"},
{"n": "台湾", "v": "台湾"},
{"n": "美国", "v": "美国"},
{"n": "日本", "v": "日本"},
{"n": "韩国", "v": "韩国"},
{"n": "泰国", "v": "泰国"},
{"n": "英国", "v": "英国"},
],
},
{"key": "year", "name": "年份", "value": self._generate_years("剧集")},
{
"key": "by",
"name": "排序",
"value": [
{"n": "最热", "v": "hits"},
{"n": "最新", "v": "time"},
{"n": "评分", "v": "score"},
],
},
],
"3": [
{
"key": "class",
"name": "类型",
"value": [
{"n": "全部", "v": ""},
{"n": "冒险", "v": "冒险"},
{"n": "奇幻", "v": "奇幻"},
{"n": "科幻", "v": "科幻"},
{"n": "武侠", "v": "武侠"},
{"n": "悬疑", "v": "悬疑"},
],
},
{
"key": "area",
"name": "地区",
"value": [
{"n": "全部", "v": ""},
{"n": "大陆", "v": "大陆"},
{"n": "日本", "v": "日本"},
{"n": "欧美", "v": "欧美"},
],
},
{"key": "year", "name": "年份", "value": self._generate_years("动漫")},
{
"key": "by",
"name": "排序",
"value": [
{"n": "最热", "v": "hits"},
{"n": "最新", "v": "time"},
{"n": "评分", "v": "score"},
],
},
],
"4": [
{
"key": "class",
"name": "类型",
"value": [
{"n": "全部", "v": ""},
{"n": "真人秀", "v": "真人秀"},
{"n": "音乐", "v": "音乐"},
{"n": "脱口秀", "v": "脱口秀"},
{"n": "歌舞", "v": "歌舞"},
{"n": "爱情", "v": "爱情"},
],
},
{
"key": "area",
"name": "地区",
"value": [
{"n": "全部", "v": ""},
{"n": "大陆", "v": "大陆"},
{"n": "香港", "v": "香港"},
{"n": "台湾", "v": "台湾"},
{"n": "美国", "v": "美国"},
{"n": "日本", "v": "日本"},
{"n": "韩国", "v": "韩国"},
],
},
{"key": "year", "name": "年份", "value": self._generate_years("综艺")},
{
"key": "by",
"name": "排序",
"value": [
{"n": "最热", "v": "hits"},
{"n": "最新", "v": "time"},
{"n": "评分", "v": "score"},
],
},
],
}
def init(self, extend=""):
return None
def getName(self):
return self.name
def homeContent(self, filter):
return {"class": self.classes, "filters": self.filters}
def homeVideoContent(self):
payload = self._request_json("/api.php/web/index/home", headers=self._get_web_headers())
categories = ((payload.get("data") or {}).get("categories") or [])
videos = []
for category in categories:
videos.extend(self._convert_json_to_vods(category.get("videos") or []))
if not videos:
for type_name in ("电影", "剧集", "综艺", "动漫"):
videos.extend(self._get_class_list(type_name, 1, {"by": "hits"}).get("list", []))
return {"list": videos}
def categoryContent(self, tid, pg, filter, extend):
type_name = self._map_type_name(tid)
if type_name not in ("电影", "剧集", "动漫", "综艺"):
videos = []
for name in ("电影", "剧集", "综艺", "动漫"):
videos.extend(self._get_class_list(name, 1, {"by": "hits"}).get("list", []))
return {"page": 1, "limit": len(videos), "total": len(videos), "list": videos}
return self._get_class_list(type_name, int(pg), extend or {})
def detailContent(self, ids):
vod_id = self._stringify(ids[0] if isinstance(ids, list) and ids else ids).strip()
if not vod_id:
return {"list": []}
payload = self._request_json(
"/api.php/web/vod/get_detail",
headers=self._get_web_headers(),
params={"vod_id": vod_id},
)
detail = payload.get("data")
if isinstance(detail, list):
detail = detail[0] if detail else None
if not detail:
return {"list": []}
play_data = self._build_play_data(detail.get("vod_play_from", ""), detail.get("vod_play_url", ""))
return {
"list": [
{
"vod_id": self._stringify(detail.get("vod_id") or vod_id),
"vod_name": self._stringify(detail.get("vod_name")),
"vod_pic": self._stringify(detail.get("vod_pic")),
"vod_remarks": self._stringify(detail.get("vod_remarks") or detail.get("vod_duration")),
"vod_year": self._stringify(detail.get("vod_year")),
"vod_area": self._stringify(detail.get("vod_area")),
"vod_actor": self._stringify(detail.get("vod_actor")),
"vod_director": self._stringify(detail.get("vod_director")),
"vod_content": self._clean_html_content(detail.get("vod_content")),
"type_name": self._stringify(detail.get("vod_class") or detail.get("type_name")),
"vod_play_from": play_data["vod_play_from"],
"vod_play_url": play_data["vod_play_url"],
}
]
}
def searchContent(self, key, quick, pg="1"):
keyword = self._stringify(key).strip()
page = int(pg)
if not keyword:
return {"page": page, "limit": 0, "total": 0, "list": []}
payload = self._request_json(
"/api.php/app/search/index",
headers=self._get_app_headers(),
params={"wd": keyword, "page": page, "limit": 15},
)
items = self._convert_json_to_vods(payload.get("data") or [])
return {"page": page, "limit": len(items), "total": len(items), "list": items}
def playerContent(self, flag, id, vipFlags):
raw = self._stringify(id).strip()
if not raw:
return {"parse": 0, "playUrl": "", "url": "", "jx": 0}
play_from = ""
play_url = raw
need_decode = False
matched = re.match(r"^([^@]+)@([01])@([\s\S]+)$", raw)
if matched:
play_from = matched.group(1)
need_decode = matched.group(2) == "1"
play_url = matched.group(3)
elif not raw.startswith(("http://", "https://")):
need_decode = True
final_url = play_url
if need_decode:
payload = self._request_json(
"/api.php/app/decode/url/",
headers=self._get_app_headers(),
params={"url": play_url, "vodFrom": play_from},
)
decoded = self._extract_decode_url(payload)
if decoded:
final_url = decoded
need_jx = 1 if re.search(r"(iqiyi\.com|v\.qq\.com|youku\.com|mgtv\.com|bilibili\.com)", final_url, re.I) else 0
return {
"parse": 0,
"playUrl": "",
"url": final_url,
"jx": need_jx,
"header": {"User-Agent": "okhttp/4.12.0"},
}
def _stringify(self, value):
return "" if value is None else str(value)
def _join_text(self, value):
if isinstance(value, list):
return "/".join([self._stringify(item) for item in value if self._stringify(item)])
return self._stringify(value)
def _clean_html_content(self, content):
if not content:
return ""
text = str(content).replace("<p>", "").replace("</p>", "\n")
text = re.sub(r"<br\s*/?>", "\n", text, flags=re.I)
text = re.sub(r"<[^>]+>", "", text)
return text.strip()
def _safe_split(self, value, sep):
if value or value == "":
return str(value).split(sep)
return []
def _build_play_data(self, vod_play_from, vod_play_url):
from_list = self._safe_split(vod_play_from, "$$$")
url_groups = self._safe_split(vod_play_url, "$$$")
final_from = []
final_url = []
for index in range(max(len(from_list), len(url_groups))):
raw_from = (from_list[index] if index < len(from_list) else f"line{index + 1}").strip() or f"line{index + 1}"
group = (url_groups[index] if index < len(url_groups) else "").strip()
if not group:
continue
episodes = []
for item in [part.strip() for part in group.split("#") if part.strip()]:
if "$" in item:
title, raw_url = item.split("$", 1)
else:
title, raw_url = "播放", item
title = title.strip() or "播放"
raw_url = raw_url.strip()
if raw_url:
episodes.append(f"{title}${raw_from}@1@{raw_url}")
if episodes:
final_from.append(f"{raw_from}({len(episodes)})")
final_url.append("#".join(episodes))
return {"vod_play_from": "$$$".join(final_from), "vod_play_url": "$$$".join(final_url)}
def _generate_years(self, type_name):
years = [{"n": "全部", "v": ""}]
if type_name == "电影":
for year in range(self.current_year, 2015, -1):
years.append({"n": str(year), "v": str(year)})
years.extend(
[
{"n": "2015-2011", "v": "2015-2011"},
{"n": "2010-2000", "v": "2010-2000"},
{"n": "90年代", "v": "90年代"},
{"n": "80年代", "v": "80年代"},
{"n": "更早", "v": "更早"},
]
)
return years
floor = 2021 if type_name == "剧集" else 2011
for year in range(self.current_year, floor - 1, -1):
years.append({"n": str(year), "v": str(year)})
years.append({"n": "更早", "v": "更早"})
return years
def _create_sign_data(self):
timestamp = str(int(time.time()))
nonce = f"{random.randint(0, 9)}{random.randint(10, 99)}"
sign_source = (
f"finger={self.finger}&id={self.pkg}&nonce={nonce}"
f"&sk={self.sk}&time={timestamp}&v={self.ver}"
)
sign = hashlib.sha256(sign_source.encode("utf-8")).hexdigest().upper()
return {"timestamp": timestamp, "nonce": nonce, "sign": sign}
def _get_app_headers(self):
sign_data = self._create_sign_data()
headers = dict(self.headers)
headers.update(
{
"x-aid": self.pkg,
"x-time": sign_data["timestamp"],
"x-sign": sign_data["sign"],
"x-nonc": sign_data["nonce"],
"x-ave": self.ver,
}
)
return headers
def _get_web_headers(self):
headers = self._get_app_headers()
headers.update(
{
"web-sign": self.web_sign,
"X-Client": self.x_client,
}
)
return headers
def _request_json(self, path, headers=None, params=None):
url = path if str(path).startswith("http") else self.host + path
response = self.fetch(url, headers=headers, params=params, timeout=15, verify=False)
if response.status_code != 200:
return {}
return response.json()
def _convert_json_to_vods(self, video_list):
items = []
for item in video_list or []:
items.append(
{
"vod_id": self._stringify(item.get("vod_id") or item.get("id")),
"vod_name": self._stringify(item.get("vod_name") or item.get("name")),
"vod_pic": self._stringify(item.get("vod_pic") or item.get("pic")),
"vod_remarks": self._stringify(
item.get("vod_remarks") or item.get("vod_duration") or item.get("remark")
),
"type_name": self._join_text(item.get("vod_class") or item.get("type_name") or item.get("class")),
"vod_year": self._join_text(item.get("vod_year") or item.get("year")),
"vod_area": self._join_text(item.get("vod_area") or item.get("area")),
}
)
return items
def _map_type_name(self, value):
mapping = {
"1": "电影",
"2": "剧集",
"3": "动漫",
"4": "综艺",
"电影": "电影",
"剧集": "剧集",
"动漫": "动漫",
"综艺": "综艺",
}
return mapping.get(self._stringify(value), self._stringify(value))
def _get_class_list(self, tid, page=1, extend=None):
type_name = self._map_type_name(tid)
ext = extend or {}
payload = self._request_json(
"/api.php/web/filter/vod",
headers=self._get_web_headers(),
params={
"type_name": type_name,
"page": int(page),
"sort": ext.get("by", "hits"),
"class": ext.get("class", ""),
"area": ext.get("area", ""),
"year": ext.get("year", ""),
},
)
items = self._convert_json_to_vods(payload.get("data") or [])
return {"page": int(page), "limit": len(items), "total": len(items), "list": items}
def _extract_decode_url(self, payload):
if isinstance(payload, dict):
data = payload.get("data")
if isinstance(data, str) and data:
return data
if isinstance(data, dict) and isinstance(data.get("url"), str) and data.get("url"):
return data.get("url")
if isinstance(payload.get("url"), str) and payload.get("url"):
return payload.get("url")
return ""