From 72a7f561d4ba54382ccd3ce07549bf828ca573f4 Mon Sep 17 00:00:00 2001 From: XC-Pro <19840+xc-pro@noreply.example.org> Date: Thu, 13 Aug 2026 18:03:35 +0200 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8Capi=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/华数.py | 552 ++++++++++++++++++++++++++++++++++++++++++++++++ api/枫叶.py | 445 ++++++++++++++++++++++++++++++++++++++ api/毒舌影视.js | 138 ++++++++++++ api/高清剧迷.js | 271 ++++++++++++++++++++++++ api/鬼片之家.js | 115 ++++++++++ 5 files changed, 1521 insertions(+) create mode 100644 api/华数.py create mode 100644 api/枫叶.py create mode 100644 api/毒舌影视.js create mode 100644 api/高清剧迷.js create mode 100644 api/鬼片之家.js diff --git a/api/华数.py b/api/华数.py new file mode 100644 index 0000000..b78d94b --- /dev/null +++ b/api/华数.py @@ -0,0 +1,552 @@ +# coding=utf-8 +# !/usr/bin/python + +""" + +作者 精彩一瞬间 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除 + ====================Diudiumiao==================== + +""" + +from Crypto.Util.Padding import unpad +from Crypto.Util.Padding import pad +from urllib.parse import urlparse +from urllib.parse import unquote +from Crypto.Cipher import ARC4 +from urllib.parse import quote +from base.spider import Spider +from Crypto.Cipher import AES +from datetime import datetime +from bs4 import BeautifulSoup +from base64 import b64decode +import concurrent.futures +import urllib.request +import urllib.parse +import datetime +import binascii +import requests +import hashlib +import base64 +import json +import time +import hmac +import sys +import re +import os + +sys.path.append('..') + +xurl = "https://www.wasu.cn" + +headerx = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36' + } + +headerz = { + 'accept': '*/*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'cache-control': 'no-cache', + 'origin': xurl, + 'pragma': 'no-cache', + 'priority': 'u=1, i', + 'referer': xurl, + 'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'cross-site', + 'user-agent': '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', + } + +class Spider(Spider): + + def getName(self): + return "精彩" + + def init(self, extend): + pass + + def isVideoFormat(self, url): + pass + + def manualVideoCheck(self): + pass + + def homeVideoContent(self): + pass + + def homeContent(self, filter): + result = {} + result = {"class": [{"type_id": "961", "type_name": "电影"}, + {"type_id": "962", "type_name": "剧集"}, + {"type_id": "963", "type_name": "少儿"}, + {"type_id": "965", "type_name": "栏目"}, + {"type_id": "966", "type_name": "新闻"}], + "list": [], + "filters": {"961": [{"key": "地区", + "name": "地区", + "value": [{"n": "全部", "v": ""}, + {"n": "内地", "v": "内地"}, + {"n": "港台", "v": "港台"}, + {"n": "欧美", "v": "欧美"}, + {"n": "日韩", "v": "日韩"}, + {"n": "泰国", "v": "泰国"}, + {"n": "其他", "v": "其他"}]}, + {"key": "类型", + "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": "年代", + "name": "年代", + "value": [{"n": "全部", "v": ""}, + + {"n": "2026", "v": "2026"}, + {"n": "2025", "v": "2025"}, + {"n": "2024", "v": "2024"}, + {"n": "2023", "v": "2023"}, + {"n": "2022", "v": "2022"}, + {"n": "2021", "v": "2021"}, + {"n": "2020", "v": "2020"}, + {"n": "2019", "v": "2019"}, + {"n": "2018", "v": "2018"}, + {"n": "2017", "v": "2017"}, + {"n": "2016", "v": "2016"}, + {"n": "2015", "v": "2015"}, + {"n": "2014", "v": "2014"}, + {"n": "2013", "v": "2013"}, + {"n": "2012", "v": "2012"}, + {"n": "2011", "v": "2011"}, + {"n": "2010", "v": "2010"}]}], + "962": [{"key": "地区", + "name": "地区", + "value": [{"n": "全部", "v": ""}, + {"n": "内地", "v": "内地"}, + {"n": "港台", "v": "港台"}, + {"n": "日韩", "v": "日韩"}, + {"n": "欧美", "v": "欧美"}, + {"n": "泰国", "v": "泰国"}, + {"n": "其他", "v": "其他"}]}, + {"key": "类型", + "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": "历史"}]}, + {"key": "年代", + "name": "年代", + "value": [{"n": "全部", "v": ""}, + + {"n": "2026", "v": "2026"}, + {"n": "2025", "v": "2025"}, + {"n": "2024", "v": "2024"}, + {"n": "2023", "v": "2023"}, + {"n": "2022", "v": "2022"}, + {"n": "2021", "v": "2021"}, + {"n": "2020", "v": "2020"}, + {"n": "2019", "v": "2019"}, + {"n": "2018", "v": "2018"}, + {"n": "2017", "v": "2017"}, + {"n": "2016", "v": "2016"}, + {"n": "2015", "v": "2015"}, + {"n": "2014", "v": "2014"}, + {"n": "2013", "v": "2013"}, + {"n": "2012", "v": "2012"}, + {"n": "2011", "v": "2011"}, + {"n": "2010", "v": "2010"}]}], + "963": [{"key": "地区", + "name": "地区", + "value": [{"n": "全部", "v": ""}, + {"n": "内地", "v": "内地"}, + {"n": "日韩", "v": "日韩"}, + {"n": "欧美", "v": "欧美"}, + {"n": "港台", "v": "港台"}, + {"n": "其他", "v": "其他"}]}, + {"key": "类型", + "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": "校园"}, + {"n": "童话", "v": "童话"}, + {"n": "推理", "v": "推理"}, + {"n": "怀旧", "v": "怀旧"}, + {"n": "宠物", "v": "宠物"}, + {"n": "舞蹈", "v": "舞蹈"}]}, + {"key": "年代", + "name": "年代", + "value": [{"n": "全部", "v": ""}, + + {"n": "2026", "v": "2026"}, + {"n": "2025", "v": "2025"}, + {"n": "2024", "v": "2024"}, + {"n": "2023", "v": "2023"}, + {"n": "2022", "v": "2022"}, + {"n": "2021", "v": "2021"}, + {"n": "2020", "v": "2020"}, + {"n": "2019", "v": "2019"}, + {"n": "2018", "v": "2018"}, + {"n": "2017", "v": "2017"}, + {"n": "2016", "v": "2016"}, + {"n": "2015", "v": "2015"}, + {"n": "2014", "v": "2014"}, + {"n": "2013", "v": "2013"}, + {"n": "2012", "v": "2012"}, + {"n": "2011", "v": "2011"}, + {"n": "2010", "v": "2010"}]}], + "965": [{"key": "地区", + "name": "地区", + "value": [{"n": "全部", "v": ""}, + {"n": "内地", "v": "内地"}, + {"n": "欧美", "v": "欧美"}]}, + {"key": "类型", + "name": "类型", + "value": [{"n": "全部", "v": ""}, + {"n": "文化", "v": "文化"}, + {"n": "纪实", "v": "纪实"}, + {"n": "访谈", "v": "访谈"}, + {"n": "历史", "v": "历史"}, + {"n": "美食", "v": "美食"}, + {"n": "旅游", "v": "旅游"}, + {"n": "时尚", "v": "时尚"}, + {"n": "情感", "v": "情感"}, + {"n": "生活", "v": "生活"}, + {"n": "真人秀", "v": "真人秀"}]}, + {"key": "年代", + "name": "年代", + "value": [{"n": "全部", "v": ""}, + {"n": "2026", "v": "2026"}, + {"n": "2025", "v": "2025"}, + {"n": "2024", "v": "2024"}, + {"n": "2023", "v": "2023"}, + {"n": "2022", "v": "2022"}, + {"n": "2021", "v": "2021"}, + {"n": "2020", "v": "2020"}, + {"n": "2019", "v": "2019"}, + {"n": "2018", "v": "2018"}, + {"n": "2017", "v": "2017"}, + {"n": "2016", "v": "2016"}, + {"n": "2015", "v": "2015"}, + {"n": "2014", "v": "2014"}, + {"n": "2013", "v": "2013"}, + {"n": "2012", "v": "2012"}, + {"n": "2011", "v": "2011"}, + {"n": "2010", "v": "2010"}]}], + "966": [{"key": "类型", + "name": "类型", + "value": [{"n": "全部", "v": ""}, + {"n": "国内视野", "v": "国内视野"}, + {"n": "国际纵览", "v": "国际纵览"}, + {"n": "军事话题", "v": "军事话题"}, + {"n": "社会百态", "v": "社会百态"}, + {"n": "央视频", "v": "央视频"}]}]}} + return result + + def generate_x_sign(self, secret_b64): + secret_key = self.decode_secret_key(secret_b64) + data = self.get_data_string() + signature = self.compute_signature(secret_key, data) + x_sign = self.encode_signature(signature) + return x_sign + + def decode_secret_key(self, secret_b64): + return base64.b64decode(secret_b64) + + def get_data_string(self): + return "{}" + + def compute_signature(self, secret_key, data): + return hmac.new(secret_key, data.encode('utf-8'), hashlib.sha256).digest() + + def encode_signature(self, signature): + return base64.b64encode(signature).decode('utf-8') + + def get_current_app_key(self, xurl, headerx): + resp = self.fetch_index_page(xurl, headerx) + js_path = self.extract_js_path(resp.text) + js_url = f"{xurl}{js_path}" + js_content = self.fetch_js_content(js_url, headerx) + target_key_b64 = self.extract_target_key(js_content) + return target_key_b64 + + def fetch_index_page(self, xurl, headerx): + resp = requests.get(xurl, headers=headerx) + resp.raise_for_status() + return resp + + def extract_js_path(self, html_content): + js_path_pattern = r'src="(/[\d\.]+/assets/js/index-[\w\.-]+\.js)"' + match = re.search(js_path_pattern, html_content) + return match.group(1) + + def fetch_js_content(self, js_url, headerx): + js_resp = requests.get(js_url, headers=headerx) + js_resp.raise_for_status() + return js_resp.text + + def extract_target_key(self, js_content): + key_pattern = r'const \w+="([^"]+)",\w+="([^"]+)",\w+="([^"]+)";' + key_match = re.search(key_pattern, js_content) + return key_match.group(2) + + def get_headers(self, sign1, xurl): + return { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'cache-control': 'no-cache', + 'launchchannel': 'web_channel', + 'origin': xurl, + 'pragma': 'no-cache', + 'priority': 'u=1, i', + 'referer': xurl, + 'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'cross-site', + 'siteid': '1000101', + 'user-agent': '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', + 'x-sign': sign1, + } + + def categoryContent(self, cid, pg, filter, ext): + secret_b64 = self.get_current_app_key(xurl, headerx) + sign1 = self.generate_x_sign(secret_b64) + page = self.get_page_number(pg) + NdType, DqType, LxType = self.extract_filter_types(ext) + headers = self.get_headers(sign1, xurl) + params = self.build_category_params(cid, page, NdType, DqType, LxType) + data = self.fetch_category_data(params, headers) + videos = self.parse_category_videos(data) + return self.build_category_result(videos, pg) + + def get_page_number(self, pg): + return int(pg) if pg else 1 + + def extract_filter_types(self, ext): + NdType = ext.get('年代', '全部') + DqType = ext.get('地区', '全部') + LxType = ext.get('类型', '全部') + return NdType, DqType, LxType + + def build_category_params(self, cid, page, NdType, DqType, LxType): + return {'functionName': 'getNewsSearchedByCondition','nodeId': cid,'nodeTag': LxType,'yearTag': NdType,'countryTag': DqType,'orderType': '0','pageSize': '40','page': page,'keyword': '','siteId': '1000101',} + + def fetch_category_data(self, params, headers): + detail = requests.get('https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet', params=params,headers=headers) + detail.encoding = "utf-8" + return detail.json() + + def parse_category_videos(self, data): + videos = [] + for vod in data['data']: + videos.append(self.parse_category_video(vod)) + return videos + + def parse_category_video(self, vod): + return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}","vod_name": vod['title'],"vod_pic": vod['hPic'],"vod_year": vod.get('pubTime', '暂无日期'),"vod_remarks": vod.get('episodeDesc', '暂无备注')} + + def build_category_result(self, videos, pg): + result = {'list': videos} + result['page'] = pg + result['pagecount'] = 9999 + result['limit'] = 90 + result['total'] = 999999 + return result + + def detailContent(self, ids): + did = ids[0] + secret_b64 = self.get_current_app_key(xurl, headerx) + sign1 = self.generate_x_sign(secret_b64) + fenge = did.split("@") + headers = self.get_headers(sign1, xurl) + params = self.build_detail_params(fenge) + data = self.fetch_detail_data(params, headers) + content = self.build_content(data) + director = self.extract_detail_field(data, 'director') + actor = self.extract_detail_field(data, 'actor') + remarks = self.extract_detail_field(data, 'episodeDesc') + year = self.extract_detail_field(data, 'pubTime') + area = self.extract_detail_field(data, 'countryTag') + bofang = self.build_play_url(data) + videos = [self.build_video_data(did, director, actor, remarks, year, area, content, bofang)] + return self.build_result(videos) + + def build_detail_params(self, fenge): + return {'siteId': '1000101', 'functionName': 'getCurrentNews', 'nodeId': fenge[0], 'newsId': fenge[1],'platform': 'web',} + + def fetch_detail_data(self, params, headers): + detail = requests.get('https://mcspapp.5g.wasu.tv/bvradio_app/hzhs/newsServlet', params=params, headers=headers) + detail.encoding = "utf-8" + return detail.json() + + def build_content(self, data): + return '介绍剧情👉' + data.get('data', {}).get('newsAbstract', '') + + def extract_detail_field(self, data, field_name): + return data.get('data', {}).get(field_name, '') + + def build_play_url(self, data): + bofang = '' + for vod in data['data']['vodList']: + name = vod['title'] + if len(vod['fileList']) > 1: + id = vod['fileList'][1]['playUrl'] + else: + id = vod['fileList'][0]['playUrl'] + bofang += name + '$' + id + '#' + return bofang[:-1] + + def build_video_data(self, did, director, actor, remarks, year, area, content, bofang): + return {"vod_id": did, "vod_director": director, "vod_actor": actor, "vod_remarks": remarks, "vod_year": year, "vod_area": area, "vod_content": content, "vod_play_from": "华数", "vod_play_url": bofang} + + def build_result(self, videos): + result = {} + result['list'] = videos + return result + + def get_x_sign_for_post(self, secret_b64, playUrl): + secret_key = self.decode_secret_key(secret_b64) + payload_dict = self.build_payload_dict(playUrl) + data_string = self.serialize_payload(payload_dict) + signature = self.compute_hmac_signature(secret_key, data_string) + x_sign = self.encode_signature(signature) + return x_sign + + def decode_secret_key(self, secret_b64): + return base64.b64decode(secret_b64) + + def build_payload_dict(self, playUrl): + return {"playUrl": playUrl, "platform": "web"} + + def serialize_payload(self, payload_dict): + return json.dumps(payload_dict, separators=(',', ':'), ensure_ascii=False) + + def compute_hmac_signature(self, secret_key, data_string): + return hmac.new(secret_key, data_string.encode('utf-8'), hashlib.sha256).digest() + + def encode_signature(self, signature): + return base64.b64encode(signature).decode('utf-8') + + def playerContent(self, flag, id, vipFlags): + secret_b64 = self.get_current_app_key(xurl, headerx) + id = self.normalize_play_url(id) + sign2 = self.get_x_sign_for_post(secret_b64, id) + headers = self.get_headers(sign2, xurl) + json_data = self.build_request_json(id) + result = self.post_play_url_request(headers, json_data) + play_url = result['data']['playUrl'] + return self.build_player_result(play_url) + + def normalize_play_url(self, id): + return id.replace('.mp4', '/playlist.m3u8') + + def build_request_json(self, playUrl): + return {'playUrl': playUrl, 'platform': 'web', } + + def post_play_url_request(self, headers, json_data): + response = requests.post('https://mcspapp.5g.wasu.tv/thirdApiFile/file/getPlayUrl', headers=headers, json=json_data) + return response.json() + + def build_player_result(self, play_url): + result = {} + result["parse"] = 0 + result["playUrl"] = '' + result["url"] = play_url + result["header"] = headerz + return result + + def searchContentPage(self, key, quick, pg): + page = self.get_page_number(pg) + secret_b64 = self.get_current_app_key(xurl, headerx) + sign1 = self.generate_x_sign(secret_b64) + headers = self.get_headers(sign1, xurl) + params = self.build_search_params(key, page) + data = self.fetch_search_data(params, headers) + videos = self.parse_search_videos(data) + return self.build_search_result(videos, pg) + + def get_page_number(self, pg): + return int(pg) if pg else 1 + + def build_search_params(self, key, page): + return {'functionName': 'getNewsSearched', 'searchNewsType': '3,4,5', 'keyword': key, 'pageSize': 10, 'page': page, 'siteId': 1000101} + + def fetch_search_data(self, params, headers): + detail = requests.get("https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet", params=params, headers=headers) + detail.encoding = "utf-8" + return detail.json() + + def parse_search_videos(self, data): + videos = [] + for vod in data['data']: + videos.append(self.parse_search_video(vod)) + return videos + + def parse_search_video(self, vod): + return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}", "vod_name": vod['title'], "vod_pic": vod['hPic'], "vod_year": vod.get('pubTime', '暂无日期'), "vod_remarks": vod.get('episodeDesc', '暂无备注')} + + def build_search_result(self, videos, pg): + result = {'list': videos} + result['page'] = pg + result['pagecount'] = 9999 + result['limit'] = 90 + result['total'] = 999999 + return result + + def searchContent(self, key, quick, pg="1"): + return self.searchContentPage(key, quick, '1') + + def localProxy(self, params): + if params['type'] == "m3u8": + return self.proxyM3u8(params) + elif params['type'] == "media": + return self.proxyMedia(params) + elif params['type'] == "ts": + return self.proxyTs(params) + return None + + + + + + + + + + diff --git a/api/枫叶.py b/api/枫叶.py new file mode 100644 index 0000000..60292ee --- /dev/null +++ b/api/枫叶.py @@ -0,0 +1,445 @@ +# -*- coding: utf-8 -*- +import re, urllib.parse +import json +import time +from bs4 import BeautifulSoup +import requests +from base.spider import Spider as BaseSpider + + +class Spider(BaseSpider): + # 缓存变量,避免频繁请求发布页 + _cache_host = "" + _cache_time = 0 + CACHE_DURATION = 300 # 缓存5分钟 + + def init(self, extend=""): + self.headers = { + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "zh-CN,zh;q=0.9", + } + + # 优先读取缓存,没有或过期则重新获取 + now = time.time() + if self._cache_host and now - self._cache_time < self.CACHE_DURATION: + self.host = self._cache_host + else: + publish_url = "https://www.vip1949.com/" + self.host = self.get_online_host(publish_url) + self._cache_host = self.host + self._cache_time = now + + def get_online_host(self, publish_url): + """从发布页解析JS域名列表,返回第一个在线可用主站""" + try: + resp = requests.get(publish_url, headers=self.headers, timeout=15) + resp.raise_for_status() + html = resp.text + + # 正则提取JS中的domains数组 + pattern = r'const domains = (\[.*?\]);' + match = re.search(pattern, html, re.S) + if not match: + return "https://www.cd-zj.com" + + js_str = match.group(1) + # 修复JS对象格式为标准JSON + js_str = re.sub(r'(\w+)\s*:', r'"\1":', js_str) + js_str = re.sub(r'"+', '"', js_str) + domains = json.loads(js_str) + + # 逐个检测连通性,返回第一个可用 + for item in domains: + url = item["url"] + if self.check_url_online(url): + return url.rstrip("/") + + return "https://www.cd-zj.com" + except Exception as e: + print(f"获取发布页异常: {e}") + return "https://www.cd-zj.com" + + def check_url_online(self, url, timeout=3): + """检测网址是否可正常访问""" + try: + r = requests.head(url, headers=self.headers, timeout=timeout, allow_redirects=True) + return 200 <= r.status_code < 400 + except: + return False + + def getName(self): + return '枫叶影院' + + def homeContent(self, filter): + return {"class": [ + {'type_id': "/label/qq", 'type_name': "腾讯VIP精选"}, + {'type_id': "/label/bli", 'type_name': "B站VIP精选"}, + {'type_id': "/label/youku", 'type_name': "优酷VIP精选"}, + {"type_id": "2", "type_name": "电视剧"}, + {"type_id": "1", "type_name": "电影"}, + {"type_id": "4", "type_name": "动漫"}, + {"type_id": "3", "type_name": "综艺"}, + {"type_id": "5", "type_name": "热门短剧"}, + ], "filters": self._build_filters()} + + def _build_filters(self): + area = [{"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": "俄罗斯"}, {"n": "其它", "v": "其它"}] + year = [{"n": "全部", "v": ""}, {"n": "2026", "v": "2026"}, {"n": "2025", "v": "2025"}, + {"n": "2024", "v": "2024"}, {"n": "2023", "v": "2023"}, {"n": "2022", "v": "2022"}, + {"n": "2021", "v": "2021"}, {"n": "2020", "v": "2020"}, {"n": "2019", "v": "2019"}, + {"n": "2018", "v": "2018"}, {"n": "2017", "v": "2017"}, {"n": "2016", "v": "2016"}, + {"n": "2015", "v": "2015"}, {"n": "2014", "v": "2014"}, {"n": "2013", "v": "2013"}, + {"n": "2012", "v": "2012"}, {"n": "2011", "v": "2011"}, {"n": "2010", "v": "2010"}, + {"n": "2009", "v": "2009"}, {"n": "2008", "v": "2008"}, {"n": "2007", "v": "2007"}, + {"n": "2006", "v": "2006"}, {"n": "2005", "v": "2005"}, {"n": "2004", "v": "2004"}] + lang = [{"n": "全部", "v": ""}, {"n": "国语", "v": "国语"}, {"n": "英语", "v": "英语"}, + {"n": "粤语", "v": "粤语"}, {"n": "闽南语", "v": "闽南语"}, {"n": "韩语", "v": "韩语"}, + {"n": "日语", "v": "日语"}, {"n": "法语", "v": "法语"}, {"n": "德语", "v": "德语"}, + {"n": "其它", "v": "其它"}] + sort = [{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"}, {"n": "评分", "v": "score"}] + letter = [{"n": "全部", "v": ""}, {"n": "A", "v": "A"}, {"n": "B", "v": "B"}, {"n": "C", "v": "C"}, + {"n": "D", "v": "D"}, {"n": "E", "v": "E"}, {"n": "F", "v": "F"}, {"n": "G", "v": "G"}, + {"n": "H", "v": "H"}, {"n": "I", "v": "I"}, {"n": "J", "v": "J"}, {"n": "K", "v": "K"}, + {"n": "L", "v": "L"}, {"n": "M", "v": "M"}, {"n": "N", "v": "N"}, {"n": "O", "v": "O"}, + {"n": "P", "v": "P"}, {"n": "Q", "v": "Q"}, {"n": "R", "v": "R"}, {"n": "S", "v": "S"}, + {"n": "T", "v": "T"}, {"n": "U", "v": "U"}, {"n": "V", "v": "V"}, {"n": "W", "v": "W"}, + {"n": "X", "v": "X"}, {"n": "Y", "v": "Y"}, {"n": "Z", "v": "Z"}, {"n": "0-9", "v": "0-9"}] + return { + "2": [ + {"key": "class", "name": "类型", + "value": [{"n": "全部", "v": "2"}, {"n": "国产剧", "v": "13"}, {"n": "日韩剧", "v": "15"}, + {"n": "海外剧", "v": "16"}]}, + {"key": "area", "name": "地区", "value": area}, + {"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in + [("全部", ""), ("古装", "古装"), ("战争", "战争"), + ("青春偶像", "青春偶像"), ("喜剧", "喜剧"), + ("家庭", "家庭"), ("犯罪", "犯罪"), ("动作", "动作"), + ("奇幻", "奇幻"), ("剧情", "剧情"), ("历史", "历史"), + ("经典", "经典"), ("乡村", "乡村"), ("情景", "情景"), + ("商战", "商战"), ("网剧", "网剧"), ("其他", "其他")]]}, + {"key": "year", "name": "年份", "value": year}, + {"key": "lang", "name": "语言", "value": lang}, + {"key": "letter", "name": "字母", "value": letter}, + {"key": "sort", "name": "排序", "value": sort}, + ], + "1": [ + {"key": "class", "name": "类型", + "value": [{"n": "全部", "v": "1"}, {"n": "动作片", "v": "6"}, {"n": "喜剧片", "v": "7"}, + {"n": "恐怖片", "v": "8"}, {"n": "科幻片", "v": "9"}, {"n": "爱情片", "v": "10"}, + {"n": "剧情片", "v": "11"}, {"n": "战争片", "v": "12"}, {"n": "纪录片", "v": "20"}]}, + {"key": "area", "name": "地区", "value": area}, + {"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in + [("全部", ""), ("喜剧", "喜剧"), ("爱情", "爱情"), + ("恐怖", "恐怖"), ("动作", "动作"), ("科幻", "科幻"), + ("剧情", "剧情"), ("战争", "战争"), ("警匪", "警匪"), + ("犯罪", "犯罪"), ("动画", "动画"), ("奇幻", "奇幻"), + ("武侠", "武侠"), ("冒险", "冒险"), ("枪战", "枪战"), + ("悬疑", "悬疑"), ("惊悚", "惊悚"), ("经典", "经典"), + ("青春", "青春"), ("文艺", "文艺"), ("微电影", "微电影"), + ("古装", "古装"), ("历史", "历史"), ("运动", "运动"), + ("农村", "农村"), ("儿童", "儿童"), + ("网络电影", "网络电影")]]}, + {"key": "year", "name": "年份", "value": year}, + {"key": "lang", "name": "语言", "value": lang}, + {"key": "letter", "name": "字母", "value": letter}, + {"key": "sort", "name": "排序", "value": sort}, + ], + "4": [ + {"key": "class", "name": "类型", + "value": [{"n": "全部", "v": "4"}, {"n": "国产动漫", "v": "25"}, {"n": "日韩动漫", "v": "26"}]}, + {"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in + [("全部", ""), ("情感", "情感"), ("科幻", "科幻"), + ("热血", "热血"), ("推理", "推理"), ("搞笑", "搞笑"), + ("冒险", "冒险"), ("奇幻", "奇幻"), ("战斗", "战斗"), + ("校园", "校园"), ("萝莉", "萝莉"), ("治愈", "治愈"), + ("原创", "原创"), ("亲子", "亲子"), ("益智", "益智"), + ("励志", "励志"), ("其他", "其他")]]}, + {"key": "area", "name": "地区", + "value": [{"n": "全部", "v": ""}, {"n": "大陆", "v": "大陆"}, {"n": "香港", "v": "香港"}, + {"n": "台湾", "v": "台湾"}, {"n": "美国", "v": "美国"}, {"n": "韩国", "v": "韩国"}, + {"n": "日本", "v": "日本"}, {"n": "法国", "v": "法国"}, {"n": "英国", "v": "英国"}, + {"n": "其它", "v": "其它"}]}, + {"key": "year", "name": "年份", "value": year}, + {"key": "lang", "name": "语言", "value": lang}, + {"key": "letter", "name": "字母", "value": letter}, + {"key": "sort", "name": "排序", "value": sort}, + ], + "3": [ + {"key": "class", "name": "类型", + "value": [{"n": "全部", "v": "3"}, {"n": "大陆综艺", "v": "21"}, {"n": "日韩综艺", "v": "22"}]}, + {"key": "genre", "name": "剧情", "value": [{"n": v[0], "v": v[1]} for v in + [("全部", ""), ("选秀", "选秀"), ("情感", "情感"), + ("访谈", "访谈"), ("播报", "播报"), ("音乐", "音乐"), + ("美食", "美食"), ("旅游", "旅游"), ("搞笑", "搞笑"), + ("游戏", "游戏"), ("亲子", "亲子"), ("其它", "其它")]]}, + {"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": year}, + {"key": "lang", "name": "语言", "value": lang}, + {"key": "letter", "name": "字母", "value": letter}, + {"key": "sort", "name": "排序", "value": sort}, + ], + } + + def homeVideoContent(self): + html = self._fetch('/') + return {"list": self._parse_video_list(html)} + + def categoryContent(self, tid, pg, filter, extend): + # 构建筛选参数:参照歪比巴卜,直接取extend里的值,fallback到filter + if tid.startswith('/label'): + url = f'{tid}/page/{pg}.html' + html = self._fetch(url) + items = self._parse_video_list(html) + page = int(pg) + page_count = page if len(items) < 24 else page + 2 + return {"list": items, "page": page, "pagecount": page_count, "limit": 24, "total": page_count * 24} + + args = {} + if extend and isinstance(extend, dict): + for k, v in extend.items(): + if v: + args[k] = str(v) + if isinstance(filter, dict): + for k, v in filter.items(): + if v and k not in args: + args[k] = str(v) + route_tid = args.get('class', args.get('tid', str(tid))) + area = args.get('area', '') + genre = args.get('genre', '') + year = args.get('year', '') + lang = args.get('lang', '') + letter = args.get('letter', '') + sort = args.get('sort', '') + # 无筛选走正常分页 + if not area and not genre and not year and not lang and not letter and not sort: + url = f'/cupfox-list/{route_tid}--------{pg}---.html' + html = self._fetch(url) + items = self._parse_video_list(html) + page = int(pg) + soup = BeautifulSoup(html, 'html.parser') + pagecount = page + for a in soup.select('a.page-link'): + if a.text == '尾页': + m = re.search(r'---(\d+)---', a.get('href', '')) + if m: + pagecount = int(m.group(1)) + break + if not items: + pagecount = 0 + return {"list": items, "page": page, "pagecount": pagecount, "limit": 36, "total": 9999} + # 有筛选:{tid}-{area}-{sort}-{genre}-{lang}-{letter}------{year}.html + segs = [route_tid, area, sort, genre, lang, letter, '', '', year] + url = '/cupfox-list/' + '-'.join(segs) + '.html' + html = self._fetch(url) + items = self._parse_video_list(html) + return {"list": items, "page": 1, "pagecount": 1, "limit": 36, "total": 9999} + + def detailContent(self, ids): + result = {"list": []} + vid = ids[0].split(',')[0].strip() + try: + html = self._fetch(f'/detail/{vid}.html') + if not html: return result + soup = BeautifulSoup(html, 'html.parser') + vod_name = soup.select_one('h3.slide-info-title') + vod_name = vod_name.text.strip() if vod_name else '' + vod_pic = soup.select_one('img.lazy') + vod_pic = self._fix_pic(vod_pic.get('data-src', '')) if vod_pic else '' + vod_director = '' + vod_actor = '' + for el in soup.select('.slide-info'): + text = el.get_text(' ').strip() + if text.startswith('导演:'): + vod_director = text.replace('导演:', '').strip() + elif text.startswith('演员:'): + vod_actor = text.replace('演员:', '').strip() + vod_content = soup.select_one('#height_limit') + vod_content = vod_content.get_text(' ', strip=True) if vod_content else '' + play_from, play_url = [], [] + for tab in soup.select('.anthology-tab a.swiper-slide'): + src_name = re.sub(r'<[^>]+>', '', str(tab)).strip() or tab.get_text(' ', strip=True).strip() + if src_name: + play_from.append(src_name) + tab_blocks = soup.select('.anthology-list-box') + for i, block in enumerate(tab_blocks): + ep_list = [] + for a in block.select('li a'): + href = a.get('href', '') + m = re.search(r'/play/(.*?)\.html', href) + if m: + ep_list.append(f'{a.text.strip()}${vid}-{m.group(1)}') + ep_list.reverse() + if ep_list and i < len(play_from): + play_url.append('#'.join(ep_list)) + valid_from = [pf for i, pf in enumerate(play_from) if i < len(play_url)] + result["list"].append({ + "vod_id": vid, "vod_name": vod_name, "vod_pic": vod_pic, + "vod_director": vod_director, "vod_actor": vod_actor, + "vod_content": vod_content, + "vod_play_from": "$$$".join(valid_from), + "vod_play_url": "$$$".join(play_url), + }) + except: + pass + return result + + def searchContent(self, key, quick, pg="1"): + try: + decoded = urllib.parse.unquote(key) + except: + decoded = key + html = self._fetch(f'/cupfox-search/{urllib.parse.quote(decoded)}----------{pg}---.html') + items = self._parse_search_list(html) + return {"list": items, "page": int(pg), "pagecount": 1, "limit": 36, "total": len(items)} + + def playerContent(self, flag, id, vipFlags): + url = '' + try: + url = id if id.startswith('http') else f'{self.host}/play/{id}.html' + html = self._fetch(url) + if html: + m = re.search(r'player_aaaa=(.*?)', html, re.S) + if m: + + try: + pd = json.loads(m.group(1)) + except Exception as e: + print(e) + pd = {} + # print('pd:', pd) + play_url = pd.get('url') + play_id = pd.get('from') + + api_map = { + 'YYNB': 'https://zzrs.mfdyvip.com/player/mplayer.php', + 'JD4K': 'https://fgsrg.hzqingshan.com/player/mplayer.php', + } + if not play_url: + return {"parse": 0, "url": 'https://php.doube.eu.org/error.m3u8', + "header": {'User-Agent': 'Mozilla/5.0'}} + if play_url.startswith('http') and (play_url.endswith('.m3u8') or play_url.endswith('.mp4')): + return {"parse": 0, "url": play_url, "header": {'User-Agent': 'Mozilla/5.0'}} + + else: + headers = { + 'User-Agent': "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", + 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + 'accept-language': "zh-CN,zh;q=0.9", + 'cache-control': "no-cache", + 'pragma': "no-cache", + 'priority': "u=0, i", + 'referer': "https://www.ht10010.com/", + 'Content-Type': 'application/x-www-form-urlencoded', + } + response = requests.get(f"https://fgsrg.hzqingshan.com/player/?url={play_url}", headers=headers) + token = re.search(r'data-te="(.*?)"', response.text) + if token: + token = token.group(1) + payload = { + 'url': play_url, + 'token': token + } + # print('payload', payload) + try: + response = self.post(api_map[play_id], data=payload, headers=headers) + + response.raise_for_status() + result = response.json() + # print('result:', result) + if result['code'] == 200 and 'url' in result: + play_url = result['url'] + return {"parse": 0, "url": play_url, "header": { + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'}} + except Exception as e: + print(e) + except Exception as e: + print(e) + return {"parse": 1, "url": url} + + def localProxy(self, param=''): + return {} + + def isVideoFormat(self, url): + return False + + def manualVideoCheck(self): + return False + + def _fetch(self, url): + try: + if not url.startswith('http'): + url = self.host + url + rsp = self.fetch(url, headers=self.headers) + return rsp.text if rsp else '' + except: + return '' + + def _fix_pic(self, u): + if not u: return '' + if u.startswith('//'): return 'https:' + u + return u.replace('&', '&') + + def _parse_video_list(self, html): + videos, seen = [], set() + soup = BeautifulSoup(html, 'html.parser') + cards = soup.select('a.public-list-exp') + for a in cards: + href = a.get('href', '') + m = re.search(r'/detail/(\d+)\.html', href) + if not m: continue + vod_id = m.group(1) + if vod_id in seen: continue + seen.add(vod_id) + span = ','.join([span.text for span in a.select('span.public-prt')]) + # print('span', span) + vod_name = a.get('title', '') or (a.select_one('img') and a.select_one('img').get('alt', '')) or '' + pic_el = a.select_one('img') + vod_pic = self._fix_pic(pic_el.get('data-src', '')) if pic_el else '' + remark_el = a.select_one('.ft2') or a.select_one('.public-list-prb') + vod_remarks = remark_el.text.strip() if remark_el else '' + videos.append( + {"vod_id": vod_id, "vod_name": vod_name.strip(), "vod_pic": vod_pic, "vod_remarks": vod_remarks, "vod_year": span}) + return videos + + def _parse_search_list(self, html): + videos, seen = [], set() + soup = BeautifulSoup(html, 'html.parser') + cards = soup.select('a.public-list-exp') + for a in cards: + href = a.get('href', '') + m = re.search(r'/detail/(\d+)\.html', href) + if not m: continue + vod_id = m.group(1) + if vod_id in seen: continue + seen.add(vod_id) + pic_el = a.select_one('img') + vod_pic = self._fix_pic(pic_el.get('data-src', '')) if pic_el else '' + title_el = soup.select_one(f'a.thumb-txt[href="/detail/{vod_id}.html"]') + if title_el: + vod_name = title_el.text.strip() + else: + vod_name = a.select_one('img') and a.select_one('img').get('alt', '') or '' + remark_el = a.select_one('.public-list-prb') or a.select_one('.ft2') + vod_remarks = remark_el.text.strip() if remark_el else '' + videos.append( + {"vod_id": vod_id, "vod_name": vod_name.strip(), "vod_pic": vod_pic, "vod_remarks": vod_remarks}) + return videos + + +if __name__ == '__main__': + sp = Spider() + sp.init() + # 20067-5-189 + print(sp.categoryContent('/label/qq','1',True, {})) + # print(sp.playerContent('', '20067-6-189', [])) + # print(sp.playerContent('', '20067-5-189', [])) + pass diff --git a/api/毒舌影视.js b/api/毒舌影视.js new file mode 100644 index 0000000..d4efff9 --- /dev/null +++ b/api/毒舌影视.js @@ -0,0 +1,138 @@ +/** + * 毒舌影视 - 2026/01/22 源码定点适配版 (移除console.log) + * 适配路径: /dsshiyidt/ + * 适配图片: data-original + */ + +const host = 'https://www.xnhrsb.com'; +const headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer': host + '/' +}; + +async function init(cfg) {} + +const m = (s, r, i = 1) => (s.match(r) || [])[i] || ""; +const fixPic = p => p && p.startsWith('/') ? host + p : p; + +/** + * 核心解析:针对
]*>([\s\S]*?)<\/p>/)?.[1] || "").replace(/<.*?>/g, "").trim() || "暂无简介"; + + return JSON.stringify({ + list: [{ + vod_id: id, + vod_name, + vod_pic: vod_pic.startsWith('/') ? host + vod_pic : vod_pic, + vod_content, + vod_director: m(/