Sync all projects

This commit is contained in:
github-actions[bot]
2026-07-10 13:30:06 +00:00
parent c05b2ae3fd
commit 143b0bc4a7
20 changed files with 7073 additions and 4276 deletions
+1
View File
@@ -0,0 +1 @@
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
+255
View File
@@ -0,0 +1,255 @@
#author Kyle
import sys
import time
import json
import re
import urllib.parse
from base64 import b64decode, b64encode
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.eporner.com"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': self.host + '/',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
}
self.timeout = 10
self.retries = 2
self.cookies = {"EPRNS": "1"}
def getName(self):
return "EP涩"
def isVideoFormat(self, url):
return True
def manualVideoCheck(self):
return False
def destroy(self):
pass
def homeContent(self, filter):
result = {}
result['class'] = [
{"type_id": "/cat/all/", "type_name": "最新"},
{"type_id": "/best-videos/", "type_name": "最佳视频"},
{"type_id": "/top-rated/", "type_name": "最高评分"},
{"type_id": "/cat/4k-porn/", "type_name": "4K"},
]
return result
def homeVideoContent(self):
result = {}
videos = self._api_search(query="all", page=1, order="latest", gay="0", per_page=20)
result['list'] = videos
return result
def categoryContent(self, tid, pg, filter, extend):
result = {}
params = self._map_tid_to_api(tid)
videos = self._api_search(page=int(pg), **params)
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = len(videos) or 20
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
result = {}
videos = self._api_search(query=key or "all", page=int(pg), order="latest", gay="0")
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = len(videos) or 20
result['total'] = 999999
return result
def detailContent(self, ids):
result = {}
url = ids[0]
if not url.startswith('http'):
url = self.host + url
r = self.fetch(url, headers=self.headers)
root = self.html(r.text)
og_title = root.xpath('//meta[@property="og:title"]/@content')
if og_title:
title = og_title[0].replace(' - EPORNER', '').strip()
else:
h1 = "".join(root.xpath('//h1//text()'))
title = re.sub(r"\s*(\d+\s*min.*)$", "", h1).strip() or "爱看AV"
img_elem = root.xpath('//meta[@property="og:image"]/@content') or root.xpath('//img[@id="mainvideoimg"]/@src')
thumbnail = img_elem[0] if img_elem else ""
meta_desc = (root.xpath('//meta[@name="description"]/@content') or root.xpath('//meta[@property="og:description"]/@content'))
desc_text = meta_desc[0] if meta_desc else ""
dur_match = re.search(r"Duration:\s*([0-9:]+)", desc_text)
duration = dur_match.group(1) if dur_match else ""
encoded_url = self.e64(url)
play_url = f"播放${encoded_url}"
vod = {
"vod_id": url,
"vod_name": title,
"vod_pic": thumbnail,
"vod_remarks": duration,
"vod_content": desc_text.strip(),
"vod_play_from": "🍑Play",
"vod_play_url": play_url
}
result['list'] = [vod]
return result
def playerContent(self, flag, id, vipFlags):
result = {}
url = self.d64(id)
if not url.startswith('http'):
url = self.host + url
r = self.fetch(url, headers=self.headers)
html_content = r.text
pattern = r"vid\s*=\s*'([^']+)';\s*[\w*\.]+hash\s*=\s*['\"]([\da-f]{32})"
match = re.search(pattern, html_content)
if match:
vid, hash_val = match.groups()
hash_code = ''.join((self.encode_base_n(int(hash_val[i:i + 8], 16), 36) for i in range(0, 32, 8)))
xhr_url = f"{self.host}/xhr/video/{vid}?hash={hash_code}&device=generic&domain=www.eporner.com&fallback=false&embed=false&supportedFormats=mp4"
xhr_headers = {
**self.headers,
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01'
}
resp = self.fetch(xhr_url, headers=xhr_headers)
data = json.loads(resp.text)
if data.get("available", True):
sources_block = data.get("sources", {})
sources = sources_block.get("mp4", {})
final_url = None
if sources:
quality_sorted = sorted(sources.keys(), key=lambda q: int(re.sub(r"\D", "", q) or 0), reverse=True)
best_quality = quality_sorted[0]
final_url = sources[best_quality].get("src")
else:
hls = sources_block.get("hls")
if isinstance(hls, dict):
final_url = hls.get("src")
elif isinstance(hls, list) and hls:
final_url = hls[0].get("src")
best_quality = "hls"
if final_url:
result["parse"] = 0
result["url"] = final_url
result["header"] = {
'User-Agent': self.headers['User-Agent'],
'Referer': url,
'Origin': self.host,
'Accept': '*/*'
}
return result
def encode_base_n(self, num, n, table=None):
FULL_TABLE = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
if not table:
table = FULL_TABLE[:n]
if n > len(table):
raise ValueError('base %d exceeds table length %d' % (n, len(table)))
if num == 0:
return table[0]
ret = ''
while num:
ret = table[num % n] + ret
num = num // n
return ret
def e64(self, text):
return b64encode(text.encode()).decode()
def d64(self, encoded_text):
return b64decode(encoded_text.encode()).decode()
def html(self, content):
from lxml import etree
return etree.HTML(content)
def fetch(self, url, headers=None, timeout=None):
import requests, ssl
ssl._create_default_https_context = ssl._create_unverified_context
if headers is None:
headers = self.headers
if timeout is None:
timeout = self.timeout
for _ in range(self.retries + 1):
resp = requests.get(url, headers=headers, timeout=timeout, verify=False, cookies=getattr(self, 'cookies', None))
resp.encoding = 'utf-8'
return resp
time.sleep(1)
raise Exception("Fetch failed")
def _parse_video_list(self, root):
videos = []
items = root.xpath('//div[contains(@class, "mb") and @data-id]')
for item in items:
link = item.xpath('.//a[contains(@href, "/video-")]/@href')
if not link:
continue
vod_id = self.host + link[0]
title = "".join(item.xpath('.//p[contains(@class, "mbtit")]//text()')).strip()
img = item.xpath('.//img/@src')
thumbnail = img[0] if img else ""
duration = "".join(item.xpath('.//span[contains(@class,"mbtim")]/text()')).strip()
videos.append({
"vod_id": vod_id,
"vod_name": title or "爱看AV",
"vod_pic": thumbnail,
"vod_remarks": duration
})
return videos
def _map_tid_to_api(self, tid: str):
params = {"query": "all", "order": "latest", "gay": "0", "per_page": 30}
t = (tid or '').strip('/').lower()
if t.startswith('best-videos'):
params["order"] = "most-popular"
elif t.startswith('top-rated'):
params["order"] = "top-rated"
elif t.startswith('cat/4k-porn'):
params["query"] = "4k"
elif t.startswith('cat/gay'):
params["gay"] = "2"
params["order"] = "latest"
else:
params["gay"] = "0"
return params
def _api_search(self, query="all", page=1, order="latest", gay="0", per_page=30, thumbsize="medium"):
base = f"{self.host}/api/v2/video/search/"
q = {
"query": query,
"per_page": per_page,
"page": page,
"thumbsize": thumbsize,
"order": order,
"format": "json"
}
if gay is not None:
q["gay"] = gay
url = base + "?" + urllib.parse.urlencode(q)
r = self.fetch(url, headers={**self.headers, 'X-Requested-With': 'XMLHttpRequest'})
data = json.loads(r.text)
return self._parse_api_list(data)
def _parse_api_list(self, data: dict):
videos = []
for v in (data or {}).get('videos', []):
vurl = v.get('url') or ''
title = v.get('title') or ''
thumb = (v.get('default_thumb') or {}).get('src') or ''
remarks = v.get('length_min') or ''
videos.append({
"vod_id": vurl if vurl.startswith('http') else (self.host + vurl),
"vod_name": title or "爱看AV",
"vod_pic": thumb,
"vod_remarks": remarks
})
return videos
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1068
View File
@@ -0,0 +1,1068 @@
import { Crypto, _ } from 'assets://js/lib/cat.js'
let host = '';
let header = {
'User-Agent': 'okhttp/3.12.11'
};
let siteKey = '';
let siteType = '';
let siteJx = '';
const urlPattern1 = /api\.php\/.*?\/vod/;
const urlPattern2 = /api\.php\/.+?\.vod/;
const parsePattern = /\/.+\\?.+=/;
const parsePattern1 = /.*(url|v|vid|php\?id)=/;
const parsePattern2 = /https?:\/\/[^\/]*/;
const htmlVideoKeyMatch = [
/player=new/,
/<div id="video"/,
/<div id="[^"]*?player"/,
/\/\/视频链接/,
/HlsJsPlayer\(/,
/<iframe[\s\S]*?src="[^"]+?"/,
/<video[\s\S]*?src="[^"]+?"/,
];
const parseUrlMap = new Map();
async function init(cfg) {
siteKey = cfg.skey;
siteType = cfg.stype;
host = cfg.ext;
if (cfg.ext.hasOwnProperty('host')) {
host = cfg.ext.host;
siteJx = cfg.ext;
}
};
async function request(reqUrl, ua, timeout = 60000) {
let res = await req(reqUrl, {
method: 'get',
headers: ua ? ua : {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'},
timeout: timeout,
});
return res.content;
}
async function home(filter) {
try {
// 苹果CMS V10模式检测
if (host.includes('/vod') || host.includes('/provide/vod')) {
const url = host;
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
const result = { class: [] };
if (obj.class && Array.isArray(obj.class)) {
for (const item of obj.class) {
const typeName = item.type_name;
if (isBan(typeName)) continue;
result.class.push({
type_id: item.type_id,
type_name: typeName
});
}
}
return JSON.stringify(result);
} else {
// 原有AppYsV2模式
let url = getCateUrl(host);
let jsonArray = null;
if (url) {
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
if (obj.hasOwnProperty("class") && Array.isArray(obj.class)) {
jsonArray = obj.class;
} else if (obj.hasOwnProperty("list") && Array.isArray(obj.list)) {
jsonArray = obj.list;
} else if (obj.hasOwnProperty("data") && obj.data.hasOwnProperty("list") && Array.isArray(obj.data.list)) {
jsonArray = obj.data.list;
} else if (obj.hasOwnProperty("data") && Array.isArray(obj.data)) {
jsonArray = obj.data;
}
} else {
const filterStr = getFilterTypes(url, null);
const classes = filterStr.split("\n")[0].split("+");
jsonArray = [];
for (let i = 1; i < classes.length; i++) {
const kv = classes[i].trim().split("=");
if (kv.length < 2) continue;
const newCls = {
type_name: kv[0].trim(),
type_id: kv[1].trim(),
};
jsonArray.push(newCls);
}
}
const result = { class: [] };
if (jsonArray != null) {
for (let i = 0; i < jsonArray.length; i++) {
const jObj = jsonArray[i];
const typeName = jObj.type_name;
if (isBan(typeName)) continue;
const typeId = jObj.type_id;
const newCls = {
type_id: typeId,
type_name: (typeName || "").replace(/奇迹云|-加q群[:]?\s*\d+\s*获取更多免费资源/g, ''),
};
const typeExtend = jObj.type_extend;
if (filter) {
const filterStr = getFilterTypes(url, typeExtend);
const filters = filterStr.split("\n");
const filterArr = [];
for (let k = (url) ? 1 : 0; k < filters.length; k++) {
const l = filters[k].trim();
if (!l) continue;
const oneLine = l.split("+");
let type = oneLine[0].trim();
let typeN = type;
if (type.includes("筛选")) {
type = type.replace(/筛选/g, "");
if (type === "class") typeN = "类型";
else if (type === "area") typeN = "地区";
else if (type === "lang") typeN = "语言";
else if (type === "year") typeN = "年份";
}
const jOne = {
key: type,
name: typeN,
value: [],
};
for (let j = 1; j < oneLine.length; j++) {
const kv = oneLine[j].trim();
const sp = kv.indexOf("=");
if (sp === -1) {
if (isBan(kv)) continue;
jOne.value.push({ n: kv, v: kv });
} else {
const n = kv.substring(0, sp);
if (isBan(n)) continue;
jOne.value.push({
n: n.trim(),
v: kv.substring(sp + 1).trim(),
});
}
}
filterArr.push(jOne);
}
if (!result.hasOwnProperty("filters")) {
result.filters = {};
}
result.filters[typeId] = filterArr;
}
result.class.push(newCls);
}
}
return JSON.stringify(result);
}
} catch (e) {
SpiderDebug.log("分类接口错误:" + e);
}
return JSON.stringify({ class: [] });
}
async function homeVod() {
try {
// 苹果CMS V10模式检测
if (host.includes('/vod') || host.includes('/provide/vod')) {
const url = `${host}?ac=videolist&t=1&pg=1`;
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
const videos = [];
if (obj.list && Array.isArray(obj.list)) {
for (const item of obj.list) {
videos.push({
vod_id: item.vod_id,
vod_name: (item.vod_name || "").replace(/奇迹云/g, ''),
vod_pic: item.vod_pic || "",
vod_remarks: (item.vod_remarks || "").replace(/奇迹云/g, '')
});
}
}
return JSON.stringify({ list: videos });
} else {
// 原有AppYsV2模式
const apiUrl = host;
let url = getRecommendUrl(apiUrl);
let isTV = false;
if (!url) {
url = getCateFilterUrlPrefix(apiUrl) + "movie&page=1&area=&type=&start=";
isTV = true;
}
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
const videos = [];
if (isTV) {
const jsonArray = obj.data;
for (let i = 0; i < jsonArray.length; i++) {
const vObj = jsonArray[i];
const v = {
vod_id: vObj.nextlink,
vod_name: (vObj.title || "").replace(/奇迹云/g, ''),
vod_pic: vObj.pic,
vod_remarks: (vObj.state || "").replace(/奇迹云/g, ''),
};
videos.push(v);
}
} else {
const arrays = [];
findJsonArray(obj, "vlist", arrays);
if (arrays.length === 0) {
findJsonArray(obj, "vod_list", arrays);
}
const ids = [];
for (const jsonArray of arrays) {
for (let i = 0; i < jsonArray.length; i++) {
const vObj = jsonArray[i];
const vid = vObj.vod_id;
if (ids.includes(vid)) continue;
ids.push(vid);
const v = {
vod_id: vid,
vod_name: (vObj.vod_name || "").replace(/奇迹云/g, ''),
vod_pic: vObj.vod_pic,
vod_remarks: (vObj.vod_remarks || "").replace(/奇迹云/g, ''),
};
videos.push(v);
}
}
}
const result = {
list: videos,
};
return JSON.stringify(result);
}
} catch (e) {
SpiderDebug.log(e);
}
return "";
}
async function category(tid, pg, filter, extend) {
try {
// 苹果CMS V10模式检测
if (host.includes('/vod') || host.includes('/provide/vod')) {
const url = `${host}?ac=videolist&t=${tid}&pg=${pg}`;
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
const videos = [];
if (obj.list && Array.isArray(obj.list)) {
for (const item of obj.list) {
videos.push({
vod_id: item.vod_id,
vod_name: (item.vod_name || "").replace(/奇迹云/g, ''),
vod_pic: item.vod_pic || "",
vod_remarks: (item.vod_remarks || "").replace(/奇迹云/g, '')
});
}
}
return JSON.stringify({
page: pg,
pagecount: obj.pagecount || 1,
limit: obj.limit || 20,
total: obj.total || 0,
list: videos
});
} else {
// 原有AppYsV2模式
const apiUrl = host;
let url = getCateFilterUrlPrefix(apiUrl) + tid + getCateFilterUrlSuffix(apiUrl);
url = url.replace(/#PN#/g, pg);
url = url.replace(/筛选class/g, extend?.class ?? "");
url = url.replace(/筛选area/g, extend?.area ?? "");
url = url.replace(/筛选lang/g, extend?.lang ?? "");
url = url.replace(/筛选year/g, extend?.year ?? "");
url = url.replace(/排序/g, extend?.排序 ?? "");
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
let totalPg = Infinity;
try {
if (obj.totalpage !== undefined && typeof obj.totalpage === "number") {
totalPg = obj.totalpage;
} else if (
obj.pagecount !== undefined &&
typeof obj.pagecount === "number"
) {
totalPg = obj.pagecount;
} else if (
obj.data !== undefined &&
typeof obj.data === "object" &&
obj.data.total !== undefined &&
typeof obj.data.total === "number" &&
obj.data.limit !== undefined &&
typeof obj.data.limit === "number"
) {
const limit = obj.data.limit;
const total = obj.data.total;
totalPg = total % limit === 0 ? total / limit : Math.floor(total / limit) + 1;
}
} catch (e) {
SpiderDebug.log(e);
}
const jsonArray =
obj.list !== undefined
? obj.list
: obj.data !== undefined && obj.data.list !== undefined
? obj.data.list
: obj.data;
const videos = [];
if (jsonArray !== undefined) {
for (let i = 0; i < jsonArray.length; i++) {
const vObj = jsonArray[i];
const v = {
vod_id: vObj.vod_id !== undefined ? vObj.vod_id : vObj.nextlink,
vod_name: ((vObj.vod_name !== undefined ? vObj.vod_name : vObj.title) || "").replace(/奇迹云/g, ''),
vod_pic: vObj.vod_pic !== undefined ? vObj.vod_pic : vObj.pic,
vod_remarks: ((vObj.vod_remarks !== undefined ? vObj.vod_remarks : vObj.state) || "").replace(/奇迹云/g, ''),
};
videos.push(v);
}
}
const result = {
page: pg,
pagecount: totalPg,
limit: 90,
total: Infinity,
list: videos,
};
return JSON.stringify(result);
}
} catch (e) {
SpiderDebug.log(e);
}
return "";
}
// 辅助函数:只替换文字,不改变分隔符和结构
function replacePlayUrlText(playUrl) {
if (!playUrl) return playUrl;
// 使用正则匹配 $$$ 或 # 作为分隔符,但保留原始分隔符不变
// 匹配模式:任意内容后跟 $$$ 或 #,或者末尾
let result = '';
let i = 0;
while (i < playUrl.length) {
// 查找 $$$ 或 # 的位置
let found = -1;
let sep = '';
let dollarPos = playUrl.indexOf('$$$', i);
let hashPos = playUrl.indexOf('#', i);
if (dollarPos !== -1 && (hashPos === -1 || dollarPos < hashPos)) {
found = dollarPos;
sep = '$$$';
} else if (hashPos !== -1) {
found = hashPos;
sep = '#';
}
if (found !== -1) {
let segment = playUrl.substring(i, found);
// 替换 segment 中 $ 前面的集数名称里的文字
let dollarInSegment = segment.indexOf('$');
if (dollarInSegment > 0) {
let episodeName = segment.substring(0, dollarInSegment);
let urlPart = segment.substring(dollarInSegment);
episodeName = episodeName.replace(/奇迹云/g, '');
result += episodeName + urlPart;
} else {
result += segment.replace(/奇迹云/g, '');
}
result += sep;
i = found + sep.length;
} else {
// 最后一段
let segment = playUrl.substring(i);
let dollarInSegment = segment.indexOf('$');
if (dollarInSegment > 0) {
let episodeName = segment.substring(0, dollarInSegment);
let urlPart = segment.substring(dollarInSegment);
episodeName = episodeName.replace(/奇迹云/g, '');
result += episodeName + urlPart;
} else {
result += segment.replace(/奇迹云/g, '');
}
break;
}
}
return result;
}
async function detail(ids) {
try {
// 苹果CMS V10模式检测
if (host.includes('/vod') || host.includes('/provide/vod')) {
const url = `${host}?ac=detail&ids=${ids}`;
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
const result = { list: [] };
const vod = {};
const data = obj.list && obj.list[0] ? obj.list[0] : {};
vod.vod_id = data.vod_id || ids;
vod.vod_name = (data.vod_name || "").replace(/奇迹云/g, '');
vod.vod_pic = data.vod_pic || "";
vod.type_name = (data.type_name || "").replace(/奇迹云/g, '');
vod.vod_year = (data.vod_year || "").replace(/奇迹云/g, '');
vod.vod_area = (data.vod_area || "").replace(/奇迹云/g, '');
vod.vod_remarks = (data.vod_remarks || "").replace(/奇迹云/g, '');
vod.vod_actor = (data.vod_actor || "").replace(/奇迹云/g, '');
vod.vod_director = (data.vod_director || "").replace(/奇迹云/g, '');
vod.vod_content = (data.vod_content || "").replace(/奇迹云/g, '');
// 处理线路名称
let playFrom = data.vod_play_from || "";
if (playFrom) {
let lines = playFrom.split('$$$');
let newLines = [];
for (let line of lines) {
if (line && line.toLowerCase().includes('qijiyun4k')) {
newLines.push('拒绝收费');
} else {
newLines.push((line || "").replace(/奇迹云/g, ''));
}
}
vod.vod_play_from = newLines.join('$$$');
} else {
vod.vod_play_from = "";
}
// 处理选集:只替换文字,保留原始分隔符和结构
vod.vod_play_url = replacePlayUrlText(data.vod_play_url || "");
result.list.push(vod);
return JSON.stringify(result);
} else {
// 原有AppYsV2模式
const apiUrl = host;
const url = getPlayUrlPrefix(apiUrl) + ids;
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
const result = {
list: [],
};
const vod = {};
genPlayList(apiUrl, obj, json, vod, ids);
// 替换详情中的所有文字
if (vod.vod_name) vod.vod_name = vod.vod_name.replace(/奇迹云/g, '');
if (vod.type_name) vod.type_name = vod.type_name.replace(/奇迹云/g, '');
if (vod.vod_year) vod.vod_year = vod.vod_year.replace(/奇迹云/g, '');
if (vod.vod_area) vod.vod_area = vod.vod_area.replace(/奇迹云/g, '');
if (vod.vod_remarks) vod.vod_remarks = vod.vod_remarks.replace(/奇迹云/g, '');
if (vod.vod_actor) vod.vod_actor = vod.vod_actor.replace(/奇迹云/g, '');
if (vod.vod_director) vod.vod_director = vod.vod_director.replace(/奇迹云/g, '');
if (vod.vod_content) vod.vod_content = vod.vod_content.replace(/奇迹云/g, '');
// 替换线路名称
if (vod.vod_play_from) {
let lines = vod.vod_play_from.split('$$$');
let newLines = [];
for (let line of lines) {
if (line && line.toLowerCase().includes('qijiyun4k')) {
newLines.push('拒绝收费');
} else {
newLines.push((line || "").replace(/奇迹云/g, ''));
}
}
vod.vod_play_from = newLines.join('$$$');
}
// 处理选集:只替换文字,保留原始分隔符和结构
vod.vod_play_url = replacePlayUrlText(vod.vod_play_url || "");
result.list.push(vod);
return JSON.stringify(result);
}
} catch (e) {
SpiderDebug.log(e);
}
return "";
}
async function play(flag, id, vipFlags) {
try {
let parseUrls = siteJx[flag];
if (!parseUrls) {
if (siteJx.hasOwnProperty('*')) {
parseUrls = siteJx['*'];
} else {
parseUrls = [];
}
}
if (parseUrls.length > 0) {
const result = await getFinalVideo(flag, parseUrls, id);
if (result !== null) {
return JSON.stringify(result);
}
}
if (isVideoFormat(id)) {
const result = {
parse: 0,
playUrl: "",
url: id
};
return JSON.stringify(result);
} else {
const result = {
parse: 1,
jx: "1",
url: id
};
return JSON.stringify(result);
}
} catch (e) {
SpiderDebug.log(e);
}
return "";
}
async function search(key, quick) {
try {
// 苹果CMS V10模式检测
if (host.includes('/vod') || host.includes('/provide/vod')) {
const url = `${host}?ac=videolist&wd=${encodeURIComponent(key)}&pg=1`;
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
const videos = [];
if (obj.list && Array.isArray(obj.list)) {
for (const item of obj.list) {
videos.push({
vod_id: item.vod_id,
vod_name: (item.vod_name || "").replace(/奇迹云/g, ''),
vod_pic: item.vod_pic || "",
vod_remarks: (item.vod_remarks || "").replace(/奇迹云/g, '')
});
}
}
return JSON.stringify({ list: videos });
} else {
// 原有AppYsV2模式
const apiUrl = host;
const url = getSearchUrl(apiUrl, encodeURIComponent(key));
const json = await request(url, getHeaders(url));
const obj = JSON.parse(json);
let jsonArray = null;
const videos = [];
if (obj.list instanceof Array) {
jsonArray = obj.list;
} else if (obj.data instanceof Object && obj.data.list instanceof Array) {
jsonArray = obj.data.list;
} else if (obj.data instanceof Array) {
jsonArray = obj.data;
}
if (jsonArray !== null) {
for (const vObj of jsonArray) {
if (vObj.vod_id) {
const v = {
vod_id: vObj.vod_id,
vod_name: (vObj.vod_name || "").replace(/奇迹云/g, ''),
vod_pic: vObj.vod_pic,
vod_remarks: (vObj.vod_remarks || "").replace(/奇迹云/g, '')
};
videos.push(v);
} else {
const v = {
vod_id: vObj.nextlink,
vod_name: (vObj.title || "").replace(/奇迹云/g, ''),
vod_pic: vObj.pic,
vod_remarks: (vObj.state || "").replace(/奇迹云/g, '')
};
videos.push(v);
}
}
}
const result = { list: videos };
return JSON.stringify(result);
}
} catch (error) {
SpiderDebug.log(error);
}
return "";
}
// 辅助函数
async function getFinalVideo(flag, parseUrls, url) {
let htmlPlayUrl = "";
for (const parseUrl of parseUrls) {
if (parseUrl === "" || parseUrl === "null") {
continue;
}
const playUrl = parseUrl + url;
const content = await request(playUrl, null, 10000);
let tryJson = null;
try {
tryJson = jsonParse(url, content);
} catch (error) { }
if (tryJson !== null && tryJson.hasOwnProperty("url") && tryJson.hasOwnProperty("header")) {
tryJson.header = JSON.stringify(tryJson.header);
return tryJson;
}
if (content.includes("<html")) {
let sniffer = false;
for (const p of htmlVideoKeyMatch) {
if (p.test(content)) {
sniffer = true;
break;
}
}
if (sniffer) {
htmlPlayUrl = parseUrl;
}
}
}
if (htmlPlayUrl !== "") {
const result = {
parse: 0,
playUrl: "",
url: url
};
return JSON.stringify(result);
}
return null;
}
function genPlayList(URL, object, json, vod, vid) {
const playUrls = [];
const playFlags = [];
// 苹果CMS V10模式
if (URL.includes('/vod') || URL.includes('/provide/vod')) {
const data = object.list && object.list[0] ? object.list[0] : {};
vod.vod_id = data.vod_id || vid;
vod.vod_name = data.vod_name || "";
vod.vod_pic = data.vod_pic || "";
vod.type_name = data.type_name || "";
vod.vod_year = data.vod_year || "";
vod.vod_area = data.vod_area || "";
vod.vod_remarks = data.vod_remarks || "";
vod.vod_actor = data.vod_actor || "";
vod.vod_director = data.vod_director || "";
vod.vod_content = data.vod_content || "";
vod.vod_play_from = data.vod_play_from || "";
vod.vod_play_url = data.vod_play_url || "";
return;
}
// AppYsV2模式
if (URL.includes("api.php/app") || URL.includes("xgapp")) {
const data = object.data || {};
vod.vod_id = data.vod_id || vid;
vod.vod_name = data.vod_name || "";
vod.vod_pic = data.vod_pic || "";
vod.type_name = data.vod_class || "";
vod.vod_year = data.vod_year || "";
vod.vod_area = data.vod_area || "";
vod.vod_remarks = data.vod_remarks || "";
vod.vod_actor = data.vod_actor || "";
vod.vod_director = data.vod_director || "";
vod.vod_content = data.vod_content || "";
// 处理播放源
if (data.vod_url_with_player && Array.isArray(data.vod_url_with_player)) {
for (const from of data.vod_url_with_player) {
let flag = from.code?.trim() || from.name?.trim() || "";
if (!flag) continue;
playFlags.push(flag);
playUrls.push(from.url || "");
// 处理解析地址
if (from.parse_api) {
const parseUrls = parseUrlMap.get(flag) || [];
if (!parseUrls.includes(from.parse_api)) {
parseUrls.push(from.parse_api);
}
parseUrlMap.set(flag, parseUrls);
}
}
}
} else if (URL.includes(".vod")) {
const data = object.data || {};
vod.vod_id = data.vod_id || vid;
vod.vod_name = data.vod_name || "";
vod.vod_pic = data.vod_pic || "";
vod.type_name = data.vod_class || "";
vod.vod_year = data.vod_year || "";
vod.vod_area = data.vod_area || "";
vod.vod_remarks = data.vod_remarks || "";
vod.vod_actor = data.vod_actor || "";
vod.vod_director = data.vod_director || "";
vod.vod_content = data.vod_content || "";
if (data.vod_play_list && Array.isArray(data.vod_play_list)) {
for (const from of data.vod_play_list) {
let flag = from.player_info?.from?.trim() || from.player_info?.show?.trim() || "";
if (!flag) continue;
playFlags.push(flag);
playUrls.push(from.url || "");
// 处理解析地址
try {
const parseUrls = parseUrlMap.get(flag) || [];
if (from.player_info?.parse) {
const parse1 = from.player_info.parse.split(",");
parse1.forEach(purl => {
if (purl && !parseUrls.includes(purl)) {
parseUrls.push(purl);
}
});
}
if (from.player_info?.parse2) {
const parse2 = from.player_info.parse2.split(",");
parse2.forEach(purl => {
if (purl && !parseUrls.includes(purl)) {
parseUrls.push(purl);
}
});
}
parseUrlMap.set(flag, parseUrls);
} catch (e) {
SpiderDebug.log(e);
}
}
}
} else if (urlPattern1.test(URL)) {
const data = object.list && object.list[0] ? object.list[0] : {};
vod.vod_id = data.vod_id || vid;
vod.vod_name = data.vod_name || "";
vod.vod_pic = data.vod_pic || "";
vod.type_name = data.type_name || "";
vod.vod_year = data.vod_year || "";
vod.vod_area = data.vod_area || "";
vod.vod_remarks = data.vod_remarks || "";
vod.vod_actor = data.vod_actor || "";
vod.vod_director = data.vod_director || "";
vod.vod_content = data.vod_content || "";
vod.vod_play_from = data.vod_play_from || "";
vod.vod_play_url = data.vod_play_url || "";
}
// 合并播放源
if (playFlags.length > 0 && playUrls.length > 0) {
vod.vod_play_from = playFlags.join("$$$");
vod.vod_play_url = playUrls.join("$$$");
}
}
function jsonParse(input, json) {
try {
let jsonPlayData = JSON.parse(json);
if (jsonPlayData.hasOwnProperty("data") && typeof jsonPlayData.data === "object" && !jsonPlayData.hasOwnProperty("url")) {
jsonPlayData = jsonPlayData.data;
}
let url = jsonPlayData.url;
if (url.startsWith("//")) {
url = "https:" + url;
}
if (!url.trim().startsWith("http")) {
return null;
}
if (url === input) {
if (isVip(url) || !isVideoFormat(url)) {
return null;
}
}
if (isBlackVodUrl(input, url)) {
return null;
}
let headers = {};
if (jsonPlayData.hasOwnProperty("header")) {
headers = jsonPlayData.header;
} else if (jsonPlayData.hasOwnProperty("Header")) {
headers = jsonPlayData.Header;
} else if (jsonPlayData.hasOwnProperty("headers")) {
headers = jsonPlayData.headers;
} else if (jsonPlayData.hasOwnProperty("Headers")) {
headers = jsonPlayData.Headers;
}
let ua = "";
if (jsonPlayData.hasOwnProperty("user-agent")) {
ua = jsonPlayData["user-agent"];
} else if (jsonPlayData.hasOwnProperty("User-Agent")) {
ua = jsonPlayData["User-Agent"];
}
if (ua.trim().length > 0) {
headers["User-Agent"] = " " + ua;
}
let referer = "";
if (jsonPlayData.hasOwnProperty("referer")) {
referer = jsonPlayData.referer;
} else if (jsonPlayData.hasOwnProperty("Referer")) {
referer = jsonPlayData.Referer;
}
if (referer.trim().length > 0) {
headers["Referer"] = " " + referer;
}
headers = fixJsonVodHeader(headers, input, url);
const taskResult = {
header: headers,
url: url,
parse: "0"
};
return taskResult;
} catch (error) {
SpiderDebug.log(error);
}
return null;
}
function isVip(url) {
try {
let isVip = false;
const host = new URL(url).hostname;
const vipWebsites = ["iqiyi.com", "v.qq.com", "youku.com", "le.com", "tudou.com", "mgtv.com", "sohu.com", "acfun.cn", "bilibili.com", "baofeng.com", "pptv.com"];
for (let b = 0; b < vipWebsites.length; b++) {
if (host.includes(vipWebsites[b])) {
if (vipWebsites[b] === "iqiyi.com") {
if (url.includes("iqiyi.com/a_") || url.includes("iqiyi.com/w_") || url.includes("iqiyi.com/v_")) {
isVip = true;
break;
}
} else {
isVip = true;
break;
}
}
}
return isVip;
} catch (e) {
SpiderDebug.log(e);
}
return false;
}
function isBlackVodUrl(input, url) {
return url.includes("973973.xyz") || url.includes(".fit:");
}
function fixJsonVodHeader(headers, input, url) {
if (headers === null) {
headers = {};
}
if (input.includes("www.mgtv.com")) {
headers["Referer"] = " ";
headers["User-Agent"] = " Mozilla/5.0";
} else if (url.includes("titan.mgtv")) {
headers["Referer"] = " ";
headers["User-Agent"] = " Mozilla/5.0";
} else if (input.includes("bilibili")) {
headers["Referer"] = " https://www.bilibili.com/";
headers["User-Agent"] = " " + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
}
return headers;
}
const snifferMatch = /http((?!http).){26,}?\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg)\?.*|http((?!http).){26,}\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg)|http((?!http).){26,}\/m3u8\?pt=m3u8.*|http((?!http).)*?default\.ixigua\.com\/.*|http((?!http).)*?cdn-tos[^\?]*|http((?!http).)*?\/obj\/tos[^\?]*|http.*?\/player\/m3u8play\.php\?url=.*|http.*?\/player\/.*?[pP]lay\.php\?url=.*|http.*?\/playlist\/m3u8\/\?vid=.*|http.*?\.php\?type=m3u8&.*|http.*?\/download.aspx\?.*|http.*?\/api\/up_api.php\?.*|https.*?\.66yk\.cn.*|http((?!http).)*?netease\.com\/file\/.*/;
function isVideoFormat(url) {
if (snifferMatch.test(url)) {
return !url.includes("cdn-tos") || !url.includes(".js");
}
return false;
}
function isVideo(url) {
return !url.includes(".mp4") && !url.includes(".m3u8");
}
function UA(url) {
if (url.includes(".vod")) {
return "okhttp/4.1.0";
}
return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
}
function getCateUrl(URL) {
if (URL.includes("api.php/app") || URL.includes("xgapp")) {
return URL + "nav?token=";
} else if (URL.includes(".vod")) {
return URL + "/types";
} else {
return "";
}
}
function getPlayUrlPrefix(URL) {
if (URL.includes("api.php/app") || URL.includes("xgapp")) {
return URL + "video_detail?id=";
} else if (URL.includes(".vod")) {
return URL + "/detail?vod_id=";
} else {
return "";
}
}
function getRecommendUrl(URL) {
if (URL.includes("api.php/app") || URL.includes("xgapp")) {
return URL + "index_video?token=";
} else if (URL.includes(".vod")) {
return URL + "/vodPhbAll";
} else {
return "";
}
}
function getFilterTypes(URL, typeExtend) {
let str = "";
if (typeExtend !== null) {
for (let key in typeExtend) {
if (key === "class" || key === "area" || key === "lang" || key === "year") {
try {
str += "筛选" + key + "+全部=+" + typeExtend[key].replace(/,/g, "+") + "\n";
} catch (e) { }
}
}
}
if (URL.includes(".vod")) {
str += "\n" + "排序+全部=+最新=time+最热=hits+评分=score";
} else if (URL.includes("api.php/app") || URL.includes("xgapp")) {
// Do nothing, leave the string as it is.
} else {
str = "分类+全部=+电影=movie+连续剧=tvplay+综艺=tvshow+动漫=comic+4K=movie_4k+体育=tiyu\n筛选class+全部=+喜剧+爱情+恐怖+动作+科幻+剧情+战争+警匪+犯罪+动画+奇幻+武侠+冒险+枪战+恐怖+悬疑+惊悚+经典+青春+文艺+微电影+古装+历史+运动+农村+惊悚+惊悚+伦理+情色+福利+三级+儿童+网络电影\n筛选area+全部=+大陆+香港+台湾+美国+英国+法国+日本+韩国+德国+泰国+印度+西班牙+加拿大+其他\n筛选year+全部=+2025+2024+2023+2022+2021+2020+2019+2018+2017+2016+2015+2014+2013+2012+2011+2010+2009+2008+2007+2006+2005+2004+2003+2002+2001+2000";
}
return str;
}
function getCateFilterUrlSuffix(URL) {
if (URL.includes("api.php/app") || URL.includes("xgapp")) {
return "&class=筛选class&area=筛选area&lang=筛选lang&year=筛选year&limit=18&pg=#PN#";
} else if (URL.includes(".vod")) {
return "&class=筛选class&area=筛选area&lang=筛选lang&year=筛选year&by=排序&limit=18&page=#PN#";
} else {
return "&page=#PN#&area=筛选area&type=筛选class&start=筛选year";
}
}
function getCateFilterUrlPrefix(URL) {
if (URL.includes("api.php/app") || URL.includes("xgapp")) {
return URL + "video?tid=";
} else if (URL.includes(".vod")) {
return URL + "?type=";
} else {
return URL + "?ac=list&class=";
}
}
function isBan(key) {
return key === "伦理" || key === "情色" || key === "福利";
}
function getSearchUrl(URL, KEY) {
if (URL.includes(".vod")) {
return URL + "?wd=" + KEY + "&page=";
} else if (URL.includes("api.php/app") || URL.includes("xgapp")) {
return URL + "search?text=" + KEY + "&pg=";
} else if (urlPattern1.test(URL)) {
return URL + "?ac=list&zm=" + KEY + "&page=";
}
return "";
}
function findJsonArray(obj, match, result) {
Object.keys(obj).forEach((k) => {
try {
const o = obj[k];
if (k === match && Array.isArray(o)) {
result.push(o);
}
if (typeof o === "object" && o !== null) {
if (Array.isArray(o)) {
o.forEach((item) => {
if (typeof item === "object" && item !== null) {
findJsonArray(item, match, result);
}
});
} else {
findJsonArray(o, match, result);
}
}
} catch (e) {
SpiderDebug.log(e);
}
});
}
function jsonArr2Str(array) {
const strings = [];
for (let i = 0; i < array.length; i++) {
try {
strings.push(array[i]);
} catch (e) {
SpiderDebug.log(e);
}
}
return strings.join(",");
}
function getHeaders(URL) {
const headers = {};
headers["User-Agent"] = UA(URL);
return headers;
}
function isJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
export function __jsEvalReturn() {
return {
init: init,
home: home,
homeVod: homeVod,
category: category,
detail: detail,
play: play,
search: search,
};
}
+37
View File
@@ -0,0 +1,37 @@
var rule = {
title: 'drpy',
host: 'https://frodo.douban.com',
apidoc: 'https://www.doubanapi.com',
homeUrl: '',
searchUrl: '',
searchable: 1,
quickSearch: 1,
filterable: 1,
// 分类链接fypage参数支持1个()表达式
url: '/?pg=fypage&class=fyclass&douban=2297669',
filter_url: 'fl={{fl}}',
图片来源: '@Referer=https://api.douban.com/@User-Agent=Mozilla/5.0%20(Windows%20NT%2010.0;%20Win64;%20x64)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/113.0.0.0%20Safari/537.36',
headers: {
"Host": "frodo.douban.com",
// "Host": "api.douban.com",
"Connection": "Keep-Alive",
"Referer": "https://servicewechat.com/wx2f9b06c1de1ccfca/84/page-frame.html",
// "content-type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat"
},
timeout: 5000,
class_name: '我的豆瓣&热门电影&热播剧集&热播综艺&热门动漫&电影筛选&电视剧筛选&电影榜单&电视剧榜单',
class_url: 'interests&hot_gaia&tv_hot&show_hot&tv_animation&movie&tv&rank_list_movie&rank_list_tv',
filter: { 'interests': [{ 'key': 'status', 'name': '状态', 'value': [{ 'n': '想看', 'v': 'mark' }, { 'n': '在看', 'v': 'doing' }, { 'n': '看过', 'v': 'done' }] }, { 'key': 'subtype_tag', 'name': '形式', 'value': [{ 'n': '全部', 'v': '' }, { 'n': '电影', 'v': 'movie' }, { 'n': '电视', 'v': 'tv' }] }, { 'key': 'year_tag', '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': '2010年代', 'v': '2010年代' }, { 'n': '2000年代', 'v': '2000年代' }, { 'n': '90年代', 'v': '90年代' }, { 'n': '80年代', 'v': '80年代' }, { 'n': '70年代', 'v': '70年代' }, { 'n': '60年代', 'v': '60年代' }, { 'n': '更早', 'v': '更早' }] }], 'hot_gaia': [{ 'key': 'sort', 'name': '排序', 'value': [{ 'n': '热度', 'v': 'recommend' }, { 'n': '最新', 'v': 'time' }, { 'n': '评分', 'v': 'rank' }] }, { 'key': 'area', 'name': '地区', 'value': [{ 'n': '全部', 'v': '全部' }, { 'n': '华语', 'v': '华语' }, { 'n': '欧美', 'v': '欧美' }, { 'n': '韩国', 'v': '韩国' }, { 'n': '日本', 'v': '日本' }] }], 'tv_hot': [{ 'key': 'type', 'name': '分类', 'value': [{ 'n': '综合', 'v': 'tv_hot' }, { 'n': '国产剧', 'v': 'tv_domestic' }, { 'n': '欧美剧', 'v': 'tv_american' }, { 'n': '日剧', 'v': 'tv_japanese' }, { 'n': '韩剧', 'v': 'tv_korean' }, { 'n': '动画', 'v': 'tv_animation' }] }], 'show_hot': [{ 'key': 'type', 'name': '分类', 'value': [{ 'n': '综合', 'v': 'show_hot' }, { 'n': '国内', 'v': 'show_domestic' }, { 'n': '国外', 'v': 'show_foreign' }] }], 'movie': [{ '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': '西部' }, { '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': '瑞典' }, { 'n': '巴西', 'v': '巴西' }, { 'n': '丹麦', 'v': '丹麦' }] }, { 'key': 'sort', 'name': '排序', 'value': [{ 'n': '近期热度', 'v': 'T' }, { 'n': '首映时间', 'v': 'R' }, { 'n': '高分优先', 'v': 'S' }] }, { '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': '2010年代', 'v': '2010年代' }, { 'n': '2000年代', 'v': '2000年代' }, { 'n': '90年代', 'v': '90年代' }, { 'n': '80年代', 'v': '80年代' }, { 'n': '70年代', 'v': '70年代' }, { 'n': '60年代', 'v': '60年代' }, { 'n': '更早', 'v': '更早' }] }], 'tv': [{ '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': '恐怖' }, { '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': '真人秀', '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': '印度' }, { 'n': '加拿大', 'v': '加拿大' }, { 'n': '爱尔兰', 'v': '爱尔兰' }, { 'n': '澳大利亚', 'v': '澳大利亚' }] }, { 'key': 'sort', 'name': '排序', 'value': [{ 'n': '近期热度', 'v': 'T' }, { 'n': '首播时间', 'v': 'R' }, { 'n': '高分优先', 'v': 'S' }] }, { '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': '2010年代', 'v': '2010年代' }, { 'n': '2000年代', 'v': '2000年代' }, { 'n': '90年代', 'v': '90年代' }, { 'n': '80年代', 'v': '80年代' }, { 'n': '70年代', 'v': '70年代' }, { 'n': '60年代', 'v': '60年代' }, { 'n': '更早', 'v': '更早' }] }, { 'key': '平台', 'name': '平台', 'value': [{ 'n': '全部', 'v': '' }, { 'n': '腾讯视频', 'v': '腾讯视频' }, { 'n': '爱奇艺', 'v': '爱奇艺' }, { 'n': '优酷', 'v': '优酷' }, { 'n': '湖南卫视', 'v': '湖南卫视' }, { 'n': 'Netflix', 'v': 'Netflix' }, { 'n': 'HBO', 'v': 'HBO' }, { 'n': 'BBC', 'v': 'BBC' }, { 'n': 'NHK', 'v': 'NHK' }, { 'n': 'CBS', 'v': 'CBS' }, { 'n': 'NBC', 'v': 'NBC' }, { 'n': 'tvN', 'v': 'tvN' }] }], 'rank_list_movie': [{ 'key': '榜单', 'name': '榜单', 'value': [{ 'n': '实时热门电影', 'v': 'movie_real_time_hotest' }, { 'n': '一周口碑电影榜', 'v': 'movie_weekly_best' }, { 'n': '豆瓣电影Top250', 'v': 'movie_top250' }] }], 'rank_list_tv': [{ 'key': '榜单', 'name': '榜单', 'value': [{ 'n': '实时热门电视', 'v': 'tv_real_time_hotest' }, { 'n': '华语口碑剧集榜', 'v': 'tv_chinese_best_weekly' }, { 'n': '全球口碑剧集榜', 'v': 'tv_global_best_weekly' }, { 'n': '国内口碑综艺榜', 'v': 'show_chinese_best_weekly' }, { 'n': '国外口碑综艺榜', 'v': 'show_global_best_weekly' }] }] },
limit: 20,
play_parse: false,
推荐: '',
推荐: 'js:let d=[];let douban_api_host="https://frodo.douban.com/api/v2";let miniapp_apikey="0ac44ae016490db2204ce0a042db2916";const count=30;function miniapp_request(path,query){try{let url=douban_api_host+path;query.apikey=miniapp_apikey;fetch_params.headers=oheaders;url=buildUrl(url,query);let html=fetch(url,fetch_params);return JSON.parse(html)}catch(e){print("发生了错误:"+e.message);return{}}}function subject_real_time_hotest(){try{let res=miniapp_request("/subject_collection/subject_real_time_hotest/items",{});let lists=[];let arr=res.subject_collection_items||[];arr.forEach(function(item){if(item.type==="movie"||item.type==="tv"){let rating=item.rating?item.rating.value:"暂无评分";let honnor=(item.honor_infos||[]).map(function(it){return it.title}).join("|");lists.append({vod_id:"msearch:"+TYPE,vod_name:item.title||"",vod_pic:item.pic.normal,vod_remarks:rating+" "+honnor})}});return lists}catch(e){print("发生了错误:"+e.message);return[]}}VODS=subject_real_time_hotest();print(VODS);',
// 手动调用解析请求json的url,此lazy不方便
lazy: '',
// 推荐:'.list_item;img&&alt;img&&src;a&&Text;a&&data-float',
一级: '',
一级: 'js:let d=[];let douban=input.split("douban=")[1].split("&")[0];let douban_api_host="https://frodo.douban.com/api/v2";let miniapp_apikey="0ac44ae016490db2204ce0a042db2916";const count=30;function miniapp_request(path,query){try{let url=douban_api_host+path;query.apikey=miniapp_apikey;fetch_params.headers=oheaders;url=buildUrl(url,query);let html=fetch(url,fetch_params);if(/request_error/.test(html)){print(html)}return JSON.parse(html)}catch(e){print("发生了错误:"+e.message);return{}}}function cate_filter(d,douban){douban=douban||"";try{let res={};if(MY_CATE==="interests"){if(douban){let status=MY_FL.status||"mark";let subtype_tag=MY_FL.subtype_tag||"";let year_tag=MY_FL.year_tag||"全部";let path="/user/"+douban+"/interests";res=miniapp_request(path,{type:"movie",status:status,subtype_tag:subtype_tag,year_tag:year_tag,start:(MY_PAGE-1)*count,count:count})}else{return{}}}else if(MY_CATE==="hot_gaia"){let sort=MY_FL.sort||"recommend";let area=MY_FL.area||"全部";let path="/movie/"+MY_CATE;res=miniapp_request(path,{area:area,sort:sort,start:(MY_PAGE-1)*count,count:count})}else if(MY_CATE==="tv_hot"||MY_CATE==="show_hot"||MY_CATE==="tv_animation"){let stype=MY_FL.type||MY_CATE;let path="/subject_collection/"+stype+"/items";res=miniapp_request(path,{start:(MY_PAGE-1)*count,count:count})}else if(MY_CATE.startsWith("rank_list")){let id=MY_CATE==="rank_list_movie"?"movie_real_time_hotest":"tv_real_time_hotest";id=MY_FL.榜单||id;let path="/subject_collection/"+id+"/items";res=miniapp_request(path,{start:(MY_PAGE-1)*count,count:count})}else{let path="/"+MY_CATE+"/recommend";let selected_categories;let tags;let sort;if(Object.keys(MY_FL).length>0){sort=MY_FL.sort||"T";tags=Object.values(MY_FL).join(",");if(MY_CATE==="movie"){selected_categories={"类型":MY_FL.类型||"","地区":MY_FL.地区||""}}else{selected_categories={"类型":MY_FL.类型||"","形式":MY_FL.类型?MY_FL.类型+"地区":"","地区":MY_FL.地区||""}}}else{sort="T";tags="";if(MY_CATE==="movie"){selected_categories={"类型":"","地区":""}}else{selected_categories={"类型":"","形式":"","地区":""}}}let params={tags:tags,sort:sort,refresh:0,selected_categories:stringify(selected_categories),start:(MY_PAGE-1)*count,count:count};res=miniapp_request(path,params)}let result={page:MY_PAGE,pagecount:Math.ceil(res.total/count),limit:count,total:res.total};let items=[];if(/^rank_list|tv_hot|show_hot|tv_animation/.test(MY_CATE)){items=res["subject_collection_items"]}else if(MY_CATE==="interests"){res["interests"].forEach(function(it){items.push(it.subject)})}else{items=res.items}let lists=[];items.forEach(function(item){if(item.type==="movie"||item.type==="tv"){let rating=item.rating?item.rating.value:"";let rat_str=rating||"暂无评分";let title=item.title;let honor=item.honor_infos||[];let honor_str=honor.map(function(it){return it.title}).join("|");let vod_obj={vod_name:title!=="未知电影"?title:"暂不支持展示",vod_pic:item.pic.normal,vod_remarks:rat_str+" "+honor_str};let vod_obj_d={url:item.type+"$"+item.id,title:title!=="未知电影"?title:"暂不支持展示",pic_url:item.pic.normal,desc:rat_str+" "+honor_str};lists.push(vod_obj);d.push(vod_obj_d)}});result.list=lists;return result}catch(e){print(e.message)}return{}}let res=cate_filter(d,douban);setResult2(res);',
二级: '',
搜索: '',
}
+37
View File
@@ -0,0 +1,37 @@
var rule = {
title:'drpy',
host:'https://frodo.douban.com',
apidoc:'https://www.doubanapi.com',
homeUrl:'',
searchUrl:'',
searchable:1,
quickSearch:1,
filterable:1,
// 分类链接fypage参数支持1个()表达式
url:'/?pg=fypage&class=fyclass&douban=$douban',
filter_url:'fl={{fl}}',
图片来源:'@Referer=https://api.douban.com/@User-Agent=Mozilla/5.0%20(Windows%20NT%2010.0;%20Win64;%20x64)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/113.0.0.0%20Safari/537.36',
headers:{
"Host": "frodo.douban.com",
// "Host": "api.douban.com",
"Connection": "Keep-Alive",
"Referer": "https://servicewechat.com/wx2f9b06c1de1ccfca/84/page-frame.html",
// "content-type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36 MicroMessenger/7.0.9.501 NetType/WIFI MiniProgramEnv/Windows WindowsWechat"
},
timeout:5000,
class_name:'近期电影&近期电视剧&综艺&电影排行&电视剧排行&分享者电影推荐&分享者电视剧推荐',
class_url:'hot_gaia&tv_hot&show_hot&movie&tv&rank_list_movie&rank_list_tv',
filter:{'interests': [{'key': 'status', 'name': '状态', 'value': [{'n': '想看', 'v': 'mark'}, {'n': '在看', 'v': 'doing'}, {'n': '看过', 'v': 'done'}]}, {'key': 'subtype_tag', 'name': '形式', 'value': [{'n': '全部', 'v': ''}, {'n': '电影', 'v': 'movie'}, {'n': '电视', 'v': 'tv'}]}, {'key': 'year_tag', 'name': '年代', 'value': [{'n': '全部', 'v': '全部'}, {'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': '2010年代', 'v': '2010年代'}, {'n': '2000年代', 'v': '2000年代'}, {'n': '90年代', 'v': '90年代'}, {'n': '80年代', 'v': '80年代'}, {'n': '70年代', 'v': '70年代'}, {'n': '60年代', 'v': '60年代'}, {'n': '更早', 'v': '更早'}]}], 'hot_gaia': [{'key': 'sort', 'name': '排序', 'value': [{'n': '热度', 'v': 'recommend'}, {'n': '最新', 'v': 'time'}, {'n': '评分', 'v': 'rank'}]}, {'key': 'area', 'name': '地区', 'value': [{'n': '全部', 'v': '全部'}, {'n': '华语', 'v': '华语'}, {'n': '欧美', 'v': '欧美'}, {'n': '韩国', 'v': '韩国'}, {'n': '日本', 'v': '日本'}]}], 'tv_hot': [{'key': 'type', 'name': '分类', 'value': [{'n': '综合', 'v': 'tv_hot'}, {'n': '国产剧', 'v': 'tv_domestic'}, {'n': '欧美剧', 'v': 'tv_american'}, {'n': '日剧', 'v': 'tv_japanese'}, {'n': '韩剧', 'v': 'tv_korean'}, {'n': '动画', 'v': 'tv_animation'}]}], 'show_hot': [{'key': 'type', 'name': '分类', 'value': [{'n': '综合', 'v': 'show_hot'}, {'n': '国内', 'v': 'show_domestic'}, {'n': '国外', 'v': 'show_foreign'}]}], 'movie': [{'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': '西部'}, {'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': '瑞典'}, {'n': '巴西', 'v': '巴西'}, {'n': '丹麦', 'v': '丹麦'}]}, {'key': 'sort', 'name': '排序', 'value': [{'n': '近期热度', 'v': 'T'}, {'n': '首映时间', 'v': 'R'}, {'n': '高分优先', 'v': 'S'}]}, {'key': '年代', 'name': '年代', 'value': [{'n': '全部年代', 'v': ''}, {'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': '2010年代', 'v': '2010年代'}, {'n': '2000年代', 'v': '2000年代'}, {'n': '90年代', 'v': '90年代'}, {'n': '80年代', 'v': '80年代'}, {'n': '70年代', 'v': '70年代'}, {'n': '60年代', 'v': '60年代'}, {'n': '更早', 'v': '更早'}]}], 'tv': [{'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': '恐怖'}, {'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': '真人秀', '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': '印度'}, {'n': '加拿大', 'v': '加拿大'}, {'n': '爱尔兰', 'v': '爱尔兰'}, {'n': '澳大利亚', 'v': '澳大利亚'}]}, {'key': 'sort', 'name': '排序', 'value': [{'n': '近期热度', 'v': 'T'}, {'n': '首播时间', 'v': 'R'}, {'n': '高分优先', 'v': 'S'}]}, {'key': '年代', 'name': '年代', 'value': [{'n': '全部', 'v': ''}, {'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': '2010年代', 'v': '2010年代'}, {'n': '2000年代', 'v': '2000年代'}, {'n': '90年代', 'v': '90年代'}, {'n': '80年代', 'v': '80年代'}, {'n': '70年代', 'v': '70年代'}, {'n': '60年代', 'v': '60年代'}, {'n': '更早', 'v': '更早'}]}, {'key': '平台', 'name': '平台', 'value': [{'n': '全部', 'v': ''}, {'n': '腾讯视频', 'v': '腾讯视频'}, {'n': '爱奇艺', 'v': '爱奇艺'}, {'n': '优酷', 'v': '优酷'}, {'n': '湖南卫视', 'v': '湖南卫视'}, {'n': 'Netflix', 'v': 'Netflix'}, {'n': 'HBO', 'v': 'HBO'}, {'n': 'BBC', 'v': 'BBC'}, {'n': 'NHK', 'v': 'NHK'}, {'n': 'CBS', 'v': 'CBS'}, {'n': 'NBC', 'v': 'NBC'}, {'n': 'tvN', 'v': 'tvN'}]}], 'rank_list_movie': [{'key': '榜单', 'name': '榜单', 'value': [{'n': '实时热门电影', 'v': 'movie_real_time_hotest'}, {'n': '一周口碑电影榜', 'v': 'movie_weekly_best'}, {'n': '豆瓣电影Top250', 'v': 'movie_top250'}]}], 'rank_list_tv': [{'key': '榜单', 'name': '榜单', 'value': [{'n': '实时热门电视', 'v': 'tv_real_time_hotest'}, {'n': '华语口碑剧集榜', 'v': 'tv_chinese_best_weekly'}, {'n': '全球口碑剧集榜', 'v': 'tv_global_best_weekly'}, {'n': '国内口碑综艺榜', 'v': 'show_chinese_best_weekly'}, {'n': '国外口碑综艺榜', 'v': 'show_global_best_weekly'}]}]},
limit:20,
play_parse:false,
推荐:'',
推荐:'js:let d=[];let douban_api_host="http://api.douban.com/api/v2";let miniapp_apikey="0ac44ae016490db2204ce0a042db2916";const count=30;function miniapp_request(path,query){try{let url=douban_api_host+path;query.apikey=miniapp_apikey;fetch_params.headers=oheaders;url=buildUrl(url,query);let html=fetch(url,fetch_params);return JSON.parse(html)}catch(e){print("发生了错误:"+e.message);return{}}}function subject_real_time_hotest(){try{let res=miniapp_request("/subject_collection/subject_real_time_hotest/items",{});let lists=[];let arr=res.subject_collection_items||[];arr.forEach(function(item){if(item.type==="movie"||item.type==="tv"){let rating=item.rating?item.rating.value:"暂无评分";let honnor=(item.honor_infos||[]).map(function(it){return it.title}).join("|");lists.append({vod_id:"msearch:"+TYPE,vod_name:item.title||"",vod_pic:item.pic.normal,vod_remarks:rating+" "+honnor})}});return lists}catch(e){print("发生了错误:"+e.message);return[]}}VODS=subject_real_time_hotest();print(VODS);',
// 手动调用解析请求json的url,此lazy不方便
lazy:'',
// 推荐:'.list_item;img&&alt;img&&src;a&&Text;a&&data-float',
一级:'',
一级:'js:let d=[];let douban=input.split("douban=")[1].split("&")[0];let douban_api_host="http://api.douban.com/api/v2";let miniapp_apikey="0ac44ae016490db2204ce0a042db2916";const count=30;function miniapp_request(path,query){try{let url=douban_api_host+path;query.apikey=miniapp_apikey;fetch_params.headers=oheaders;url=buildUrl(url,query);let html=fetch(url,fetch_params);if(/request_error/.test(html)){print(html)}return JSON.parse(html)}catch(e){print("发生了错误:"+e.message);return{}}}function cate_filter(d,douban){douban=douban||"";try{let res={};if(MY_CATE==="interests"){if(douban){let status=MY_FL.status||"mark";let subtype_tag=MY_FL.subtype_tag||"";let year_tag=MY_FL.year_tag||"全部";let path="/user/"+douban+"/interests";res=miniapp_request(path,{type:"movie",status:status,subtype_tag:subtype_tag,year_tag:year_tag,start:(MY_PAGE-1)*count,count:count})}else{return{}}}else if(MY_CATE==="hot_gaia"){let sort=MY_FL.sort||"recommend";let area=MY_FL.area||"全部";let path="/movie/"+MY_CATE;res=miniapp_request(path,{area:area,sort:sort,start:(MY_PAGE-1)*count,count:count})}else if(MY_CATE==="tv_hot"||MY_CATE==="show_hot"){let stype=MY_FL.type||MY_CATE;let path="/subject_collection/"+stype+"/items";res=miniapp_request(path,{start:(MY_PAGE-1)*count,count:count})}else if(MY_CATE.startsWith("rank_list")){let id=MY_CATE==="rank_list_movie"?"movie_real_time_hotest":"tv_real_time_hotest";id=MY_FL.榜单||id;let path="/subject_collection/"+id+"/items";res=miniapp_request(path,{start:(MY_PAGE-1)*count,count:count})}else{let path="/"+MY_CATE+"/recommend";let selected_categories;let tags;let sort;if(Object.keys(MY_FL).length>0){sort=MY_FL.sort||"T";tags=Object.values(MY_FL).join(",");if(MY_CATE==="movie"){selected_categories={"类型":MY_FL.类型||"","地区":MY_FL.地区||""}}else{selected_categories={"类型":MY_FL.类型||"","形式":MY_FL.类型?MY_FL.类型+"地区":"","地区":MY_FL.地区||""}}}else{sort="T";tags="";if(MY_CATE==="movie"){selected_categories={"类型":"","地区":""}}else{selected_categories={"类型":"","形式":"","地区":""}}}let params={tags:tags,sort:sort,refresh:0,selected_categories:stringify(selected_categories),start:(MY_PAGE-1)*count,count:count};res=miniapp_request(path,params)}let result={page:MY_PAGE,pagecount:Math.ceil(res.total/count),limit:count,total:res.total};let items=[];if(/^rank_list|tv_hot|show_hot/.test(MY_CATE)){items=res["subject_collection_items"]}else if(MY_CATE==="interests"){res["interests"].forEach(function(it){items.push(it.subject)})}else{items=res.items}let lists=[];items.forEach(function(item){if(item.type==="movie"||item.type==="tv"){let rating=item.rating?item.rating.value:"";let rat_str=rating||"暂无评分";let title=item.title;let honor=item.honor_infos||[];let honor_str=honor.map(function(it){return it.title}).join("|");let vod_obj={vod_name:title!=="未知电影"?title:"暂不支持展示",vod_pic:item.pic.normal,vod_remarks:rat_str+" "+honor_str};let vod_obj_d={url:item.type+"$"+item.id,title:title!=="未知电影"?title:"暂不支持展示",pic_url:item.pic.normal,desc:rat_str+" "+honor_str};lists.push(vod_obj);d.push(vod_obj_d)}});result.list=lists;return result}catch(e){print(e.message)}return{}}let res=cate_filter(d,douban);setResult2(res);',
二级:'',
搜索:'',
}
+1
View File
@@ -0,0 +1 @@
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
+255
View File
@@ -0,0 +1,255 @@
#author Kyle
import sys
import time
import json
import re
import urllib.parse
from base64 import b64decode, b64encode
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.eporner.com"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': self.host + '/',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
}
self.timeout = 10
self.retries = 2
self.cookies = {"EPRNS": "1"}
def getName(self):
return "EP涩"
def isVideoFormat(self, url):
return True
def manualVideoCheck(self):
return False
def destroy(self):
pass
def homeContent(self, filter):
result = {}
result['class'] = [
{"type_id": "/cat/all/", "type_name": "最新"},
{"type_id": "/best-videos/", "type_name": "最佳视频"},
{"type_id": "/top-rated/", "type_name": "最高评分"},
{"type_id": "/cat/4k-porn/", "type_name": "4K"},
]
return result
def homeVideoContent(self):
result = {}
videos = self._api_search(query="all", page=1, order="latest", gay="0", per_page=20)
result['list'] = videos
return result
def categoryContent(self, tid, pg, filter, extend):
result = {}
params = self._map_tid_to_api(tid)
videos = self._api_search(page=int(pg), **params)
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = len(videos) or 20
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
result = {}
videos = self._api_search(query=key or "all", page=int(pg), order="latest", gay="0")
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = len(videos) or 20
result['total'] = 999999
return result
def detailContent(self, ids):
result = {}
url = ids[0]
if not url.startswith('http'):
url = self.host + url
r = self.fetch(url, headers=self.headers)
root = self.html(r.text)
og_title = root.xpath('//meta[@property="og:title"]/@content')
if og_title:
title = og_title[0].replace(' - EPORNER', '').strip()
else:
h1 = "".join(root.xpath('//h1//text()'))
title = re.sub(r"\s*(\d+\s*min.*)$", "", h1).strip() or "爱看AV"
img_elem = root.xpath('//meta[@property="og:image"]/@content') or root.xpath('//img[@id="mainvideoimg"]/@src')
thumbnail = img_elem[0] if img_elem else ""
meta_desc = (root.xpath('//meta[@name="description"]/@content') or root.xpath('//meta[@property="og:description"]/@content'))
desc_text = meta_desc[0] if meta_desc else ""
dur_match = re.search(r"Duration:\s*([0-9:]+)", desc_text)
duration = dur_match.group(1) if dur_match else ""
encoded_url = self.e64(url)
play_url = f"播放${encoded_url}"
vod = {
"vod_id": url,
"vod_name": title,
"vod_pic": thumbnail,
"vod_remarks": duration,
"vod_content": desc_text.strip(),
"vod_play_from": "🍑Play",
"vod_play_url": play_url
}
result['list'] = [vod]
return result
def playerContent(self, flag, id, vipFlags):
result = {}
url = self.d64(id)
if not url.startswith('http'):
url = self.host + url
r = self.fetch(url, headers=self.headers)
html_content = r.text
pattern = r"vid\s*=\s*'([^']+)';\s*[\w*\.]+hash\s*=\s*['\"]([\da-f]{32})"
match = re.search(pattern, html_content)
if match:
vid, hash_val = match.groups()
hash_code = ''.join((self.encode_base_n(int(hash_val[i:i + 8], 16), 36) for i in range(0, 32, 8)))
xhr_url = f"{self.host}/xhr/video/{vid}?hash={hash_code}&device=generic&domain=www.eporner.com&fallback=false&embed=false&supportedFormats=mp4"
xhr_headers = {
**self.headers,
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json, text/javascript, */*; q=0.01'
}
resp = self.fetch(xhr_url, headers=xhr_headers)
data = json.loads(resp.text)
if data.get("available", True):
sources_block = data.get("sources", {})
sources = sources_block.get("mp4", {})
final_url = None
if sources:
quality_sorted = sorted(sources.keys(), key=lambda q: int(re.sub(r"\D", "", q) or 0), reverse=True)
best_quality = quality_sorted[0]
final_url = sources[best_quality].get("src")
else:
hls = sources_block.get("hls")
if isinstance(hls, dict):
final_url = hls.get("src")
elif isinstance(hls, list) and hls:
final_url = hls[0].get("src")
best_quality = "hls"
if final_url:
result["parse"] = 0
result["url"] = final_url
result["header"] = {
'User-Agent': self.headers['User-Agent'],
'Referer': url,
'Origin': self.host,
'Accept': '*/*'
}
return result
def encode_base_n(self, num, n, table=None):
FULL_TABLE = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
if not table:
table = FULL_TABLE[:n]
if n > len(table):
raise ValueError('base %d exceeds table length %d' % (n, len(table)))
if num == 0:
return table[0]
ret = ''
while num:
ret = table[num % n] + ret
num = num // n
return ret
def e64(self, text):
return b64encode(text.encode()).decode()
def d64(self, encoded_text):
return b64decode(encoded_text.encode()).decode()
def html(self, content):
from lxml import etree
return etree.HTML(content)
def fetch(self, url, headers=None, timeout=None):
import requests, ssl
ssl._create_default_https_context = ssl._create_unverified_context
if headers is None:
headers = self.headers
if timeout is None:
timeout = self.timeout
for _ in range(self.retries + 1):
resp = requests.get(url, headers=headers, timeout=timeout, verify=False, cookies=getattr(self, 'cookies', None))
resp.encoding = 'utf-8'
return resp
time.sleep(1)
raise Exception("Fetch failed")
def _parse_video_list(self, root):
videos = []
items = root.xpath('//div[contains(@class, "mb") and @data-id]')
for item in items:
link = item.xpath('.//a[contains(@href, "/video-")]/@href')
if not link:
continue
vod_id = self.host + link[0]
title = "".join(item.xpath('.//p[contains(@class, "mbtit")]//text()')).strip()
img = item.xpath('.//img/@src')
thumbnail = img[0] if img else ""
duration = "".join(item.xpath('.//span[contains(@class,"mbtim")]/text()')).strip()
videos.append({
"vod_id": vod_id,
"vod_name": title or "爱看AV",
"vod_pic": thumbnail,
"vod_remarks": duration
})
return videos
def _map_tid_to_api(self, tid: str):
params = {"query": "all", "order": "latest", "gay": "0", "per_page": 30}
t = (tid or '').strip('/').lower()
if t.startswith('best-videos'):
params["order"] = "most-popular"
elif t.startswith('top-rated'):
params["order"] = "top-rated"
elif t.startswith('cat/4k-porn'):
params["query"] = "4k"
elif t.startswith('cat/gay'):
params["gay"] = "2"
params["order"] = "latest"
else:
params["gay"] = "0"
return params
def _api_search(self, query="all", page=1, order="latest", gay="0", per_page=30, thumbsize="medium"):
base = f"{self.host}/api/v2/video/search/"
q = {
"query": query,
"per_page": per_page,
"page": page,
"thumbsize": thumbsize,
"order": order,
"format": "json"
}
if gay is not None:
q["gay"] = gay
url = base + "?" + urllib.parse.urlencode(q)
r = self.fetch(url, headers={**self.headers, 'X-Requested-With': 'XMLHttpRequest'})
data = json.loads(r.text)
return self._parse_api_list(data)
def _parse_api_list(self, data: dict):
videos = []
for v in (data or {}).get('videos', []):
vurl = v.get('url') or ''
title = v.get('title') or ''
thumb = (v.get('default_thumb') or {}).get('src') or ''
remarks = v.get('length_min') or ''
videos.append({
"vod_id": vurl if vurl.startswith('http') else (self.host + vurl),
"vod_name": title or "爱看AV",
"vod_pic": thumb,
"vod_remarks": remarks
})
return videos
+121
View File
@@ -0,0 +1,121 @@
import re,json,base64,requests,threading
from urllib.parse import quote
from base.spider import Spider
class Spider(Spider):
def getName(self): return "光鸭社区"
def init(self,extend=""):
self.host="https://gy.lubao2.de5.net";self.sb="https://houhvngggkkcqcjmzafx.supabase.co";self.tmdb="https://api.tmdb.org";self.tmdb_img="https://images.tmdb.org/t/p";self.tmdb_key="2894d9a1baf7812b451de03c801b0281"
self.key="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhvdWh2bmdnZ2trY3Fjam16YWZ4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc2Mjg2MjgsImV4cCI6MjA5MzIwNDYyOH0.FUUT9ETU-lP5iNqwK3i7nflwGLSBHM1b-qIs9CoVles"
self.cookie="";self.page_size=40;self.default_pic="https://miaoda-edit-image.cdn.bcebos.com/b74i02x880e9/IMG-b7c60ycpg3r4.png";self.pic_cache={};self.pic_pending=set();self.pic_sem=threading.BoundedSemaphore(3);self.cache_file="/storage/emulated/0/Download/gy_tmdb_pic_cache.json";self.list_fields="id,title,cover_url,year,region,genres,category_id"
self.headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/122 Safari/537.36","Referer":self.host+"/","apikey":self.key,"Authorization":"Bearer "+self.key,"Accept":"application/json"}
self.session=requests.Session();self.session.headers.update(self.headers)
if extend:
try:
e=json.loads(extend);self.cookie=e.get("cookie","") or self.cookie;self.page_size=int(e.get("page_size",40) or 40);self.tmdb_key=e.get("tmdb_key",self.tmdb_key);self.tmdb=e.get("tmdb_api",self.tmdb);self.tmdb_img=e.get("tmdb_img",self.tmdb_img);self.default_pic=e.get("default_pic",self.default_pic);self.cache_file=e.get("cache_file",self.cache_file)
except Exception:self.cookie=extend
if self.cookie:self.headers["Cookie"]=self.cookie;self.session.headers.update({"Cookie":self.cookie})
self._load_cache()
def _load_cache(self):
try:
with open(self.cache_file,"r",encoding="utf-8") as f:self.pic_cache=json.load(f)
except Exception:self.pic_cache={}
def _save_cache(self):
try:
if len(self.pic_cache)%5==0:
with open(self.cache_file,"w",encoding="utf-8") as f:json.dump(self.pic_cache,f,ensure_ascii=False,separators=(",",":"))
except Exception:None
def _get(self,path):
try:
r=self.session.get(self.sb+path,timeout=12);return r.json() if r.text else []
except Exception:return []
def _tmget(self,path,params):
try:
params.update({"api_key":self.tmdb_key,"language":"zh-CN"});r=requests.get(self.tmdb+path,params=params,headers={"User-Agent":self.headers["User-Agent"]},timeout=5);return r.json() if r.text else {}
except Exception:return {}
def _enc(self,o): return base64.urlsafe_b64encode(json.dumps(o,ensure_ascii=False,separators=(",",":")).encode()).decode().rstrip("=")
def _dec(self,s):
try:return json.loads(base64.urlsafe_b64decode((s+"="*(-len(s)%4)).encode()).decode())
except Exception:return {"url":s}
def _clean(self,s): return re.sub(r"\s+"," ",str(s or "")).replace("#","").replace("$","").strip()
def _title(self,s):
s=re.sub(r"\[tmdb-\d+\]","",str(s or ""),flags=re.I);s=re.sub(r"\[[^\]]+\]|【[^】]+】|\([^)]*\)|[^]*"," ",s);s=re.sub(r"\b(19|20)\d{2}\b.*$","",s);s=re.sub(r"\.(2160p|1080p|720p|WEB-DL|BluRay|H265|H264|AAC|SONYHD).*$","",s,flags=re.I);return self._clean(s).strip(" ._-—")
def _pkey(self,x):
name=x.get("title") or "";year=str(x.get("year") or "");mid=re.search(r"tmdb-(\d+)",name,re.I);return mid.group(1) if mid else self._title(name)+year
def _poster(self,x,search=False):
pic=x.get("cover_url") or ""
if pic:return pic
if not search:return self.default_pic
ck=self._pkey(x)
if not ck:return self.default_pic
if ck in self.pic_cache:return self.pic_cache[ck]
if ck not in self.pic_pending:
self.pic_pending.add(ck);threading.Thread(target=self._fill_poster,args=(dict(x),ck),daemon=True).start()
return self.default_pic
def _fill_poster(self,x,ck):
self.pic_sem.acquire();p="";name=x.get("title") or "";year=str(x.get("year") or "");mid=re.search(r"tmdb-(\d+)",name,re.I)
try:
if mid:
for t in ["movie","tv"]:
j=self._tmget("/3/%s/%s"%(t,mid.group(1)),{});p=j.get("poster_path") or j.get("backdrop_path") or ""
if p:break
if not p:
q=self._title(name);j=self._tmget("/3/search/multi",{"query":q,"year":year}) if q else {};rs=[i for i in j.get("results",[]) if i.get("poster_path")]
if not rs and q:j=self._tmget("/3/search/multi",{"query":q});rs=[i for i in j.get("results",[]) if i.get("poster_path")]
p=rs[0].get("poster_path","") if rs else ""
self.pic_cache[ck]=self.tmdb_img+"/w500"+p if p else self.default_pic;self._save_cache()
except Exception:self.pic_cache[ck]=self.default_pic
self.pic_pending.discard(ck);self.pic_sem.release()
def _cats_raw(self): return self._get("/rest/v1/categories?select=id,name&parent_id=is.null&order=sort_order.asc")
def _cats(self): return [{"type_id":x.get("id",""),"type_name":x.get("name","")} for x in self._cats_raw() if x.get("id") and x.get("name")]
def _cat_names(self): return {x.get("id",""):x.get("name","") for x in self._cats_raw() if x.get("id")}
def _item(self,x,search=True):
name=self._clean(x.get("title") or "光鸭资源");remark=" ".join([str(x.get("year") or ""),x.get("region") or ""]).strip()
if not remark and x.get("genres"):remark="/".join(x.get("genres")[:2])
return {"vod_id":x.get("id",""),"vod_name":name,"vod_pic":self._poster(x,search),"vod_remarks":remark}
def _folder(self,name,count,kw,cid,pic=""): return {"vod_id":"folder:"+self._enc({"kw":kw,"cid":cid,"name":name}),"vod_name":"📁 %s %s"%(name,count),"vod_pic":pic or self.default_pic,"vod_remarks":"点击查看","vod_tag":"folder"}
def _search_arr(self,key,limit=300,offset=0):
q=quote("*"+str(key or "")+"*")
return self._get("/rest/v1/resources?select=%s&status=eq.approved&title=ilike.%s&order=created_at.desc&limit=%s&offset=%s"%(self.list_fields,q,limit,offset)) if key else []
def _list(self,path): return [self._item(x) for x in self._get(path) if x.get("id")]
def homeContent(self,filter): return {"class":self._cats(),"filters":{},"list":self._list("/rest/v1/resources?select=%s&status=eq.approved&order=created_at.desc&limit=30"%self.list_fields)}
def homeVideoContent(self): return {"list":self._list("/rest/v1/resources?select=%s&status=eq.approved&order=created_at.desc&limit=30"%self.list_fields)}
def categoryContent(self,tid,pg,filter,extend):
page=int(pg) if str(pg).isdigit() else 1;start=(page-1)*self.page_size
if str(tid).startswith("folder:"):
o=self._dec(str(tid)[7:]);arr=[x for x in self._search_arr(o.get("kw",""),500,0) if x.get("category_id")==o.get("cid")];lst=arr[start:start+self.page_size]
return {"page":page,"pagecount":max(1,(len(arr)+self.page_size-1)//self.page_size),"limit":self.page_size,"total":len(arr),"list":[self._item(x) for x in lst if x.get("id")]}
arr=self._get("/rest/v1/resources?select=%s&status=eq.approved&order=created_at.desc&category_id=eq.%s&limit=%s&offset=%s"%(self.list_fields,quote(str(tid)),self.page_size,start));total=start+len(arr)+(1 if len(arr)>=self.page_size else 0)
return {"page":page,"pagecount":page+(1 if len(arr)>=self.page_size else 0),"limit":self.page_size,"total":total,"list":[self._item(x) for x in arr if x.get("id")]}
def searchContent(self,key,quick,pg="1"):
page=int(pg) if str(pg).isdigit() else 1;arr=self._search_arr(key,500,0);names=self._cat_names();groups={}
for x in arr:
cid=x.get("category_id") or "other";groups.setdefault(cid,[]).append(x)
if page==1 and len(groups)>1:
order=sorted(groups.items(),key=lambda kv:-len(kv[1]));return {"page":1,"pagecount":1,"limit":self.page_size,"total":len(arr),"list":[self._folder(names.get(cid,"其他"),len(v),key,cid,next((i.get("cover_url") for i in v if i.get("cover_url")),"")) for cid,v in order]}
start=(page-1)*self.page_size;lst=arr[start:start+self.page_size]
return {"page":page,"pagecount":max(1,(len(arr)+self.page_size-1)//self.page_size),"limit":self.page_size,"total":len(arr),"list":[self._item(x) for x in lst if x.get("id")]}
def detailContent(self,ids):
vid=ids[0] if ids else "";arr=self._get("/rest/v1/resources?select=*&id=eq.%s&limit=1"%quote(str(vid))) if vid and not str(vid).startswith("folder:") else []
if not arr:return {"list":[]}
x=arr[0];name=self._clean(x.get("title") or "光鸭资源");pic=self._poster(x,True);content=self._clean(x.get("description") or "");links=[]
if x.get("pan_link"):links.append(("主链接",x.get("pan_link"),x.get("pan_password") or ""))
for i,u in enumerate(x.get("backup_links") or []):
if isinstance(u,dict):links.append((u.get("name") or "备用%d"%(i+1),u.get("url") or u.get("link") or "",u.get("password") or u.get("pwd") or ""))
elif isinstance(u,str):links.append(("备用%d"%(i+1),u,""))
if not links:links.append(("详情页",self.host+"/resource/"+vid,""))
eps=[self._clean(n)+(" 提取码:"+p if p else "")+"$"+self._enc({"url":u,"pwd":p}) for n,u,p in links if u]
return {"list":[{"vod_id":vid,"vod_name":name,"vod_pic":pic,"vod_year":str(x.get("year") or ""),"vod_area":x.get("region") or "","vod_content":content,"vod_play_from":"光鸭云盘","vod_play_url":"#".join(eps)}]}
def playerContent(self,flag,id,vipFlags):
o=self._dec(id);u=o.get("url",id) if isinstance(o,dict) else id
if not u:return {"parse":1,"jx":0,"url":""}
if re.search(r"\.(m3u8|mp4|mkv|flv|avi|mov)(\?|$)",u,re.I):return {"parse":0,"jx":0,"url":u,"header":json.dumps({"User-Agent":self.headers["User-Agent"],"Referer":self.host+"/"})}
return {"parse":0,"jx":0,"url":"push://"+u if u.startswith("http") else u}
def localProxy(self,params): return [404,"text/plain",""]
def isVideoFormat(self,url): return bool(re.search(r"\.(m3u8|mp4|mkv|flv|avi|mov)(\?|$)",str(url),re.I))
def manualVideoCheck(self): return False
def destroy(self):
try:
with open(self.cache_file,"w",encoding="utf-8") as f:json.dump(self.pic_cache,f,ensure_ascii=False,separators=(",",":"))
except Exception:None
return None
@@ -0,0 +1,199 @@
#Kyele
import sys
import json
import time
import re
import requests
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def __init__(self):
super().__init__()
self.base = 'https://www.pandalive.co.kr'; self.api = 'https://api.pandalive.co.kr'; self.session = requests.Session()
self.ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36'
self.common_headers = {'User-Agent': self.ua, 'Accept': 'application/json, text/plain, */*', 'Origin': self.base, 'Referer': self.base + '/'}
self.x_device_info = {"t": "webPc", "v": "1.0", "ui": "0", "ck": {"sessKeyAsp": ""}}; self.extra_cookie = ''
def init(self, extend=""):
try:
if extend:
cfg = extend if isinstance(extend, dict) else json.loads(extend)
self.x_device_info = cfg.get('x_device_info', self.x_device_info); self.extra_cookie = cfg.get('cookie', '')
except Exception: pass
try:
self.session.headers.update(self.common_headers)
if self.extra_cookie: self.session.headers['Cookie'] = self.extra_cookie
self.session.get(self.base, timeout=8); self._app_token()
except Exception: pass
return self
def getName(self): return 'PandaLive'
def isVideoFormat(self, url): return url.endswith('.m3u8') or url.endswith('.mp4')
def manualVideoCheck(self): return False
def destroy(self):
try: self.session.close()
except Exception: pass
def _app_token(self):
headers = self._with_x_device_info(dict(self.session.headers))
return self.session.get(f'{self.api}/v1/member/app_token', headers=headers, timeout=8)
def _list_live(self, page=None, page_size=None, order_by='user', only_new='N'):
if page_size is None: page_size = 60
limit = page_size; offset = 0 if page is None else max(0, (page - 1) * limit)
headers = self._with_x_device_info(dict(self.session.headers))
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
data = {'orderBy': order_by, 'onlyNewBj': only_new, 'limit': str(limit), 'offset': str(offset)}
try:
r = self.session.post(f'{self.api}/v1/live', data=data, headers=headers, timeout=8)
j = {}
try: j = r.json()
except Exception: pass
if isinstance(j, dict) and isinstance(j.get('list'), list) and len(j['list']) > 0: return j
except Exception: pass
try: return self.session.get(f'{self.api}/v1/live', timeout=8).json()
except Exception: return {}
def _list_live_page(self, page, page_size, order_by='user', only_new='N'):
j = self._list_live(page=page, page_size=page_size, order_by=order_by, only_new=only_new)
return j.get('list', []) if isinstance(j, dict) else []
def _list_live_aggregate(self, max_pages=5, page_size=60, min_expect=60, order_by='user', only_new='N'):
try:
cache_key = f'pandalive_agg_v2_{order_by}_{only_new}'; cached = self.getCache(cache_key)
if isinstance(cached, dict) and isinstance(cached.get('list'), list): return cached['list']
except Exception: pass
seen = set(); result = []
first = self._list_live(page=None, page_size=page_size, order_by=order_by, only_new=only_new)
items = first.get('list', []) if isinstance(first, dict) else []
for it in items:
code = it.get('code') or it.get('userId')
if code and code not in seen: seen.add(code); result.append(it)
p = 1
while len(result) < min_expect and p <= max_pages:
page_items = self._list_live_page(page=p, page_size=page_size, order_by=order_by, only_new=only_new)
added = 0
for it in page_items:
code = it.get('code') or it.get('userId')
if code and code not in seen: seen.add(code); result.append(it); added += 1
if added == 0 and p > 1: break
p += 1
try:
payload = {"expiresAt": int(time.time()) + 20, "list": result}
self.setCache(f'pandalive_agg_v2_{order_by}_{only_new}', payload)
except Exception: pass
return result
def _live_play(self, play_id):
body = {'play_id': play_id, 'device': 'webPc', 'player': 'ivs'}
headers = self._with_x_device_info(dict(self.session.headers))
headers['Content-Type'] = 'application/json'; headers['Referer'] = f'{self.base}/play/{play_id.split("_")[0]}'
r = self.session.post(f'{self.api}/v1/live/play', headers=headers, data=json.dumps(body), timeout=8)
if r.status_code != 200:
try: self._app_token()
except Exception: pass
r = self.session.post(f'{self.api}/v1/live/play', headers=headers, data=json.dumps(body), timeout=8)
try: return r.json()
except Exception: return {'result': False, 'status': r.status_code, 'text': r.text}
def _with_x_device_info(self, headers):
try: headers['x-device-info'] = json.dumps(self.x_device_info, separators=(',', ':'))
except Exception: headers['x-device-info'] = '{"t":"webPc","v":"1.0","ui":"0","ck":{"sessKeyAsp":""}}'
return headers
def homeContent(self, filter):
classes = [{"type_name": "LIVE", "type_id": "live"}]
if filter:
filters = {"live": [{"key": "sort", "value": [
{"n": "观看次数", "v": "user-N"}, {"n": "热门", "v": "hot-N"},
{"n": "最新", "v": "new-N"}, {"n": "新人", "v": "user-Y"}
]}]}
else: filters = {}
return {"class": classes, "filters": filters}
def homeVideoContent(self):
items = self._list_live_aggregate(max_pages=3, page_size=60, min_expect=48, order_by='user', only_new='N')
if not items:
items = (self._list_live().get('list', []))
return {"list": [self._to_vod(it) for it in items[:48]]}
def categoryContent(self, tid, pg, filter, extend):
page_size = 24; p = 1
try: p = int(pg)
except Exception: pass
order_by, only_new = 'user', 'N'
try:
if isinstance(extend, str):
try: extend = json.loads(extend) if extend.strip().startswith('{') else {}
except Exception: extend = {}
if isinstance(extend, dict):
s = extend.get('sort')
if isinstance(s, str) and '-' in s:
ab = s.split('-', 1)
if len(ab) == 2: order_by, only_new = (ab[0] or 'user'), (ab[1] or 'N')
if (order_by, only_new) == ('user', 'N') and (tid or '').lower() != 'live':
order_by, only_new = self._tid_to_sort(tid)
except Exception: pass
server_items = self._list_live_page(page=p, page_size=page_size, order_by=order_by, only_new=only_new)
if len(server_items) >= page_size:
return {"page": p, "pagecount": 99999, "limit": page_size, "total": 999999, "list": [self._to_vod(it) for it in server_items]}
all_items = self._list_live_aggregate(max_pages=12, page_size=100, min_expect=120, order_by=order_by, only_new=only_new)
total = len(all_items)
if total <= 0:
base_list = self._list_live().get('list', [])
total = len(base_list)
if total <= 0: return {"page": p, "pagecount": 1, "limit": page_size, "total": 0, "list": []}
start = (p - 1) * page_size; end = start + page_size
part = base_list[start:end]
videos = [self._to_vod(it) for it in part]
return {"page": p, "pagecount": (total + page_size - 1)//page_size, "limit": page_size, "total": total, "list": videos}
start = ((p - 1) * page_size) % total; part = []
for i in range(page_size): part.append(all_items[(start + i) % total])
return {"page": p, "pagecount": 99999, "limit": page_size, "total": 999999, "list": [self._to_vod(it) for it in part]}
def _tid_to_sort(self, tid):
t = (tid or '').lower()
if t == 'live_newbj': return 'user', 'Y'
if t == 'live_hot': return 'hot', 'N'
if t == 'live_new': return 'new', 'N'
return 'user', 'N'
def detailContent(self, ids):
vid = ids[0]; parts = vid.split('|'); play_id = parts[0]
user_id = parts[1] if len(parts) > 1 else play_id.split('_')[0]
title = parts[2] if len(parts) > 2 else user_id
vod = {"vod_id": vid, "vod_name": title, "vod_pic": "", "type_name": "LIVE", "vod_year": "", "vod_area": "", "vod_remarks": "PandaLive", "vod_actor": "", "vod_director": "", "vod_content": title, "vod_play_from": "IVS", "vod_play_url": f"直播${vid}"}
return {"list": [vod]}
def searchContent(self, key, quick, pg="1"):
items = self._list_live().get('list', [])
key_l = key.lower(); result = []
for it in items:
title = str(it.get('title', '')); user_id = str(it.get('userId', '')); user_nick = str(it.get('userNick', ''))
if key_l in title.lower() or key_l in user_id.lower() or key_l in user_nick.lower():
result.append(self._to_vod(it))
return {"list": result, "page": 1}
def playerContent(self, flag, id, vipFlags):
try:
vid = id; parts = vid.split('|'); play_id = parts[0]
j = self._live_play(play_id); m3u8 = self._find_first_m3u8(j) if isinstance(j, dict) else ''
if not m3u8: return {"parse": 1, "playUrl": "", "url": f"{self.base}/play/{play_id.split('_')[0]}", "header": self._play_headers()}
return {"parse": 0, "playUrl": "", "url": m3u8, "header": self._play_headers()}
except Exception: return {"parse": 1, "playUrl": "", "url": f"{self.base}", "header": self._play_headers()}
def liveContent(self, url):
try:
play_id = url; j = self._live_play(play_id)
m3u8 = self._find_first_m3u8(j)
if m3u8: return {"parse": 0, "url": m3u8, "header": self._play_headers()}
except Exception: pass
return {"parse": 1, "url": f"{self.base}/play/{url}", "header": self._play_headers()}
def localProxy(self, param):
action = param.get('action') if isinstance(param, dict) else None
if action == 'play':
play_id = param.get('play_id', ''); j = self._live_play(play_id)
m3u8 = self._find_first_m3u8(j)
if m3u8: return self._redirect(m3u8)
return None
def _redirect(self, url):
return {"code": 302, "headers": {"Location": url}}
def _find_first_m3u8(self, obj):
try:
text = json.dumps(obj, ensure_ascii=False)
m = re.search(r'https?://[^\s"\\]+\.m3u8[^\s"\\]*', text)
if m: return m.group(0)
except Exception: pass
return ''
def _play_headers(self):
return {'User-Agent': self.ua, 'Referer': self.base + '/', 'Origin': self.base}
def _to_vod(self, it):
title = it.get('title') or it.get('userNick') or it.get('userId') or 'LIVE'
pic = it.get('thumbUrl') or it.get('ivsThumbnail') or ''
user_id = it.get('userId', ''); play_id = it.get('code', user_id); vod_id = f"{play_id}|{user_id}|{title}"
remarks = f"观众 {it.get('user', 0)} | 点赞 {it.get('likeCnt', 0)}"
return {'vod_id': vod_id, 'vod_name': title, 'vod_pic': pic, 'vod_remarks': remarks}
+330
View File
@@ -0,0 +1,330 @@
# -*- coding: utf-8 -*-
import json
import sys
import requests
import re
# Ensure bs4 is available or handled in your environment
try:
from bs4 import BeautifulSoup
except ImportError:
sys.exit("BeautifulSoup4 is required. Please install it via 'pip install beautifulsoup4'")
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.iyf.lv"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36',
'Referer': self.host + '/',
'Origin': self.host
}
def getName(self):
return "爱壹帆"
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
# Mapping from JS: class_name='电影&剧集&综艺&动漫&更新&热榜', class_url='1&2&3&4&new&hot'
classes = [
{"type_name": "电影", "type_id": "1"},
{"type_name": "剧集", "type_id": "2"},
{"type_name": "综艺", "type_id": "3"},
{"type_name": "动漫", "type_id": "4"},
{"type_name": "更新", "type_id": "new"},
{"type_name": "热榜", "type_id": "hot"}
]
# JS says filterable: 0, so we return empty filters
return {'class': classes, 'filters': {}}
def homeVideoContent(self):
# JS Recommendation rule: .tab-list.active;a.module-poster-item.module-item
try:
html = self.fetch(self.host, headers=self.headers).text
soup = BeautifulSoup(html, 'html.parser')
# Locate the active tab list or default recommendation section
items = soup.select('.tab-list.active a.module-poster-item.module-item')
if not items:
items = soup.select('a.module-poster-item.module-item')[:10] # Fallback
vods = []
for item in items:
vod = self._parse_vod_item(item)
if vod:
vods.append(vod)
return {'list': vods}
except Exception as e:
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
# JS Logic:
# if new -> /label/new/
# if hot -> /label/hot/
# else -> /t/tid/
# paging -> url + 'page/' + pg + '/'
url = ""
if tid == 'new':
url = f"{self.host}/label/new/"
elif tid == 'hot':
url = f"{self.host}/label/hot/"
else:
url = f"{self.host}/t/{tid}/"
if int(pg) > 1:
url += f"page/{pg}/"
try:
html = self.fetch(url, headers=self.headers).text
soup = BeautifulSoup(html, 'html.parser')
items = soup.select('a.module-poster-item.module-item')
vods = []
for item in items:
vod = self._parse_vod_item(item)
if vod:
vods.append(vod)
return {
'list': vods,
'page': pg,
'pagecount': 9999,
'limit': 20,
'total': 99999
}
except Exception as e:
return {'list': []}
def detailContent(self, ids):
# JS Rule 二级 (Detail)
# title: h1
# img: .lazyload -> data-original
# content: .module-info-introduction
# tabs: .module-tab-item
# lists: .module-play-list
try:
url = ids[0]
if not url.startswith('http'):
url = self.host + url
html = self.fetch(url, headers=self.headers).text
soup = BeautifulSoup(html, 'html.parser')
vod = {}
vod['vod_id'] = ids[0]
# 标题
title_elem = soup.select_one('h1')
vod['vod_name'] = title_elem.get_text(strip=True) if title_elem else "未知"
# 图片 - 修复图片获取逻辑
img_elem = soup.select_one('.module-info-poster img.lazyload')
if not img_elem:
img_elem = soup.select_one('.module-item-pic img.lazyload')
if not img_elem:
img_elem = soup.select_one('.lazyload')
if img_elem:
# 优先获取data-original,如果没有则获取src
vod['vod_pic'] = img_elem.get('data-original') or img_elem.get('src') or ""
# 确保图片URL完整
if vod['vod_pic'] and not vod['vod_pic'].startswith('http'):
vod['vod_pic'] = self.host + vod['vod_pic']
else:
vod['vod_pic'] = ""
# 描述内容
desc_elem = soup.select_one('.module-info-introduction-content')
vod['vod_content'] = desc_elem.get_text(strip=True) if desc_elem else ""
# 提取详细信息
info_items = {}
info_elems = soup.select('.module-info-item')
for elem in info_elems:
title_elem = elem.select_one('.module-info-item-title')
content_elem = elem.select_one('.module-info-item-content')
if title_elem and content_elem:
title = title_elem.get_text(strip=True).replace('', '').replace(':', '')
info_items[title] = content_elem.get_text(strip=True)
# 构建详细信息字符串
vod['vod_actor'] = info_items.get('主演', '')
vod['vod_director'] = info_items.get('导演', '')
vod['vod_area'] = info_items.get('地区', '')
vod['vod_year'] = info_items.get('年份', '') or info_items.get('上映', '')
vod['vod_remarks'] = info_items.get('更新', '') or info_items.get('集数', '')
# Play Lists
tabs = [t.get_text(strip=True) for t in soup.select('.module-tab-item')]
play_lists = soup.select('.module-play-list')
vod_play_from = []
vod_play_url = []
for i, play_list in enumerate(play_lists):
# If tabs count matches lists, use tab name, else default
from_name = tabs[i] if i < len(tabs) else f"线路{i+1}"
vod_play_from.append(from_name)
links = []
for a in play_list.select('a'):
name = a.get_text(strip=True)
href = a.get('href')
if href:
if not href.startswith('http'):
href = self.host + href
links.append(f"{name}${href}")
if links:
vod_play_url.append("#".join(links))
if vod_play_from and vod_play_url:
vod['vod_play_from'] = "$$$".join(vod_play_from)
vod['vod_play_url'] = "$$$".join(vod_play_url)
return {'list': [vod]}
except Exception as e:
return {'list': []}
def searchContent(self, key, quick, pg="1"):
# JS SearchUrl: /s/**-------------/
url = f"{self.host}/s/{key}-------------.html"
if int(pg) > 1:
url += f"page/{pg}/"
try:
html = self.fetch(url, headers=self.headers).text
soup = BeautifulSoup(html, 'html.parser')
# 多种搜索结果选择器
items = soup.select('.module-search-item, .module-poster-item, .module-item')
if not items:
items = soup.select('a[href*="/iyftv/"]')
vods = []
for item in items:
vod = self._parse_vod_item(item, is_search=True)
if vod and vod.get('vod_id'):
vods.append(vod)
return {'list': vods, 'page': pg}
except Exception as e:
return {'list': []}
def playerContent(self, flag, id, vipFlags):
# JS Lazy Rule:
# 1. Match .m3u8
# 2. Match .mp4
url = id
if not url.startswith('http'):
url = self.host + url
try:
html = self.fetch(url, headers=self.headers).text
# 首先尝试从播放器配置中提取
player_match = re.search(r'var player_aaaa=({[^;]+});', html)
if player_match:
try:
player_data = json.loads(player_match.group(1))
if player_data.get('url'):
return {'parse': 0, 'url': player_data['url'], 'header': self.headers}
except:
pass
# 正则匹配视频地址
m3u8_match = re.search(r'(https?://[^\s"\'<>]+\.m3u8[^\s"\'<>]*)', html)
if m3u8_match:
return {'parse': 0, 'url': m3u8_match.group(1), 'header': self.headers}
mp4_match = re.search(r'(https?://[^\s"\'<>]+\.(mp4|m4a)[^\s"\'<>]*)', html)
if mp4_match:
return {'parse': 0, 'url': mp4_match.group(1), 'header': self.headers}
# Fallback: Return original url to let the player sniff it
return {'parse': 1, 'url': url, 'header': self.headers}
except Exception as e:
return {'parse': 1, 'url': url, 'header': self.headers}
def _parse_vod_item(self, item, is_search=False):
# Helper to extract vod data from a list item (<a> or div)
try:
link = item if item.name == 'a' else item.select_one('a')
if not link:
return {}
url = link.get('href')
if not url:
return {}
# 处理图片 - 修复图片获取逻辑
pic = ""
# 多种图片选择器
img_selectors = [
'.lazyload',
'.module-item-pic img',
'.module-item-cover img',
'img'
]
for selector in img_selectors:
img_elem = item.select_one(selector)
if img_elem:
# 优先获取data-original,如果没有则获取src
pic = img_elem.get('data-original') or img_elem.get('src') or ""
if pic:
break
# 确保图片URL完整
if pic and not pic.startswith('http'):
pic = self.host + pic
# 标题
title = ""
if is_search:
title_selectors = ['.module-card-item-title', '.module-poster-item-title', '.module-item-title']
for selector in title_selectors:
title_elem = item.select_one(selector)
if title_elem:
title = title_elem.get_text(strip=True)
break
else:
title = link.get('title') or ""
if not title:
title_elem = item.select_one('.module-poster-item-title')
title = title_elem.get_text(strip=True) if title_elem else ""
# 备注/更新信息
remarks = ""
remarks_selectors = ['.module-item-note', '.module-card-item-note', '.pic-text']
for selector in remarks_selectors:
remarks_elem = item.select_one(selector)
if remarks_elem:
remarks = remarks_elem.get_text(strip=True)
break
return {
'vod_id': url,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remarks
}
except Exception as e:
return {}
def localProxy(self, param):
pass
+121
View File
@@ -0,0 +1,121 @@
import re,json,base64,requests,threading
from urllib.parse import quote
from base.spider import Spider
class Spider(Spider):
def getName(self): return "光鸭社区"
def init(self,extend=""):
self.host="https://gy.lubao2.de5.net";self.sb="https://houhvngggkkcqcjmzafx.supabase.co";self.tmdb="https://api.tmdb.org";self.tmdb_img="https://images.tmdb.org/t/p";self.tmdb_key="2894d9a1baf7812b451de03c801b0281"
self.key="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhvdWh2bmdnZ2trY3Fjam16YWZ4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc2Mjg2MjgsImV4cCI6MjA5MzIwNDYyOH0.FUUT9ETU-lP5iNqwK3i7nflwGLSBHM1b-qIs9CoVles"
self.cookie="";self.page_size=40;self.default_pic="https://miaoda-edit-image.cdn.bcebos.com/b74i02x880e9/IMG-b7c60ycpg3r4.png";self.pic_cache={};self.pic_pending=set();self.pic_sem=threading.BoundedSemaphore(3);self.cache_file="/storage/emulated/0/Download/gy_tmdb_pic_cache.json";self.list_fields="id,title,cover_url,year,region,genres,category_id"
self.headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/122 Safari/537.36","Referer":self.host+"/","apikey":self.key,"Authorization":"Bearer "+self.key,"Accept":"application/json"}
self.session=requests.Session();self.session.headers.update(self.headers)
if extend:
try:
e=json.loads(extend);self.cookie=e.get("cookie","") or self.cookie;self.page_size=int(e.get("page_size",40) or 40);self.tmdb_key=e.get("tmdb_key",self.tmdb_key);self.tmdb=e.get("tmdb_api",self.tmdb);self.tmdb_img=e.get("tmdb_img",self.tmdb_img);self.default_pic=e.get("default_pic",self.default_pic);self.cache_file=e.get("cache_file",self.cache_file)
except Exception:self.cookie=extend
if self.cookie:self.headers["Cookie"]=self.cookie;self.session.headers.update({"Cookie":self.cookie})
self._load_cache()
def _load_cache(self):
try:
with open(self.cache_file,"r",encoding="utf-8") as f:self.pic_cache=json.load(f)
except Exception:self.pic_cache={}
def _save_cache(self):
try:
if len(self.pic_cache)%5==0:
with open(self.cache_file,"w",encoding="utf-8") as f:json.dump(self.pic_cache,f,ensure_ascii=False,separators=(",",":"))
except Exception:None
def _get(self,path):
try:
r=self.session.get(self.sb+path,timeout=12);return r.json() if r.text else []
except Exception:return []
def _tmget(self,path,params):
try:
params.update({"api_key":self.tmdb_key,"language":"zh-CN"});r=requests.get(self.tmdb+path,params=params,headers={"User-Agent":self.headers["User-Agent"]},timeout=5);return r.json() if r.text else {}
except Exception:return {}
def _enc(self,o): return base64.urlsafe_b64encode(json.dumps(o,ensure_ascii=False,separators=(",",":")).encode()).decode().rstrip("=")
def _dec(self,s):
try:return json.loads(base64.urlsafe_b64decode((s+"="*(-len(s)%4)).encode()).decode())
except Exception:return {"url":s}
def _clean(self,s): return re.sub(r"\s+"," ",str(s or "")).replace("#","").replace("$","").strip()
def _title(self,s):
s=re.sub(r"\[tmdb-\d+\]","",str(s or ""),flags=re.I);s=re.sub(r"\[[^\]]+\]|【[^】]+】|\([^)]*\)|[^]*"," ",s);s=re.sub(r"\b(19|20)\d{2}\b.*$","",s);s=re.sub(r"\.(2160p|1080p|720p|WEB-DL|BluRay|H265|H264|AAC|SONYHD).*$","",s,flags=re.I);return self._clean(s).strip(" ._-—")
def _pkey(self,x):
name=x.get("title") or "";year=str(x.get("year") or "");mid=re.search(r"tmdb-(\d+)",name,re.I);return mid.group(1) if mid else self._title(name)+year
def _poster(self,x,search=False):
pic=x.get("cover_url") or ""
if pic:return pic
if not search:return self.default_pic
ck=self._pkey(x)
if not ck:return self.default_pic
if ck in self.pic_cache:return self.pic_cache[ck]
if ck not in self.pic_pending:
self.pic_pending.add(ck);threading.Thread(target=self._fill_poster,args=(dict(x),ck),daemon=True).start()
return self.default_pic
def _fill_poster(self,x,ck):
self.pic_sem.acquire();p="";name=x.get("title") or "";year=str(x.get("year") or "");mid=re.search(r"tmdb-(\d+)",name,re.I)
try:
if mid:
for t in ["movie","tv"]:
j=self._tmget("/3/%s/%s"%(t,mid.group(1)),{});p=j.get("poster_path") or j.get("backdrop_path") or ""
if p:break
if not p:
q=self._title(name);j=self._tmget("/3/search/multi",{"query":q,"year":year}) if q else {};rs=[i for i in j.get("results",[]) if i.get("poster_path")]
if not rs and q:j=self._tmget("/3/search/multi",{"query":q});rs=[i for i in j.get("results",[]) if i.get("poster_path")]
p=rs[0].get("poster_path","") if rs else ""
self.pic_cache[ck]=self.tmdb_img+"/w500"+p if p else self.default_pic;self._save_cache()
except Exception:self.pic_cache[ck]=self.default_pic
self.pic_pending.discard(ck);self.pic_sem.release()
def _cats_raw(self): return self._get("/rest/v1/categories?select=id,name&parent_id=is.null&order=sort_order.asc")
def _cats(self): return [{"type_id":x.get("id",""),"type_name":x.get("name","")} for x in self._cats_raw() if x.get("id") and x.get("name")]
def _cat_names(self): return {x.get("id",""):x.get("name","") for x in self._cats_raw() if x.get("id")}
def _item(self,x,search=True):
name=self._clean(x.get("title") or "光鸭资源");remark=" ".join([str(x.get("year") or ""),x.get("region") or ""]).strip()
if not remark and x.get("genres"):remark="/".join(x.get("genres")[:2])
return {"vod_id":x.get("id",""),"vod_name":name,"vod_pic":self._poster(x,search),"vod_remarks":remark}
def _folder(self,name,count,kw,cid,pic=""): return {"vod_id":"folder:"+self._enc({"kw":kw,"cid":cid,"name":name}),"vod_name":"📁 %s %s"%(name,count),"vod_pic":pic or self.default_pic,"vod_remarks":"点击查看","vod_tag":"folder"}
def _search_arr(self,key,limit=300,offset=0):
q=quote("*"+str(key or "")+"*")
return self._get("/rest/v1/resources?select=%s&status=eq.approved&title=ilike.%s&order=created_at.desc&limit=%s&offset=%s"%(self.list_fields,q,limit,offset)) if key else []
def _list(self,path): return [self._item(x) for x in self._get(path) if x.get("id")]
def homeContent(self,filter): return {"class":self._cats(),"filters":{},"list":self._list("/rest/v1/resources?select=%s&status=eq.approved&order=created_at.desc&limit=30"%self.list_fields)}
def homeVideoContent(self): return {"list":self._list("/rest/v1/resources?select=%s&status=eq.approved&order=created_at.desc&limit=30"%self.list_fields)}
def categoryContent(self,tid,pg,filter,extend):
page=int(pg) if str(pg).isdigit() else 1;start=(page-1)*self.page_size
if str(tid).startswith("folder:"):
o=self._dec(str(tid)[7:]);arr=[x for x in self._search_arr(o.get("kw",""),500,0) if x.get("category_id")==o.get("cid")];lst=arr[start:start+self.page_size]
return {"page":page,"pagecount":max(1,(len(arr)+self.page_size-1)//self.page_size),"limit":self.page_size,"total":len(arr),"list":[self._item(x) for x in lst if x.get("id")]}
arr=self._get("/rest/v1/resources?select=%s&status=eq.approved&order=created_at.desc&category_id=eq.%s&limit=%s&offset=%s"%(self.list_fields,quote(str(tid)),self.page_size,start));total=start+len(arr)+(1 if len(arr)>=self.page_size else 0)
return {"page":page,"pagecount":page+(1 if len(arr)>=self.page_size else 0),"limit":self.page_size,"total":total,"list":[self._item(x) for x in arr if x.get("id")]}
def searchContent(self,key,quick,pg="1"):
page=int(pg) if str(pg).isdigit() else 1;arr=self._search_arr(key,500,0);names=self._cat_names();groups={}
for x in arr:
cid=x.get("category_id") or "other";groups.setdefault(cid,[]).append(x)
if page==1 and len(groups)>1:
order=sorted(groups.items(),key=lambda kv:-len(kv[1]));return {"page":1,"pagecount":1,"limit":self.page_size,"total":len(arr),"list":[self._folder(names.get(cid,"其他"),len(v),key,cid,next((i.get("cover_url") for i in v if i.get("cover_url")),"")) for cid,v in order]}
start=(page-1)*self.page_size;lst=arr[start:start+self.page_size]
return {"page":page,"pagecount":max(1,(len(arr)+self.page_size-1)//self.page_size),"limit":self.page_size,"total":len(arr),"list":[self._item(x) for x in lst if x.get("id")]}
def detailContent(self,ids):
vid=ids[0] if ids else "";arr=self._get("/rest/v1/resources?select=*&id=eq.%s&limit=1"%quote(str(vid))) if vid and not str(vid).startswith("folder:") else []
if not arr:return {"list":[]}
x=arr[0];name=self._clean(x.get("title") or "光鸭资源");pic=self._poster(x,True);content=self._clean(x.get("description") or "");links=[]
if x.get("pan_link"):links.append(("主链接",x.get("pan_link"),x.get("pan_password") or ""))
for i,u in enumerate(x.get("backup_links") or []):
if isinstance(u,dict):links.append((u.get("name") or "备用%d"%(i+1),u.get("url") or u.get("link") or "",u.get("password") or u.get("pwd") or ""))
elif isinstance(u,str):links.append(("备用%d"%(i+1),u,""))
if not links:links.append(("详情页",self.host+"/resource/"+vid,""))
eps=[self._clean(n)+(" 提取码:"+p if p else "")+"$"+self._enc({"url":u,"pwd":p}) for n,u,p in links if u]
return {"list":[{"vod_id":vid,"vod_name":name,"vod_pic":pic,"vod_year":str(x.get("year") or ""),"vod_area":x.get("region") or "","vod_content":content,"vod_play_from":"光鸭云盘","vod_play_url":"#".join(eps)}]}
def playerContent(self,flag,id,vipFlags):
o=self._dec(id);u=o.get("url",id) if isinstance(o,dict) else id
if not u:return {"parse":1,"jx":0,"url":""}
if re.search(r"\.(m3u8|mp4|mkv|flv|avi|mov)(\?|$)",u,re.I):return {"parse":0,"jx":0,"url":u,"header":json.dumps({"User-Agent":self.headers["User-Agent"],"Referer":self.host+"/"})}
return {"parse":0,"jx":0,"url":"push://"+u if u.startswith("http") else u}
def localProxy(self,params): return [404,"text/plain",""]
def isVideoFormat(self,url): return bool(re.search(r"\.(m3u8|mp4|mkv|flv|avi|mov)(\?|$)",str(url),re.I))
def manualVideoCheck(self): return False
def destroy(self):
try:
with open(self.cache_file,"w",encoding="utf-8") as f:json.dump(self.pic_cache,f,ensure_ascii=False,separators=(",",":"))
except Exception:None
return None
+330
View File
@@ -0,0 +1,330 @@
# -*- coding: utf-8 -*-
import json
import sys
import requests
import re
# Ensure bs4 is available or handled in your environment
try:
from bs4 import BeautifulSoup
except ImportError:
sys.exit("BeautifulSoup4 is required. Please install it via 'pip install beautifulsoup4'")
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.iyf.lv"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36',
'Referer': self.host + '/',
'Origin': self.host
}
def getName(self):
return "爱壹帆"
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
# Mapping from JS: class_name='电影&剧集&综艺&动漫&更新&热榜', class_url='1&2&3&4&new&hot'
classes = [
{"type_name": "电影", "type_id": "1"},
{"type_name": "剧集", "type_id": "2"},
{"type_name": "综艺", "type_id": "3"},
{"type_name": "动漫", "type_id": "4"},
{"type_name": "更新", "type_id": "new"},
{"type_name": "热榜", "type_id": "hot"}
]
# JS says filterable: 0, so we return empty filters
return {'class': classes, 'filters': {}}
def homeVideoContent(self):
# JS Recommendation rule: .tab-list.active;a.module-poster-item.module-item
try:
html = self.fetch(self.host, headers=self.headers).text
soup = BeautifulSoup(html, 'html.parser')
# Locate the active tab list or default recommendation section
items = soup.select('.tab-list.active a.module-poster-item.module-item')
if not items:
items = soup.select('a.module-poster-item.module-item')[:10] # Fallback
vods = []
for item in items:
vod = self._parse_vod_item(item)
if vod:
vods.append(vod)
return {'list': vods}
except Exception as e:
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
# JS Logic:
# if new -> /label/new/
# if hot -> /label/hot/
# else -> /t/tid/
# paging -> url + 'page/' + pg + '/'
url = ""
if tid == 'new':
url = f"{self.host}/label/new/"
elif tid == 'hot':
url = f"{self.host}/label/hot/"
else:
url = f"{self.host}/t/{tid}/"
if int(pg) > 1:
url += f"page/{pg}/"
try:
html = self.fetch(url, headers=self.headers).text
soup = BeautifulSoup(html, 'html.parser')
items = soup.select('a.module-poster-item.module-item')
vods = []
for item in items:
vod = self._parse_vod_item(item)
if vod:
vods.append(vod)
return {
'list': vods,
'page': pg,
'pagecount': 9999,
'limit': 20,
'total': 99999
}
except Exception as e:
return {'list': []}
def detailContent(self, ids):
# JS Rule 二级 (Detail)
# title: h1
# img: .lazyload -> data-original
# content: .module-info-introduction
# tabs: .module-tab-item
# lists: .module-play-list
try:
url = ids[0]
if not url.startswith('http'):
url = self.host + url
html = self.fetch(url, headers=self.headers).text
soup = BeautifulSoup(html, 'html.parser')
vod = {}
vod['vod_id'] = ids[0]
# 标题
title_elem = soup.select_one('h1')
vod['vod_name'] = title_elem.get_text(strip=True) if title_elem else "未知"
# 图片 - 修复图片获取逻辑
img_elem = soup.select_one('.module-info-poster img.lazyload')
if not img_elem:
img_elem = soup.select_one('.module-item-pic img.lazyload')
if not img_elem:
img_elem = soup.select_one('.lazyload')
if img_elem:
# 优先获取data-original,如果没有则获取src
vod['vod_pic'] = img_elem.get('data-original') or img_elem.get('src') or ""
# 确保图片URL完整
if vod['vod_pic'] and not vod['vod_pic'].startswith('http'):
vod['vod_pic'] = self.host + vod['vod_pic']
else:
vod['vod_pic'] = ""
# 描述内容
desc_elem = soup.select_one('.module-info-introduction-content')
vod['vod_content'] = desc_elem.get_text(strip=True) if desc_elem else ""
# 提取详细信息
info_items = {}
info_elems = soup.select('.module-info-item')
for elem in info_elems:
title_elem = elem.select_one('.module-info-item-title')
content_elem = elem.select_one('.module-info-item-content')
if title_elem and content_elem:
title = title_elem.get_text(strip=True).replace('', '').replace(':', '')
info_items[title] = content_elem.get_text(strip=True)
# 构建详细信息字符串
vod['vod_actor'] = info_items.get('主演', '')
vod['vod_director'] = info_items.get('导演', '')
vod['vod_area'] = info_items.get('地区', '')
vod['vod_year'] = info_items.get('年份', '') or info_items.get('上映', '')
vod['vod_remarks'] = info_items.get('更新', '') or info_items.get('集数', '')
# Play Lists
tabs = [t.get_text(strip=True) for t in soup.select('.module-tab-item')]
play_lists = soup.select('.module-play-list')
vod_play_from = []
vod_play_url = []
for i, play_list in enumerate(play_lists):
# If tabs count matches lists, use tab name, else default
from_name = tabs[i] if i < len(tabs) else f"线路{i+1}"
vod_play_from.append(from_name)
links = []
for a in play_list.select('a'):
name = a.get_text(strip=True)
href = a.get('href')
if href:
if not href.startswith('http'):
href = self.host + href
links.append(f"{name}${href}")
if links:
vod_play_url.append("#".join(links))
if vod_play_from and vod_play_url:
vod['vod_play_from'] = "$$$".join(vod_play_from)
vod['vod_play_url'] = "$$$".join(vod_play_url)
return {'list': [vod]}
except Exception as e:
return {'list': []}
def searchContent(self, key, quick, pg="1"):
# JS SearchUrl: /s/**-------------/
url = f"{self.host}/s/{key}-------------.html"
if int(pg) > 1:
url += f"page/{pg}/"
try:
html = self.fetch(url, headers=self.headers).text
soup = BeautifulSoup(html, 'html.parser')
# 多种搜索结果选择器
items = soup.select('.module-search-item, .module-poster-item, .module-item')
if not items:
items = soup.select('a[href*="/iyftv/"]')
vods = []
for item in items:
vod = self._parse_vod_item(item, is_search=True)
if vod and vod.get('vod_id'):
vods.append(vod)
return {'list': vods, 'page': pg}
except Exception as e:
return {'list': []}
def playerContent(self, flag, id, vipFlags):
# JS Lazy Rule:
# 1. Match .m3u8
# 2. Match .mp4
url = id
if not url.startswith('http'):
url = self.host + url
try:
html = self.fetch(url, headers=self.headers).text
# 首先尝试从播放器配置中提取
player_match = re.search(r'var player_aaaa=({[^;]+});', html)
if player_match:
try:
player_data = json.loads(player_match.group(1))
if player_data.get('url'):
return {'parse': 0, 'url': player_data['url'], 'header': self.headers}
except:
pass
# 正则匹配视频地址
m3u8_match = re.search(r'(https?://[^\s"\'<>]+\.m3u8[^\s"\'<>]*)', html)
if m3u8_match:
return {'parse': 0, 'url': m3u8_match.group(1), 'header': self.headers}
mp4_match = re.search(r'(https?://[^\s"\'<>]+\.(mp4|m4a)[^\s"\'<>]*)', html)
if mp4_match:
return {'parse': 0, 'url': mp4_match.group(1), 'header': self.headers}
# Fallback: Return original url to let the player sniff it
return {'parse': 1, 'url': url, 'header': self.headers}
except Exception as e:
return {'parse': 1, 'url': url, 'header': self.headers}
def _parse_vod_item(self, item, is_search=False):
# Helper to extract vod data from a list item (<a> or div)
try:
link = item if item.name == 'a' else item.select_one('a')
if not link:
return {}
url = link.get('href')
if not url:
return {}
# 处理图片 - 修复图片获取逻辑
pic = ""
# 多种图片选择器
img_selectors = [
'.lazyload',
'.module-item-pic img',
'.module-item-cover img',
'img'
]
for selector in img_selectors:
img_elem = item.select_one(selector)
if img_elem:
# 优先获取data-original,如果没有则获取src
pic = img_elem.get('data-original') or img_elem.get('src') or ""
if pic:
break
# 确保图片URL完整
if pic and not pic.startswith('http'):
pic = self.host + pic
# 标题
title = ""
if is_search:
title_selectors = ['.module-card-item-title', '.module-poster-item-title', '.module-item-title']
for selector in title_selectors:
title_elem = item.select_one(selector)
if title_elem:
title = title_elem.get_text(strip=True)
break
else:
title = link.get('title') or ""
if not title:
title_elem = item.select_one('.module-poster-item-title')
title = title_elem.get_text(strip=True) if title_elem else ""
# 备注/更新信息
remarks = ""
remarks_selectors = ['.module-item-note', '.module-card-item-note', '.pic-text']
for selector in remarks_selectors:
remarks_elem = item.select_one(selector)
if remarks_elem:
remarks = remarks_elem.get_text(strip=True)
break
return {
'vod_id': url,
'vod_name': title,
'vod_pic': pic,
'vod_remarks': remarks
}
except Exception as e:
return {}
def localProxy(self, param):
pass