Sync all projects
This commit is contained in:
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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);',
|
||||
二级: '',
|
||||
搜索: '',
|
||||
}
|
||||
@@ -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);',
|
||||
二级:'',
|
||||
搜索:'',
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
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
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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
|
||||
@@ -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,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
|
||||
Reference in New Issue
Block a user