上传文件至「api」

This commit is contained in:
2026-08-13 18:03:35 +02:00
parent d4df4a4949
commit 72a7f561d4
5 changed files with 1521 additions and 0 deletions
+552
View File
@@ -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
+445
View File
@@ -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=(.*?)</script>', 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('&amp;', '&')
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
+138
View File
@@ -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;
/**
* 核心解析:针对 <li> 结构进行精准提取
*/
function getList(html) {
let list = [];
// 源码特征:<li class="..."><a href="/dsshiyidt/ID.html">...<img data-original="PIC" alt="NAME">...</h3>
// 使用 pdfa 提取所有 li 元素
const items = pdfa(html, ".bt_img ul li");
items.forEach(it => {
// 提取 ID (匹配 dsshiyidt 或 dsshiyixq 或 dsshiyipy 以防万一)
let id = m(it, /href="\/dsshiyi(?:dt|xq|py)\/(.*?)\.html"/);
if (!id) return;
// 提取图片:源码明确使用 data-original
let pic = m(it, /data-original="([^"]+)"/) || m(it, /src="([^"]+)"/);
// 提取标题:源码在 alt 属性里,也在 dytit 类的 a 标签里
let name = m(it, /alt="([^"]+)"/) || m(it, /class="dytit"[\s\S]*?>(.*?)<\/a>/);
name = name ? name.replace(/<.*?>/g, "").trim() : "";
// 提取备注:源码在 hdinfo 类里
let remarks = m(it, /class="hdinfo">[\s\S]*?<span>(.*?)<\/span>/);
if (id && name) {
list.push({
vod_id: id,
vod_name: name,
vod_pic: fixPic(pic),
vod_remarks: remarks.trim()
});
}
});
return list;
}
async function home(filter) {
return JSON.stringify({
class: [
{ type_id: "1", type_name: "电影" },
{ type_id: "2", type_name: "电视剧" },
{ type_id: "3", type_name: "综艺" },
{ type_id: "4", type_name: "动漫" },
{ type_id: "5", type_name: "短剧" }
]
});
}
async function homeVod() {
const r = await req(host, { headers });
return JSON.stringify({ list: getList(r.content) });
}
async function category(tid, pg, filter, extend = {}) {
let p = pg || 1;
// 适配源码中的 11 横杠分页逻辑
// 第一页: /dsshiyisw/1-----------.html
// 第二页: /dsshiyisw/1--------2---.html
let url = `${host}/dsshiyisw/${tid}-----------.html`;
if (p > 1) {
url = `${host}/dsshiyisw/${tid}--------${p}---.html`;
}
const r = await req(url, { headers });
const list = getList(r.content);
// 已移除 console.log
return JSON.stringify({ page: p, list: list });
}
async function detail(id) {
// 详情页路径统一尝试 dt (源码中新发现的路径)
const r = await req(`${host}/dsshiyidt/${id}.html`, { headers });
const h = r.content;
// 详情页解析:标题在 h1,图片在 dyimg 类的 img 里
const name = m(h, /<h1[^>]*>(.*?)<\/h1>/);
const pic = fixPic(m(h, /class="dyimg"[\s\S]*?src="(.*?)"/));
const content = m(h, /id="p_content"[\s\S]*?>(.*?)<\/div>/s).replace(/<.*?>/g, "").trim();
// 播放源解析
const playFrom = pdfa(h, ".mi_paly_box .ypxingq_t").map(it => it.replace(/<.*?>/g, "").trim()).join("$$$") || "播放源";
const playUrl = pdfa(h, ".paly_list_btn").map(l =>
pdfa(l, "a").map(it => {
let aName = it.replace(/<.*?>/g, "").trim();
let aId = m(it, /href="\/dsshiyipy\/(.*?)\.html"/);
if (!aId || aName.includes('立即播放')) return null;
return `${aName}$${aId}`;
}).filter(Boolean).join("#")
).join("$$$");
return JSON.stringify({
list: [{
vod_id: id,
vod_name: name,
vod_pic: pic,
vod_content: content || "暂无简介",
vod_play_from: playFrom,
vod_play_url: playUrl
}]
});
}
async function search(wd, quick, pg = 1) {
let url = `${host}/dsshiyisc/${encodeURIComponent(wd)}----------${pg}---.html`;
const r = await req(url, { headers });
return JSON.stringify({ list: getList(r.content) });
}
async function play(flag, id, flags) {
const r = await req(`${host}/dsshiyipy/${id}.html`, { headers });
const html = r.content;
// 提取播放器配置中的 m3u8 地址
let u = m(html, /"url"\s*:\s*"([^"]+\.m3u8[^"]*)"/);
if (u) return JSON.stringify({ parse: 0, url: u.replace(/\\/g, ""), header: headers });
// 如果没有直链,调用 webview 解析
return JSON.stringify({ parse: 1, url: `${host}/dsshiyipy/${id}.html` });
}
export default { init, home, homeVod, category, detail, search, play };
+271
View File
@@ -0,0 +1,271 @@
import { Crypto, load, _ } from 'assets://js/lib/cat.js';
let siteUrl = 'https://gaoqingju.com';
let siteKey = '';
let siteType = 0;
let headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.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',
'Referer': 'https://gaoqingju.com/'
};
async function request(reqUrl, postData, agentSp, get) {
let res = await req(reqUrl, {
method: get ? 'get' : 'post',
headers: headers,
data: postData || {},
postType: get ? '' : 'form',
});
return res.content;
}
async function init(cfg) {
siteKey = cfg.skey;
siteType = cfg.stype;
}
async function home(filter) {
let classes = [
{ type_id: 'movie', type_name: '电影' },
{ type_id: 'tv', type_name: '电视剧' },
{ type_id: 'manga', type_name: '动漫' },
{ type_id: 'jilu', type_name: '纪录片' }
];
let filterObj = genFilterObj();
return JSON.stringify({
class: classes,
filters: filterObj
});
}
async function homeVod() {
let html = await request(siteUrl, null, null, true);
const $ = load(html);
let videos = [];
$('.grid > a.group').each((i, n) => {
let pic = $(n).find('img').attr('src') || '';
if (pic.startsWith('/')) {
pic = siteUrl + pic;
}
videos.push({
vod_id: $(n).attr('href'),
vod_name: $(n).find('h3').text().trim(),
vod_pic: pic,
vod_remarks: $(n).find('.absolute.top-2.right-2').text().trim() || $(n).find('.absolute.bottom-0 p').text().trim()
});
});
return JSON.stringify({
list: videos,
});
}
async function category(tid, pg, filter, extend) {
let url = `${siteUrl}/index/${tid}.html`;
if (extend && Object.keys(extend).length > 0) {
let id = extend['id'] || 't';
let area = extend['area'] || 'a';
let year = extend['year'] || 'y';
let clazz = extend['class'] || 's';
if (tid === 'tv') {
let status = extend['status'] || 'n';
url = `${siteUrl}/index/${tid}-${id}-${area}-${year}-${clazz}-${status}.html`;
} else {
url = `${siteUrl}/index/${tid}-${id}-${area}-${year}-${clazz}.html`;
}
}
if (pg && pg > 1) {
url += `?page=${pg}`;
}
let videos = await getVideos(url);
return JSON.stringify({
list: videos,
});
}
async function detail(id) {
try {
let url = id.startsWith('http') ? id : siteUrl + id;
const html = await request(url, null, null, true);
const $ = load(html);
let title = $('h1.text-4xl').text().trim();
let pic = $('.aspect-poster img').attr('src') || '';
if (pic.startsWith('/')) {
pic = siteUrl + pic;
}
let director = '';
let actor = '';
$('div.mb-6.space-y-2 > p').each((i, el) => {
let text = $(el).text();
if (text.includes('导演:')) director = text.replace('导演:', '').trim();
if (text.includes('主演:')) actor = text.replace('主演:', '').trim();
});
let content = $('h3:contains("剧情简介")').next('p').text().trim();
let fromList = [];
let urlList = [];
let containers = $('[x-ref="episodesContainer"], [x-ref="backupEpisodesContainer"]');
if (containers.length > 0) {
containers.each((idx, container) => {
let sourceName = idx === 0 ? '在线播放' : '在线播放' + (idx + 1);
let currentUrls = [];
$(container).find('a').each((i, n) => {
let name = $(n).text().replace(/\s+/g, '').trim();
let link = $(n).attr('href');
if (link) {
currentUrls.push(`${name}$${link}`);
}
});
if (currentUrls.length > 0) {
fromList.push(sourceName);
urlList.push(currentUrls.join('#'));
}
});
} else {
let backupUrls = [];
$('.bg-gray-900\\/50 a').each((i, n) => {
let name = $(n).text().replace(/\s+/g, '').trim();
let link = $(n).attr('href');
if (link) {
backupUrls.push(`${name}$${link}`);
}
});
if (backupUrls.length > 0) {
fromList.push('在线播放');
urlList.push(backupUrls.join('#'));
}
}
const video = {
vod_id: id,
vod_name: title,
vod_pic: pic,
vod_actor: actor,
vod_director: director,
vod_content: content,
vod_play_from: fromList.join('$$$'),
vod_play_url: urlList.join('$$$'),
};
return JSON.stringify({ list: [video] });
} catch (e) {
console.error(e);
}
return null;
}
async function search(wd, quick, pg) {
let url = `${siteUrl}/index/search/get_search_data.html?keyword=${encodeURIComponent(wd)}`;
if (pg && pg > 1) url += `&page=${pg}`;
let videos = await getVideos(url);
return JSON.stringify({
list: videos,
});
}
async function play(flag, id, flags) {
let url = id.startsWith('http') ? id : siteUrl + id;
const html = await request(url, null, null, true);
let playUrlMatch = html.match(/let\s+playUrl\s*=\s*['"](.*?)['"]/);
let rawUrl = playUrlMatch ? playUrlMatch[1] : '';
let finalUrl = rawUrl;
if (rawUrl.includes('target=')) {
try {
let urlObj = new URL(rawUrl);
let targetBase64 = urlObj.searchParams.get('target');
if (targetBase64) {
finalUrl = base64Decode(targetBase64);
}
} catch (e) {
console.error(e);
}
}
return JSON.stringify({
parse: 0,
url: finalUrl,
});
}
async function getVideos(url) {
const html = await request(url, null, null, true);
const $ = load(html);
let videos = [];
$('.grid > div.group a').each((i, n) => {
let id = $(n).attr('href');
let name = $(n).find('h3').text().trim();
let pic = $(n).find('img').attr('src') || '';
if (pic.startsWith('/')) {
pic = siteUrl + pic;
}
let remark = $(n).find('.absolute.top-2.right-2').text().trim();
videos.push({
vod_id: id,
vod_name: name,
vod_pic: pic,
vod_remarks: remark,
});
});
return videos;
}
function base64Encode(text) {
return Crypto.enc.Base64.stringify(Crypto.enc.Utf8.parse(text));
}
function base64Decode(text) {
return Crypto.enc.Utf8.stringify(Crypto.enc.Base64.parse(text));
}
function genFilterObj() {
return {
'movie': [
{ key: 'id', name: '类型', value: [{n:'全部',v:'t'},{n:'动作片',v:'3'},{n:'恐怖片',v:'4'},{n:'剧情片',v:'5'},{n:'战争片',v:'6'},{n:'爱情片',v:'9'},{n:'科幻片',v:'10'},{n:'惊悚片',v:'11'},{n:'犯罪片',v:'12'},{n:'悬疑片',v:'13'},{n:'喜剧片',v:'29'}] },
{ key: 'year', name: '年份', value: [{n:'全部',v:'y'},{n:'2026',v:'2026'},{n:'2025',v:'2025'},{n:'2024',v:'2024'},{n:'2023',v:'2023'},{n:'2022',v:'2022'},{n:'2021',v:'2021'}] }
],
'tv': [
{ key: 'id', name: '地区', value: [{n:'全部',v:'t'},{n:'国产剧',v:'18'},{n:'港台剧',v:'19'},{n:'欧美剧',v:'20'},{n:'韩剧',v:'21'},{n:'日剧',v:'40'}] },
{ key: 'year', name: '年份', value: [{n:'全部',v:'y'},{n:'2026',v:'2026'},{n:'2025',v:'2025'},{n:'2024',v:'2024'},{n:'2023',v:'2023'},{n:'2022',v:'2022'}] },
{ key: 'status', name: '状态', value: [{n:'全部',v:'n'},{n:'连载中',v:'2'},{n:'已完结',v:'1'}] }
],
'manga': [
{ key: 'id', name: '分类', value: [{n:'全部',v:'t'},{n:'中国',v:'8'},{n:'日本',v:'34'},{n:'韩国',v:'35'},{n:'欧美',v:'37'}] }
],
'jilu': [
{ key: 'id', name: '分类', value: [{n:'全部',v:'t'},{n:'中国',v:'8'},{n:'日本',v:'34'},{n:'韩国',v:'35'},{n:'欧美',v:'37'}] }
]
};
}
export function __jsEvalReturn() {
return {
init: init,
home: home,
homeVod: homeVod,
category: category,
detail: detail,
play: play,
search: search,
};
}
+115
View File
@@ -0,0 +1,115 @@
let host = 'https://www.guipian360.com';
let headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Referer": host + "/"
};
const extractVideos = html => {
if (!html) return [];
return pdfa(html, '.m-movies .u-movie').map(it => {
const url = it.match(/href="([^"]+)"/)?.[1] || "";
const id = url.match(/\/(\d+)\.html/)?.[1];
let name = it.match(/title="([^"]+)"/)?.[1] || "";
name = name.split('/')[0].trim().replace(/.*《([^》]+)》.*/, "$1");
const pic = it.match(/src="([^"]+)"/)?.[1] || "";
const remarks = it.match(/class="zhuangtai"[^>]*>([^<]+)/)?.[1] || "";
return id && name ? {
vod_id: id,
vod_name: name,
vod_pic: pic.startsWith('/') ? host + pic : pic,
vod_remarks: remarks.trim()
} : null;
}).filter(Boolean);
};
const home = async () => {
const list = extractVideos((await req(host, { headers }))?.content || '');
return JSON.stringify({
class: [
{type_id:"1",type_name:"鬼片大全"},{type_id:"6",type_name:"大陆鬼片"},{type_id:"9",type_name:"港台鬼片"},
{type_id:"8",type_name:"林正英鬼片"},{type_id:"7",type_name:"日韩鬼片"},{type_id:"11",type_name:"欧美鬼片"},
{type_id:"10",type_name:"泰国鬼片"},{type_id:"3",type_name:"恐怖片"},{type_id:"2",type_name:"电视剧"},
{type_id:"12",type_name:"国产剧"},{type_id:"20",type_name:"港台剧"},{type_id:"13",type_name:"美剧"},
{type_id:"14",type_name:"韩剧"},{type_id:"15",type_name:"日剧"},{type_id:"16",type_name:"泰剧"},
{type_id:"22",type_name:"其它剧"},{type_id:"4",type_name:"动漫"}
],
filters: {},
list
});
};
const homeVod = async () => {
const list = extractVideos((await req(host, { headers }))?.content || '');
return JSON.stringify({ list });
};
const category = async (tid, pg, _, extend) => {
const url = pg > 1 ? `${host}/list/${tid}_${pg}.html` : `${host}/list/${tid}.html`;
const html = (await req(url, { headers }))?.content || '';
const list = extractVideos(html);
const m = html.match(/href="\/list\/\d+_(\d+)\.html"[^>]*>\.\.(\d+)<\/a>/);
return JSON.stringify({ list, page: +pg, pagecount: m ? +m[1] : 999, limit: 20 });
};
const detail = async id => {
const html = (await req(`${host}/nv/${id}.html`, { headers }))?.content || '';
if (!html) return JSON.stringify({ list: [] });
const tabs = pdfa(html, '#tv_tab li a').map(a => (a.match(/>([^<]+)/)?.[1] || "线路").trim());
const urls = pdfa(html, '#tv_tab .list').map(list =>
pdfa(list, 'ul.abc li a').map(a => {
const name = (a.match(/>([^<]+)/)?.[1] || "播放").trim();
const vid = (a.match(/href="\/play\/(.*?)\.html"/)?.[1] || "");
return `${name}$${vid}`;
}).join('#')
);
if (!tabs.length || !urls.length) return JSON.stringify({ list: [] });
let vod_name = (html.match(/<h1>(.*?)<\/h1>/)?.[1] || "").split('/')[0].trim().replace(/.*《([^》]+)】?.*/, "$1");
const m = s => (html.match(s) || ["", ""])[1].trim();
const vod_pic = m(/<img[^>]+src="([^"]+)"[^>]*class="lazy"/);
const vod_content = (html.match(/<p class="jianjie-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(/<li class="hidden-xs"><span>导演:<\/span>(.*?)<\/li>/),
vod_actor: m(/<li class="hidden-xs"><span>主演:<\/span>(.*?)<\/li>/),
vod_year: m(/<strong>(\d{4})年<\/strong>/),
vod_area: m(/地区:<\/span><a[^>]*>(.*?)<\/a>/),
vod_lang: m(/语言:<\/span>(.*?)<\/li>/),
vod_play_from: tabs.join('$$$'),
vod_play_url: urls.join('$$$')
}]
});
};
const search = async (wd, _, pg = 1) => {
const url = `${host}/vodsearch/${encodeURIComponent(wd)}----------${pg}---.html`;
const list = extractVideos((await req(url, { headers }))?.content || '');
return JSON.stringify({ list });
};
const play = async (_, id) => {
const html = (await req(`${host}/play/${id}.html`, { headers }))?.content || '';
const m3u8 = html.match(/var now="([^"]+)";/);
return JSON.stringify({
parse: m3u8 ? 0 : 1,
url: m3u8 ? m3u8[1] : `${host}/play/${id}.html`,
header: headers
});
};
export default {
init: async () => {},
home,
homeVod,
category,
detail,
search,
play
};