Sync all projects

This commit is contained in:
github-actions[bot]
2026-07-01 17:15:33 +00:00
parent 4b380c198b
commit 24e565b4c5
54 changed files with 20882 additions and 8896 deletions
+1 -1
View File
@@ -230,7 +230,7 @@
{ "name": "📺猫眼", "api": "https://api.maoyanapi.top/api.php/provide/vod/" },
{ "name": "📺极速", "api": "https://jszyapi.com/api.php/provide/vod/from/jsm3u8/" },
{ "name": "📺豆瓣", "api": "https://caiji.dbzy5.com/api.php/provide/vod/" },
{ "name": "📺九八", "api": "https://98zy.vip/api.php/provide/vod/" },
{ "name": "📺大众", "api": "https://cdn.dzzyapi.com/api.php/provide/vod/" },
{ "name": "📺新浪", "api": "https://api.xinlangapi.com/xinlangapi.php/provide/vod/from/xlm3u8" },
{ "name": "📺百度", "api": "https://api.apibdzy.com/api.php/provide/vod/from/dbm3u8/" },
{ "name": "📺金鹰", "api": "https://jyzyapi.com/provide/vod/" },
+1 -1
View File
@@ -237,7 +237,7 @@
{ "name": "📺索尼", "api": "https://suoniapi.com/api.php/provide/vod/from/snm3u8/" },
{ "name": "📺CK", "api": "https://ckzy.me/api.php/provide/vod/" },
{ "name": "📺天涯", "api": "https://tyyszyapi.com/api.php/provide/vod/" },
{ "name": "📺人人", "api": "http://jiduo666.dpdns.org/rrsp.php" },
{ "name": "📺大众", "api": "https://cdn.dzzyapi.com/api.php/provide/vod/" },
{ "name": "📺荐片", "api": "http://zhangqun1818.serv00.net/jianpian1.php" },
{ "name": "📺奇艺", "api": "https://iqiyizyapi.com/api.php/provide/vod/" },
{ "name": "📺魔都", "api": "https://www.mdzyapi.com/api.php/provide/vod/" },
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
+917
View File
@@ -0,0 +1,917 @@
const API_BASE = 'https://x.xvideos4.tk/api.php/v1/records';
const API_KEY = 'text';
const NAV = [
{ sort: 'daily', label: '今日', href: '/zh-CN/', icon: '🔥', range: 'today' },
{ sort: 'weekly', label: '本周', href: '/zh-CN/weekly/', icon: '📅', range: 'week' },
{ sort: 'monthly', label: '本月', href: '/zh-CN/monthly/',icon: '🗓', range: 'month' },
{ sort: 'favorite', label: '收藏', href: '/zh-CN/all/', icon: '⭐', range: 'all' },
];
const CATEGORIES = [
{ id: 'gay', name: '男娘' },
{ id: 'anime', name: '动漫' },
{ id: 'lolita', name: '少女' },
{ id: 'shaved', name: '白虎' },
{ id: 'kyonyu', name: '巨乳' },
{ id: 'jk', name: '高中生' },
{ id: 'beautiful-girl', name: '美少女' },
{ id: 'small-breasts', name: '贫乳' },
{ id: 'sm', name: 'SM' },
{ id: 'masturbation', name: '自慰' },
{ id: 'hamedori', name: '自拍' },
{ id: 'female-pervert', name: '痴女' },
{ id: 'personal-filming',name: '私拍' },
{ id: 'outdoor', name: '户外' },
{ id: 'big-sister', name: '姐姐' },
{ id: 'incest', name: '乱伦' },
{ id: 'married-woman', name: '人妻' },
{ id: 'orgy', name: '群交' },
{ id: 'fellatio', name: '口交' },
{ id: 'bukkake', name: '颜射' },
];
async function apiList(options) {
const sort = options.sort;
const category = options.category;
const page = options.page;
const q = options.q;
const navItem = NAV.find(function(n) { return n.sort === sort; }) || NAV[0];
const qp = new URLSearchParams({
api_key: API_KEY,
range: navItem.range,
type: 'video',
page: String(page || 1),
limit: '20'
});
let keyword = '';
if (q) {
keyword = q;
qp.set('q', q);
} else if (category) {
const cat = CATEGORIES.find(function(c) { return c.id === category; });
keyword = cat ? cat.name : category;
qp.set('q', keyword);
}
const res = await fetch(API_BASE + '?' + qp.toString(), {
headers: { 'Accept': 'application/json' }
});
if (!res.ok) return null;
const json = await res.json().catch(function() { return null; });
if (json && json.data && keyword) {
json.data = json.data.filter(function(item) {
return (item.tweet_text && item.tweet_text.indexOf(keyword) !== -1) ||
(item.author_name && item.author_name.indexOf(keyword) !== -1);
});
}
return json;
}
async function apiDetail(id) {
const qpQ = new URLSearchParams({
api_key: API_KEY,
range: 'all',
type: 'video',
page: '1',
limit: '1',
q: id
});
const res = await fetch(API_BASE + '?' + qpQ.toString(), {
headers: { 'Accept': 'application/json' }
});
if (!res.ok) return null;
const data = await res.json().catch(function() { return null; });
const list = (data && data.data) ? data.data : [];
if (!list.length) return null;
return list.find(function(i) { return String(i.id) === String(id); }) || list[0] || null;
}
function normalizeItem(item) {
const video = (item.media && item.media.videos && item.media.videos[0]) ? item.media.videos[0] : {};
return {
id: item.id || '',
title: item.tweet_text || '未知',
thumb: video.thumbnail || '',
videoUrl: video.url || '',
time: item.duration_str || '00:00',
pv: '0',
favorite: '0',
};
}
function isHlsUrl(url) {
if (!url) return false;
return /\.m3u8|playlist|\/HLS\/|\/hls\//i.test(url);
}
function isMp4Url(url) {
if (!url) return false;
return /\.mp4(\?|$)|\.webm(\?|$)|\.ogg(\?|$)/i.test(url);
}
function jsonResponse(data, status) {
status = status || 200;
return new Response(JSON.stringify(data), {
status: status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'no-cache',
},
});
}
export default {
async fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
const params = url.searchParams;
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS, POST',
'Access-Control-Allow-Headers': 'Range, Content-Type',
}
});
}
try {
if (path === '/play') return handlePlay(params);
if (path === '/api/resolve') return handleResolve(params);
if (path === '/api/refresh') return handleRefresh(params.get('id') || '');
if (path === '/api/extract' && request.method === 'POST') {
const body = await request.json();
const res = await fetch('https://x.xvideos4.tk/extract', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await res.json();
return jsonResponse(data, res.status);
}
if (path.indexOf('/movie/') !== -1) {
const parts = path.split('/').filter(Boolean);
const id = parts.pop();
return handleDetail(id);
}
if (path === '/search') {
const q = params.get('tag') || params.get('q') || '';
const page = parseInt(params.get('page') || '1', 10);
return handleList({ sort: 'favorite', category: '', q: q, page: page, isSearch: true });
}
const catMatch = path.match(/\/category\/([^\/\s?]+)/);
if (catMatch) {
const page = parseInt(params.get('page') || '1', 10);
return handleList({ sort: 'favorite', category: catMatch[1], page: page });
}
const sortMap = {
'/zh-cn/weekly': 'weekly', '/zh-CN/weekly': 'weekly',
'/zh-cn/weekly/': 'weekly', '/zh-CN/weekly/': 'weekly',
'/zh-cn/monthly': 'monthly', '/zh-CN/monthly': 'monthly',
'/zh-cn/monthly/':'monthly', '/zh-CN/monthly/':'monthly',
'/zh-cn/all': 'favorite','/zh-CN/all': 'favorite',
'/zh-cn/all/': 'favorite','/zh-CN/all/': 'favorite',
};
const sort = sortMap[path] || 'daily';
const page = parseInt(params.get('page') || '1', 10);
return handleList({ sort: sort, category: '', page: page });
} catch (e) {
return new Response('Error: ' + e.message + '\n' + e.stack, { status: 500 });
}
}
};
async function handleResolve(params) {
const rawUrl = params.get('url') || '';
if (!rawUrl || rawUrl.indexOf('http') !== 0) {
return jsonResponse({ error: 'invalid_url' }, 400);
}
let origin = '';
try { origin = new URL(rawUrl).origin; } catch (_) {}
const commonHeaders = {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) '
+ 'AppleWebKit/605.1.15 (KHTML, like Gecko) '
+ 'Version/17.0 Mobile/15E148 Safari/604.1',
'Referer': origin ? origin + '/' : 'https://x.xvideos4.tk/',
'Origin': origin || 'https://x.xvideos4.tk',
'Accept': 'video/mp4,video/webm,application/x-mpegurl,*/*;q=0.9',
};
try {
const ctrl = new AbortController();
const timer = setTimeout(function() { ctrl.abort(); }, 8000);
const res = await fetch(rawUrl, {
method: 'HEAD', redirect: 'follow', signal: ctrl.signal, headers: commonHeaders,
});
clearTimeout(timer);
const ct = res.headers.get('content-type') || '';
const finalUrl = res.url || rawUrl;
const isHls = ct.indexOf('mpegurl') !== -1 || ct.indexOf('x-mpegurl') !== -1 || isHlsUrl(finalUrl);
const isMp4 = ct.indexOf('video/') === 0 || isMp4Url(finalUrl);
return jsonResponse({ url: finalUrl, isHls: isHls, isMp4: isMp4, contentType: ct, status: res.status });
} catch (headErr) {
try {
const ctrl2 = new AbortController();
const timer2 = setTimeout(function() { ctrl2.abort(); }, 8000);
const res2 = await fetch(rawUrl, {
method: 'GET', redirect: 'follow', signal: ctrl2.signal, headers: commonHeaders,
});
clearTimeout(timer2);
const ct2 = res2.headers.get('content-type') || '';
const finalUrl2 = res2.url || rawUrl;
const isHls2 = ct2.indexOf('mpegurl') !== -1 || ct2.indexOf('x-mpegurl') !== -1 || isHlsUrl(finalUrl2);
const isMp42 = ct2.indexOf('video/') === 0 || isMp4Url(finalUrl2);
return jsonResponse({ url: finalUrl2, isHls: isHls2, isMp4: isMp42, contentType: ct2, status: res2.status });
} catch (getErr) {
return jsonResponse({
url: rawUrl, isHls: isHlsUrl(rawUrl), isMp4: isMp4Url(rawUrl), error: getErr.message,
});
}
}
}
async function handleRefresh(id) {
if (!id) return jsonResponse({ error: 'no id' }, 400);
try {
const raw = await apiDetail(id);
if (!raw) return jsonResponse({ error: 'not_found' }, 404);
const item = normalizeItem(raw);
const proxySrc = item.videoUrl;
if (!proxySrc) return jsonResponse({ error: 'no_url' }, 404);
return jsonResponse({ proxySrc: proxySrc, isHls: isHlsUrl(proxySrc) });
} catch (e) {
return jsonResponse({ error: e.message }, 500);
}
}
async function handleList(options) {
if (options.sort === 'favorite' && !options.category && !options.q && !options.isSearch) {
return handleFavorites();
}
const data = await apiList(options);
const list = (data && data.data) ? data.data : [];
const movies = list.map(normalizeItem);
const page = options.page;
const lastPage = page + 1;
const category = options.category;
const catLabel = CATEGORIES.find(function(c) { return c.id === category; })?.name || category;
const title = category ? '# ' + catLabel : (options.isSearch ? '搜索:' + options.q : (NAV.find(function(n) { return n.sort === options.sort; })?.label || '今日'));
return new Response(
renderLayout(renderList(movies, options), title, options.sort),
{ headers: { 'Content-Type': 'text/html;charset=UTF-8' } }
);
}
function handleFavorites() {
return new Response(
renderLayout(renderFavoritesPage(), '我的收藏', 'favorite'),
{ headers: { 'Content-Type': 'text/html;charset=UTF-8' } }
);
}
function renderFavoritesPage() {
return '<div class="page-wrap">' +
'<div class="fav-page-header">' +
'<h2 class="fav-page-title">⭐ 我的收藏</h2>' +
'<button class="fav-clear-btn" onclick="clearAllFavs()">清空收藏</button>' +
'</div>' +
'<div id="fav-grid" class="grid"></div>' +
'<div id="fav-empty" class="empty-state" style="display:none">' +
'<div class="empty-icon">⭐</div>' +
'<div class="empty-text">暂无收藏,去浏览视频并点击 ⭐ 收藏吧</div>' +
'</div>' +
'</div>' +
`<script>
document.addEventListener('DOMContentLoaded', function() {
renderFavPage();
});
function renderFavPage() {
var favs = {};
try { favs = JSON.parse(localStorage.getItem('otc_favs') || '{}'); } catch(e) {}
var list = Object.values(favs).reverse();
var grid = document.getElementById('fav-grid');
var empty = document.getElementById('fav-empty');
if (!list.length) {
grid.style.display = 'none';
empty.style.display = 'block';
return;
}
grid.style.display = '';
empty.style.display = 'none';
grid.innerHTML = list.map(function(m) {
var thumb = m.thumb || 'https://placehold.co/300x533/111/333?text=No+Image';
var id = String(m.id || '');
var title = String(m.title || id).replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
return '<div class="card" id="fav-card-'+id+'">' +
'<a href="/zh-CN/movie/' + encodeURIComponent(id) + '" class="card-thumb">' +
'<img loading="lazy" src="' + thumb + '" alt="' + title + '"' +
' onerror="this.src=\'https://placehold.co/300x533/111/333?text=No+Image\'">' +
'<span class="badge-time">' + (m.time || '') + '</span>' +
'</a>' +
'<div class="card-body">' +
'<a href="/zh-CN/movie/' + encodeURIComponent(id) + '" class="card-title-link">' +
'<h3 class="card-title">' + title + '</h3>' +
'</a>' +
'<div class="card-meta">' +
'<span></span>' +
'<button class="card-fav-btn faved"' +
' data-id="' + id + '"' +
' data-title="' + title + '"' +
' data-thumb="' + (m.thumb||'').replace(/"/g,'&quot;') + '"' +
' data-video="' + (m.video||'').replace(/"/g,'&quot;') + '"' +
' data-time="' + (m.time ||'') + '"' +
' onclick="event.preventDefault();event.stopPropagation();toggleFav(this)">⭐ 已收藏' +
'</button>' +
'</div>' +
'</div>' +
'</div>';
}).join('');
}
function clearAllFavs() {
if (!confirm('确认清空所有收藏?')) return;
localStorage.removeItem('otc_favs');
renderFavPage();
_toast('已清空收藏');
}
</script>`;
}
async function handleDetail(id) {
const raw = await apiDetail(id);
const movie = raw ? normalizeItem(raw) : {
id: id, title: id, thumb: '', videoUrl: '', time: '00:00', pv: '0', favorite: '0'
};
return new Response(
renderLayout(renderDetail(movie), movie.title, ''),
{ headers: { 'Content-Type': 'text/html;charset=UTF-8' } }
);
}
async function handlePlay(params) {
const videoUrl = params.get('v') || '';
if (!videoUrl || videoUrl.indexOf('http') !== 0) {
return new Response('无效的播放链接', { status: 400 });
}
return new Response(null, { status: 302, headers: { Location: videoUrl } });
}
function renderList(movies, options) {
const sort = options.sort;
const category = options.category;
const q = options.q;
const page = options.page;
const isSearch = options.isSearch;
const grid = movies.map(function(m) {
const thumb = m.thumb || 'https://placehold.co/300x533/111/333?text=No+Image';
const playHref = m.videoUrl ? m.videoUrl : '/zh-CN/movie/' + encodeURIComponent(m.id);
const playTarget = m.videoUrl ? ' target="_blank" rel="noopener noreferrer"' : '';
return '<div class="card">' +
'<a href="' + playHref + '"' + playTarget + ' class="card-thumb">' +
'<img loading="lazy" src="' + thumb + '" alt="' + esc(m.title) + '"' +
' onerror="this.src=\'https://placehold.co/300x533/111/333?text=No+Image\'"> ' +
'<span class="badge-time">' + esc(m.time) + '</span>' +
'<button class="card-fav-btn" data-id="' + esc(m.id) + '" data-title="' + esc(m.title) + '" data-thumb="' + esc(m.thumb) + '" data-video="' + esc(m.videoUrl) + '" data-time="' + esc(m.time) + '" onclick="event.preventDefault();event.stopPropagation();toggleFav(this)" aria-label="收藏">⭐</button>' +
'</a>' +
'<div class="card-body">' +
'<a href="/zh-CN/movie/' + encodeURIComponent(m.id) + '" class="card-title-link">' +
'<h3 class="card-title">' + esc(m.title) + '</h3>' +
'</a>' +
'<div class="card-meta">' +
'<span>⏱ ' + esc(m.time) + '</span>' +
'</div>' +
'</div>' +
'</div>';
}).join('');
function pageUrl(p) {
if (isSearch) return '/search?tag=' + encodeURIComponent(q || category || '') + '&page=' + p;
if (category) return '/zh-CN/category/' + encodeURIComponent(category) + '?page=' + p;
return (NAV.find(function(n) { return n.sort === sort; })?.href || '/zh-CN/') + '?page=' + p;
}
const pagination = (page > 1 || movies.length > 0) ?
'<div class="pagination">' +
(page > 1
? '<a href="' + pageUrl(page-1) + '" class="page-btn">← 上一页</a>'
: '<span class="page-btn disabled">← 上一页</span>') +
'<span class="page-info">第 ' + page + ' 页</span>' +
(movies.length >= 20
? '<a href="' + pageUrl(page+1) + '" class="page-btn">下一页 →</a>'
: '<span class="page-btn disabled">下一页 →</span>') +
'</div>' : '';
const catChips = CATEGORIES.map(function(c) {
return '<a href="/zh-CN/category/' + c.id + '" class="chip' + (category === c.id ? ' chip-active' : '') + '">' + esc(c.name) + '</a>';
}).join('');
const empty = movies.length === 0 ?
'<div class="empty-state">' +
'<div class="empty-icon">📭</div>' +
'<div class="empty-text">暂无内容</div>' +
'</div>' : '';
return '<div class="page-wrap">' +
'<div class="extract-section">' +
'<div class="extract-label">帖子链接</div>' +
'<div class="extract-box">' +
'<input type="text" id="extractUrl" placeholder="https://x.com/user/status/1234567890…" autocomplete="off">' +
'<button id="extractBtn" onclick="doExtract()">' +
'<span id="extractBtnIcon">⬇</span>' +
'<span id="extractBtnText">提取</span>' +
'</button>' +
'</div>' +
'<div id="extractStatus" class="extract-status"></div>' +
'<div id="extractResult" class="extract-result-area" style="display:none"></div>' +
'</div>' +
'<div class="search-bar-wrap">' +
'<form action="/search" method="GET" class="search-form">' +
'<input type="search" name="tag" placeholder="搜索标签 / 账号…"' +
' value="' + esc(isSearch ? (q || category) : '') + '"' +
' autocomplete="off" enterkeyhint="search">' +
'<button type="submit" aria-label="搜索">' +
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">' +
'<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>' +
'</svg>' +
'</button>' +
'</form>' +
'</div>' +
'<div class="chips-wrap">' +
'<div class="chips-scroll">' +
'<a href="/zh-CN/" class="chip' + (!category ? ' chip-active' : '') + '">全部分类</a>' +
catChips +
'</div>' +
'</div>' +
(movies.length > 0 ? '<div class="grid">' + grid + '</div>' + pagination : empty) +
'</div>' +
'<script>' +
'async function doExtract() {' +
' var url = document.getElementById("extractUrl").value.trim();' +
' var btn = document.getElementById("extractBtn");' +
' var status = document.getElementById("extractStatus");' +
' var resultArea = document.getElementById("extractResult");' +
' if (!url) {' +
' status.innerText = "请输入帖子链接";' +
' status.className = "extract-status error";' +
' return;' +
' }' +
' btn.disabled = true;' +
' document.getElementById("extractBtnText").innerText = "提取中...";' +
' status.innerText = "正在提取中...";' +
' status.className = "extract-status loading";' +
' resultArea.style.display = "none";' +
' try {' +
' var res = await fetch("/api/extract", {' +
' method: "POST",' +
' headers: { "Content-Type": "application/json" },' +
' body: JSON.stringify({ url: url })' +
' });' +
' var json = await res.json();' +
' if (res.ok) {' +
' status.innerText = "提取成功!";' +
' status.className = "extract-status success";' +
' renderExtractResult(json);' +
' } else {' +
' status.innerText = json.error || "提取失败,请检查链接";' +
' status.className = "extract-status error";' +
' }' +
' } catch (e) {' +
' status.innerText = "网络错误: " + e.message;' +
' status.className = "extract-status error";' +
' } finally {' +
' btn.disabled = false;' +
' document.getElementById("extractBtnText").innerText = "提取";' +
' }' +
'}' +
'function renderExtractResult(json) {' +
' var area = document.getElementById("extractResult");' +
' var videos = (json.media && json.media.videos) ? json.media.videos : [];' +
' var images = (json.media && json.media.images) ? json.media.images : [];' +
' var html = "<div class=\'res-grid\'>";' +
' videos.forEach(function(v) {' +
' html += "<div class=\'res-item\'>" +' +
' "<div class=\'res-thumb\'>" +' +
' "<img src=\'" + (v.thumbnail || "") + "\' border=\'0\'>" +' +
' "<span class=\'res-badge\'>视频</span>" +' +
' "</div>" +' +
' "<a href=\'" + v.url + "\' target=\'_blank\' class=\'res-dl-btn\'>下载视频</a>" +' +
' "</div>";' +
' });' +
' images.forEach(function(img) {' +
' html += "<div class=\'res-item\'>" +' +
' "<div class=\'res-thumb\'>" +' +
' "<img src=\'" + img.url + "\' border=\'0\'>" +' +
' "<span class=\'res-badge\'>图片</span>" +' +
' "</div>" +' +
' "<a href=\'" + img.url + "\' target=\'_blank\' class=\'res-dl-btn\'>下载图片</a>" +' +
' "</div>";' +
' });' +
' html += "</div>";' +
' area.innerHTML = html;' +
' area.style.display = "block";' +
'}' +
'</script>';
}
function renderDetail(movie) {
const thumbSrc = movie.thumb || '';
const proxySrc = movie.videoUrl || '';
const movieId = movie.id || '';
const guessHls = isHlsUrl(proxySrc);
const noVideoBlock = '<div class="no-video"><span>🎬</span><p>视频链接不可用</p></div>';
const playerBlock = proxySrc ?
'<div class="player-shell" id="playerShell">' +
'<div class="player-poster" id="playerPoster">' +
(thumbSrc
? '<img src="' + esc(thumbSrc) + '" alt="' + esc(movie.title) + '" class="poster-img">'
: '<div class="poster-img poster-blank"></div>') +
'<button class="big-play-btn" id="bigPlayBtn" aria-label="播放">' +
'<svg viewBox="0 0 24 24" fill="white" width="44" height="44"><polygon points="5,3 19,12 5,21"/></svg>' +
'</button>' +
'<div class="player-spinner hidden" id="playerSpinner">' +
'<div class="spinner-ring"></div>' +
'</div>' +
'<div class="player-error hidden" id="playerError">' +
'<span>⚠️</span>' +
'<p id="playerErrorMsg">加载失败</p>' +
'<button class="retry-btn" onclick="initPlayer()">重试</button>' +
'</div>' +
'</div>' +
'<video id="mainVideo" class="main-video" playsinline webkit-playsinline preload="none" ' +
(thumbSrc ? 'poster="' + esc(thumbSrc) + '"' : '') +
' crossorigin="anonymous"></video>' +
'</div>' : noVideoBlock;
const playerScript = proxySrc ?
'<script>' +
'(function () {' +
' var SRC = ' + JSON.stringify(proxySrc) + ';' +
' var THUMB = ' + JSON.stringify(thumbSrc) + ';' +
' var GUESS_HLS = ' + guessHls + ';' +
' var HLS_CDN = "https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js";' +
' var video = document.getElementById("mainVideo");' +
' var poster = document.getElementById("playerPoster");' +
' var bigPlayBtn = document.getElementById("bigPlayBtn");' +
' var spinner = document.getElementById("playerSpinner");' +
' var errorBox = document.getElementById("playerError");' +
' var errorMsg = document.getElementById("playerErrorMsg");' +
' var hlsInstance = null;' +
' var started = false;' +
' function show(el){ el && el.classList.remove("hidden"); }' +
' function hide(el){ el && el.classList.add("hidden"); }' +
' function initPlayer() {' +
' hide(errorBox); show(bigPlayBtn); show(poster);' +
' video.src = "";' +
' if(hlsInstance){ hlsInstance.destroy(); hlsInstance = null; }' +
' }' +
' function startLoad() {' +
' if(started) return; started = true;' +
' hide(bigPlayBtn); show(spinner);' +
' if(GUESS_HLS) {' +
' if(video.canPlayType("application/vnd.apple.mpegurl")) {' +
' video.src = SRC; video.play().catch(function(e){ console.error(e); });' +
' } else {' +
' var s = document.createElement("script");' +
' s.src = HLS_CDN;' +
' s.onload = function() {' +
' if(!Hls.isSupported()) {' +
' hide(spinner); show(errorBox); errorMsg.innerText = "浏览器不支持 HLS"; return;' +
' }' +
' hlsInstance = new Hls(); hlsInstance.loadSource(SRC); hlsInstance.attachMedia(video);' +
' hlsInstance.on(Hls.Events.MANIFEST_PARSED, function(){ video.play().catch(function(e){ console.error(e); }); });' +
' };' +
' document.head.appendChild(s);' +
' }' +
' } else {' +
' video.src = SRC; video.play().catch(function(e){ console.error(e); });' +
' }' +
' }' +
' video.addEventListener("playing", function(){ hide(poster); hide(spinner); });' +
' video.addEventListener("error", function(){ hide(spinner); show(errorBox); errorMsg.innerText = "视频加载出错"; });' +
' bigPlayBtn.onclick = startLoad;' +
'})();' +
'</script>' : '';
return '<div class="detail-wrap">' +
'<a href="javascript:history.back()" class="back-btn">' +
'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">' +
'<path d="M19 12H5M12 19l-7-7 7-7"/>' +
'</svg>' +
' 返回' +
'</a>' +
'<div class="detail-card">' +
'<div class="video-wrap">' + playerBlock + '</div>' +
'<div class="detail-body">' +
'<h1 class="detail-title">' + esc(movie.title) + '</h1>' +
'<div class="detail-meta">' +
'<span>⏱ ' + esc(movie.time) + '</span>' +
'</div>' +
'<div class="action-bar">' +
'<button class="action-btn like-btn" data-id="' + esc(movieId) + '" onclick="toggleLike(this)">' +
'<span class="action-icon">❤</span>' +
'<span class="btn-label">点赞</span>' +
'</button>' +
'<button class="action-btn fav-btn"' +
' data-id="' + esc(movieId) + '"' +
' data-title="' + esc(movie.title) + '"' +
' data-thumb="' + esc(thumbSrc) + '"' +
' data-video="' + esc(proxySrc) + '"' +
' data-time="' + esc(movie.time) + '"' +
' onclick="toggleFav(this)">' +
'<span class="action-icon">⭐</span>' +
'<span class="btn-label">收藏</span>' +
'</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
playerScript +
`<script>
(function(){
var id = ` + JSON.stringify(movieId) + `;
var likes = {}; var favs = {};
try { likes = JSON.parse(localStorage.getItem('otc_likes') || '{}'); } catch(e) {}
try { favs = JSON.parse(localStorage.getItem('otc_favs') || '{}'); } catch(e) {}
var lb = document.querySelector('.like-btn[data-id]');
var fb = document.querySelector('.fav-btn[data-id]');
if (lb && likes[id]) { lb.classList.add('liked'); lb.querySelector('.btn-label').textContent = '已点赞'; }
if (fb && favs[id]) { fb.classList.add('faved'); fb.querySelector('.btn-label').textContent = '已收藏'; }
})();
</script>`;
}
function esc(s) {
return String(s || '')
.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}
function renderLayout(content, title, activeSort) {
const navLinks = NAV.map(function(n) {
return '<a href="' + n.href + '" class="nav-link' + (activeSort === n.sort ? ' active' : '') + '">' + n.label + '</a>';
}).join('');
const bottomNav = NAV.map(function(n) {
return '<a href="' + n.href + '" class="bnav-item' + (activeSort === n.sort ? ' active' : '') + '">' +
'<span class="bnav-icon">' + n.icon + '</span>' +
'<span class="bnav-label">' + n.label + '</span>' +
'</a>';
}).join('');
return '<!DOCTYPE html>' +
'<html lang="zh-CN">' +
'<head>' +
'<meta charset="UTF-8">' +
'<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
'<meta name="theme-color" content="#000000">' +
'<title>' + esc(title) + ' - OTC VIDEO</title>' +
'<style>' +
'*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}' +
':root{' +
' --bg:#000;--surface:#0d0d0d;--surface2:#161616;--border:rgba(255,255,255,0.07);' +
' --text:#e8e8e8;--muted:#555;--accent:#e8195a;--accent2:#ff6b9d;' +
' --radius:14px;--nav-h:56px;--bnav-h:60px;--safe-b:env(safe-area-inset-bottom,0px);' +
'}' +
'html{scroll-behavior:smooth}' +
'body{background:var(--bg);color:var(--text);font-family:sans-serif;font-size:14px;' +
' line-height:1.5;-webkit-font-smoothing:antialiased;overflow-x:hidden;}' +
'a{color:inherit;text-decoration:none}' +
'img{display:block;max-width:100%}' +
'button{cursor:pointer;border:none;font-family:inherit}' +
'.topnav{' +
' position:sticky;top:0;z-index:100;height:var(--nav-h);' +
' background:rgba(0,0,0,0.85);' +
' backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);' +
' border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 20px;gap:16px;}' +
'.logo{font-size:20px;font-weight:800;letter-spacing:-0.5px;white-space:nowrap;flex-shrink:0;}' +
'.logo span{color:var(--accent)}' +
'.topnav-search{' +
' flex:1;max-width:340px;display:flex;align-items:center;' +
' background:var(--surface2);border:1px solid var(--border);' +
' border-radius:10px;overflow:hidden;transition:border-color .2s;}' +
'.topnav-search:focus-within{border-color:rgba(232,25,90,.4)}' +
'.topnav-search input{' +
' flex:1;background:transparent;border:none;outline:none;' +
' color:var(--text);font-size:13px;padding:8px 12px;}' +
'.topnav-search input::placeholder{color:var(--muted)}' +
'.topnav-search button{' +
' background:transparent;color:var(--muted);padding:8px 12px;' +
' display:flex;align-items:center;transition:color .2s;}' +
'.topnav-search button:hover{color:var(--accent2)}' +
'.topnav-links{display:flex;gap:4px;margin-left:auto}' +
'.nav-link{' +
' padding:6px 14px;border-radius:8px;font-size:13px;font-weight:700;' +
' color:var(--muted);transition:background .15s,color .15s;white-space:nowrap;}' +
'.nav-link:hover{color:var(--text);background:var(--surface2)}' +
'.nav-link.active{color:#fff;background:var(--accent)}' +
'.extract-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 24px; }' +
'.extract-label { font-size: 14px; color: var(--text); margin-bottom: 12px; font-weight: 500; }' +
'.extract-box { display: flex; gap: 10px; align-items: stretch; }' +
'.extract-box input { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); padding: 12px 16px; font-size: 14px; outline: none; }' +
'.extract-box input:focus { border-color: var(--accent); }' +
'.extract-box button { background: #1d9bf0; color: #fff; padding: 0 20px; border-radius: 8px; font-weight: 700; display: flex; align-items: center; gap: 6px; transition: opacity .2s; }' +
'.extract-box button:hover { opacity: 0.9; }' +
'.extract-box button:disabled { opacity: 0.5; cursor: not-allowed; }' +
'.extract-status { margin-top: 10px; font-size: 12px; }' +
'.extract-status.error { color: #ff4d4f; }' +
'.extract-status.success { color: #52c41a; }' +
'.extract-status.loading { color: #1d9bf0; }' +
'.extract-result-area { margin-top: 20px; padding-top: 20px; border-top: 1px dashed var(--border); }' +
'.res-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }' +
'.res-item { background: var(--surface2); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; }' +
'.res-thumb { position: relative; aspect-ratio: 16/9; }' +
'.res-thumb img { width: 100%; height: 100%; object-fit: cover; }' +
'.res-badge { position: absolute; top: 4px; right: 4px; background: rgba(0,0,0,0.6); color: #fff; font-size: 10px; padding: 2px 6px; border-radius: 4px; }' +
'.res-dl-btn { padding: 8px; text-align: center; font-size: 12px; font-weight: 700; background: var(--accent); color: #fff; }' +
'.bnav{' +
' position:fixed;bottom:0;left:0;right:0;z-index:100;' +
' height:calc(var(--bnav-h) + var(--safe-b));padding-bottom:var(--safe-b);' +
' background:rgba(0,0,0,0.92);' +
' backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);' +
' border-top:1px solid var(--border);flex-direction:row;align-items:stretch;display:none;}' +
'.bnav-item{' +
' flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;' +
' gap:3px;color:var(--muted);transition:color .15s;' +
' -webkit-tap-highlight-color:transparent;user-select:none;}' +
'.bnav-item.active{color:var(--accent)}' +
'.bnav-icon{font-size:18px;line-height:1}' +
'.bnav-label{font-size:10px;font-weight:700}' +
'.main-content{min-height:calc(100vh - var(--nav-h))}' +
'.page-wrap{max-width:1400px;margin:0 auto;padding:20px 16px 32px}' +
'.search-bar-wrap{display:none;margin-bottom:16px}' +
'.search-form{' +
' display:flex;align-items:center;background:var(--surface2);' +
' border:1px solid var(--border);border-radius:12px;overflow:hidden;transition:border-color .2s;}' +
'.search-form:focus-within{border-color:rgba(232,25,90,.4)}' +
'.search-form input{' +
' flex:1;background:transparent;border:none;outline:none;color:var(--text);' +
' font-size:15px;padding:13px 16px;min-width:0;}' +
'.search-form input::placeholder{color:var(--muted)}' +
'.search-form button{' +
' background:var(--accent);color:#fff;padding:0 18px;height:100%;min-height:50px;' +
' display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s;}' +
'.search-form button:hover{background:var(--accent2)}' +
'.chips-wrap{' +
' margin-bottom:20px;' +
' -webkit-mask:linear-gradient(to right,#000 88%,transparent 100%);' +
' mask:linear-gradient(to right,#000 88%,transparent 100%);}' +
'.chips-scroll{display:flex;gap:8px;overflow-x:auto;padding-bottom:4px;scrollbar-width:none}' +
'.chips-scroll::-webkit-scrollbar{display:none}' +
'.chip{' +
' flex-shrink:0;padding:7px 14px;border-radius:999px;font-size:12px;font-weight:700;' +
' background:var(--surface2);border:1px solid var(--border);color:var(--muted);' +
' white-space:nowrap;transition:background .15s,color .15s,border-color .15s;' +
' -webkit-tap-highlight-color:transparent;}' +
'.chip:hover{color:var(--accent2);border-color:rgba(232,25,90,.35)}' +
'.chip-active{background:var(--accent);border-color:var(--accent);color:#fff !important}' +
'.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}' +
'.card{' +
' background:var(--surface);border-radius:var(--radius);overflow:hidden;' +
' border:1px solid var(--border);transition:border-color .2s,transform .2s;will-change:transform;}' +
'.card:hover{border-color:rgba(232,25,90,.35);transform:translateY(-2px)}' +
'.card:active{transform:scale(.97)}' +
'.card-thumb{display:block;position:relative;aspect-ratio:9/16;overflow:hidden;background:var(--surface2)}' +
'.card-thumb img{width:100%;height:100%;object-fit:cover;transition:transform .4s}' +
'.card:hover .card-thumb img{transform:scale(1.05)}' +
'.badge-time{' +
' position:absolute;bottom:6px;right:6px;background:rgba(0,0,0,.75);color:#fff;' +
' font-size:10px;font-family:monospace;' +
' padding:2px 6px;border-radius:5px;backdrop-filter:blur(4px);}' +
'.card-body{padding:10px 10px 8px}' +
'.card-title{' +
' font-size:11px;font-weight:500;color:#ccc;' +
' display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;' +
' overflow:hidden;line-height:1.45;min-height:32px;margin-bottom:6px;}' +
'.card-title-link{display:block;color:inherit;text-decoration:none}' +
'.card-title-link:hover .card-title{color:var(--accent2)}' +
'.card-meta{display:flex;justify-content:space-between;font-size:10px;color:var(--muted)}' +
'.fav{color:var(--accent2)}' +
'.pagination{' +
' display:flex;align-items:center;justify-content:center;' +
' gap:12px;padding:32px 0 8px;flex-wrap:wrap;}' +
'.page-btn{' +
' display:inline-flex;align-items:center;padding:11px 24px;' +
' background:var(--surface2);border:1px solid var(--border);' +
' border-radius:10px;font-size:13px;font-weight:700;color:var(--text);' +
' transition:background .15s,border-color .15s;-webkit-tap-highlight-color:transparent;}' +
'.page-btn:not(.disabled):hover{background:var(--accent);border-color:var(--accent)}' +
'.page-btn.disabled{opacity:.3;pointer-events:none}' +
'.page-info{font-size:12px;color:var(--muted)}' +
'.empty-state{text-align:center;padding:80px 20px}' +
'.empty-icon{font-size:48px;opacity:.25;margin-bottom:12px}' +
'.empty-text{font-size:16px;font-weight:700;color:var(--muted)}' +
'.detail-wrap{max-width:960px;margin:0 auto;padding:20px 16px 40px}' +
'.back-btn{' +
' display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:700;' +
' color:var(--muted);padding:8px 0;margin-bottom:16px;transition:color .15s;' +
' -webkit-tap-highlight-color:transparent;min-height:44px;}' +
'.back-btn:hover{color:var(--accent2)}' +
'.detail-card{' +
' background:var(--surface);border-radius:20px;overflow:hidden;' +
' border:1px solid var(--border);}' +
'.video-wrap{' +
' position:relative; width:100%; aspect-ratio:16/9; background:#000; overflow:hidden;' +
'}' +
'.player-shell{' +
' position:absolute;inset:0; display:flex;align-items:center;justify-content:center; background:#000;' +
'}' +
'.main-video{' +
' position:absolute;inset:0; width:100%;height:100%; object-fit:contain; background:#000; z-index:1;' +
'}' +
'.player-poster{' +
' position:absolute;inset:0; display:flex;flex-direction:column; align-items:center;justify-content:center; z-index:10; background:#000; transition:opacity .4s ease;' +
'}' +
'.poster-img{' +
' position:absolute;inset:0; width:100%;height:100%; object-fit:cover; opacity:.55;' +
'}' +
'.poster-blank{background:var(--surface2)}' +
'.big-play-btn{' +
' position:relative;z-index:11; width:76px;height:76px;border-radius:50%; background:var(--accent);' +
' display:flex;align-items:center;justify-content:center; padding-left:5px; box-shadow:0 6px 32px rgba(232,25,90,.55);' +
' transition:transform .15s,background .15s; -webkit-tap-highlight-color:transparent;' +
'}' +
'.big-play-btn:hover{transform:scale(1.1);background:var(--accent2)}' +
'.big-play-btn:active{transform:scale(.95)}' +
'.player-spinner{' +
' position:absolute;inset:0;z-index:12; display:flex;align-items:center;justify-content:center; background:rgba(0,0,0,.45);' +
'}' +
'.spinner-ring{' +
' width:44px;height:44px;border-radius:50%; border:3px solid rgba(255,255,255,.15); border-top-color:var(--accent); animation:spin .8s linear infinite;' +
'}' +
'@keyframes spin{to{transform:rotate(360deg)}}' +
'.player-error{' +
' position:absolute;inset:0;z-index:13; display:flex;flex-direction:column; align-items:center;justify-content:center;gap:10px; background:rgba(0,0,0,.75);color:var(--text);text-align:center;padding:20px;' +
'}' +
'.player-error span{font-size:32px}' +
'.player-error p{font-size:13px;color:var(--muted);max-width:260px}' +
'.retry-btn{' +
' margin-top:4px;padding:9px 22px;border-radius:999px; background:var(--accent);color:#fff;font-size:13px;font-weight:700; transition:background .15s;' +
'}' +
'.retry-btn:hover{background:var(--accent2)}' +
'.no-video{' +
' position:absolute;inset:0; display:flex;flex-direction:column; align-items:center;justify-content:center; gap:10px;color:var(--muted);font-size:14px;' +
'}' +
'.no-video span{font-size:40px;opacity:.4}' +
'.hidden{display:none !important}' +
'.detail-body{padding:20px}' +
'.detail-title{' +
' font-size:18px; font-weight:800;line-height:1.3;margin-bottom:12px;}' +
'.detail-meta{display:flex;gap:18px;font-size:13px;color:var(--muted);margin-bottom:4px;flex-wrap:wrap}' +
'.action-bar{display:flex;gap:12px;margin-top:16px;flex-wrap:wrap}' +
'.action-btn{display:inline-flex;align-items:center;gap:8px;padding:11px 22px;border-radius:999px;font-size:14px;font-weight:700;border:1.5px solid var(--border);background:var(--surface2);color:var(--text);transition:all .2s;-webkit-tap-highlight-color:transparent;min-height:44px;}' +
'.action-btn:hover{border-color:rgba(232,25,90,.4);color:var(--accent2)}' +
'.action-btn.liked{background:rgba(232,25,90,.15);border-color:var(--accent);color:var(--accent)}' +
'.action-btn.faved{background:rgba(255,200,0,.12);border-color:#f5a623;color:#f5a623}' +
'.action-icon{font-size:18px;line-height:1}' +
'.card-fav-btn{position:absolute;top:6px;right:6px;z-index:5;background:rgba(0,0,0,.6);color:#aaa;border:none;border-radius:8px;font-size:14px;padding:4px 8px;line-height:1;backdrop-filter:blur(4px);transition:all .2s;-webkit-tap-highlight-color:transparent;}' +
'.card-fav-btn:hover,.card-fav-btn.faved{color:#f5a623}' +
'.card-fav-btn.faved{background:rgba(245,166,35,.2)}' +
'.fav-page-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px}' +
'.fav-page-title{font-size:20px;font-weight:800}' +
'.fav-clear-btn{padding:8px 18px;border-radius:999px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;transition:all .15s;}' +
'.fav-clear-btn:hover{border-color:var(--accent);color:var(--accent)}' +
'.otc-toast{position:fixed;bottom:calc(var(--bnav-h) + var(--safe-b) + 16px);left:50%;transform:translateX(-50%) translateY(20px);background:rgba(30,30,30,.95);color:#fff;padding:10px 22px;border-radius:999px;font-size:13px;font-weight:700;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .25s,transform .25s;z-index:9999;backdrop-filter:blur(10px);}' +
'.otc-toast.show{opacity:1;transform:translateX(-50%) translateY(0)}' +
'@media(min-width:1024px){.otc-toast{bottom:24px}}' +
'@media(min-width:540px){ .grid{grid-template-columns:repeat(3,1fr);gap:12px} .card-title{font-size:12px} }' +
'@media(min-width:768px){ .grid{grid-template-columns:repeat(4,1fr);gap:14px} .page-wrap{padding:24px 24px 40px} .detail-body{padding:24px 28px} .big-play-btn{width:88px;height:88px} }' +
'@media(min-width:1024px){ .grid{grid-template-columns:repeat(5,1fr);gap:16px} .search-bar-wrap{display:none !important} .topnav-search{display:flex} .topnav-links{display:flex} .bnav{display:none !important} .main-content{padding-bottom:0} .detail-wrap{padding:28px 24px 56px} .detail-body{padding:28px 36px} .big-play-btn{width:96px;height:96px} }' +
'@media(min-width:1280px){ .grid{grid-template-columns:repeat(6,1fr)} }' +
'@media(max-width:1023px){ .topnav-search{display:none} .topnav-links{display:none} .search-bar-wrap{display:block} .bnav{display:flex} .main-content{padding-bottom:calc(var(--bnav-h) + var(--safe-b) + 8px)} }' +
'@media(max-width:500px) and (orientation:portrait){ .video-wrap{aspect-ratio:9/16;max-height:72vw;aspect-ratio:unset;height:56vw} }' +
'</style>' +
'</head>' +
'<body>' +
'<nav class="topnav">' +
' <a href="/zh-CN/" class="logo">OTC<span>.</span>VIDEO</a>' +
' <div class="topnav-links">' +
navLinks +
' </div>' +
'</nav>' +
'<main class="main-content">' +
content +
'</main>' +
'<nav class="bnav">' +
bottomNav +
'</nav>' +
`<script>
var OTC_LIKES_KEY='otc_likes', OTC_FAVS_KEY='otc_favs';
function _getLikes(){try{return JSON.parse(localStorage.getItem(OTC_LIKES_KEY)||'{}')}catch(e){return{}}}
function _getFavs() {try{return JSON.parse(localStorage.getItem(OTC_FAVS_KEY) ||'{}')}catch(e){return{}}}
function toggleLike(btn){
var id=btn.dataset.id; var likes=_getLikes(); var on=!!likes[id];
if(on){delete likes[id];}else{likes[id]=Date.now();}
localStorage.setItem(OTC_LIKES_KEY,JSON.stringify(likes));
_applyLike(btn,!on); _toast(on?'已取消点赞':'点赞成功 ❤');
}
function toggleFav(btn){
var id=btn.dataset.id; var favs=_getFavs(); var on=!!favs[id];
if(on){
delete favs[id];
var card=document.getElementById('fav-card-'+id);
if(card){ card.style.transition='opacity .3s'; card.style.opacity='0'; setTimeout(function(){card.remove();_checkFavEmpty();},300); }
} else {
favs[id]={id:id,title:btn.dataset.title||id,thumb:btn.dataset.thumb||'',video:btn.dataset.video||'',time:btn.dataset.time||''};
}
localStorage.setItem(OTC_FAVS_KEY,JSON.stringify(favs));
document.querySelectorAll('[data-id="'+id+'"].card-fav-btn,[data-id="'+id+'"].fav-btn').forEach(function(b){_applyFav(b,!on);});
_toast(on?'已取消收藏':'收藏成功 ⭐');
}
function _applyLike(btn,on){
btn.classList.toggle('liked',on);
var lb=btn.querySelector('.btn-label'); if(lb) lb.textContent=on?'已点赞':'点赞';
}
function _applyFav(btn,on){
btn.classList.toggle('faved',on);
var lb=btn.querySelector('.btn-label'); if(lb) lb.textContent=on?'已收藏':'收藏';
}
function _checkFavEmpty(){
var grid=document.getElementById('fav-grid');
var empty=document.getElementById('fav-empty');
if(grid && empty && grid.children.length===0){grid.style.display='none';empty.style.display='block';}
}
function _toast(msg){
var t=document.createElement('div'); t.className='otc-toast'; t.textContent=msg;
document.body.appendChild(t);
requestAnimationFrame(function(){t.classList.add('show');});
setTimeout(function(){t.classList.remove('show');setTimeout(function(){t.remove();},300);},2000);
}
document.addEventListener('DOMContentLoaded',function(){
var likes=_getLikes(); var favs=_getFavs();
document.querySelectorAll('.like-btn[data-id]').forEach(function(btn){_applyLike(btn,!!likes[btn.dataset.id]);});
document.querySelectorAll('.fav-btn[data-id],.card-fav-btn[data-id]').forEach(function(btn){_applyFav(btn,!!favs[btn.dataset.id]);});
});
</script>` +
'</body>' +
'</html>';
}
File diff suppressed because one or more lines are too long
+3201
View File
@@ -0,0 +1,3201 @@
import {
cheerio,
模板
} from "./drpy-core-lite.min.js";
let vercode = typeof pdfl === "function" ? "drpy2.1" : "drpy2";
const VERSION = vercode + " 3.9.52beta3 20250801";
const UpdateInfo = [{
date: "20250801",
title: "drpy依赖更新,使用drpy-core-lite.min.js",
version: "3.9.52beta3 20250801",
msg: `
drpy-core.min.js 更换为更小的drpy-core-lite.min.js
`
}, {
date: "20250729",
title: "drpy更新,所有依赖打包成一个js文件",
version: "3.9.52beta2 20250729",
msg: `
1. wasm支持
2. 引入 TextEncoder、TextDecoder对象
3. 引入 WXXH 加解密库
4. 所有依赖打包成一个js
5. 增加 buildQueryString
`
}, {
date: "20250728",
title: "drpy更新,增加tab_order线路模糊排序,优化解密算法支持文件头",
version: "3.9.52beta1 20250728",
msg: `
1. 增加tab_order线路模糊排序
2. 优化解密算法支持文件头
3. wasm支持
4. 增加 removeHeader 函数可用于清除js/py文件的头信息及所有头注释
5. 引入 TextEncoder、TextDecoder对象
6. 引入 WXXH 加解密库
`
}, {
date: "20241126",
title: "drpy更新,优化去广告算法",
version: "3.9.51beta6 20241126",
msg: `
1. 更新龙头大佬提供的去广告算法
`
}, {
date: "20241104",
title: "drpy更新,增加新特性",
version: "3.9.51beta5 20241104",
msg: `
1. rule增加 搜索验证标识 属性,可以不定义,默认为 '系统安全验证|请输入验证码'
2. rule增加 searchNoPage 属性,可以不定义,如果定义 1 将关闭该源的搜索翻页功能,超过1页直接返回空
`
}];
function getUpdateInfo() {
return UpdateInfo.map(_o => {
_o.msg = _o.msg.trim().split("\n").map(_it => _it.trim()).join("\n");
return _o
})
}
function init_test() {
console.log("init_test_start");
console.log("当前版本号:" + VERSION);
console.log("本地代理地址:" + getProxyUrl());
console.log(RKEY);
console.log(JSON.stringify(rule));
console.log("init_test_end")
}
function ocr_demo_test() {
let img_base64 = `iVBORw0KGgoAAAANSUhEUgAAAIAAAAAoBAMAAADEX+97AAAAG1BMVEXz+/4thQTa7N6QwIFFkyNeokKozqDB3b93sWHFR+MEAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABN0lEQVRIie2TQU+DQBCFt9vScvQpxR4xrcSjJCZ67JDGXsX+AdR4B3vpsSYm/m2HXaRLmuySepR3Gdidb/btDAjRq5dT96eCMlfBuzi1QLZUoZy2yz5sOvI+9iomaPEZ6nWnEtxqIyiM1RcAy44GNDhBXUjot/VVNweV1ah68FqWRyjKIOqAcyYF6rGcmpYnHzGt3fycNoMw0d3/THFu7hFSJ/8OXO6iTM8/KSg09obAzIHLO250LgQ0txOZSfgrV4Exdw98uGycJ0ErAeExZGhOmFHV9zHO6qVSj0MpLq7xZON56o++MjlsEgfVhbQWWME+xQX7J4V6zfi9A1Ly9rP1BvEXp+BbVJ/M77n+wfOIDVp51pZ4iBxvmj9AGrtvry6emwfKnVkW+ZRKd5ZNMvob36vXP9YPDmQki8QiCFAAAAAASUVORK5CYII=`;
OcrApi.api = OCR_API;
let code = OcrApi.classification(img_base64);
log("测试验证码图片的ocr识别结果为:" + code)
}
function rsa_demo_test() {
let t1 = (new Date).getTime();
let pkcs1_public = `
-----BEGIN RSA PUBLIC KEY-----
MEgCQQCrI0pQ/ERRpJ3Ou190XJedFq846nDYP52rOtXyDxlFK5D3p6JJu2RwsKwy
lsQ9xY0xYPpRZUZKMEeR7e9gmRNLAgMBAAE=
-----END RSA PUBLIC KEY-----
`.trim();
let pkcs1_public_pem = `
MEgCQQCrI0pQ/ERRpJ3Ou190XJedFq846nDYP52rOtXyDxlFK5D3p6JJu2RwsKwy
lsQ9xY0xYPpRZUZKMEeR7e9gmRNLAgMBAAE=
`.trim();
let pkcs8_public = `
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKsjSlD8RFGknc67X3Rcl50WrzjqcNg/
nas61fIPGUUrkPenokm7ZHCwrDKWxD3FjTFg+lFlRkowR5Ht72CZE0sCAwEAAQ==
-----END PUBLIC KEY-----`.trim();
let pkcs8_public_pem = `
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKsjSlD8RFGknc67X3Rcl50WrzjqcNg/
nas61fIPGUUrkPenokm7ZHCwrDKWxD3FjTFg+lFlRkowR5Ht72CZE0sCAwEAAQ==
`.trim();
let pkcs1_private = `
-----BEGIN RSA PRIVATE KEY-----
MIIBOAIBAAJBAKsjSlD8RFGknc67X3Rcl50WrzjqcNg/nas61fIPGUUrkPenokm7
ZHCwrDKWxD3FjTFg+lFlRkowR5Ht72CZE0sCAwEAAQI/b6OV1z65UokQaMvSeRXt
0Yv6wiYtduQI9qpq5nzy/ytaqsbBfClNTi/HifKPKxlRouWFkc518EQI8LBxoarJ
AiEA4DaONMplV8PQNa3TKn2F+SDEvLOCjdL0kHKdN90Ti28CIQDDZnTBaHgZwZbA
hS7Bbf5yvwjWMhO6Y7l04/Qm7R+35QIgPuQuqXIoUSD080mp1N5WyRW++atksIF+
5lGv9e6GP/MCICnj8y/rl6Pd7tXDN6zcSeqLrfdNsREKhB3dKOCXgW9JAiAFYtFS
EJNBXVRTK42SNsZ2hJ/9xLwOwnH2epT8Q43s3Q==
-----END RSA PRIVATE KEY-----
`.trim();
let pkcs8_private = `
-----BEGIN PRIVATE KEY-----
MIIBUgIBADANBgkqhkiG9w0BAQEFAASCATwwggE4AgEAAkEAqyNKUPxEUaSdzrtf
dFyXnRavOOpw2D+dqzrV8g8ZRSuQ96eiSbtkcLCsMpbEPcWNMWD6UWVGSjBHke3v
YJkTSwIDAQABAj9vo5XXPrlSiRBoy9J5Fe3Ri/rCJi125Aj2qmrmfPL/K1qqxsF8
KU1OL8eJ8o8rGVGi5YWRznXwRAjwsHGhqskCIQDgNo40ymVXw9A1rdMqfYX5IMS8
s4KN0vSQcp033ROLbwIhAMNmdMFoeBnBlsCFLsFt/nK/CNYyE7pjuXTj9CbtH7fl
AiA+5C6pcihRIPTzSanU3lbJFb75q2SwgX7mUa/17oY/8wIgKePzL+uXo93u1cM3
rNxJ6out902xEQqEHd0o4JeBb0kCIAVi0VIQk0FdVFMrjZI2xnaEn/3EvA7CcfZ6
lPxDjezd
-----END PRIVATE KEY-----
`.trim();
let data = `
NodeRsa
这是node-rsa 现在修改集成在drpy里使用`.trim();
let encryptedWithPublic = NODERSA.encryptRSAWithPublicKey(data, pkcs1_public, {
outputEncoding: "base64",
options: {
environment: "browser",
encryptionScheme: "pkcs1_oaep"
}
});
console.log("公钥加密");
console.log(encryptedWithPublic);
let decryptedWithPrivate = NODERSA.decryptRSAWithPrivateKey(encryptedWithPublic, pkcs1_private, {
options: {
environment: "browser",
encryptionScheme: "pkcs1_oaep"
}
});
console.log("私钥解密");
console.log(decryptedWithPrivate);
let pkcs1_sha256_sign = NODERSA.sign("1", pkcs1_private, {
outputEncoding: "base64",
options: {
environment: "browser",
encryptionScheme: "pkcs1",
signingScheme: "pkcs1-sha256"
}
});
console.log("pkcs1_sha256_sign");
console.log(pkcs1_sha256_sign);
let pkcs1_sha256_sign_verify = NODERSA.verify("1", "Oulx2QrgeipKYBtqEDqFb2s/+ndk2cGQxO4CkhU7iBM1vyNmmvqubpsmeoUuN3waGrYZLknSEdwBkfv0tUMpFQ==", pkcs1_private, {
options: {
environment: "browser",
encryptionScheme: "pkcs1",
signingScheme: "pkcs1-sha256"
}
});
console.log("pkcs1_sha256_sign_verify");
console.log(pkcs1_sha256_sign_verify);
let pkcs1_oaep_sha256 = NODERSA.encryptRSAWithPublicKey(data, `-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA5KOq1gRNyllLNWKQy8sGpZE3Q1ULLSmzZw+eaAhj9lvqn7IsT1du
SYn08FfoOA2qMwtz+1O2l1mgzNoSVCyVpVabnTG+C9XKeZXAnJHd8aYA7l7Sxhdm
kte+iymYZ0ZBPzijo8938iugtVvqi9UgDmnY3u/NlQDqiL5BGqSxSTd/Sgmy3zD8
PYzEa3wD9vehQ5fZZ45vKIq8GNVh2Z8+IGO85FF1OsN7+b2yGJa/FmDDNn0+HP+m
PfI+kYBqEVpo0Ztbc3UdxgFwGC8O1n8AQyriwHnSOtIiuBH62J/7qyC/3LEAApRb
Dd9YszqzmODjQUddZKHmvc638VW+azc0EwIDAQAB
-----END RSA PUBLIC KEY-----
`, {
outputEncoding: "base64",
options: {
environment: "browser",
encryptionScheme: {
scheme: "pkcs1_oaep",
hash: "sha256"
}
}
});
console.log("pkcs1_oaep_sha256");
console.log(pkcs1_oaep_sha256);
decryptedWithPrivate = NODERSA.decryptRSAWithPrivateKey("kSZesAAyYh2hdsQnYMdGqb6gKAzTauBKouvBzWcc4+F8RvGd0nwO6mVkUMVilPgUuNxjEauHayHiY8gI3Py45UI3+km0rSGyHrS6dHiHgCkMejXHieglYzAB0IxX3Jkm4z/66bdB/D+GFy0oct5fGCMI1UHPjEAYOsazJDa8lBFNbjiWFeb/qiZtIx3vGM7KYPAZzyRf/zPbbQ8zy9xOmRuOl5nnIxgo0Okp3KO/RIPO4GZOSBA8f2lx1UtNwwrXAMpcNavtoqHVcjJ/9lcotXYQFrn5b299pSIRf2gVm8ZJ31SK6Z8cc14nKtvgnmsgClDzIXJ1o1RcDK+knVAySg==", `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA5KOq1gRNyllLNWKQy8sGpZE3Q1ULLSmzZw+eaAhj9lvqn7Is
T1duSYn08FfoOA2qMwtz+1O2l1mgzNoSVCyVpVabnTG+C9XKeZXAnJHd8aYA7l7S
xhdmkte+iymYZ0ZBPzijo8938iugtVvqi9UgDmnY3u/NlQDqiL5BGqSxSTd/Sgmy
3zD8PYzEa3wD9vehQ5fZZ45vKIq8GNVh2Z8+IGO85FF1OsN7+b2yGJa/FmDDNn0+
HP+mPfI+kYBqEVpo0Ztbc3UdxgFwGC8O1n8AQyriwHnSOtIiuBH62J/7qyC/3LEA
ApRbDd9YszqzmODjQUddZKHmvc638VW+azc0EwIDAQABAoIBADZ/QGgUzInvsLp/
zO2WbfYm39o/uhNAvk9RbLt1TIZbMFhyOpeKynHi3Swwd9xsfWX/U9zS/lGi/m31
iKrhmaW4OA1G3vqpMcK7TBbFufYwUEaA+ZJX344euH8pIfdzyneMQ4z3Far2dS7l
QsmjuilVV2kEFadveXewiYoVOWCu00w6bN8wy2SIHlQn+kIL6HQhWz12iKKflIKu
eGRdzLHsKmBt6WbY1Wuhx7HU0fAKdlBDPxCHNlI+kybUYE9o5C2vJiaVM5wqJBgZ
8Dz8kt1QbLJ910JoLXkLVQ8uC8NJKQwFtqQjTGPnEq0+wbgz6Ij599rKZkwW/xq9
l6KoUiECgYEA6Ah42tVdkNW047f03xVYXFH96RgorHRS36mR8Y+ONUq1fwKidovC
WjwVujt4OPf3l1W6iyn/F6cu/bsmvPrSc3HTN0B1V31QK4OjgetxQ2PSbTldH02J
NPzkt+v+cPxXpx/P5mgt7Weefw5txU547KubGrHUV5rBKFtIx9pj16MCgYEA/EF0
o19+D24DZAPwlDS5VbEd7FStnwY4oQ5PqbuNOSbSJLMWU0AqzXcRokp8UTyCZ0X3
ATkS1REq97kShCuR+npTR6a6DlY7sdpPI1SMLNajgB2tkx0EOzX+PfNIbHUd4jpJ
I0ZMAHv/OOtkzQHDaeTWBTrzsWm6/nTiykfduNECgYEA46AMD4HpPECqKAs66e5i
tI6q7JSKskObWVdcmQEfnSAhVOwcvPb2Ptda6UuV8S0xcwDi88rLOUUFUFzc79+P
vTkY38cYVi/VChsluDpk7ptqv0PbGu5Rf+3n4pZdEjI7OvR2W64wAAn67uIUxc7p
yiO/ET0K9rYWb6S9jXGtKMkCgYEA2kPAqoO7zZoBMQ7/oR0lp/HC1HRIbiqx4RlC
8Lgpb+QZPEwA6zPAVVvLVENi4d+bbcRp/xLlKpraNNJcJSSWAMbLPFoU7sbKjA87
HnTPfRSTEA2d3Ibk3F7Rh8TzS3Ti0JZiJjVzGZAwu41iAMifzwaD8K6boUy80eNN
QH2CaaECgYBUsLYvC/MiYg3w+LGOONuQongoVUXjGqnw2bjVa9RK7lwRdXPUqJ51
MpVO98IkoLvGSI/0sGNP3GKNhC+eMGjJAVwFyEuOn+JsmMv9Y9uStIVi5tIHIhKw
m7mp8il0kaftHdSxTbspG3tZ2fjIiFIZkLEOmRpd7ogWumgOajzUdA==
-----END RSA PRIVATE KEY-----`, {
options: {
environment: "browser",
encryptionScheme: "pkcs1_oaep"
}
});
console.log("decryptedWithPrivate");
console.log(decryptedWithPrivate);
(() => {
let key = new NODERSA.NodeRSA({
b: 1024
});
key.setOptions({
encryptionScheme: "pkcs1"
});
let text = `你好drpy node-ras`;
let encrypted = key.encrypt(text, "base64");
console.log("encrypted: ", encrypted);
const decrypted = key.decrypt(encrypted, "utf8");
console.log("decrypted: ", decrypted)
})();
let t2 = (new Date).getTime();
console.log("rsa_demo_test 测试耗时:" + (t2 - t1) + "毫秒")
}
function pre() {
if (typeof rule.预处理 === "string" && rule.预处理 && rule.预处理.trim()) {
let code = rule.预处理.trim();
console.log("执行预处理代码:" + code);
if (code.startsWith("js:")) {
code = code.replace("js:", "")
}
try {
eval(code)
} catch (e) {
console.log(`预处理执行失败:${e.message}`)
}
}
}
let rule = {};
const MOBILE_UA = "Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36";
const PC_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36";
const UA = "Mozilla/5.0";
const UC_UA = "Mozilla/5.0 (Linux; U; Android 9; zh-CN; MI 9 Build/PKQ1.181121.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.5.5.1035 Mobile Safari/537.36";
const IOS_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
const RULE_CK = "cookie";
const CATE_EXCLUDE = "首页|留言|APP|下载|资讯|新闻|动态";
const TAB_EXCLUDE = "猜你|喜欢|下载|剧情|榜|评论";
const OCR_RETRY = 3;
const OCR_API = "https://api.nn.ci/ocr/b64/text";
if (typeof MY_URL === "undefined") {
var MY_URL
}
var HOST;
var RKEY;
var fetch;
var print;
var log;
var rule_fetch_params;
var fetch_params;
var oheaders;
var _pdfh;
var _pdfa;
var _pd;
const DOM_CHECK_ATTR = /(url|src|href|-original|-src|-play|-url|style)$/;
const SPECIAL_URL = /^(ftp|magnet|thunder|ws):/;
const NOADD_INDEX = /:eq|:lt|:gt|:first|:last|^body$|^#/;
const URLJOIN_ATTR = /(url|src|href|-original|-src|-play|-url|style)$|^(data-|url-|src-)/;
const SELECT_REGEX = /:eq|:lt|:gt|#/g;
const SELECT_REGEX_A = /:eq|:lt|:gt/g;
const $js = {
toString(func) {
let strfun = func.toString();
return strfun.replace(/^\(\)(\s+)?=>(\s+)?\{/, "js:").replace(/\}$/, "")
}
};
function window_b64() {
let b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
let base64DecodeChars = new Array(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1);
function btoa(str) {
var out, i, len;
var c1, c2, c3;
len = str.length;
i = 0;
out = "";
while (i < len) {
c1 = str.charCodeAt(i++) & 255;
if (i == len) {
out += b64map.charAt(c1 >> 2);
out += b64map.charAt((c1 & 3) << 4);
out += "==";
break
}
c2 = str.charCodeAt(i++);
if (i == len) {
out += b64map.charAt(c1 >> 2);
out += b64map.charAt((c1 & 3) << 4 | (c2 & 240) >> 4);
out += b64map.charAt((c2 & 15) << 2);
out += "=";
break
}
c3 = str.charCodeAt(i++);
out += b64map.charAt(c1 >> 2);
out += b64map.charAt((c1 & 3) << 4 | (c2 & 240) >> 4);
out += b64map.charAt((c2 & 15) << 2 | (c3 & 192) >> 6);
out += b64map.charAt(c3 & 63)
}
return out
}
function atob(str) {
var c1, c2, c3, c4;
var i, len, out;
len = str.length;
i = 0;
out = "";
while (i < len) {
do {
c1 = base64DecodeChars[str.charCodeAt(i++) & 255]
} while (i < len && c1 == -1);
if (c1 == -1) break;
do {
c2 = base64DecodeChars[str.charCodeAt(i++) & 255]
} while (i < len && c2 == -1);
if (c2 == -1) break;
out += String.fromCharCode(c1 << 2 | (c2 & 48) >> 4);
do {
c3 = str.charCodeAt(i++) & 255;
if (c3 == 61) return out;
c3 = base64DecodeChars[c3]
} while (i < len && c3 == -1);
if (c3 == -1) break;
out += String.fromCharCode((c2 & 15) << 4 | (c3 & 60) >> 2);
do {
c4 = str.charCodeAt(i++) & 255;
if (c4 == 61) return out;
c4 = base64DecodeChars[c4]
} while (i < len && c4 == -1);
if (c4 == -1) break;
out += String.fromCharCode((c3 & 3) << 6 | c4)
}
return out
}
return {
atob: atob,
btoa: btoa
}
}
if (typeof atob !== "function" || typeof btoa !== "function") {
var {
atob,
btoa
} = window_b64()
}
if (typeof Object.assign !== "function") {
Object.assign = function() {
let target = arguments[0];
for (let i = 1; i < arguments.length; i++) {
let source = arguments[i];
for (let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key]
}
}
}
return target
}
}
if (!String.prototype.includes) {
String.prototype.includes = function(search, start) {
if (typeof start !== "number") {
start = 0
}
if (start + search.length > this.length) {
return false
} else {
return this.indexOf(search, start) !== -1
}
}
}
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, "includes", {
value: function(searchElement, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined')
}
var o = Object(this);
var len = o.length >>> 0;
if (len === 0) {
return false
}
var n = fromIndex | 0;
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
while (k < len) {
if (o[k] === searchElement) {
return true
}
k++
}
return false
},
enumerable: false
})
}
if (typeof String.prototype.startsWith !== "function") {
String.prototype.startsWith = function(prefix) {
return this.slice(0, prefix.length) === prefix
}
}
if (typeof String.prototype.endsWith !== "function") {
String.prototype.endsWith = function(suffix) {
return this.indexOf(suffix, this.length - suffix.length) !== -1
}
}
Object.defineProperty(Object.prototype, "myValues", {
value: function(obj) {
if (obj == null) {
throw new TypeError("Cannot convert undefined or null to object")
}
var res = [];
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
res.push(obj[k])
}
}
return res
},
enumerable: false
});
if (typeof Object.prototype.values !== "function") {
Object.defineProperty(Object.prototype, "values", {
value: function(obj) {
if (obj == null) {
throw new TypeError("Cannot convert undefined or null to object")
}
var res = [];
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
res.push(obj[k])
}
}
return res
},
enumerable: false
})
}
if (typeof Array.prototype.join !== "function") {
Object.defineProperty(Array.prototype, "join", {
value: function(emoji) {
emoji = emoji || "";
let self = this;
let str = "";
let i = 0;
if (!Array.isArray(self)) {
throw String(self) + "is not Array"
}
if (self.length === 0) {
return ""
}
if (self.length === 1) {
return String(self[0])
}
i = 1;
str = this[0];
for (; i < self.length; i++) {
str += String(emoji) + String(self[i])
}
return str
},
enumerable: false
})
}
if (typeof Array.prototype.toReversed !== "function") {
Object.defineProperty(Array.prototype, "toReversed", {
value: function() {
const clonedList = this.slice();
const reversedList = clonedList.reverse();
return reversedList
},
enumerable: false
})
}
Object.defineProperty(Array.prototype, "append", {
value: Array.prototype.push,
enumerable: false
});
Object.defineProperty(String.prototype, "strip", {
value: String.prototype.trim,
enumerable: false
});
Object.defineProperty(String.prototype, "rstrip", {
value: function(chars) {
let regex = new RegExp(chars + "$");
return this.replace(regex, "")
},
enumerable: false
});
function 是否正版(vipUrl) {
let flag = new RegExp("qq.com|iqiyi.com|youku.com|mgtv.com|bilibili.com|sohu.com|ixigua.com|pptv.com|miguvideo.com|le.com|1905.com|fun.tv");
return flag.test(vipUrl)
}
function urlDeal(vipUrl) {
if (!vipUrl) {
return ""
}
if (!是否正版(vipUrl)) {
return vipUrl
}
if (!/miguvideo/.test(vipUrl)) {
vipUrl = vipUrl.split("#")[0].split("?")[0]
}
return vipUrl
}
function setResult(d) {
if (!Array.isArray(d)) {
return []
}
VODS = [];
d.forEach(function(it) {
let obj = {
vod_id: it.url || "",
vod_name: it.title || "",
vod_remarks: it.desc || "",
vod_content: it.content || "",
vod_pic: it.pic_url || it.img || ""
};
let keys = Object.keys(it);
if (keys.includes("tname")) {
obj.type_name = it.tname || ""
}
if (keys.includes("tid")) {
obj.type_id = it.tid || ""
}
if (keys.includes("year")) {
obj.vod_year = it.year || ""
}
if (keys.includes("actor")) {
obj.vod_actor = it.actor || ""
}
if (keys.includes("director")) {
obj.vod_director = it.director || ""
}
if (keys.includes("area")) {
obj.vod_area = it.area || ""
}
VODS.push(obj)
});
return VODS
}
function setResult2(res) {
VODS = res.list || [];
return VODS
}
function setHomeResult(res) {
if (!res || typeof res !== "object") {
return []
}
return setResult(res.list)
}
function rc(js) {
if (js === "maomi_aes.js") {
var a = CryptoJS.enc.Utf8.parse("625222f9149e961d");
var t = CryptoJS.enc.Utf8.parse("5efdtf6060e2o330");
return {
De: function(word) {
word = CryptoJS.enc.Hex.parse(word);
return CryptoJS.AES.decrypt(CryptoJS.enc.Base64.stringify(word), a, {
iv: t,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8)
},
En: function(word) {
var Encrypted = CryptoJS.AES.encrypt(word, a, {
iv: t,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return Encrypted.ciphertext.toString()
}
}
}
return {}
}
function maoss(jxurl, ref, key) {
fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
eval(getCryptoJS());
try {
var getVideoInfo = function(text) {
return CryptoJS.AES.decrypt(text, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8)
};
var token_key = key == undefined ? "dvyYRQlnPRCMdQSe" : key;
if (ref) {
var html = request(jxurl, {
headers: {
Referer: ref
}
})
} else {
var html = request(jxurl)
}
if (html.indexOf("&btwaf=") != -1) {
html = request(jxurl + "&btwaf" + html.match(/&btwaf(.*?)"/)[1], {
headers: {
Referer: ref
}
})
}
var token_iv = html.split('_token = "')[1].split('"')[0];
var key = CryptoJS.enc.Utf8.parse(token_key);
var iv = CryptoJS.enc.Utf8.parse(token_iv);
eval(html.match(/var config = {[\s\S]*?}/)[0] + "");
if (!config.url.startsWith("http")) {
config.url = CryptoJS.AES.decrypt(config.url, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8)
}
return config.url
} catch (e) {
return ""
}
}
function urlencode(str) {
str = (str + "").toString();
return encodeURIComponent(str).replace(/!/g, "%21").replace(/'/g, "%27").replace(/\(/g, "%28").replace(/\)/g, "%29").replace(/\*/g, "%2A").replace(/%20/g, "+")
}
function encodeUrl(str) {
if (typeof encodeURI == "function") {
return encodeURI(str)
} else {
str = (str + "").toString();
return encodeURIComponent(str).replace(/%2F/g, "/").replace(/%3F/g, "?").replace(/%3A/g, ":").replace(/%40/g, "@").replace(/%3D/g, "=").replace(/%3A/g, ":").replace(/%2C/g, ",").replace(/%2B/g, "+").replace(/%24/g, "$")
}
}
function base64Encode(text) {
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(text))
}
function base64Decode(text) {
return CryptoJS.enc.Utf8.stringify(CryptoJS.enc.Base64.parse(text))
}
function md5(text) {
return CryptoJS.MD5(text).toString()
}
function uint8ArrayToBase64(uint8Array) {
let binaryString = String.fromCharCode.apply(null, Array.from(uint8Array));
return btoa(binaryString)
}
function Utf8ArrayToStr(array) {
var out, i, len, c;
var char2, char3;
out = "";
len = array.length;
i = 0;
while (i < len) {
c = array[i++];
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
out += String.fromCharCode(c);
break;
case 12:
case 13:
char2 = array[i++];
out += String.fromCharCode((c & 31) << 6 | char2 & 63);
break;
case 14:
char2 = array[i++];
char3 = array[i++];
out += String.fromCharCode((c & 15) << 12 | (char2 & 63) << 6 | (char3 & 63) << 0);
break
}
}
return out
}
function gzip(str) {
let arr = pako.gzip(str, {});
return uint8ArrayToBase64(arr)
}
function ungzip(b64Data) {
let strData = atob(b64Data);
const charData = strData.split("").map(function(x) {
return x.charCodeAt(0)
});
const binData = new Uint8Array(charData);
const data = pako.inflate(binData);
return Utf8ArrayToStr(data)
}
function encodeStr(input, encoding) {
encoding = encoding || "gbk";
if (encoding.startsWith("gb")) {
input = gbkTool.encode(input)
}
return input
}
function decodeStr(input, encoding) {
encoding = encoding || "gbk";
if (encoding.startsWith("gb")) {
input = gbkTool.decode(input)
}
return input
}
function getCryptoJS() {
return 'console.log("CryptoJS已装载");'
}
const RSA = {
decode: function(data, key, option) {
option = option || {};
if (typeof JSEncrypt === "function") {
let chunkSize = option.chunkSize || 117;
let privateKey = this.getPrivateKey(key);
const decryptor = new JSEncrypt;
decryptor.setPrivateKey(privateKey);
let uncrypted = "";
uncrypted = decryptor.decryptUnicodeLong(data);
return uncrypted
} else {
return false
}
},
encode: function(data, key, option) {
option = option || {};
if (typeof JSEncrypt === "function") {
let chunkSize = option.chunkSize || 117;
let publicKey = this.getPublicKey(key);
const encryptor = new JSEncrypt;
encryptor.setPublicKey(publicKey);
let encrypted = "";
encrypted = encryptor.encryptUnicodeLong(data);
return encrypted
} else {
return false
}
},
fixKey(key, prefix, endfix) {
if (!key.includes(prefix)) {
key = prefix + key
}
if (!key.includes(endfix)) {
key += endfix
}
return key
},
getPrivateKey(key) {
let prefix = "-----BEGIN RSA PRIVATE KEY-----";
let endfix = "-----END RSA PRIVATE KEY-----";
return this.fixKey(key, prefix, endfix)
},
getPublicKey(key) {
let prefix = "-----BEGIN PUBLIC KEY-----";
let endfix = "-----END PUBLIC KEY-----";
return this.fixKey(key, prefix, endfix)
}
};
function getProxyUrl() {
if (typeof getProxy === "function") {
return getProxy(true)
} else {
return "http://127.0.0.1:9978/proxy?do=js"
}
}
function fixAdM3u8(m3u8_text, m3u8_url, ad_remove) {
if (!m3u8_text && !m3u8_url || !m3u8_text && m3u8_url && !m3u8_url.startsWith("http")) {
return ""
}
if (!m3u8_text) {
log("m3u8_url:" + m3u8_url);
m3u8_text = request(m3u8_url)
}
log("len(m3u8_text):" + m3u8_text.length);
if (!ad_remove) {
return m3u8_text
}
if (ad_remove.startsWith("reg:")) {
ad_remove = ad_remove.slice(4)
} else if (ad_remove.startsWith("js:")) {
ad_remove = ad_remove.slice(3)
}
let m3u8_start = m3u8_text.slice(0, m3u8_text.indexOf("#EXTINF")).trim();
let m3u8_body = m3u8_text.slice(m3u8_text.indexOf("#EXTINF"), m3u8_text.indexOf("#EXT-X-ENDLIST")).trim();
let m3u8_end = m3u8_text.slice(m3u8_text.indexOf("#EXT-X-ENDLIST")).trim();
let murls = [];
let m3_body_list = m3u8_body.split("\n");
let m3_len = m3_body_list.length;
let i = 0;
while (i < m3_len) {
let mi = m3_body_list[i];
let mi_1 = m3_body_list[i + 1];
if (mi.startsWith("#EXTINF")) {
murls.push([mi, mi_1].join("&"));
i += 2
} else if (mi.startsWith("#EXT-X-DISCONTINUITY")) {
let mi_2 = m3_body_list[i + 2];
murls.push([mi, mi_1, mi_2].join("&"));
i += 3
} else {
break
}
}
let new_m3u8_body = [];
for (let murl of murls) {
if (ad_remove && new RegExp(ad_remove).test(murl)) {} else {
let murl_list = murl.split("&");
if (!murl_list[murl_list.length - 1].startsWith("http") && m3u8_url.startsWith("http")) {
murl_list[murl_list.length - 1] = urljoin(m3u8_url, murl_list[murl_list.length - 1])
}
murl_list.forEach(it => {
new_m3u8_body.push(it)
})
}
}
new_m3u8_body = new_m3u8_body.join("\n").trim();
m3u8_text = [m3u8_start, new_m3u8_body, m3u8_end].join("\n").trim();
return m3u8_text
}
function fixAdM3u8Ai(m3u8_url, headers) {
let ts = (new Date).getTime();
let option = headers ? {
headers: headers
} : {};
function b(s1, s2) {
let i = 0;
while (i < s1.length) {
if (s1[i] !== s2[i]) {
break
}
i++
}
return i
}
function reverseString(str) {
return str.split("").reverse().join("")
}
let m3u8 = request(m3u8_url, option);
m3u8 = m3u8.trim().split("\n").map(it => it.startsWith("#") ? it : urljoin(m3u8_url, it)).join("\n");
m3u8 = m3u8.replace(/\n\n/gi, "\n");
let last_url = m3u8.split("\n").slice(-1)[0];
if (last_url.length < 5) {
last_url = m3u8.split("\n").slice(-2)[0]
}
if (last_url.includes(".m3u8") && last_url !== m3u8_url) {
m3u8_url = urljoin2(m3u8_url, last_url);
log("嵌套的m3u8_url:" + m3u8_url);
m3u8 = request(m3u8_url, option)
}
let s = m3u8.trim().split("\n").filter(it => it.trim()).join("\n");
let ss = s.split("\n");
if (m3u8_url.indexOf("ffzy") > 0) {
let j = 0,
k1 = 0,
m = 0,
n = 0,
t = 0;
let s2 = "";
for (let i = 0; i < ss.length; i++) {
let s = ss[i];
let s1 = "";
if (s.startsWith("#EXTINF")) {
s1 = s.slice(8);
n++;
if (n == 1) k1 = i;
if (s2.indexOf(s1) == -1) {
s2 = s2 + s1;
m++
}
t = t + parseFloat(s1);
i++;
s = ss[i]
}
if (s.startsWith("#EXT-X-DISCONTINUITY")) {
if (n == 5) {
log("n:" + n);
log("m:" + m);
for (let j = k1; j < k1 + n * 2; j++) {
log(ss[j])
}
log("广告位置:" + k1);
log("数据条数:" + n);
log("数据种类:" + m);
log("广告时间:" + t.toFixed(5));
ss.splice(k1, 2 * n + 1);
i = i - 2 * n + 1
}
t = 0;
m = 0;
n = 0;
s2 = ""
}
}
}
let firststr = "";
let maxl = 0;
let kk = 0;
let kkk1 = 1;
let kkk2 = 0;
let secondstr = "";
for (let i = 0; i < ss.length; i++) {
let s = ss[i];
if (!s.startsWith("#")) {
if (kk == 0) firststr = s;
if (kk > 0) {
if (maxl > b(firststr, s) + 1) {
if (secondstr.length < 5) secondstr = s;
kkk2++
} else {
maxl = b(firststr, s);
kkk1++
}
}
kk++;
if (kk >= 30) break
}
}
if (kkk2 > kkk1) firststr = secondstr;
let firststrlen = firststr.length;
let ml = Math.round(ss.length / 2).toString().length;
let maxc = 0;
let laststr = ss.toReversed().find(x => {
if (!x.startsWith("#")) {
let k = b(reverseString(firststr), reverseString(x));
maxl = b(firststr, x);
maxc++;
if (firststrlen - maxl <= ml + k || maxc > 10) {
return true
}
}
return false
});
log("最后一条切片:" + laststr);
let ad_urls = [];
for (let i = 0; i < ss.length; i++) {
let s = ss[i];
if (!s.startsWith("#")) {
if (b(firststr, s) < maxl) {
ad_urls.push(s);
ss.splice(i - 1, 2);
i = i - 2
} else {
ss[i] = urljoin(m3u8_url, s)
}
} else {
ss[i] = s.replace(/URI=\"(.*)\"/, 'URI="' + urljoin(m3u8_url, "$1") + '"')
}
}
log("处理的m3u8地址:" + m3u8_url);
log("----广告地址----");
log(ad_urls);
m3u8 = ss.join("\n");
log("处理耗时:" + ((new Date).getTime() - ts).toString());
log(m3u8);
return m3u8
}
function forceOrder(lists, key, option) {
let start = Math.floor(lists.length / 2);
let end = Math.min(lists.length - 1, start + 1);
if (start >= end) {
return lists
}
let first = lists[start];
let second = lists[end];
if (key) {
try {
first = first[key];
second = second[key]
} catch (e) {}
}
if (option && typeof option === "function") {
try {
first = option(first);
second = option(second)
} catch (e) {}
}
first += "";
second += "";
if (first.match(/(\d+)/) && second.match(/(\d+)/)) {
let num1 = Number(first.match(/(\d+)/)[1]);
let num2 = Number(second.match(/(\d+)/)[1]);
if (num1 > num2) {
lists.reverse()
}
}
return lists
}
let VODS = [];
let VOD = {};
let TABS = [];
let LISTS = [];
function getQuery(url) {
try {
if (url.indexOf("?") > -1) {
url = url.slice(url.indexOf("?") + 1)
}
let arr = url.split("#")[0].split("&");
const resObj = {};
arr.forEach(item => {
let arr1 = item.split("=");
let key = arr1[0];
let value = arr1.slice(1).join("=");
resObj[key] = value
});
return resObj
} catch (err) {
log(`getQuery发生错误:${e.message}`);
return {}
}
}
function urljoin(fromPath, nowPath) {
fromPath = fromPath || "";
nowPath = nowPath || "";
return joinUrl(fromPath, nowPath)
}
var urljoin2 = urljoin;
const defaultParser = {
pdfh: pdfh,
pdfa: pdfa,
pd: pd
};
function pdfh2(html, parse) {
let html2 = html;
try {
if (typeof html !== "string") {
html2 = html.rr(html.ele).toString()
}
} catch (e) {
print(`html对象转文本发生了错误:${e.message}`)
}
let result = defaultParser.pdfh(html2, parse);
let option = parse.includes("&&") ? parse.split("&&").slice(-1)[0] : parse.split(" ").slice(-1)[0];
if (/style/.test(option.toLowerCase()) && /url\(/.test(result)) {
try {
result = result.match(/url\((.*?)\)/)[1];
result = result.replace(/^['|"](.*)['|"]$/, "$1")
} catch (e) {}
}
return result
}
function pdfa2(html, parse) {
let html2 = html;
try {
if (typeof html !== "string") {
html2 = html.rr(html.ele).toString()
}
} catch (e) {
print(`html对象转文本发生了错误:${e.message}`)
}
return defaultParser.pdfa(html2, parse)
}
function pd2(html, parse, uri) {
let ret = pdfh2(html, parse);
if (typeof uri === "undefined" || !uri) {
uri = ""
}
if (DOM_CHECK_ATTR.test(parse) && !SPECIAL_URL.test(ret)) {
if (/http/.test(ret)) {
ret = ret.slice(ret.indexOf("http"))
} else {
ret = urljoin(MY_URL, ret)
}
}
return ret
}
const parseTags = {
jsp: {
pdfh: pdfh2,
pdfa: pdfa2,
pd: pd2
},
json: {
pdfh(html, parse) {
if (!parse || !parse.trim()) {
return ""
}
if (typeof html === "string") {
html = JSON.parse(html)
}
parse = parse.trim();
if (!parse.startsWith("$.")) {
parse = "$." + parse
}
parse = parse.split("||");
for (let ps of parse) {
let ret = cheerio.jp(ps, html);
if (Array.isArray(ret)) {
ret = ret[0] || ""
} else {
ret = ret || ""
}
if (ret && typeof ret !== "string") {
ret = ret.toString()
}
if (ret) {
return ret
}
}
return ""
},
pdfa(html, parse) {
if (!parse || !parse.trim()) {
return ""
}
if (typeof html === "string") {
html = JSON.parse(html)
}
parse = parse.trim();
if (!parse.startsWith("$.")) {
parse = "$." + parse
}
let ret = cheerio.jp(parse, html);
if (Array.isArray(ret) && Array.isArray(ret[0]) && ret.length === 1) {
return ret[0] || []
}
return ret || []
},
pd(html, parse) {
let ret = parseTags.json.pdfh(html, parse);
if (ret) {
return urljoin(MY_URL, ret)
}
return ret
}
},
jq: {
pdfh(html, parse) {
if (!html || !parse || !parse.trim()) {
return ""
}
parse = parse.trim();
let result = defaultParser.pdfh(html, parse);
return result
},
pdfa(html, parse) {
if (!html || !parse || !parse.trim()) {
return []
}
parse = parse.trim();
let result = defaultParser.pdfa(html, parse);
print(`pdfa解析${parse}=>${result.length}`);
return result
},
pd(html, parse, base_url) {
if (!html || !parse || !parse.trim()) {
return ""
}
parse = parse.trim();
base_url = base_url || MY_URL;
return defaultParser.pd(html, parse, base_url)
}
},
getParse(p0) {
if (p0.startsWith("jsp:")) {
return this.jsp
} else if (p0.startsWith("json:")) {
return this.json
} else if (p0.startsWith("jq:")) {
return this.jq
} else {
return this.jq
}
}
};
const stringify = JSON.stringify;
const jsp = parseTags.jsp;
const jq = parseTags.jq;
function readFile(filePath) {
filePath = filePath || "./uri.min.js";
var fd = os.open(filePath);
var buffer = new ArrayBuffer(1024);
var len = os.read(fd, buffer, 0, 1024);
console.log(len);
let text = String.fromCharCode.apply(null, new Uint8Array(buffer));
console.log(text);
return text
}
function dealJson(html) {
try {
html = html.trim();
if (!(html.startsWith("{") && html.endsWith("}") || html.startsWith("[") && html.endsWith("]"))) {
html = "{" + html.match(/.*?\{(.*)\}/m)[1] + "}"
}
} catch (e) {}
try {
html = JSON.parse(html)
} catch (e) {}
return html
}
var OcrApi = {
api: OCR_API,
classification: function(img) {
let code = "";
try {
log("通过drpy_ocr验证码接口过验证...");
let html = "";
if (this.api.endsWith("drpy/text")) {
html = request(this.api, {
data: {
img: img
},
headers: {
"User-Agent": PC_UA
},
method: "POST"
}, true)
} else {
html = post(this.api, {
body: img
})
}
code = html || ""
} catch (e) {
log(`OCR识别验证码发生错误:${e.message}`)
}
return code
}
};
function verifyCode(url) {
let cnt = 0;
let host = getHome(url);
let cookie = "";
while (cnt < OCR_RETRY) {
try {
let yzm_url = `${host}/index.php/verify/index.html`;
console.log(`验证码链接:${yzm_url}`);
let hhtml = request(yzm_url, {
withHeaders: true,
toBase64: true
}, true);
let json = JSON.parse(hhtml);
if (!cookie) {
let setCk = Object.keys(json).find(it => it.toLowerCase() === "set-cookie");
cookie = setCk ? json[setCk].split(";")[0] : ""
}
console.log("cookie:" + cookie);
let img = json.body;
let code = OcrApi.classification(img);
console.log(`${cnt+1}次验证码识别结果:${code}`);
let submit_url = `${host}/index.php/ajax/verify_check?type=search&verify=${code}`;
console.log(submit_url);
let html = request(submit_url, {
headers: {
Cookie: cookie
},
method: "POST"
});
html = JSON.parse(html);
if (html.msg === "ok") {
console.log(`${cnt+1}次验证码提交成功`);
return cookie
} else if (html.msg !== "ok" && cnt + 1 >= OCR_RETRY) {
cookie = ""
}
} catch (e) {
console.log(`${cnt+1}次验证码提交失败:${e.message}`);
if (cnt + 1 >= OCR_RETRY) {
cookie = ""
}
}
cnt += 1
}
return cookie
}
function setItem(k, v) {
local.set(RKEY, k, v);
console.log(`规则${RKEY}设置${k} => ${v}`)
}
function getItem(k, v) {
return local.get(RKEY, k) || v
}
function clearItem(k) {
local.delete(RKEY, k)
}
function getHome(url) {
if (!url) {
return ""
}
let tmp = url.split("//");
url = tmp[0] + "//" + tmp[1].split("/")[0];
try {
url = decodeURIComponent(url)
} catch (e) {}
return url
}
function buildUrl(url, obj) {
obj = obj || {};
if (url.indexOf("?") < 0) {
url += "?"
}
let param_list = [];
let keys = Object.keys(obj);
keys.forEach(it => {
param_list.push(it + "=" + obj[it])
});
let prs = param_list.join("&");
if (keys.length > 0 && !url.endsWith("?")) {
url += "&"
}
url += prs;
return url
}
function $require(url) {
eval(request(url))
}
function keysToLowerCase(obj) {
return Object.keys(obj).reduce((result, key) => {
const newKey = key.toLowerCase();
result[newKey] = obj[key];
return result
}, {})
}
function buildQueryString(params) {
const queryArray = [];
for (const key in params) {
if (params.hasOwnProperty(key)) {
let value = params[key];
if (value === undefined || value === null) {
value = ""
} else {
value = value.toString()
}
const encodedKey = encodeURIComponent(key);
const encodedValue = encodeURIComponent(value);
queryArray.push(encodedKey + "=" + encodedValue)
}
}
return queryArray.join("&")
}
function parseQueryString(query) {
const params = {};
query.split("&").forEach(function(part) {
const regex = /^(.*?)=(.*)/;
const match = part.match(regex);
if (match) {
const key = decodeURIComponent(match[1]);
const value = decodeURIComponent(match[2]);
params[key] = value
}
});
return params
}
function encodeIfContainsSpecialChars(value) {
const specialChars = ":/?#[]@!$'()*+,;=%";
if (specialChars.split("").some(char => value.includes(char))) {
return encodeURIComponent(value)
}
return value
}
function objectToQueryString(obj) {
const encoded = [];
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
encoded.push(encodeURIComponent(key) + "=" + encodeIfContainsSpecialChars(obj[key]))
}
}
return encoded.join("&")
}
function request(url, obj, ocr_flag) {
ocr_flag = ocr_flag || false;
if (typeof obj === "undefined" || !obj || obj === {}) {
if (!fetch_params || !fetch_params.headers) {
let headers = {
"User-Agent": MOBILE_UA
};
if (rule.headers) {
Object.assign(headers, rule.headers)
}
if (!fetch_params) {
fetch_params = {}
}
fetch_params.headers = headers
}
if (!fetch_params.headers.Referer) {
fetch_params.headers.Referer = getHome(url)
}
obj = fetch_params
} else {
let headers = obj.headers || {};
let keys = Object.keys(headers).map(it => it.toLowerCase());
if (!keys.includes("user-agent")) {
headers["User-Agent"] = MOBILE_UA;
if (typeof fetch_params === "object" && fetch_params && fetch_params.headers) {
let fetch_headers = keysToLowerCase(fetch_params.headers);
if (fetch_headers["user-agent"]) {
headers["User-Agent"] = fetch_headers["user-agent"]
}
}
}
if (!keys.includes("referer")) {
headers["Referer"] = getHome(url)
}
obj.headers = headers
}
if (rule.encoding && rule.encoding !== "utf-8" && !ocr_flag) {
if (!obj.headers.hasOwnProperty("Content-Type") && !obj.headers.hasOwnProperty("content-type")) {
obj.headers["Content-Type"] = "text/html; charset=" + rule.encoding
}
}
if (typeof obj.body != "undefined" && obj.body && typeof obj.body === "string") {
if (!obj.headers.hasOwnProperty("Content-Type") && !obj.headers.hasOwnProperty("content-type")) {
obj.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=" + rule.encoding
}
} else if (typeof obj.body != "undefined" && obj.body && typeof obj.body === "object") {
obj.data = obj.body;
delete obj.body
}
if (!url) {
return obj.withHeaders ? "{}" : ""
}
if (obj.toBase64) {
obj.buffer = 2;
delete obj.toBase64
}
if (obj.redirect === false) {
obj.redirect = 0
}
if (obj.headers.hasOwnProperty("Content-Type") || obj.headers.hasOwnProperty("content-type")) {
let _contentType = obj.headers["Content-Type"] || obj.headers["content-type"] || "";
if (_contentType.includes("application/x-www-form-urlencoded")) {
log("custom body is application/x-www-form-urlencoded");
if (typeof obj.body == "string") {
let temp_obj = parseQueryString(obj.body);
console.log(JSON.stringify(temp_obj))
}
}
}
console.log(JSON.stringify(obj.headers));
console.log("request:" + url + `|method:${obj.method||"GET"}|body:${obj.body||""}`);
let res = req(url, obj);
let html = res.content || "";
if (obj.withHeaders) {
let htmlWithHeaders = res.headers;
htmlWithHeaders.body = html;
return JSON.stringify(htmlWithHeaders)
} else {
return html
}
}
function post(url, obj) {
obj = obj || {};
obj.method = "POST";
return request(url, obj)
}
function reqCookie(url, obj, all_cookie) {
obj = obj || {};
obj.withHeaders = true;
all_cookie = all_cookie || false;
let html = request(url, obj);
let json = JSON.parse(html);
let setCk = Object.keys(json).find(it => it.toLowerCase() === "set-cookie");
let cookie = setCk ? json[setCk] : "";
if (Array.isArray(cookie)) {
cookie = cookie.join(";")
}
if (!all_cookie) {
cookie = cookie.split(";")[0]
}
html = json.body;
return {
cookie: cookie,
html: html
}
}
fetch = request;
print = function(data) {
data = data || "";
if (typeof data == "object" && Object.keys(data).length > 0) {
try {
data = JSON.stringify(data);
console.log(data)
} catch (e) {
console.log(typeof data + ":" + data.length);
return
}
} else if (typeof data == "object" && Object.keys(data).length < 1) {
console.log("null object")
} else {
console.log(data)
}
};
log = print;
function checkHtml(html, url, obj) {
if (/\?btwaf=/.test(html)) {
let btwaf = html.match(/btwaf(.*?)"/)[1];
url = url.split("#")[0] + "?btwaf" + btwaf;
print("宝塔验证访问链接:" + url);
html = request(url, obj)
}
return html
}
function getCode(url, obj) {
let html = request(url, obj);
html = checkHtml(html, url, obj);
return html
}
function getHtml(url) {
let obj = {};
if (rule.headers) {
obj.headers = rule.headers
}
let cookie = getItem(RULE_CK, "");
if (cookie) {
if (obj.headers && !Object.keys(obj.headers).map(it => it.toLowerCase()).includes("cookie")) {
log("历史无cookie,新增过验证后的cookie");
obj.headers["Cookie"] = cookie
} else if (obj.headers && obj.headers.cookie && obj.headers.cookie !== cookie) {
obj.headers["Cookie"] = cookie;
log("历史有小写过期的cookie,更新过验证后的cookie")
} else if (obj.headers && obj.headers.Cookie && obj.headers.Cookie !== cookie) {
obj.headers["Cookie"] = cookie;
log("历史有大写过期的cookie,更新过验证后的cookie")
} else if (!obj.headers) {
obj.headers = {
Cookie: cookie
};
log("历史无headers,更新过验证后的含cookie的headers")
}
}
let html = getCode(url, obj);
return html
}
function homeParse(homeObj) {
fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
let classes = [];
if (homeObj.class_name && homeObj.class_url) {
let names = homeObj.class_name.split("&");
let urls = homeObj.class_url.split("&");
let cnt = Math.min(names.length, urls.length);
for (let i = 0; i < cnt; i++) {
classes.push({
type_id: urls[i],
type_name: names[i]
})
}
}
if (homeObj.class_parse) {
if (homeObj.class_parse.startsWith("js:")) {
var input = homeObj.MY_URL;
try {
eval(homeObj.class_parse.replace("js:", ""));
if (Array.isArray(input)) {
classes = input
}
} catch (e) {
log(`通过js动态获取分类发生了错误:${e.message}`)
}
} else {
let p = homeObj.class_parse.split(";");
let p0 = p[0];
let _ps = parseTags.getParse(p0);
let is_json = p0.startsWith("json:");
_pdfa = _ps.pdfa;
_pdfh = _ps.pdfh;
_pd = _ps.pd;
MY_URL = rule.url;
if (is_json) {
try {
let cms_cate_url = homeObj.MY_URL.replace("ac=detail", "ac=list");
let html = homeObj.home_html || getHtml(cms_cate_url);
if (html) {
if (cms_cate_url === homeObj.MY_URL) {
homeHtmlCache = html
}
let list = _pdfa(html, p0.replace("json:", ""));
if (list && list.length > 0) {
classes = list
}
}
} catch (e) {
console.log(e.message)
}
} else if (p.length >= 3 && !is_json) {
try {
let html = homeObj.home_html || getHtml(homeObj.MY_URL);
if (html) {
homeHtmlCache = html;
let list = _pdfa(html, p0);
if (list && list.length > 0) {
list.forEach((it, idex) => {
try {
let name = _pdfh(it, p[1]);
if (homeObj.cate_exclude && new RegExp(homeObj.cate_exclude).test(name)) {
return
}
let url = _pd(it, p[2]);
if (p.length > 3 && p[3] && !homeObj.home_html) {
let exp = new RegExp(p[3]);
url = url.match(exp)[1]
}
classes.push({
type_id: url.trim(),
type_name: name.trim()
})
} catch (e) {
console.log(`分类列表定位第${idex}个元素正常报错:${e.message}`)
}
})
}
}
} catch (e) {
console.log(e.message)
}
}
}
}
classes = classes.filter(it => !homeObj.cate_exclude || !new RegExp(homeObj.cate_exclude).test(it.type_name));
let resp = {
class: classes
};
if (homeObj.filter) {
resp.filters = homeObj.filter
}
console.log(JSON.stringify(resp));
return JSON.stringify(resp)
}
function getPP(p, pn, pp, ppn) {
try {
let ps = p[pn] === "*" && pp.length > ppn ? pp[ppn] : p[pn];
return ps
} catch (e) {
return ""
}
}
function homeVodParse(homeVodObj) {
fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
let d = [];
MY_URL = homeVodObj.homeUrl;
console.log(MY_URL);
let t1 = (new Date).getTime();
let p = homeVodObj.推荐;
print("p:" + p);
if (p === "*" && rule.一级) {
p = rule.一级;
homeVodObj.double = false
}
if (!p || typeof p !== "string") {
return "{}"
}
p = p.trim();
let pp = rule.一级 ? rule.一级.split(";") : [];
if (p.startsWith("js:")) {
const TYPE = "home";
var input = MY_URL;
HOST = rule.host;
eval(p.replace("js:", ""));
d = VODS
} else {
p = p.split(";");
if (!homeVodObj.double && p.length < 5) {
return "{}"
} else if (homeVodObj.double && p.length < 6) {
return "{}"
}
let p0 = getPP(p, 0, pp, 0);
let _ps = parseTags.getParse(p0);
_pdfa = _ps.pdfa;
_pdfh = _ps.pdfh;
_pd = _ps.pd;
let is_json = p0.startsWith("json:");
p0 = p0.replace(/^(jsp:|json:|jq:)/, "");
let html = homeHtmlCache || getHtml(MY_URL);
homeHtmlCache = undefined;
if (is_json) {
html = dealJson(html)
}
try {
console.log("double:" + homeVodObj.double);
if (homeVodObj.double) {
let items = _pdfa(html, p0);
let p1 = getPP(p, 1, pp, 0);
let p2 = getPP(p, 2, pp, 1);
let p3 = getPP(p, 3, pp, 2);
let p4 = getPP(p, 4, pp, 3);
let p5 = getPP(p, 5, pp, 4);
let p6 = getPP(p, 6, pp, 5);
for (let item of items) {
let items2 = _pdfa(item, p1);
for (let item2 of items2) {
try {
let title = _pdfh(item2, p2);
let img = "";
try {
img = _pd(item2, p3)
} catch (e) {}
let desc = "";
try {
desc = _pdfh(item2, p4)
} catch (e) {}
let links = [];
for (let _p5 of p5.split("+")) {
let link = !homeVodObj.detailUrl ? _pd(item2, _p5, MY_URL) : _pdfh(item2, _p5);
links.push(link)
}
let content;
if (p.length > 6 && p[6]) {
content = _pdfh(item2, p6)
} else {
content = ""
}
let vid = links.join("$");
if (rule.二级 === "*") {
vid = vid + "@@" + title + "@@" + img
}
let vod = {
vod_name: title,
vod_pic: img,
vod_remarks: desc,
vod_content: content,
vod_id: vid
};
d.push(vod)
} catch (e) {
console.log(`首页列表双层定位处理发生错误:${e.message}`)
}
}
}
} else {
let items = _pdfa(html, p0);
let p1 = getPP(p, 1, pp, 1);
let p2 = getPP(p, 2, pp, 2);
let p3 = getPP(p, 3, pp, 3);
let p4 = getPP(p, 4, pp, 4);
let p5 = getPP(p, 5, pp, 5);
for (let item of items) {
try {
let title = _pdfh(item, p1);
let img = "";
try {
img = _pd(item, p2, MY_URL)
} catch (e) {}
let desc = "";
try {
desc = _pdfh(item, p3)
} catch (e) {}
let links = [];
for (let _p5 of p4.split("+")) {
let link = !homeVodObj.detailUrl ? _pd(item, _p5, MY_URL) : _pdfh(item, _p5);
links.push(link)
}
let content;
if (p.length > 5 && p[5]) {
content = _pdfh(item, p5)
} else {
content = ""
}
let vid = links.join("$");
if (rule.二级 === "*") {
vid = vid + "@@" + title + "@@" + img
}
let vod = {
vod_name: title,
vod_pic: img,
vod_remarks: desc,
vod_content: content,
vod_id: vid
};
d.push(vod)
} catch (e) {
console.log(`首页列表单层定位处理发生错误:${e.message}`)
}
}
}
} catch (e) {}
}
let t2 = (new Date).getTime();
console.log("加载首页推荐耗时:" + (t2 - t1) + "毫秒");
if (rule.图片替换) {
if (rule.图片替换.startsWith("js:")) {
d.forEach(it => {
try {
var input = it.vod_pic;
eval(rule.图片替换.trim().replace("js:", ""));
it.vod_pic = input
} catch (e) {
log(`图片:${it.vod_pic}替换错误:${e.message}`)
}
})
} else if (rule.图片替换.includes("=>")) {
let replace_from = rule.图片替换.split("=>")[0];
let replace_to = rule.图片替换.split("=>")[1];
d.forEach(it => {
if (it.vod_pic && it.vod_pic.startsWith("http")) {
it.vod_pic = it.vod_pic.replace(replace_from, replace_to)
}
})
}
}
if (rule.图片来源) {
d.forEach(it => {
if (it.vod_pic && it.vod_pic.startsWith("http")) {
it.vod_pic = it.vod_pic + rule.图片来源
}
})
}
if (d.length > 0) {
print(d.slice(0, 2))
}
return JSON.stringify({
list: d
})
}
function categoryParse(cateObj) {
fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
let p = cateObj.一级;
if (!p || typeof p !== "string") {
return "{}"
}
let d = [];
let url = cateObj.url.replaceAll("fyclass", cateObj.tid);
if (cateObj.pg === 1 && url.includes("[") && url.includes("]")) {
url = url.split("[")[1].split("]")[0]
} else if (cateObj.pg > 1 && url.includes("[") && url.includes("]")) {
url = url.split("[")[0]
}
if (rule.filter_url) {
if (!/fyfilter/.test(url)) {
if (!url.endsWith("&") && !rule.filter_url.startsWith("&")) {
url += "&"
}
url += rule.filter_url
} else {
url = url.replace("fyfilter", rule.filter_url)
}
url = url.replaceAll("fyclass", cateObj.tid);
let fl = cateObj.filter ? cateObj.extend : {};
if (rule.filter_def && typeof rule.filter_def === "object") {
try {
if (Object.keys(rule.filter_def).length > 0 && rule.filter_def.hasOwnProperty(cateObj.tid)) {
let self_fl_def = rule.filter_def[cateObj.tid];
if (self_fl_def && typeof self_fl_def === "object") {
let fl_def = JSON.parse(JSON.stringify(self_fl_def));
fl = Object.assign(fl_def, fl)
}
}
} catch (e) {
print(`合并不同分类对应的默认筛选出错:${e.message}`)
}
}
let new_url;
new_url = cheerio.jinja2(url, {
fl: fl,
fyclass: cateObj.tid
});
url = new_url
}
if (/fypage/.test(url)) {
if (url.includes("(") && url.includes(")")) {
let url_rep = url.match(/.*?\((.*)\)/)[1];
let cnt_page = url_rep.replaceAll("fypage", cateObj.pg);
let cnt_pg = eval(cnt_page);
url = url.replaceAll(url_rep, cnt_pg).replaceAll("(", "").replaceAll(")", "")
} else {
url = url.replaceAll("fypage", cateObj.pg)
}
}
MY_URL = url;
console.log(MY_URL);
p = p.trim();
const MY_CATE = cateObj.tid;
if (p.startsWith("js:")) {
var MY_FL = cateObj.extend;
const TYPE = "cate";
var input = MY_URL;
const MY_PAGE = cateObj.pg;
var desc = "";
eval(p.trim().replace("js:", ""));
d = VODS
} else {
p = p.split(";");
if (p.length < 5) {
return "{}"
}
let _ps = parseTags.getParse(p[0]);
_pdfa = _ps.pdfa;
_pdfh = _ps.pdfh;
_pd = _ps.pd;
let is_json = p[0].startsWith("json:");
p[0] = p[0].replace(/^(jsp:|json:|jq:)/, "");
try {
let html = getHtml(MY_URL);
if (html) {
if (is_json) {
html = dealJson(html)
}
let list = _pdfa(html, p[0]);
list.forEach(it => {
let links = p[4].split("+").map(p4 => {
return !rule.detailUrl ? _pd(it, p4, MY_URL) : _pdfh(it, p4)
});
let link = links.join("$");
let vod_id = rule.detailUrl ? MY_CATE + "$" + link : link;
let vod_name = _pdfh(it, p[1]).replace(/\n|\t/g, "").trim();
let vod_pic = _pd(it, p[2], MY_URL);
if (rule.二级 === "*") {
vod_id = vod_id + "@@" + vod_name + "@@" + vod_pic
}
d.push({
vod_id: vod_id,
vod_name: vod_name,
vod_pic: vod_pic,
vod_remarks: _pdfh(it, p[3]).replace(/\n|\t/g, "").trim()
})
})
}
} catch (e) {
console.log(e.message)
}
}
if (rule.图片替换) {
if (rule.图片替换.startsWith("js:")) {
d.forEach(it => {
try {
var input = it.vod_pic;
eval(rule.图片替换.trim().replace("js:", ""));
it.vod_pic = input
} catch (e) {
log(`图片:${it.vod_pic}替换错误:${e.message}`)
}
})
} else if (rule.图片替换.includes("=>")) {
let replace_from = rule.图片替换.split("=>")[0];
let replace_to = rule.图片替换.split("=>")[1];
d.forEach(it => {
if (it.vod_pic && it.vod_pic.startsWith("http")) {
it.vod_pic = it.vod_pic.replace(replace_from, replace_to)
}
})
}
}
if (rule.图片来源) {
d.forEach(it => {
if (it.vod_pic && it.vod_pic.startsWith("http")) {
it.vod_pic = it.vod_pic + rule.图片来源
}
})
}
if (d.length > 0) {
print(d.slice(0, 2))
}
let pagecount = 0;
if (rule.pagecount && typeof rule.pagecount === "object" && rule.pagecount.hasOwnProperty(MY_CATE)) {
print(`MY_CATE:${MY_CATE},pagecount:${JSON.stringify(rule.pagecount)}`);
pagecount = parseInt(rule.pagecount[MY_CATE])
}
let nodata = {
list: [{
vod_name: "无数据,防无限请求",
vod_id: "no_data",
vod_remarks: "不要点,会崩的",
vod_pic: "https://ghproxy.net/https://raw.githubusercontent.com/hjdhnx/dr_py/main/404.jpg"
}],
total: 1,
pagecount: 1,
page: 1,
limit: 1
};
let vod = d.length < 1 ? JSON.stringify(nodata) : JSON.stringify({
page: parseInt(cateObj.pg),
pagecount: pagecount || 999,
limit: 20,
total: 999,
list: d
});
return vod
}
function searchParse(searchObj) {
fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
let d = [];
if (!searchObj.searchUrl) {
return "{}"
}
if (rule.searchNoPage && Number(searchObj.pg) > 1) {
return "{}"
}
let p = searchObj.搜索 === "*" && rule.一级 ? rule.一级 : searchObj.搜索;
if (!p || typeof p !== "string") {
return "{}"
}
p = p.trim();
let pp = rule.一级 ? rule.一级.split(";") : [];
let url = searchObj.searchUrl.replaceAll("**", searchObj.wd);
if (searchObj.pg === 1 && url.includes("[") && url.includes("]") && !url.includes("#")) {
url = url.split("[")[1].split("]")[0]
} else if (searchObj.pg > 1 && url.includes("[") && url.includes("]") && !url.includes("#")) {
url = url.split("[")[0]
}
if (/fypage/.test(url)) {
if (url.includes("(") && url.includes(")")) {
let url_rep = url.match(/.*?\((.*)\)/)[1];
let cnt_page = url_rep.replaceAll("fypage", searchObj.pg);
let cnt_pg = eval(cnt_page);
url = url.replaceAll(url_rep, cnt_pg).replaceAll("(", "").replaceAll(")", "")
} else {
url = url.replaceAll("fypage", searchObj.pg)
}
}
MY_URL = url;
console.log(MY_URL);
if (p.startsWith("js:")) {
const TYPE = "search";
const MY_PAGE = searchObj.pg;
const KEY = searchObj.wd;
var input = MY_URL;
var detailUrl = rule.detailUrl || "";
eval(p.trim().replace("js:", ""));
d = VODS
} else {
p = p.split(";");
if (p.length < 5) {
return "{}"
}
let p0 = getPP(p, 0, pp, 0);
let _ps = parseTags.getParse(p0);
_pdfa = _ps.pdfa;
_pdfh = _ps.pdfh;
_pd = _ps.pd;
let is_json = p0.startsWith("json:");
p0 = p0.replace(/^(jsp:|json:|jq:)/, "");
try {
let req_method = MY_URL.split(";").length > 1 ? MY_URL.split(";")[1].toLowerCase() : "get";
let html;
if (req_method === "post") {
let rurls = MY_URL.split(";")[0].split("#");
let rurl = rurls[0];
let params = rurls.length > 1 ? rurls[1] : "";
print(`post=》rurl:${rurl},params:${params}`);
let _fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
let postData = {
body: params
};
Object.assign(_fetch_params, postData);
html = post(rurl, _fetch_params)
} else if (req_method === "postjson") {
let rurls = MY_URL.split(";")[0].split("#");
let rurl = rurls[0];
let params = rurls.length > 1 ? rurls[1] : "";
print(`postjson-》rurl:${rurl},params:${params}`);
try {
params = JSON.parse(params)
} catch (e) {
params = "{}"
}
let _fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
let postData = {
body: params
};
Object.assign(_fetch_params, postData);
html = post(rurl, _fetch_params)
} else {
html = getHtml(MY_URL)
}
if (html) {
let search_tag = rule.搜索验证标识 || "系统安全验证|输入验证码";
if (new RegExp(search_tag).test(html)) {
let cookie = verifyCode(MY_URL);
if (cookie) {
console.log(`本次成功过验证,cookie:${cookie}`);
setItem(RULE_CK, cookie)
} else {
console.log(`本次自动过搜索验证失败,cookie:${cookie}`)
}
html = getHtml(MY_URL)
}
if (!html.includes(searchObj.wd)) {
console.log("搜索结果源码未包含关键字,疑似搜索失败,正为您打印结果源码");
console.log(html)
}
if (is_json) {
html = dealJson(html)
}
let list = _pdfa(html, p0);
let p1 = getPP(p, 1, pp, 1);
let p2 = getPP(p, 2, pp, 2);
let p3 = getPP(p, 3, pp, 3);
let p4 = getPP(p, 4, pp, 4);
let p5 = getPP(p, 5, pp, 5);
list.forEach(it => {
let links = p4.split("+").map(_p4 => {
return !rule.detailUrl ? _pd(it, _p4, MY_URL) : _pdfh(it, _p4)
});
let link = links.join("$");
let content;
if (p.length > 5 && p[5]) {
content = _pdfh(it, p5)
} else {
content = ""
}
let vod_id = link;
let vod_name = _pdfh(it, p1).replace(/\n|\t/g, "").trim();
let vod_pic = _pd(it, p2, MY_URL);
if (rule.二级 === "*") {
vod_id = vod_id + "@@" + vod_name + "@@" + vod_pic
}
let ob = {
vod_id: vod_id,
vod_name: vod_name,
vod_pic: vod_pic,
vod_remarks: _pdfh(it, p3).replace(/\n|\t/g, "").trim(),
vod_content: content.replace(/\n|\t/g, "").trim()
};
d.push(ob)
})
}
} catch (e) {
print(`搜索发生错误:${e.message}`);
return "{}"
}
}
if (rule.图片替换) {
if (rule.图片替换.startsWith("js:")) {
d.forEach(it => {
try {
var input = it.vod_pic;
eval(rule.图片替换.trim().replace("js:", ""));
it.vod_pic = input
} catch (e) {
log(`图片:${it.vod_pic}替换错误:${e.message}`)
}
})
} else if (rule.图片替换.includes("=>")) {
let replace_from = rule.图片替换.split("=>")[0];
let replace_to = rule.图片替换.split("=>")[1];
d.forEach(it => {
if (it.vod_pic && it.vod_pic.startsWith("http")) {
it.vod_pic = it.vod_pic.replace(replace_from, replace_to)
}
})
}
}
if (rule.图片来源) {
d.forEach(it => {
if (it.vod_pic && it.vod_pic.startsWith("http")) {
it.vod_pic = it.vod_pic + rule.图片来源
}
})
}
return JSON.stringify({
page: parseInt(searchObj.pg),
pagecount: 10,
limit: 20,
total: 100,
list: d
})
}
function detailParse(detailObj) {
let t1 = (new Date).getTime();
fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
let orId = detailObj.orId;
let vod_name = "片名";
let vod_pic = "";
let vod_id = orId;
if (rule.二级 === "*") {
let extra = orId.split("@@");
vod_name = extra.length > 1 ? extra[1] : vod_name;
vod_pic = extra.length > 2 ? extra[2] : vod_pic
}
let vod = {
vod_id: vod_id,
vod_name: vod_name,
vod_pic: vod_pic,
type_name: "类型",
vod_year: "年份",
vod_area: "地区",
vod_remarks: "更新信息",
vod_actor: "主演",
vod_director: "导演",
vod_content: "简介"
};
let p = detailObj.二级;
let url = detailObj.url;
let detailUrl = detailObj.detailUrl;
let fyclass = detailObj.fyclass;
let tab_exclude = detailObj.tab_exclude;
let html = detailObj.html || "";
MY_URL = url;
if (detailObj.二级访问前) {
try {
print(`尝试在二级访问前执行代码:${detailObj.二级访问前}`);
eval(detailObj.二级访问前.trim().replace("js:", ""))
} catch (e) {
print(`二级访问前执行代码出现错误:${e.message}`)
}
}
if (p === "*") {
vod.vod_play_from = "在线播放";
vod.vod_remarks = detailUrl;
vod.vod_actor = "没有二级,只有一级链接直接嗅探播放";
vod.vod_content = MY_URL;
vod.vod_play_url = "嗅探播放$" + MY_URL.split("@@")[0]
} else if (typeof p === "string" && p.trim().startsWith("js:")) {
const TYPE = "detail";
var input = MY_URL;
var play_url = "";
eval(p.trim().replace("js:", ""));
vod = VOD;
console.log(JSON.stringify(vod))
} else if (p && typeof p === "object") {
let tt1 = (new Date).getTime();
if (!html) {
html = getHtml(MY_URL)
}
print(`二级${MY_URL}仅获取源码耗时:${(new Date).getTime()-tt1}毫秒`);
let _ps;
if (p.is_json) {
print("二级是json");
_ps = parseTags.json;
html = dealJson(html)
} else if (p.is_jsp) {
print("二级是jsp");
_ps = parseTags.jsp
} else if (p.is_jq) {
print("二级是jq");
_ps = parseTags.jq
} else {
print("二级默认jq");
_ps = parseTags.jq
}
let tt2 = (new Date).getTime();
print(`二级${MY_URL}获取并装载源码耗时:${tt2-tt1}毫秒`);
_pdfa = _ps.pdfa;
_pdfh = _ps.pdfh;
_pd = _ps.pd;
if (p.title) {
let p1 = p.title.split(";");
vod.vod_name = _pdfh(html, p1[0]).replace(/\n|\t/g, "").trim();
let type_name = p1.length > 1 ? _pdfh(html, p1[1]).replace(/\n|\t/g, "").replace(/ /g, "").trim() : "";
vod.type_name = type_name || vod.type_name
}
if (p.desc) {
try {
let p1 = p.desc.split(";");
vod.vod_remarks = _pdfh(html, p1[0]).replace(/\n|\t/g, "").trim();
vod.vod_year = p1.length > 1 ? _pdfh(html, p1[1]).replace(/\n|\t/g, "").trim() : "";
vod.vod_area = p1.length > 2 ? _pdfh(html, p1[2]).replace(/\n|\t/g, "").trim() : "";
vod.vod_actor = p1.length > 3 ? _pdfh(html, p1[3]).replace(/\n|\t/g, "").trim() : "";
vod.vod_director = p1.length > 4 ? _pdfh(html, p1[4]).replace(/\n|\t/g, "").trim() : ""
} catch (e) {}
}
if (p.content) {
try {
let p1 = p.content.split(";");
vod.vod_content = _pdfh(html, p1[0]).replace(/\n|\t/g, "").trim()
} catch (e) {}
}
if (p.img) {
try {
let p1 = p.img.split(";");
vod.vod_pic = _pd(html, p1[0], MY_URL)
} catch (e) {}
}
let vod_play_from = "$$$";
let playFrom = [];
if (p.重定向 && p.重定向.startsWith("js:")) {
print("开始执行重定向代码:" + p.重定向);
html = eval(p.重定向.replace("js:", ""))
}
if (p.tabs) {
if (p.tabs.startsWith("js:")) {
print("开始执行tabs代码:" + p.tabs);
var input = MY_URL;
eval(p.tabs.replace("js:", ""));
playFrom = TABS
} else {
let p_tab = p.tabs.split(";")[0];
let vHeader = _pdfa(html, p_tab);
console.log(vHeader.length);
let tab_text = p.tab_text || "body&&Text";
let new_map = {};
for (let v of vHeader) {
let v_title = _pdfh(v, tab_text).trim();
if (!v_title) {
v_title = "线路空"
}
console.log(v_title);
if (tab_exclude && new RegExp(tab_exclude).test(v_title)) {
continue
}
if (!new_map.hasOwnProperty(v_title)) {
new_map[v_title] = 1
} else {
new_map[v_title] += 1
}
if (new_map[v_title] > 1) {
v_title += Number(new_map[v_title] - 1)
}
playFrom.push(v_title)
}
}
console.log(JSON.stringify(playFrom))
} else {
playFrom = ["在线播放"]
}
vod.vod_play_from = playFrom.join(vod_play_from);
let vod_play_url = "$$$";
let vod_tab_list = [];
if (p.lists) {
if (p.lists.startsWith("js:")) {
print("开始执行lists代码:" + p.lists);
try {
var input = MY_URL;
var play_url = "";
eval(p.lists.replace("js:", ""));
for (let i in LISTS) {
if (LISTS.hasOwnProperty(i)) {
try {
LISTS[i] = LISTS[i].map(it => it.split("$").slice(0, 2).join("$"))
} catch (e) {
print(`格式化LISTS发生错误:${e.message}`)
}
}
}
vod_play_url = LISTS.map(it => it.join("#")).join(vod_play_url)
} catch (e) {
print(`js执行lists: 发生错误:${e.message}`)
}
} else {
let list_text = p.list_text || "body&&Text";
let list_url = p.list_url || "a&&href";
let list_url_prefix = p.list_url_prefix || "";
let is_tab_js = p.tabs.trim().startsWith("js:");
for (let i = 0; i < playFrom.length; i++) {
let tab_name = playFrom[i];
let tab_ext = p.tabs.split(";").length > 1 && !is_tab_js ? p.tabs.split(";")[1] : "";
let p1 = p.lists.replaceAll("#idv", tab_name).replaceAll("#id", i);
tab_ext = tab_ext.replaceAll("#idv", tab_name).replaceAll("#id", i);
let tabName = tab_ext ? _pdfh(html, tab_ext) : tab_name;
console.log(tabName);
let new_vod_list = [];
let tt1 = (new Date).getTime();
if (typeof pdfl === "function") {
new_vod_list = pdfl(html, p1, list_text, list_url, MY_URL);
if (list_url_prefix) {
new_vod_list = new_vod_list.map(it => it.split("$")[0] + "$" + list_url_prefix + it.split("$").slice(1).join("$"))
}
} else {
let vodList = [];
try {
vodList = _pdfa(html, p1);
console.log("len(vodList):" + vodList.length)
} catch (e) {}
for (let i = 0; i < vodList.length; i++) {
let it = vodList[i];
new_vod_list.push(_pdfh(it, list_text).trim() + "$" + list_url_prefix + _pd(it, list_url, MY_URL))
}
}
if (new_vod_list.length > 0) {
new_vod_list = forceOrder(new_vod_list, "", x => x.split("$")[0]);
console.log(`drpy影响性能代码共计列表数循环次数:${new_vod_list.length},耗时:${(new Date).getTime()-tt1}毫秒`)
}
let vlist = new_vod_list.join("#");
vod_tab_list.push(vlist)
}
vod_play_url = vod_tab_list.join(vod_play_url)
}
}
vod.vod_play_url = vod_play_url
}
if (rule.图片替换 && rule.图片替换.includes("=>")) {
let replace_from = rule.图片替换.split("=>")[0];
let replace_to = rule.图片替换.split("=>")[1];
vod.vod_pic = vod.vod_pic.replace(replace_from, replace_to)
}
if (rule.图片来源 && vod.vod_pic && vod.vod_pic.startsWith("http")) {
vod.vod_pic = vod.vod_pic + rule.图片来源
}
if (!vod.vod_id || vod_id.includes("$") && vod.vod_id !== vod_id) {
vod.vod_id = vod_id
}
let t2 = (new Date).getTime();
console.log(`加载二级界面${MY_URL}耗时:${t2-t1}毫秒`);
try {
vod = vodDeal(vod)
} catch (e) {
console.log(`vodDeal发生错误:${e.message}`)
}
return JSON.stringify({
list: [vod]
})
}
function get_tab_index(vod) {
let obj = {};
vod.vod_play_from.split("$$$").forEach((it, index) => {
obj[it] = index
});
return obj
}
function vodDeal(vod) {
let vod_play_from = vod.vod_play_from.split("$$$");
let vod_play_url = vod.vod_play_url.split("$$$");
let tab_removed_list = vod_play_from;
let tab_ordered_list = vod_play_from;
let tab_renamed_list = vod_play_from;
let tab_list = vod_play_from;
let play_ordered_list = vod_play_url;
if (rule.tab_remove && rule.tab_remove.length > 0 || rule.tab_order && rule.tab_order.length > 0) {
let tab_index_dict = get_tab_index(vod);
if (rule.tab_remove && rule.tab_remove.length > 0) {
tab_removed_list = vod_play_from.filter(it => !rule.tab_remove.includes(it));
tab_list = tab_removed_list
}
if (rule.tab_order && rule.tab_order.length > 0) {
let tab_order = rule.tab_order;
tab_ordered_list = tab_removed_list.sort((a, b) => {
const getOrderIndex = (tabName, orderRules) => {
for (let i = 0; i < orderRules.length; i++) {
if (tabName.includes(orderRules[i])) {
return i
}
}
return 9999
};
const indexA = getOrderIndex(a, tab_order);
const indexB = getOrderIndex(b, tab_order);
return indexA - indexB
});
tab_list = tab_ordered_list
}
play_ordered_list = tab_list.map(it => vod_play_url[tab_index_dict[it]])
}
if (rule.tab_rename && typeof rule.tab_rename === "object" & Object.keys(rule.tab_rename).length > 0) {
tab_renamed_list = tab_list.map(it => rule.tab_rename[it] || it);
tab_list = tab_renamed_list
}
vod.vod_play_from = tab_list.join("$$$");
vod.vod_play_url = play_ordered_list.join("$$$");
return vod
}
function tellIsJx(url) {
try {
let is_vip = !/\.(m3u8|mp4|m4a)$/.test(url.split("?")[0]) && 是否正版(url);
return is_vip ? 1 : 0
} catch (e) {
return 1
}
}
function playParse(playObj) {
fetch_params = JSON.parse(JSON.stringify(rule_fetch_params));
MY_URL = playObj.url;
var MY_FLAG = playObj.flag;
if (!/http/.test(MY_URL)) {
try {
MY_URL = base64Decode(MY_URL)
} catch (e) {}
}
MY_URL = decodeURIComponent(MY_URL);
var input = MY_URL;
var flag = MY_FLAG;
let common_play = {
parse: SPECIAL_URL.test(input) || /^(push:)/.test(input) ? 0 : 1,
url: input,
flag: flag,
jx: tellIsJx(input)
};
let lazy_play;
if (!rule.play_parse || !rule.lazy) {
lazy_play = common_play
} else if (rule.play_parse && rule.lazy && typeof rule.lazy === "string") {
try {
let lazy_code = rule.lazy.trim();
if (lazy_code.startsWith("js:")) {
lazy_code = lazy_code.replace("js:", "").trim()
}
print("开始执行js免嗅=>" + lazy_code);
eval(lazy_code);
lazy_play = typeof input === "object" ? input : {
parse: SPECIAL_URL.test(input) || /^(push:)/.test(input) ? 0 : 1,
jx: tellIsJx(input),
url: input
}
} catch (e) {
print(`js免嗅错误:${e.message}`);
lazy_play = common_play
}
} else {
lazy_play = common_play
}
if (Array.isArray(rule.play_json) && rule.play_json.length > 0) {
let web_url = lazy_play.url;
for (let pjson of rule.play_json) {
if (pjson.re && (pjson.re === "*" || web_url.match(new RegExp(pjson.re)))) {
if (pjson.json && typeof pjson.json === "object") {
let base_json = pjson.json;
lazy_play = Object.assign(lazy_play, base_json);
break
}
}
}
} else if (rule.play_json && !Array.isArray(rule.play_json)) {
let base_json = {
jx: 1,
parse: 1
};
lazy_play = Object.assign(lazy_play, base_json)
} else if (!rule.play_json) {
let base_json = {
jx: 0,
parse: 1
};
lazy_play = Object.assign(lazy_play, base_json)
}
console.log(JSON.stringify(lazy_play));
return JSON.stringify(lazy_play)
}
function proxyParse(proxyObj) {
var input = proxyObj.params;
if (proxyObj.proxy_rule) {
log("准备执行本地代理规则:\n" + proxyObj.proxy_rule);
try {
eval(proxyObj.proxy_rule);
if (input && input !== proxyObj.params && Array.isArray(input) && input.length >= 3) {
return input
} else {
return [404, "text/plain", "Not Found"]
}
} catch (e) {
return [500, "text/plain", "代理规则错误:" + e.message]
}
} else {
return [404, "text/plain", "Not Found"]
}
}
function isVideoParse(isVideoObj) {
var input = isVideoObj.url;
if (!isVideoObj.t) {
let re_matcher = new RegExp(isVideoObj.isVideo, "i");
return re_matcher.test(input)
} else {
try {
eval(isVideoObj.isVideo);
if (typeof input === "boolean") {
return input
} else {
return false
}
} catch (e) {
log(`执行嗅探规则发生错误:${e.message}`);
return false
}
}
}
function removeHeader(content, options = {}) {
const {
mode = "header-only", fileType
} = options;
const COMMENT_CONFIG = {
".js": {
start: "/*",
end: "*/",
regex: /^\s*\/\*([\s\S]*?)\*\/\s*/,
headerRegex: /@header\(([\s\S]*?)\)/,
topCommentsRegex: /^(\s*(\/\/[^\n]*\n|\/\*[\s\S]*?\*\/)\s*)+/
},
".py": {
start: '"""',
end: '"""',
regex: /^\s*"""([\s\S]*?)"""\s*/,
headerRegex: /@header\(([\s\S]*?)\)/,
topCommentsRegex: /^(\s*(#[^\n]*\n|'''[\s\S]*?'''|"""[\s\S]*?""")\s*)+/
}
};
if (!fileType) throw new Error("fileType option is required");
const ext = fileType.startsWith(".") ? fileType : `.${fileType}`;
const config = COMMENT_CONFIG[ext];
if (!config) throw new Error(`Unsupported file type: ${ext}`);
if (mode === "top-comments") {
const match = content.match(config.topCommentsRegex);
if (match) {
return content.substring(match[0].length).trim()
}
return content.trim()
}
const match = content.match(config.regex);
if (!match) return content.trim();
let [fullComment, innerContent] = match;
if (config.headerRegex.test(innerContent)) {
innerContent = innerContent.replace(config.headerRegex, "");
const cleanedInner = innerContent.split("\n").filter(line => line.trim().length > 0).join("\n");
if (!cleanedInner.trim()) {
return content.replace(fullComment, "").trim()
} else {
const newComment = `${config.start}${cleanedInner}${config.end}`;
return content.replace(fullComment, newComment).trim()
}
}
return content.trim()
}
function getOriginalJs(js_code) {
let current_match = /var rule|function|let |var |const|class Rule|async|this\./;
if (current_match.test(js_code)) {
return js_code
}
js_code = removeHeader(js_code, {
mode: "top-comments",
fileType: ".js"
});
let rsa_private_key = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqin/jUpqM6+fgYP/oMqj9zcdHMM0mEZXLeTyixIJWP53lzJV2N2E3OP6BBpUmq2O1a9aLnTIbADBaTulTNiOnVGoNG58umBnupnbmmF8iARbDp2mTzdMMeEgLdrfXS6Y3VvazKYALP8EhEQykQVarexR78vRq7ltY3quXx7cgI0ROfZz5Sw3UOLQJ+VoWmwIxu9AMEZLVzFDQN93hzuzs3tNyHK6xspBGB7zGbwCg+TKi0JeqPDrXxYUpAz1cQ/MO+Da0WgvkXnvrry8NQROHejdLVOAslgr6vYthH9bKbsGyNY3H+P12kcxo9RAcVveONnZbcMyxjtF5dWblaernAgMBAAECggEAGdEHlSEPFmAr5PKqKrtoi6tYDHXdyHKHC5tZy4YV+Pp+a6gxxAiUJejx1hRqBcWSPYeKne35BM9dgn5JofgjI5SKzVsuGL6bxl3ayAOu+xXRHWM9f0t8NHoM5fdd0zC3g88dX3fb01geY2QSVtcxSJpEOpNH3twgZe6naT2pgiq1S4okpkpldJPo5GYWGKMCHSLnKGyhwS76gF8bTPLoay9Jxk70uv6BDUMlA4ICENjmsYtd3oirWwLwYMEJbSFMlyJvB7hjOjR/4RpT4FPnlSsIpuRtkCYXD4jdhxGlvpXREw97UF2wwnEUnfgiZJ2FT/MWmvGGoaV/CfboLsLZuQKBgQDTNZdJrs8dbijynHZuuRwvXvwC03GDpEJO6c1tbZ1s9wjRyOZjBbQFRjDgFeWs9/T1aNBLUrgsQL9c9nzgUziXjr1Nmu52I0Mwxi13Km/q3mT+aQfdgNdu6ojsI5apQQHnN/9yMhF6sNHg63YOpH+b+1bGRCtr1XubuLlumKKscwKBgQDOtQ2lQjMtwsqJmyiyRLiUOChtvQ5XI7B2mhKCGi8kZ+WEAbNQcmThPesVzW+puER6D4Ar4hgsh9gCeuTaOzbRfZ+RLn3Aksu2WJEzfs6UrGvm6DU1INn0z/tPYRAwPX7sxoZZGxqML/z+/yQdf2DREoPdClcDa2Lmf1KpHdB+vQKBgBXFCVHz7a8n4pqXG/HvrIMJdEpKRwH9lUQS/zSPPtGzaLpOzchZFyQQBwuh1imM6Te+VPHeldMh3VeUpGxux39/m+160adlnRBS7O7CdgSsZZZ/dusS06HAFNraFDZf1/VgJTk9BeYygX+AZYu+0tReBKSs9BjKSVJUqPBIVUQXAoGBAJcZ7J6oVMcXxHxwqoAeEhtvLcaCU9BJK36XQ/5M67ceJ72mjJC6/plUbNukMAMNyyi62gO6I9exearecRpB/OGIhjNXm99Ar59dAM9228X8gGfryLFMkWcO/fNZzb6lxXmJ6b2LPY3KqpMwqRLTAU/zy+ax30eFoWdDHYa4X6e1AoGAfa8asVGOJ8GL9dlWufEeFkDEDKO9ww5GdnpN+wqLwePWqeJhWCHad7bge6SnlylJp5aZXl1+YaBTtOskC4Whq9TP2J+dNIgxsaF5EFZQJr8Xv+lY9lu0CruYOh9nTNF9x3nubxJgaSid/7yRPfAGnsJRiknB5bsrCvgsFQFjJVs=";
let decode_content = "";
function aes_decrypt(data) {
let key = CryptoJS.enc.Hex.parse("686A64686E780A0A0A0A0A0A0A0A0A0A");
let iv = CryptoJS.enc.Hex.parse("647A797964730A0A0A0A0A0A0A0A0A0A");
let encrypted = CryptoJS.AES.decrypt({
ciphertext: CryptoJS.enc.Base64.parse(data)
}, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
return encrypted
}
let error_log = false;
function logger(text) {
if (error_log) {
log(text)
}
}
let decode_funcs = [text => {
try {
return ungzip(text)
} catch (e) {
logger("非gzip加密");
return ""
}
}, text => {
try {
return base64Decode(text)
} catch (e) {
logger("非b64加密");
return ""
}
}, text => {
try {
return aes_decrypt(text)
} catch (e) {
logger("非aes加密");
return ""
}
}, text => {
try {
return RSA.decode(text, rsa_private_key, null)
} catch (e) {
logger("非rsa加密");
return ""
}
}];
let func_index = 0;
while (!current_match.test(decode_content)) {
decode_content = decode_funcs[func_index](js_code);
func_index++;
if (func_index >= decode_funcs.length) {
break
}
}
return decode_content
}
function runMain(main_func_code, arg) {
let mainFunc = function() {
return ""
};
try {
eval(main_func_code + "\nmainFunc=main;");
return mainFunc(arg)
} catch (e) {
log(`执行main_funct发生了错误:${e.message}`);
return ""
}
}
function init(ext) {
console.log("init");
rule = {};
rule_fetch_params = {};
fetch_params = null;
try {
let muban = 模板.getMubans();
if (typeof ext == "object") {
rule = ext
} else if (typeof ext == "string") {
let is_file = ext.startsWith("file://");
if (ext.startsWith("http") || is_file) {
let query = getQuery(ext);
if (is_file) {
ext = ext.split("?")[0]
}
let js = request(ext, {
method: "GET"
});
if (js) {
js = getOriginalJs(js);
eval("(function(){" + js.replace("var rule", "rule") + "})()")
}
if (query.type === "url" && query.params) {
if (is_file && /^http/.test(query.params)) {
rule.params = query.params
} else {
rule.params = urljoin(ext, query.params)
}
} else if (query.params) {
rule.params = query.params
}
} else {
ext = getOriginalJs(ext);
eval("(function(){" + ext.replace("var rule", "rule") + "})()")
}
} else {
console.log(`规则加载失败,不支持的规则类型:${typeof ext}`);
return
}
rule.host = (rule.host || "").rstrip("/");
HOST = rule.host;
if (rule.hostJs) {
console.log(`检测到hostJs,准备执行...`);
try {
eval(rule.hostJs);
rule.host = HOST.rstrip("/")
} catch (e) {
console.log(`执行${rule.hostJs}获取host发生错误:${e.message}`)
}
}
if (rule["模板"] === "自动") {
try {
let host_headers = rule["headers"] || {};
let host_html = getCode(HOST, {
headers: host_headers
});
let match_muban = "";
let muban_keys = Object.keys(muban).filter(it => !/默认|短视2|采集1/.test(it));
for (let muban_key of muban_keys) {
try {
let host_data = JSON.parse(home({}, host_html, muban[muban_key].class_parse));
if (host_data.class && host_data.class.length > 0) {
match_muban = muban_key;
console.log(`自动匹配模板:【${muban_key}`);
break
}
} catch (e) {
console.log(`自动匹配模板:【${muban_key}】错误:${e.message}`)
}
}
if (match_muban) {
muban["自动"] = muban[match_muban];
if (rule["模板修改"] && rule["模板修改"].startsWith("js:")) {
eval(rule["模板修改"].replace("js:", "").trim())
}
} else {
delete rule["模板"]
}
} catch (e) {
delete rule["模板"]
}
}
if (rule.模板 && muban.hasOwnProperty(rule.模板)) {
print("继承模板:" + rule.模板);
rule = Object.assign(muban[rule.模板], rule)
}
let rule_cate_excludes = (rule.cate_exclude || "").split("|").filter(it => it.trim());
let rule_tab_excludes = (rule.tab_exclude || "").split("|").filter(it => it.trim());
rule_cate_excludes = rule_cate_excludes.concat(CATE_EXCLUDE.split("|").filter(it => it.trim()));
rule_tab_excludes = rule_tab_excludes.concat(TAB_EXCLUDE.split("|").filter(it => it.trim()));
rule.cate_exclude = rule_cate_excludes.join("|");
rule.tab_exclude = rule_tab_excludes.join("|");
rule.类型 = rule.类型 || "影视";
rule.url = rule.url || "";
rule.double = rule.double || false;
rule.homeUrl = rule.homeUrl || "";
rule.detailUrl = rule.detailUrl || "";
rule.searchUrl = rule.searchUrl || "";
rule.homeUrl = rule.host && rule.homeUrl ? urljoin(rule.host, rule.homeUrl) : rule.homeUrl || rule.host;
rule.homeUrl = cheerio.jinja2(rule.homeUrl, {
rule: rule
});
rule.detailUrl = rule.host && rule.detailUrl ? urljoin(rule.host, rule.detailUrl) : rule.detailUrl;
rule.二级访问前 = rule.二级访问前 || "";
if (rule.url.includes("[") && rule.url.includes("]")) {
let u1 = rule.url.split("[")[0];
let u2 = rule.url.split("[")[1].split("]")[0];
rule.url = rule.host && rule.url ? urljoin(rule.host, u1) + "[" + urljoin(rule.host, u2) + "]" : rule.url
} else {
rule.url = rule.host && rule.url ? urljoin(rule.host, rule.url) : rule.url
}
if (rule.searchUrl.includes("[") && rule.searchUrl.includes("]") && !rule.searchUrl.includes("#")) {
let u1 = rule.searchUrl.split("[")[0];
let u2 = rule.searchUrl.split("[")[1].split("]")[0];
rule.searchUrl = rule.host && rule.searchUrl ? urljoin(rule.host, u1) + "[" + urljoin(rule.host, u2) + "]" : rule.searchUrl
} else {
rule.searchUrl = rule.host && rule.searchUrl ? urljoin(rule.host, rule.searchUrl) : rule.searchUrl
}
rule.timeout = rule.timeout || 5e3;
rule.encoding = rule.编码 || rule.encoding || "utf-8";
rule.search_encoding = rule.搜索编码 || rule.search_encoding || "";
rule.图片来源 = rule.图片来源 || "";
rule.图片替换 = rule.图片替换 || "";
rule.play_json = rule.hasOwnProperty("play_json") ? rule.play_json : [];
rule.pagecount = rule.hasOwnProperty("pagecount") ? rule.pagecount : {};
rule.proxy_rule = rule.hasOwnProperty("proxy_rule") ? rule.proxy_rule : "";
if (!rule.hasOwnProperty("sniffer")) {
rule.sniffer = false
}
rule.sniffer = rule.hasOwnProperty("sniffer") ? rule.sniffer : "";
rule.sniffer = !!(rule.sniffer && rule.sniffer !== "0" && rule.sniffer !== "false");
rule.isVideo = rule.hasOwnProperty("isVideo") ? rule.isVideo : "";
if (rule.sniffer && !rule.isVideo) {
rule.isVideo = "http((?!http).){12,}?\\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg|m4a|mp3)\\?.*|http((?!http).){12,}\\.(m3u8|mp4|flv|avi|mkv|rm|wmv|mpg|m4a|mp3)|http((?!http).)*?video/tos*|http((?!http).)*?obj/tos*"
}
rule.tab_remove = rule.hasOwnProperty("tab_remove") ? rule.tab_remove : [];
rule.tab_order = rule.hasOwnProperty("tab_order") ? rule.tab_order : [];
rule.tab_rename = rule.hasOwnProperty("tab_rename") ? rule.tab_rename : {};
if (rule.headers && typeof rule.headers === "object") {
try {
let header_keys = Object.keys(rule.headers);
for (let k of header_keys) {
if (k.toLowerCase() === "user-agent") {
let v = rule.headers[k];
console.log(v);
if (["MOBILE_UA", "PC_UA", "UC_UA", "IOS_UA", "UA"].includes(v)) {
rule.headers[k] = eval(v)
}
} else if (k.toLowerCase() === "cookie") {
let v = rule.headers[k];
if (v && v.startsWith("http")) {
console.log(v);
try {
v = fetch(v);
console.log(v);
rule.headers[k] = v
} catch (e) {
console.log(`${v}获取cookie发生错误:${e.message}`)
}
}
}
}
} catch (e) {
console.log(`处理headers发生错误:${e.message}`)
}
} else {
rule.headers = {}
}
oheaders = deepCopy(rule.headers);
rule_fetch_params = {
headers: rule.headers,
timeout: rule.timeout,
encoding: rule.encoding
};
RKEY = typeof key !== "undefined" && key ? key : "drpy_" + (rule.title || rule.host);
pre();
init_test()
} catch (e) {
console.log(`init_test发生错误:${e.message}`)
}
}
let homeHtmlCache = undefined;
function home(filter, home_html, class_parse) {
console.log("home");
home_html = home_html || "";
class_parse = class_parse || "";
if (typeof rule.filter === "string" && rule.filter.trim().length > 0) {
try {
let filter_json = ungzip(rule.filter.trim());
rule.filter = JSON.parse(filter_json)
} catch (e) {
rule.filter = {}
}
}
let homeObj = {
filter: rule.filter || false,
MY_URL: rule.homeUrl,
class_name: rule.class_name || "",
class_url: rule.class_url || "",
class_parse: class_parse || rule.class_parse || "",
cate_exclude: rule.cate_exclude,
home_html: home_html
};
return homeParse(homeObj)
}
function homeVod(params) {
console.log("homeVod");
let homeVodObj = {
"推荐": rule.推荐,
double: rule.double,
homeUrl: rule.homeUrl,
detailUrl: rule.detailUrl
};
return homeVodParse(homeVodObj)
}
function category(tid, pg, filter, extend) {
let cateObj = {
url: rule.url,
"一级": rule.一级,
tid: tid,
pg: parseInt(pg),
filter: filter,
extend: extend
};
return categoryParse(cateObj)
}
function detail(vod_url) {
let orId = vod_url;
let fyclass = "";
log("orId:" + orId);
if (vod_url.indexOf("$") > -1) {
let tmp = vod_url.split("$");
fyclass = tmp[0];
vod_url = tmp[1]
}
let detailUrl = vod_url.split("@@")[0];
let url;
if (!detailUrl.startsWith("http") && !detailUrl.includes("/")) {
url = rule.detailUrl.replaceAll("fyid", detailUrl).replaceAll("fyclass", fyclass)
} else if (detailUrl.includes("/")) {
url = urljoin(rule.homeUrl, detailUrl)
} else {
url = detailUrl
}
let detailObj = {
orId: orId,
url: url,
"二级": rule.二级,
"二级访问前": rule.二级访问前,
detailUrl: detailUrl,
fyclass: fyclass,
tab_exclude: rule.tab_exclude
};
return detailParse(detailObj)
}
function play(flag, id, flags) {
let playObj = {
url: id,
flag: flag,
flags: flags
};
return playParse(playObj)
}
function search(wd, quick, pg) {
if (rule.search_encoding) {
if (rule.search_encoding.toLowerCase() !== "utf-8") {
wd = encodeStr(wd, rule.search_encoding)
}
} else if (rule.encoding && rule.encoding.toLowerCase() !== "utf-8") {
wd = encodeStr(wd, rule.encoding)
}
let searchObj = {
searchUrl: rule.searchUrl,
"搜索": rule.搜索,
wd: wd,
pg: pg || 1,
quick: quick
};
return searchParse(searchObj)
}
function proxy(params) {
if (rule.proxy_rule && rule.proxy_rule.trim()) {
rule.proxy_rule = rule.proxy_rule.trim()
}
if (rule.proxy_rule.startsWith("js:")) {
rule.proxy_rule = rule.proxy_rule.replace("js:", "")
}
let proxyObj = {
params: params,
proxy_rule: rule.proxy_rule
};
return proxyParse(proxyObj)
}
function sniffer() {
let enable_sniffer = rule.sniffer || false;
if (enable_sniffer) {
log("开始执行辅助嗅探代理规则...")
}
return enable_sniffer
}
function isVideo(url) {
let t = 0;
let is_video;
if (rule.isVideo && rule.isVideo.trim()) {
is_video = rule.isVideo.trim()
}
if (is_video.startsWith("js:")) {
is_video = is_video.replace("js:", "");
t = 1
}
let isVideoObj = {
url: url,
isVideo: is_video,
t: t
};
let result = isVideoParse(isVideoObj);
if (result) {
log("成功执行辅助嗅探规则并检测到视频地址:\n" + rule.isVideo)
}
return result
}
function getRule(key) {
return key ? rule[key] || "" : rule
}
function deepCopy(_obj) {
return JSON.parse(JSON.stringify(_obj))
}
function matchesAll(str, pattern, flatten) {
if (!pattern.global) {
pattern = new RegExp(pattern.source, "g" + (pattern.ignoreCase ? "i" : "") + (pattern.multiline ? "m" : ""))
}
var matches = [];
var match;
while ((match = pattern.exec(str)) !== null) {
matches.push(match)
}
return flatten ? matches.flat() : matches
}
function stringUtils() {
Object.defineProperties(String.prototype, {
replaceX: {
value: function(regex, replacement) {
let matches = matchesAll(this, regex, true);
if (matches && matches.length > 1) {
const hasCaptureGroup = /\$\d/.test(replacement);
if (hasCaptureGroup) {
return this.replace(regex, m => m.replace(regex, replacement))
} else {
return this.replace(regex, (m, p1) => m.replace(p1, replacement))
}
}
return this.replace(regex, replacement)
},
configurable: true,
enumerable: false,
writable: true
},
parseX: {
get: function() {
try {
return JSON.parse(this)
} catch (e) {
console.log(e.message);
return this.startsWith("[") ? [] : {}
}
},
configurable: true,
enumerable: false
}
})
}
function cut(text, start, end, method, All) {
let result = "";
let c = (t, s, e) => {
let result = "";
let rs = [];
let results = [];
try {
let lr = new RegExp(String.raw`${s}`.toString());
let rr = new RegExp(String.raw`${e}`.toString());
const segments = t.split(lr);
if (segments.length < 2) return "";
let cutSegments = segments.slice(1).map(segment => {
let splitSegment = segment.split(rr);
return splitSegment.length < 2 ? undefined : splitSegment[0] + e
}).filter(f => f);
if (All) {
return `[${cutSegments.join(",")}]`
} else {
return cutSegments[0]
}
} catch (e) {
console.log(`Error cutting text:${e.message}`)
}
return result
};
result = c(text, start, end);
stringUtils();
if (method && typeof method === "function") {
result = method(result)
}
return result
}
function DRPY() {
return {
runMain: runMain,
getRule: getRule,
init: init,
home: home,
homeVod: homeVod,
category: category,
detail: detail,
play: play,
search: search,
proxy: proxy,
sniffer: sniffer,
isVideo: isVideo,
fixAdM3u8Ai: fixAdM3u8Ai
}
}
export default {
runMain: runMain,
getRule: getRule,
init: init,
home: home,
homeVod: homeVod,
category: category,
detail: detail,
play: play,
search: search,
proxy: proxy,
sniffer: sniffer,
isVideo: isVideo,
fixAdM3u8Ai: fixAdM3u8Ai,
DRPY: DRPY
};
+61
View File
@@ -0,0 +1,61 @@
var rule = {
title: '奇珍异兽[官]',
host: 'https://www.iqiyi.com',
homeUrl: '',
detailUrl: 'https://pcw-api.iqiyi.com/video/video/videoinfowithuser/fyid?agent_type=1&authcookie=&subkey=fyid&subscribe=1',
searchUrl: 'https://search.video.iqiyi.com/o?if=html5&key=**&pageNum=fypage&pos=1&pageSize=24&site=iqiyi',
searchable: 2,
multi: 1,
filterable: 1,
filter: 'H4sIAAAAAAAAA+1Z7U7bSBR9F/9mJY+/zausqihqrRZtC6u0XQlVSEBICGFJAO2Gj6ShLB+hNIHQUkqchr6MZ+y8RSd4fO8syw9UUSmL8ieKzz25vj5zfeaO8kYhyvivb5TfvGllXHkx9cRTxpTJ9AuPX4Xdr3S1wErr1C9z9I/089feNXmSB2nuqJ89GsD8QpkZi9Ew22LrLXZYiwOEJAF60ItOFyDgJDirtIPLYuh/i3Fj5tEgElfz6lnG81KP06+8p1OZ6dTEE6m0sy6tL9+xKFqp0aXGjRuHhTOWzcWgBcziUdD7T+1svhlW1mJUc1zI0FijnW4MA8jmVtlsRWRQgVo8DXvHIoOLiQubgb8Uw7ZUQ/iXSEs01G8RbkY0x4AU9WOeReA6Zs4W2fw20BO4/3adbR4kbCiPnlxQvyXKs3XTklch4z2dmJpE6WmtTf/07yr9Sik6FZmhuP7hFrs8xTzXqrogQHhVotWeCMADNRvs/GMM6pBo5z0wUZGNA1ZrCqaKjfapDVxCiIkVtql/iGXAStLcBT3JxgFTFmTaS2ckOa6fm3bOg27vjqJoqmaJG/JvEmoCakqoAaghoTqguoRqgGoSSgAlEqoCqiJK3AQlroQ6gDoSagNqS6j1C/8Qtxxck9QAlJ6eqJyhqsBQ1QFDBYarXsu5JwjEddUU/3CB4NwkOAOCgwRWPWcb7yFsGTxs87C0ihMvU7+/zjx+ln7p3VzM6PNCdN65a4fnVgRZGQcdg6/bdG3zRtsH3U1garyUR2OK9vC9t7wf7Qmbxf6NjmbBfLH7+tkevZxPuKqFr7RkwDpaQuswuHon6PASyF5GLBNyyJsA0eH9ki1Ys12CW0G+yjZykAd+ELVXwHA1Fy107oJmy4nr3Oq3lglF8s2A7RYEbqDllPK0LEwOX1W51zV0SWn30fEppW1Csx0Hkyw1UHGuLeqVr7G3yeaGlZx8oN0viVoI/53H3cZQ72+byOc4X+QFqYPLFrdsWm6zzhUY963bBHelkUGPDPr+DVp/8AYd+D6rLIoZFO1MciIb3SVfDfxlgUL/RovH4cKFcCLd1aHdWemf/pZYZBu9b/+Kqy9QIr3N/b0dSIJzG/c5VpxN5jPCm/7eTId7Sx42IV3aQnhgv4IBY2QuI3P5CeZiPHhz4UVFu7PJVKRJs9tqPWyuYQC0Y3PL/GQOAUOaU9bBTHjAgp5j73ZptYW/uPV8PgiY9+YcYjDZb/S38iI/liMfP1HsZoM7nEBvPWL+e5oauczwusyw2Yj18G2ktsPHlLCRjAGadJJcOKPlPQwRbB62tQ2DhuZCr/Z3PgWd1WTSIHgSigr1qFOAAA444Yc6jCDcSGznJ517yMgBRg7wQ6cUc2gt4P/SxsO2pGR4l3Qo9NHtodVn1PI/uKTD+0/kEOgz8x1+Ib/uqh0AAA==',
url: 'https://pcw-api.iqiyi.com/search/recommend/list?channel_id=fyclass&data_type=1&page_id=fypage&ret_num=24',
filter_url: 'is_purchase={{fl.is_purchase}}&mode={{fl.mode}}&three_category_id={{fl.three_category_id}}&market_release_date_level={{fl.year}}&region={{fl.region}}',
headers: {
'User-Agent': 'MOBILE_UA'
},
timeout: 5000,
class_name: '电视剧&短剧&电影&综艺&少儿&动漫&漫剧&纪录片&知识',
class_url: '2&35&1&6&15&4&37&3&31',
limit: 20,
play_parse: true,
lazy: $js.toString(() => {
try {
let api = "" + input.split("?")[0];
console.log(api);
let response = fetch(api, {
method: 'get',
headers: {
'User-Agent': 'okhttp/3.14.9',
'Content-Type': 'application/x-www-form-urlencoded'
}
});
let bata = JSON.parse(response);
if (bata.url.includes("qiyi")) {
input = {
parse: 0,
url: bata.url,
jx: 0,
danmaku: "http://127.0.0.1:9978/proxy?do=danmu&site=js&url=" + input.split("?")[0]
};
} else {
input = {
parse: 0,
url: input.split("?")[0],
jx: 1,
danmaku: "http://127.0.0.1:9978/proxy?do=danmu&site=js&url=" + input.split("?")[0]
};
}
} catch {
input = {
parse: 0,
url: input.split("?")[0],
jx: 1,
danmaku: "http://127.0.0.1:9978/proxy?do=danmu&site=js&url=" + input.split("?")[0]
};
}
}),
推荐: '',
一级: 'js:let d=[];var rm=input.match(/&region=([^&]+)/);if(rm&&rm[1]){input=input.replace(/three_category_id=[^&]*/,"three_category_id="+rm[1])}input=input.replace(/&region=[^&]*/g,"");if(MY_CATE==="1"||MY_CATE==="4"){input=input.replace("search/recommend/list","search/video/videolists").replace("page_id=","pageNum=").replace("ret_num=24","pageSize=24")}let html=request(input);let json=JSON.parse(html);if(json.code==="A00003"){fetch_params.headers["user-agent"]=PC_UA;json=JSON.parse(fetch(input,fetch_params))}json.data.list.forEach(function(data){var vid=data.albumId||data.tvId;if(data.channelId===1){desc=data.score?data.score+"分\\t":"";if(data.duration)desc+=data.duration}else if(data.channelId===2||data.channelId===4||data.channelId===35||data.channelId===15||data.channelId===37){if(data.latestOrder===data.videoCount){desc=(data.score?data.score+"分\\t":"")+data.latestOrder+"集全"}else{if(data.videoCount){desc=(data.score?data.score+"分\\t":"")+data.latestOrder+"/"+data.videoCount+"集"}else if(data.latestOrder){desc="更新至 "+data.latestOrder+"集"}else{desc=data.focus||""}}}else if(data.channelId===6){desc=data.period+"期"}else{if(data.latestOrder){desc="更新至 第"+data.latestOrder+"期"}else if(data.period){desc=data.period}else{desc=data.focus||""}}url=MY_CATE+"$"+vid;d.push({url:url,title:data.name,desc:desc,pic_url:data.imageUrl.replace(".jpg","_390_520.jpg?caplist=jpg,webp")})});setResult(d);',
二级: 'js:let d=[];let html=request(input);let json=JSON.parse(html).data;VOD={vod_id:"",vod_url:input,vod_name:"",type_name:"",vod_actor:"",vod_year:"",vod_director:"",vod_area:"",vod_content:"",vod_remarks:"",vod_pic:""};VOD.vod_name=json.name;try{if(json.latestOrder){VOD.vod_remarks="类型: "+(json.categories[0].name||"")+"\\t"+(json.categories[1].name||"")+"\\t"+(json.categories[2].name||"")+"\\t"+"评分:"+(json.score||"")+"\\n更新至:第"+json.latestOrder+"集(期)/共"+json.videoCount+"集(期)"}else{VOD.vod_remarks="类型: "+(json.categories[0].name||"")+"\\t"+(json.categories[1].name||"")+"\\t"+(json.categories[2].name||"")+"\\t"+"评分:"+(json.score||"")+json.period}}catch(e){VOD.vod_remarks=json.subtitle}VOD.vod_area=(json.focus||"")+"\\n资费:"+(json.payMark===1?"VIP":"免费")+"\\n地区:"+(json.areas||"");let vsize="579_772";try{vsize=json.imageSize[12]}catch(e){}VOD.vod_pic=json.imageUrl.replace(".jpg","_"+vsize+".jpg?caplist=jpg,webp");VOD.type_name=json.categories.map(function(it){return it.name}).join(",");if(json.people.main_charactor){let vod_actors=[];json.people.main_charactor.forEach(function(it){vod_actors.push(it.name)});VOD.vod_actor=vod_actors.join(",")}VOD.vod_content=json.description;let playlists=[];if(json.channelId===1){playlists=[{playUrl:json.playUrl,imageUrl:json.imageUrl,shortTitle:json.shortTitle,focus:json.focus,period:json.period}]}else{if(json.channelId===6){let qs=json.period.split("-")[0];let listUrl="https://pcw-api.iqiyi.com/album/source/svlistinfo?cid=6&sourceid="+json.albumId+"&timelist="+qs;let playData=JSON.parse(request(listUrl)).data[qs];playData.forEach(function(it){playlists.push({playUrl:it.playUrl,imageUrl:it.imageUrl,shortTitle:it.shortTitle,focus:it.focus,period:it.period})})}else{let listUrl="https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid="+json.albumId+"&size=200&page=1";let data=JSON.parse(request(listUrl)).data;let total=data.total;playlists=data.epsodelist;if(total>200){for(let i=2;i<total/200+1;i++){let listUrl="https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid="+json.albumId+"&size=200&page="+i;let data=JSON.parse(request(listUrl)).data;playlists=playlists.concat(data.epsodelist)}}}}playlists.forEach(function(it){d.push({title:it.shortTitle||"第"+it.order+"集",desc:it.subtitle||it.focus||it.period,img:it.imageUrl.replace(".jpg","_480_270.jpg?caplist=jpg,webp"),url:it.playUrl})});VOD.vod_play_from="qiyi";VOD.vod_play_url=d.map(function(it){return it.title+"$"+it.url}).join("#");',
搜索: 'json:.data.docinfos;.albumDocInfo.albumTitle;.albumDocInfo.albumVImage;.albumDocInfo.channel;.albumDocInfo.albumId;.albumDocInfo.tvFocus',
}
+243
View File
@@ -0,0 +1,243 @@
//小心儿悠悠//
var rule = {
title: '百忙无果[官]',
host: 'https://pianku.api.mgtv.com',
homeUrl: '',
searchUrl: 'https://mobileso.bz.mgtv.com/msite/search/v2?q=**&pn=fypage&pc=10',
detailUrl: 'https://pcweb.api.mgtv.com/episode/list?page=1&size=50&video_id=fyid',
searchable: 2,
quickSearch: 0,
filterable: 1,
multi: 1,
url: '/rider/list/pcweb/v3?platform=pcweb&channelId=fyclass&pn=fypage&pc=80&hudong=1&_support=10000000&kind=a1&area=a1',
filter_url: 'year={{fl.year or "all"}}&sort={{fl.sort or "all"}}&chargeInfo={{fl.chargeInfo or "all"}}',
headers: {
'User-Agent': 'PC_UA'
},
timeout: 5000,
class_name: '电视剧&电影&综艺&动漫&纪录片&教育&少儿',
class_url: '2&3&1&50&51&115&10',
filter: {
"1": getCommonFilter(),
"2": getCommonFilter(),
"3": getCommonFilter(),
"50": getCommonFilter(),
"51": getCommonFilter(),
"115": getCommonFilter()
},
limit: 20,
play_parse: true,
lazy: $js.toString(() => {
try {
let api = input.split("?")[0];
let response = fetch(api, {
method: 'get',
headers: {
'User-Agent': 'okhttp/3.14.9',
'Content-Type': 'application/x-www-form-urlencoded'
}
});
let bata = JSON.parse(response);
input = {
parse: 0,
url: bata.url.includes("mgtv") ? bata.url : input.split("?")[0],
jx: bata.url.includes("mgtv") ? 0 : 1,
danmaku: "http://103.36.220.166:98/tvbox/zh.php?url=" + input.split("?")[0]
};
} catch {
input = {
parse: 0,
url: input.split("?")[0],
jx: 1,
danmaku: "http://103.36.220.166:98/tvbox/zh.php?url=" + input.split("?")[0]
};
}
}),
一级: 'json:data.hitDocs;title;img;updateInfo||rightCorner.text;playPartId',
二级: $js.toString(() => {
fetch_params.headers.Referer = "https://www.mgtv.com";
fetch_params.headers["User-Agent"] = MOBILE_UA;
let videoId = input.split('video_id=')[1].split('&')[0];
let infoUrl = `https://pcweb.api.mgtv.com/video/info?allowedRC=1&vid=${videoId}&type=b&_support=10000000`;
let infoData = JSON.parse(request(infoUrl));
if (infoData && infoData.data && infoData.data.info) {
let detail = infoData.data.info.detail || {};
VOD = {
vod_name: infoData.data.info.title || "",
type_name: detail.kind || "",
vod_year: detail.releaseTime || "",
vod_area: detail.area || "",
vod_actor: detail.leader || "",
vod_director: detail.director || "",
vod_content: detail.story || "",
vod_remarks: detail.updateInfo || ""
};
if (detail.img) VOD.vod_pic = detail.img;
}
let d = [];
let html = request(input);
let json = JSON.parse(html);
let host = "https://www.mgtv.com";
let ourl = json.data.list.length > 0 ? json.data.list[0].url : json.data.series[0].url;
if (!/^http/.test(ourl)) ourl = host + ourl;
fetch_params.headers["User-Agent"] = MOBILE_UA;
html = request(ourl);
if (html.includes("window.location =")) {
ourl = pdfh(html, "meta[http-equiv=refresh]&&content").split("url=")[1];
html = request(ourl);
}
try {
let details = pdfh(html, ".m-details&&Html").replace(/h1>/, "h6>").replace(/div/g, "br");
let actor = "", director = "", time = "";
if (/播出时间/.test(details)) {
actor = pdfh(html, "p:eq(5)&&Text").substr(0, 25);
director = pdfh(html, "p:eq(4)&&Text");
time = pdfh(html, "p:eq(3)&&Text");
} else {
actor = pdfh(html, "p:eq(4)&&Text").substr(0, 25);
director = pdfh(html, "p:eq(3)&&Text");
time = "已完结";
}
let _img = pd(html, ".video-img&&img&&src");
let JJ = pdfh(html, ".desc&&Text").split("牛马简介:")[1];
VOD.vod_name = VOD.vod_name || pdfh(html, ".vt-txt&&Text");
VOD.type_name = VOD.type_name || pdfh(html, "p:eq(0)&&Text").substr(0, 6);
VOD.vod_area = VOD.vod_area || pdfh(html, "p:eq(1)&&Text");
VOD.vod_actor = VOD.vod_actor || actor;
VOD.vod_director = VOD.vod_director || director;
VOD.vod_remarks = VOD.vod_remarks || time;
VOD.vod_pic = VOD.vod_pic || _img;
VOD.vod_content = VOD.vod_content || JJ;
if (!VOD.vod_name) VOD.vod_name = VOD.type_name;
} catch (e) {
log("获取影片信息发生错误:" + e.message);
}
function getRjpg(imgUrl, xs) {
xs = xs || 3;
let picSize = /jpg_/.test(imgUrl) ? imgUrl.split("jpg_")[1].split(".")[0] : false;
let rjpg = false;
if (picSize) {
let a = parseInt(picSize.split("x")[0]) * xs;
let b = parseInt(picSize.split("x")[1]) * xs;
rjpg = a + "x" + b + ".jpg";
}
return /jpg_/.test(imgUrl) && rjpg ? imgUrl.replace(imgUrl.split("jpg_")[1], rjpg) : imgUrl;
}
if (json.data.total === 1 && json.data.list.length === 1) {
let data = json.data.list[0];
d.push({
title: data.t4,
desc: data.t2,
pic_url: getRjpg(data.img),
url: "https://www.mgtv.com" + data.url
});
} else if (json.data.list.length > 1) {
for (let i = 1; i <= json.data.total_page; i++) {
if (i > 1) json = JSON.parse(fetch(input.replace("page=1", "page=" + i), {}));
json.data.list.forEach(function(data) {
if (data.isIntact == "1") {
d.push({
title: data.t4,
desc: data.t2,
pic_url: getRjpg(data.img),
url: "https://www.mgtv.com" + data.url
});
}
});
}
} else {
print(input + "暂无片源");
}
VOD.vod_play_from = "芒果TV";
VOD.vod_play_url = d.map(function(it) {
return it.title + "$" + it.url;
}).join("#");
setResult(d);
}),
搜索: $js.toString(() => {
fetch_params.headers.Referer = "https://www.mgtv.com";
fetch_params.headers["User-Agent"] = MOBILE_UA;
let d = [];
let html = request(input);
let json = JSON.parse(html);
json.data.contents.forEach(function(data) {
if (data.type && data.type == 'media') {
let item = data.data[0];
if (item.source === "imgo") {
let fyclass = '';
try {
fyclass = item.rpt.match(/idx=(.*?)&/)[1] + '$';
} catch (e) {
log(e.message);
}
d.push({
title: item.title.replace(/<B>|<\/B>/g, ''),
img: item.img || '',
content: '',
desc: item.desc.join(','),
url: fyclass + item.url.match(/.*\/(.*?)\.html/)[1]
});
}
}
});
setResult(d);
})
};
function getCommonFilter() {
return [{
"key": "chargeInfo",
"name": "付费类型",
"value": [
{"n": "全部", "v": "all"},
{"n": "免费", "v": "b1"},
{"n": "vip", "v": "b2"},
{"n": "VIP用券", "v": "b3"},
{"n": "付费点播", "v": "b4"}
]
}, {
"key": "sort",
"name": "排序",
"value": [
{"n": "最新", "v": "c1"},
{"n": "最热", "v": "c2"},
{"n": "知乎高分", "v": "c4"}
]
}, {
"key": "year",
"name": "年代",
"value": [
{"n": "全部", "v": "all"},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"},
{"n": "2009", "v": "2009"},
{"n": "2008", "v": "2008"},
{"n": "2007", "v": "2007"},
{"n": "2006", "v": "2006"},
{"n": "2005", "v": "2005"},
{"n": "2004", "v": "2004"}
]
}];
}
+50 -764
View File
@@ -1,136 +1,67 @@
var rule = {
title: '腾讯视频',
title: '小心儿悠悠',
host: 'https://v.qq.com',
homeUrl: '/x/bu/pagesheet/list?_all=1&append=1&channel=cartoon&listpage=1&offset=0&pagesize=21&iarea=-1&sort=18',
detailUrl: 'https://node.video.qq.com/x/api/float_vinfo2?cid=fyid',
searchUrl: '**',
searchable: 2,
searchable: 1,
filterable: 1,
multi: 1,
url: '/x/bu/pagesheet/list?_all=1&append=1&channel=fyclass&listpage=1&offset=((fypage-1)*21)&pagesize=21&iarea=-1',
filter_url: 'sort={{fl.sort or 75}}&iyear={{fl.iyear}}&year={{fl.year}}&itype={{fl.type}}&ifeature={{fl.feature}}&iarea={{fl.area}}&itrailer={{fl.itrailer}}&gender={{fl.sex}}',
// 解析接口配置
parse_url: [
"http://jiexi.fc8001.top/PSddOykaLgfqIgIg.php?url=",
"http://niubi.69mini.com/api/?key=09a74556faba7e3b528d64b9c9a86a61&url=",
"https://test1.12321app.com/cpi.php?url=",
"http://114.66.21.157:2666/wmm.php?key=368vfij631ykdf&api=tx&url=",
"http://mlwl.7766.org:55/api/?key=Fg8hz4dZkcniXKc6J5&url=",
"http://www.ckplayer.vip/jiexi/?url=",
"http://nsys.hundong.xyz/api/?key=EdG87gW0IDYarx9ry5&url=",
"https://json.xophp.com/api/?key=5492ef7d5a1646338426e2f45b8c8e0d&url=",
'https://api.jisuyunjifei.top/api/?key=7c2c39e57dc03852ea60f0432efb2836&player&url=',
"http://103.236.72.166:188/api/?key=veniDOaEzSzIThZsyb&url=",
'https://jx.xmflv.com/?url=',
'http://global.apirun.xn--vsqw5hh18a8vw.com:2025/api/?key=63c856aac8b205a5cb972ae8950cfd78&url=',
'https://jx.77flv.cc/?url='
],
// 屏蔽地址
blocked_urls: [
'http://sspa8.top:99/jpg/1060089351.mp4',
'https://hwmov.a.yximgs.com/upic/2026/05/20/12/BMjAyNjA1MjAxMjIyNThfMTY1NTYxNDk0NF8xOTY0Mjg2MzEzNzNfMl8z_b_B2d85e883e7ab00ad52949e6bcad9fa59.mp4',
'https://txmov2.a.kwimgs.com/upic/2026/04/24/22/BMjAyNjA0MjQyMjMxMTdfNTE1Njg1NzUyXzE5NDAxNDg2MjI0N18yXzM=_b_Be9cdf9b3f66017f25b1e9f7c1135de53.mp4',
'https://gitee.com/nm_nm/interface/raw/master/ips/ips(20250418105556)_001.ts',
'IP使用次数超限,请加群签到.mp4'
],
filter: 'H4sIAAAAAAAAA+1YW08TWxR+92fMMySdFlrwEY3RnOT4YnzQ8DDBOaERqamVHGJIWksvXFIoYvEcSuViDxVpKYpYprb+mdl7Zv6Fe9rurrXdqE2OlRd4mvnW7L3u31r0mTIxGQpO6MrV+8+Uh/qsclV5EgpHlAFlWnvEUIVm1oixwt5ntKmn7c+mXTgfteJlF2YvgWFlbqCL01y1g4/4ujgpNuyjeY6ryty4K2krDM7qWhg0krMTs74naSSJkhMvdW4YZDcMXGkLvB6vvwO3HrFgGARgI3sZAnwI4z7AfRj3Au7FuAq4inEP4B6Eq6NdnD0ifATwEYwHAA9gHBxW/RgHf9kji/H4gBKZ6UtuA6Pn59a1B+X2L12LPA3roNM6rpPCUg/ZbeNW+pjGE/xuULnyxt7jMGSEPj+0ctkODAl04g1Se96BId+kckoM7ie4SXJ5srDfgSG65udtUkxx70Fl+T+zuc0LGy5JbtINbiCEim7F7eoSdwdKw9l6S7INjiP3M1VSKXAcebS1Rl8VOQ4uWcsZMFIFn6z9LDmroxRx/F2BLkY5HkB659H3I/1t18tuhW59FJoJXigZR2Yf/49uXTyyGgdyoy0eky8bUrcKjaZ62B8q5HjZ3okimR+0CF3eOgfZEfnClX2vCZhIRcwRW6XRHJZ5kPkla104h0KcfmUaC1gfZFjgl9Y5qArmuGgnqlTWwd/YCXcKdNOSIVLMJMnKe3wnyOxozFpI4XNQDnbxC2QTwZUqabwUT7n9gYrlkgn6xwQzWjioR2b7wgVukC4Z/ffkcUILR0Kh6YvkdC2sayi9+SpZNnrmdJJMsBPSBkY3ijR/KG9gh/tWMyNxCUmcmnVOr0O/cODgPQVNAcz0UGAieSLrkmvOP28loxm30xwfWygDqwXrMCsHJLbEho+0HzrldTDlO4sXqlFxtgVwuEmtJi92dHuHbHZnDLo//9o0DLTA8e8zJWs1KS+O4uxAkayvkETtx/l0x+glm/wmNpkMTj3ozz90aHHBXPLNyH+i/430RfdJ+l3vVFL8QMq8z1ATr38C2Nsn5vLhVjqPucSS/klFO815WvnYsyWeQR95H5McHxr0AwyWBAZHAQa7VQ9DzXrRrC3KG3aiJFjVpqBWxTwITYT6UzAijwcjYS04peM4pQzyIkZzZz3HaWzsmhQkstlwF+l8FXgL/L45dpvHSUV2/nnzDzm/LYZzdrNO7IXE0rfu3OX3DPsQG2d2rZNd4QzTA+rvXb8BMPSPnWjalSM7dUDSpzJlk9W02cjTvEELvMf851fh8K8blMwYa/5Uih+b1c7e6+6CD/ibpvn5X6kE2UihuZRstDA8UP9tmsaSlAg2nGH0AT/aJ7ts2MiTieUAhjP+SaNxQHfSUibFXzTQYrKRoDBAW/09PvcVk1nUEv4UAAA=',
headers: {
'User-Agent': 'PC_UA'
},
timeout: 5000,
cate_exclude: '会员|游戏|全部',
class_name: '推荐&电影&电视剧&综艺&动漫&少儿&纪录片',
class_url: 'choice&movie&tv&variety&cartoon&child&doco',
class_name: '电影&电视剧&综艺&动漫&少儿&纪录片',
class_url: 'movie&tv&variety&cartoon&child&doco',
limit: 20,
play_parse: true,
lazy: $js.toString(() => {
let parseIndex = 0;
let targetUrl = '';
// 确定要解析的目标URL
try {
let bata = JSON.parse(response);
log(bata);
if (bata.url && bata.url.includes("http")) {
targetUrl = bata.url;
} else {
targetUrl = input.split("?")[0];
}
} catch {
targetUrl = input.split("?")[0];
}
// 检查是否是屏蔽地址
function isBlockedUrl(url) {
if (!url) return true;
return rule.blocked_urls.some(blocked => url.includes(blocked));
}
// 解析函数
function tryParse(url, index) {
if (index >= rule.parse_url.length) {
// 所有解析接口都失败,使用默认解析
log('所有解析接口都尝试失败,使用默认解析');
input = {
header: { 'User-Agent': "" },
parse: 0,
url: targetUrl,
jx: 1,
danmaku: 'http://127.0.0.1:9978/proxy?do=danmu&site=js&url=' + targetUrl
};
return;
}
let parseUrl = rule.parse_url[index] + encodeURIComponent(url);
log('尝试解析接口 ' + (index + 1) + ': ' + parseUrl);
let result = fetch(parseUrl, {
method: 'GET',
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://v.qq.com/'
},
timeout: 10000
});
try {
let data = JSON.parse(result);
if (data && data.url && data.url.includes("http")) {
// 检查是否是屏蔽地址
if (isBlockedUrl(data.url)) {
log('解析接口 ' + (index + 1) + ' 返回了屏蔽地址,尝试下一个接口');
tryParse(url, index + 1);
return;
}
log('解析接口 ' + (index + 1) + ' 成功: ' + data.url);
input = {
header: { 'User-Agent': "" },
parse: 0,
url: data.url,
jx: 0,
danmaku: 'http://127.0.0.1:9978/proxy?do=danmu&site=js&url=' + targetUrl
};
} else {
log('解析接口 ' + (index + 1) + ' 返回数据无效,尝试下一个');
tryParse(url, index + 1);
}
} catch (e) {
log('解析接口 ' + (index + 1) + ' 失败: ' + e.message);
tryParse(url, index + 1);
}
let d = [];
let url1 = JSON.parse(request("" + input)).url;
function isEncrypted(url) {
return url.includes('baidu.con/') && url.length > 16;
}
// 开始解析
tryParse(targetUrl, 0);
let url;
if (isEncrypted(url1)) {
console.log("");
var withoutDomain = url1.replace(/^https:\/\/baidu\.con\//, '');
var first16Chars = withoutDomain.substring(0, 16);
var remainingString = withoutDomain.substring(16);
var key = CryptoJS.enc.Utf8.parse(first16Chars);
var iv = key;
function AES_Decrypt(word) {
var srcs = word;
var decrypt = CryptoJS.AES.decrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return decrypt.toString(CryptoJS.enc.Utf8);
};
url = AES_Decrypt(remainingString);
} else {
console.log("");
url = url1;
}
input = {
url: url,
parse: 0,
header: rule.headers
}
setResult(d);
}),
推荐: '.list_item;img&&alt;img&&src;a&&Text;a&&data-float',
一级: '.list_item;img&&alt;img&&src;a&&Text;a&&data-float',
二级: $js.toString(() => {
VOD = {};
let d = [];
@@ -141,7 +72,6 @@ var rule = {
let sourceId = /get_playsource/.test(input) ? input.match(/id=(\d*?)&/)[1] : input.split("cid=")[1];
let cid = sourceId;
let detailUrl = "https://v.qq.com/detail/m/" + cid + ".html";
try {
let json = JSON.parse(html);
VOD = {
@@ -154,10 +84,7 @@ var rule = {
vod_remarks: json.rec,
vod_pic: urljoin2(input, json.c.pic)
}
} catch (e) {
log("解析详情失败: " + e.message);
}
} catch (e) {}
if (/get_playsource/.test(input)) {
eval(html);
let indexList = QZOutputJson.PlaylistItem.indexList;
@@ -169,7 +96,6 @@ var rule = {
d.push({
title: item.title,
pic_url: item.pic,
desc: item.episode_number + "\t\t\t播放量:" + item.thirdLine,
url: item.playUrl
})
});
@@ -179,7 +105,6 @@ var rule = {
let json = JSON.parse(html);
video_lists = json.c.video_ids;
let url = "https://v.qq.com/x/cover/" + sourceId + ".html";
if (video_lists.length === 1) {
let vid = video_lists[0];
let o_url = "https://union.video.qq.com/fcgi-bin/data?otype=json&tid=1804&appid=20001238&appkey=6c03bbe9658448a4&union_platform=1&idlist=" + vid;
@@ -226,6 +151,7 @@ var rule = {
let playUrl = [];
let ygKeywords = ["预告", "花絮", "片花", "特辑", "幕后", "采访", "制作", "MV", "主题曲"];
let yg = d.filter(function(it) {
return it.type && ygKeywords.some(keyword => it.type.includes(keyword));
});
@@ -234,7 +160,7 @@ var rule = {
});
if (zp.length > 0) {
playFrom.push("腾讯(拒绝买卖!请去12315举报)");
playFrom.push("正片");
playUrl.push(zp.map(it => it.title + "$" + it.url).join("#"));
}
@@ -260,7 +186,6 @@ var rule = {
VOD.vod_play_from = playFrom.join("$$$");
VOD.vod_play_url = playUrl.join("$$$");
}),
搜索: $js.toString(() => {
let d = [],
keyword = input.split("/")[3];
@@ -279,17 +204,11 @@ var rule = {
isPrefetch: true,
pagesize: 30,
queryFrom: 0,
searchDatakey: "",
transInfo: "",
isneedQc: true,
preQid: "",
adClientInfo: "",
extraInfo: {
isNewMarkLabel: "1",
multi_terminal_pc: "1",
themeType: "1",
sugRelatedIds: "{}",
appVersion: ""
themeType: "1"
}
}),
headers: {
@@ -303,8 +222,8 @@ var rule = {
}
const nonMainContentKeywords = [
'', '#', '特辑', '剪辑', '片花', '独家', '专访', '纯享',
'制作', '幕后', '宣传', 'MV', '主题曲', '插曲', '彩蛋',
'', '#', '特辑', '“', '剪辑', '片花', '独家', '专访', '纯享',
'制作', '幕后', '宣传', '看点', '主题曲', '插曲', '彩蛋',
'精彩', '集锦', '盘点', '回顾', '解说', '评测', '反应', 'reaction'
];
@@ -314,11 +233,6 @@ var rule = {
return !nonMainContentKeywords.some(keyword => title.includes(keyword));
}
function isQQPlatform(playSites) {
if (!playSites || !Array.isArray(playSites)) return true;
return playSites.some(site => site.enName && site.enName.toLowerCase() === 'qq');
}
try {
let html = vodSearch(keyword, 0);
let json = JSON.parse(html);
@@ -327,10 +241,7 @@ var rule = {
if (!itemList) return;
itemList.forEach(it => {
if (it.doc && it.doc.id && it.videoInfo &&
isMainContent(it.videoInfo.title) &&
isQQPlatform(it.videoInfo.playSites)) {
if (it.doc && it.doc.id && it.videoInfo && isMainContent(it.videoInfo.title)) {
const itemId = it.doc.id;
if (!seenIds.has(itemId)) {
seenIds.add(itemId);
@@ -360,630 +271,5 @@ var rule = {
}
setResult(d);
}),
filter: {
"choice": [{
"key": "sort",
"name": "排序",
"value": [{
"n": "最热",
"v": "75"
}, {
"n": "最新",
"v": "83"
}, {
"n": "好评",
"v": "81"
}]
}, {
"key": "iyear",
"name": "年代",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "2025",
"v": "2025"
}, {
"n": "2024",
"v": "2024"
}, {
"n": "2023",
"v": "2023"
}, {
"n": "2022",
"v": "2022"
}, {
"n": "2021",
"v": "2021"
}, {
"n": "2020",
"v": "2020"
}, {
"n": "2019",
"v": "2019"
}, {
"n": "2018",
"v": "2018"
}, {
"n": "2017",
"v": "2017"
}, {
"n": "2016",
"v": "2016"
}, {
"n": "2015",
"v": "2015"
}]
}],
"tv": [{
"key": "sort",
"name": "排序",
"value": [{
"n": "最热",
"v": "75"
}, {
"n": "最新",
"v": "79"
}, {
"n": "好评",
"v": "16"
}]
}, {
"key": "feature",
"name": "类型",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "爱情",
"v": "1"
}, {
"n": "古装",
"v": "2"
}, {
"n": "悬疑",
"v": "3"
}, {
"n": "都市",
"v": "4"
}, {
"n": "家庭",
"v": "5"
}, {
"n": "喜剧",
"v": "6"
}, {
"n": "传奇",
"v": "7"
}, {
"n": "武侠",
"v": "8"
}, {
"n": "军旅",
"v": "9"
}, {
"n": "权谋",
"v": "10"
}, {
"n": "革命",
"v": "11"
}, {
"n": "现实",
"v": "13"
}, {
"n": "青春",
"v": "14"
}, {
"n": "猎奇",
"v": "15"
}, {
"n": "科幻",
"v": "16"
}, {
"n": "竞技",
"v": "17"
}, {
"n": "玄幻",
"v": "18"
}]
}, {
"key": "iyear",
"name": "年代",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "2025",
"v": "2025"
}, {
"n": "2024",
"v": "2024"
}, {
"n": "2023",
"v": "2023"
}, {
"n": "2022",
"v": "2022"
}, {
"n": "2021",
"v": "2021"
}, {
"n": "2020",
"v": "2020"
}, {
"n": "2019",
"v": "2019"
}, {
"n": "2018",
"v": "2018"
}, {
"n": "2017",
"v": "2017"
}, {
"n": "2016",
"v": "2016"
}, {
"n": "2015",
"v": "2015"
}]
}],
"movie": [{
"key": "sort",
"name": "排序",
"value": [{
"n": "最热",
"v": "75"
}, {
"n": "最新",
"v": "83"
}, {
"n": "好评",
"v": "81"
}]
}, {
"key": "type",
"name": "类型",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "犯罪",
"v": "4"
}, {
"n": "励志",
"v": "2"
}, {
"n": "喜剧",
"v": "100004"
}, {
"n": "热血",
"v": "100061"
}, {
"n": "悬疑",
"v": "100009"
}, {
"n": "爱情",
"v": "100005"
}, {
"n": "科幻",
"v": "100012"
}, {
"n": "恐怖",
"v": "100010"
}, {
"n": "动画",
"v": "100015"
}, {
"n": "战争",
"v": "100006"
}, {
"n": "家庭",
"v": "100017"
}, {
"n": "剧情",
"v": "100022"
}, {
"n": "奇幻",
"v": "100016"
}, {
"n": "武侠",
"v": "100011"
}, {
"n": "历史",
"v": "100021"
}, {
"n": "老片",
"v": "100013"
}, {
"n": "西部",
"v": "3"
}, {
"n": "记录片",
"v": "100020"
}]
}, {
"key": "year",
"name": "年代",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "2025",
"v": "2025"
}, {
"n": "2024",
"v": "2024"
}, {
"n": "2023",
"v": "2023"
}, {
"n": "2022",
"v": "2022"
}, {
"n": "2021",
"v": "2021"
}, {
"n": "2020",
"v": "2020"
}, {
"n": "2019",
"v": "2019"
}, {
"n": "2018",
"v": "2018"
}, {
"n": "2017",
"v": "2017"
}, {
"n": "2016",
"v": "2016"
}, {
"n": "2015",
"v": "2015"
}]
}],
"variety": [{
"key": "sort",
"name": "排序",
"value": [{
"n": "最热",
"v": "75"
}, {
"n": "最新",
"v": "23"
}]
}, {
"key": "iyear",
"name": "年代",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "2025",
"v": "2025"
}, {
"n": "2024",
"v": "2024"
}, {
"n": "2023",
"v": "2023"
}, {
"n": "2022",
"v": "2022"
}, {
"n": "2021",
"v": "2021"
}, {
"n": "2020",
"v": "2020"
}, {
"n": "2019",
"v": "2019"
}, {
"n": "2018",
"v": "2018"
}, {
"n": "2017",
"v": "2017"
}, {
"n": "2016",
"v": "2016"
}, {
"n": "2015",
"v": "2015"
}]
}],
"cartoon": [{
"key": "sort",
"name": "排序",
"value": [{
"n": "最热",
"v": "75"
}, {
"n": "最新",
"v": "83"
}, {
"n": "好评",
"v": "81"
}]
}, {
"key": "area",
"name": "地区",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "内地",
"v": "1"
}, {
"n": "日本",
"v": "2"
}, {
"n": "欧美",
"v": "3"
}, {
"n": "其他",
"v": "4"
}]
}, {
"key": "type",
"name": "类型",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "玄幻",
"v": "9"
}, {
"n": "科幻",
"v": "4"
}, {
"n": "武侠",
"v": "13"
}, {
"n": "冒险",
"v": "3"
}, {
"n": "战斗",
"v": "5"
}, {
"n": "搞笑",
"v": "1"
}, {
"n": "恋爱",
"v": "7"
}, {
"n": "魔幻",
"v": "6"
}, {
"n": "竞技",
"v": "20"
}, {
"n": "悬疑",
"v": "17"
}, {
"n": "日常",
"v": "15"
}, {
"n": "校园",
"v": "16"
}, {
"n": "真人",
"v": "18"
}, {
"n": "推理",
"v": "14"
}, {
"n": "历史",
"v": "19"
}, {
"n": "经典",
"v": "3"
}, {
"n": "其他",
"v": "12"
}]
}, {
"key": "iyear",
"name": "年代",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "2025",
"v": "2025"
}, {
"n": "2024",
"v": "2024"
}, {
"n": "2023",
"v": "2023"
}, {
"n": "2022",
"v": "2022"
}, {
"n": "2021",
"v": "2021"
}, {
"n": "2020",
"v": "2020"
}, {
"n": "2019",
"v": "2019"
}, {
"n": "2018",
"v": "2018"
}, {
"n": "2017",
"v": "2017"
}, {
"n": "2016",
"v": "2016"
}, {
"n": "2015",
"v": "2015"
}]
}],
"child": [{
"key": "sort",
"name": "排序",
"value": [{
"n": "最热",
"v": "75"
}, {
"n": "最新",
"v": "76"
}, {
"n": "好评",
"v": "20"
}]
}, {
"key": "sex",
"name": "性别",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "女孩",
"v": "1"
}, {
"n": "男孩",
"v": "2"
}]
}, {
"key": "area",
"name": "地区",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "内地",
"v": "3"
}, {
"n": "日本",
"v": "2"
}, {
"n": "其他",
"v": "1"
}]
}, {
"key": "iyear",
"name": "年龄段",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "0-3岁",
"v": "1"
}, {
"n": "4-6岁",
"v": "2"
}, {
"n": "7-9岁",
"v": "3"
}, {
"n": "10岁以上",
"v": "4"
}, {
"n": "全年龄段",
"v": "7"
}]
}],
"doco": [{
"key": "sort",
"name": "排序",
"value": [{
"n": "最热",
"v": "75"
}, {
"n": "最新",
"v": "74"
}]
}, {
"key": "itrailer",
"name": "出品方",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "BBC",
"v": "1"
}, {
"n": "国家地理",
"v": "4"
}, {
"n": "HBO",
"v": "3175"
}, {
"n": "NHK",
"v": "2"
}, {
"n": "历史频道",
"v": "7"
}, {
"n": "ITV",
"v": "3530"
}, {
"n": "探索频道",
"v": "3174"
}, {
"n": "ZDF",
"v": "3176"
}, {
"n": "腾讯自制",
"v": "15"
}, {
"n": "合作机构",
"v": "6"
}, {
"n": "其他",
"v": "5"
}]
}, {
"key": "type",
"name": "类型",
"value": [{
"n": "全部",
"v": "-1"
}, {
"n": "自然",
"v": "4"
}, {
"n": "美食",
"v": "10"
}, {
"n": "社会",
"v": "3"
}, {
"n": "人文",
"v": "6"
}, {
"n": "历史",
"v": "1"
}, {
"n": "军事",
"v": "2"
}, {
"n": "科技",
"v": "8"
}, {
"n": "财经",
"v": "14"
}, {
"n": "探险",
"v": "15"
}, {
"n": "罪案",
"v": "7"
}, {
"n": "竞技",
"v": "12"
}, {
"n": "旅游",
"v": "11"
}]
}]
}
};
})
};
+184
View File
@@ -0,0 +1,184 @@
//小心儿悠悠//
var rule = {
title: '优酷视频',
host: 'https://www.%79%6f%75%6b%75.com',
homeUrl: '',
searchUrl: 'https://search.%79%6f%75%6b%75.com/api/search?pg=fypage&keyword=**',
searchable: 2,
quickSearch: 0,
filterable: 1,
multi: 1,
url: '/category/data?optionRefresh=1&pageNo=fypage&params=fyfilter',
filter_url: '{{fl}}',
filter: 'H4sIAAAAAAAAA+1cWW9bOZZ+718x8MO8jAJY2d0PA3R19TQKaHT3Q2OAwaBgKIkmZXTi1DhOGulGAfIiW14kb7ItW3K8yZY3bV61WNKfueS9+hfNK/KcQ0qy4qSc8mQQwHDyncPLSx6S53w85PU/uuzomZMaYxOprl//9z+6/up/1/Xrrpe+vv5e34Df1+Xp6ve99AsRC+7VR/ZYIsemS0L61vfijb/xRH8bpRB1/eRRurGgqxD1S4VVSLN4RUqxEC9km0vUd1eElKqZaa1jJseLVSxRX9+nEi6IV1BnVyOkc4Gmc6bypHOBpuPLO6QTgCeOSHeitcgF7nPfu1ppxEHf89fN9rPzZfZ+6gr7kVK3X31tnsd2oFsSkFGSznZQ6RTALofyfAR0CmCdIxVWGIY6JcA6lxLULQk8fPa9fTRHPQ/FrNIE9FwCGu44X8YmSYCWzoVFabC0BPjc0DkbmYHnJMDn0rts+gCek4DeN19f2cf3NQCaYL/mnE+CCSQAnVVesaobMJ8kwP6ld0mnAOqGj+ylOdBJgO+rHPDNELxPAmxn5pyV0tBOCVAXGWMzx6CTAHXFU6u8DToJqO8JvjaHfW8AfQa+8/sGWlawW0XlqhWMSjkDfyWVd7vvPlTCxn/h/QI8IPkDXX6f5Pd1+T2S39Pld0l+V5d7Se7V5d0k79bk3h6Ui/9q8sckf6zLH5H8kS6n/nr1/nqpv169v977d8QvaqzX62LqOY+f8mWYpa6q2xio14O+wTctzsJJjfKNyyuGipSGsw3u2ZU5e+qIz8NcI7NZl7F68MIZP2Chc2gpPXlxzDLTdnlBaWh0WPiE5caswqRdqikldcxeT5O3eGB06kffu97Bdz/6qVtWOeacFq/wgc1Ks1thoVNSGu///O7PLf2Q1VDfdSu/GhikxtjlSzYb4pF5VpppaUyzUm+MPZJmpV2eCNQPwZfR1BFivpQzjOXVlWyn4mRHW2zsapKrYtB4tEpd+v6n7z1ufGaV/GcNzp0isxGMO8RnIyR3iNIq+LaNxCycE5YFfyaBFolFTDeiu6dNQMaqISCD7ijFT8HFKvAZgvU1gieb3LMqCSgiAbYycMCC2AMJrhN0O8X6TgGrU6BjO+OsWIZ2SoDvS82RToHrBOSOgXxyz46WyS4uwHaOTPLhVWinBNiWyawItdAWCfC5oVkeWILnJMD3TaTIZgpcJyDbpQNWWbQnxuGViMnPbjgZXFQSkMWnndB7tHgDaL5Uq1YC8oDnVhl6ooDp294MPPU3z+BPiCD1la12rsuNKuWE8kXNftiIN+SKhX/khaBVy7BkuMXjebsfd4P3vv+Vtdwia6FGebtN1nK3u7tHSLqpRLdbopva2NNt8FNvT0/3HfGLCjxuLvDYLfCYCjxqLvDILfCICpjUydvzsOcXZhnW5Sqbi7XMbnu4aJCsX55otGEMn8hCzEDY+8L3xP+it9F8smqhZFU2eGTPCc+2WrVJaTiUwxgLjbFa0E6AxQ0RBf8lVkoZHsYQYbncjJM95dGifYn+UBfhexeGnM2AvXnE8xA1DRHGpNQYXwyxZIp8ryFCs10eipBkvNcQ4aiV1wR/4vlhO7oOvlwXYbnEulUqNX5PGbW2UWBbp0esS6hVAaxv6NAOjfH3B7TFNkRov0pehDO7PMOCBbCfLsL+TmX54pxVWLIXp6G/uoiI37gzDXxGARyD/SMeWLNLwBwJY6sjU1Ztko1DCcLY5/wBC0fqsTOWgbVpiLC9axE7gvxIAiKPGywSY8lTg581S/GN50FnpMIm4/A6xMQmt8QzfDbNN8FxGSLF3MuXzkTplpn7z2PkKh2m7KByY5+BNndK/oTeO0VIqiiA82v9xCqCx1GAOGOQj8JaUeAjaLpVWXCGgQIqgFUXCjwEzVUArRkvsG0YFwXwtfurfAg6rwBN0XOWQ4orAepWVkUQAp0EqItFeGzdydScHBjJEGF/SscsDaZSANscXeenSOclwPqXxtk0OjwJtJlT316nmeMC6k9QWAb70wBko0m+UkIbNcBXBnh7DPD281YdU1NSeUVSSx5fKFcrldTxj01qfUHZqyZuaJLKVuLYRA3dcV0CL/VYVzq1OXYZIEv36ErBOAVvdDKltia7ceoovL2IVfbhe34ZpQBAIjRwKFaP7/HYIrhWxDrNKARYGJwoYQol+6wEagU0asEnavUFjV0ojCVCFyy55DZrMgCFdBGWO1vh+XUWPhRzFsrpIordCb5UNMoZIuz58YlVmqsXMXYi1nru1ILUbRdQv/ac2gF2qgF0neA9eHxCGC1WHm1EGihBGEuMpK3SUX0b8h2EiUil6rETtpcQP0ikNBH2s3oh+mUngcgQpnfN8aESvqgBaLFHWA7PSSTQ2igqs0vH1EaFcRaeVu0IqBWQBI9N7oktgEbwbmjj96G93dVJ5Da7Pm2B/rL8U2VngQEYqdpOnPO6vPVDKbEb4qbCmYpNIzS1ATxiP8WXlqklk3lWW4aWSEDrb1RLlUqAz80k69sReK6RZvQoGRpxaMoOwX5YAZy6HU6G+cYmi4MPVwAndG1WzFxoUrLK4lseJcNWf2KCt56O8hNwwwpo84FpjLABUHdc5qPIXyWgaV2iLa0CqIvs2bPoSSWgld3hkBsyBtCNdhkEyeypEGKjEOQAoFC7nIDrKAJDrq+AOaKwnuS2J4p8Dla1AlhBYcTZmqJNB2F9xj999fLHF/5B/zOa9k7tvVOpiO0PGztrmfbNSsMHNRiCWHgtvKWZVHlvOR/3deNwKxsHkuv99VJ/vXp/vdRfr95fY++B8m4zKazyzt5uY3bqSeGens/LRO99axxPIcT2NJIHpofTRTilkyk7emEV8JwLMXqtw5g9PgJeSwJ03LPT9gk8qgCuilrGTsBeXAFsXSDAlzHrJQHWeR60SylnKl+Pg+8xRFhH7pRvzLGdkPiBmnQR9vByg68BL1AAPdz6iR3NsQqmRxDj05kQn0wKFmHPgg0MEZZLjDm5CyghAeryedrbKED2D1vlcTR+A6CutM7XT+uBMWcL7+noIrR0dZdHtsDSElCMUylFDHOAtaAt2sRCqzwGaVVDhDW1O7aVrDM3x0ZrnymtKJmWQbs88qTf83PThgZtc+lcBrb67c4zf3j1N7WEzWjy6TnGDufLwp48DcOhABG4fRaEqaaAWadxZtF0WmGXY4L38sWgVYIWGSIs10j6QwkJiKy4A26kOAwRjl2HFJ+YpPbplntWncHTZ11E2+CT+hZMOwUMivFD34tnve2oNQuNCcNfNdlQqQ9J2zzgNVgmK1bqZWAACmhs0Y7iRQ8JaOodCVdrcFQp8jRRVauUrAdW2cUOjbwhInY+W6+uIDtvAH1uTOxr821C60CHDQObmdLuJUhAaYbGvkO9z9yEdGL8PL1tFTF7IUGH/c21tgH24Y6TXQOdBG13GE1bC6uwwE/26HSFMFogFK8HNqgEYezPyImT36QShLWW85UMtdwF9HSQLeKmQQIKlWt2WXj8slNF56uLaCqdOufbbL3IguhTdBFaIpvmS3iuKIHmJYWEvKQLsB2LOZaGq1AKUJ1Ve2/KqQZZ9hJr1kT6evU9b/GdglrVq6NXE2Kl1BeqYF/seAiY+B0icPfu3CeFAKh4cOchKQRAxSMhtso7VgHSRo/u/NL7h6YcqxIb6dibP8z/4JF8x4RPa97VfDJ67izDKx/A8SNeTCKqcFM5kQ4HP874gT0KkV0BHJEO96pEOCKHrwAFgzjFTwXIV5fIzypAdHKcZcA/K0BeZk6QN3QxDYD9S1bpwE0B3bNgklcBon9tDr2aNui+/nctK3K8xBaGrlqRqNRH4JtvfqtET5489TwZ6Bvse/2DRnMzlDiWwOOSuWRGfe0xk/M8fTr41qMSa8WamHuSucgCzuSwHc+ImMdjC26Zmka9G8ytwcfxDboIyv3h1Vv/v/zRN/hmwK+KvXAl/VKCpX7/HWpf/bWv/zlqfvOvv1MaHxX/01/+Qwn/x9//1P9184+b5y/o1PBWU0Q3dIzWxtVr2YmfdQm8nS9XB+837sidXIhInAKoG82zmW07Bb6OMJbQbx003TdwMseslBIhi63hPRpdRJR2hsePxQ9tbqTIo2RYYePkT/tSCzFWJYxEnEkC3WuhTgGseaLEE3ABSQF8br3oZNKSXsHTuojGboJt56wivh6xMfP9r358oc17OawsmWLzSy3j1qw0pt/BrlDzczAtYdzI7R/xxEF9BPwbYaxjpcKO9/kaMA7C2KmtIM/CVkQBrH8lVD8ecvKQ0iaMT+9N2itAjhXAd1eW+XRWRGR4N2IclPkpvlR0QnjbDbGZSEDy3ZJC+BoPvsaD/6/xQGV3KB78b1//3/t6n/v7B24kV5ZetEp4P04CXJjDx27qaV1s+IH/6SKPBE61Kri4AmKLL/ihxw4VxI8UeeqrUau8rYD8R95bo0bsVASXt1NhJ4ypi2yY7Z/a0T3BiD38bEpYzaPLVOG22xS3hYc7UuQRO2X3H9E1nliv72SMm2sTU+xi59vv/gte2sDP+t5pm/MV0WInNcxqcG5miGhr4o6SkbgzRGY5qzRPSRMpklsIjwKN40cF6rGgky17ZD7SI1aXaICHzWadedrhOLPTbA/S6ApgF+RtR9V48+qjm8zdhbFVwFivA/7n/b7+p+96Xw+2yS/UY+c803rY2Kw05ltSBGtMyUmgzUW3e3i2IbExVQtpQ+0eWupPr6yaT6+sXpVqfuJ78q63XcokNNYhZYLKLiNl4tVTJrTevXfukUIALZfyUM+lkON1xf+m5A8/lC2RbbqiqR/lLg3l/0Gn+TEJk+uR7MYavnmSzYJH7DIgCCNyFcBYYjYsvKX4zWJwcmSIdIZ71WXcTizWutwVW3Mjq2GIcEBmx4Srg5UvwXVyN6w24pYmgo2YhnqGn8FVFwVQl9k0niZM/mGJMqUKoE1OFlkVbK4AbUI27cwyT8A0IIw1LxxrzZYAnx6atgqrku5DBbqIskzu1/xq9qiadNHnyQD95XffKpF74QPbHDgXNNzdNGCvDBH1O2RV4kY5Q0QzLsvWdu3dGaNosxQ3BaWynRiSE0fI6ytwTNpGcR03JtNQV5iFlNdxabeV9P2Qm2r2RNdhhW1v+bkOTMZxcmDP+t72ve571d9sV5eQXXkwTEq9K/r3mtQY41L2w/aD8OBWLPvRxlPfPdy+8WhO3v+iDao+mvniDHpTZyTZDF1iUQC99PkJ6RTAls3ol19mjGsvv/3mN0rh/g+97vxCPQ8ngQp8ETNEfbF1+zOkw2z4zJdrmnQfvvV83b/H0fHvbHwgc/byzeu+p72NfdENXb7hl1EWzWsfsSGGEi/63vrtSI4hV9MEONG3htjOEgsW7CROd12Ey6vDJ4OskncPwCLr9JWeIcJ3NeamTGPAu3SRWU4wPWJHhsgs1/zHI5qlZumXb41iAja1Tv9eyhB9EctffTl5+8v/IwPEc9G23icDvv5nzQ39BL7qfpITCDrhbSeAh8q6CNszP82KJ/Zx1ZnCz+N1EW20rvf5uVO7sCobxufdhgjHLzRnr46an4HrIiyXDNsT+2I/wEMxNnLGcngM3qqgzYWb0BetYumYU4vrW9sWBfbv6MgqRcUKpK8IDRGWmz4U9nECQ/VL+AM3hghX0q7Yo23w0yrLwmgbIqyv8efj3C/qcR4bIrRr49qsMxS143h0oYs+A9NwzkadDO78JUBdatvJQk5BAQolZVoNCuDQHK/Y8XkYDgnQFB2u7ckxA50EpDtzc5/6R3KGCKd6eFFs0d0GYTAwRFif/EMJeEBJGGvqcOfTjsfs4SL2ogGw9/JLDyPboovMcsbCNkQ4CtlRfgbjpgC2MTfKxrGNEmAb02KNYT5GAnyu0wXJ8AlfPhcLjp42RFRu057Ae68S0MyZdyaO+URG+/iyIfp3JfsCYsxP/wRUPz6c31QAAA==',
headers: {
'User-Agent': 'PC_UA',
'Cookie': 'cna=VvNvGX3e0ywCAavVEXlnA2bg; __ysuid=1626676228345Rl1; __ayft=1652434048647; __arycid=dm-1-00; __arcms=dm-1-00; __ayvstp=85; __arpvid=1667204023100cWWdgM-1667204023112; __ayscnt=10; __aypstp=60; isg=BBwcqxvvk3BxkWQGugbLpUSf7TrOlcC_U7GAj_YdfYfvQbzLHqYGT4Hgp6m5TvgX; tfstk=c3JOByYUH20ilVucLOhh0pCtE40lZfGc-PjLHLLfuX7SWNyAiQvkeMBsIw7PWDC..; l=eBQguS-PjdJFGJT-BOfwourza77OSIRA_uPzaNbMiOCPOb1B5UxfW6yHp4T6C3GVhsGJR3rp2umHBeYBqQd-nxvOF8qmSVDmn',
'Referer': 'https://www.youku.com',
},
timeout: 5000,
class_name: '电视剧&电影&综艺&动漫&少儿&纪录片&文化&亲子&教育&搞笑&生活&体育&音乐&游戏',
class_url: '电视剧&电影&综艺&动漫&少儿&纪录片&文化&亲子&教育&搞笑&生活&体育&音乐&游戏',
limit: 20,
play_parse: true,
lazy: `js:
const blockedLinks = [
''
];
if (/\.m3u8$/.test(input)) {
input = { jx: 0, parse: 0, url: input };
} else if (/\.mp4$/.test(input)) {
input = blockedLinks.includes(input) ? null : { jx: 0, parse: 0, url: input };
} else if (/qq|iqiyi|youku|mgtv|NBY/.test(input)) {
let kurl ='' + encodeURIComponent(input);
kurl = JSON.parse(request(kurl)).url;
input = blockedLinks.includes(kurl) ? null : { jx: 0, parse: 0, url: kurl };
} else {
input = { jx: 0, parse: 1, url: input };
}`,
一级: `js:
let d = [];
MY_FL.type = MY_CATE;
let fl = stringify(MY_FL);
fl = encodeUrl(fl);
input = input.split("{")[0] + fl;
if (MY_PAGE > 1) {
let old_session = getItem("yk_session_" + MY_CATE, "{}");
input = input.replace("optionRefresh=1", "session=" + encodeUrl(old_session));
}
let html = fetch(input, fetch_params);
try {
html = JSON.parse(html);
let lists = html.data.filterData.listData;
let session = html.data.filterData.session;
session = stringify(session);
if (session !== getItem("yk_session_" + MY_CATE, "{}")) {
setItem("yk_session_" + MY_CATE, session);
}
lists.forEach(function(it) {
let vid = it.videoLink.includes("id_") ? it.videoLink.split("id_")[1].split(".html")[0] : "msearch:";
d.push({
title: it.title,
img: it.img,
desc: it.summary,
url: "https://search.youku.com/api/search?appScene=show_episode&showIds=" + vid,
content: it.subTitle
});
});
} catch (e) {
log("一级列表解析发生错误:" + e.message);
}
setResult(d);`,
二级: `js:
var d = [];
VOD = {};
let html = request(input);
let json = JSON.parse(html);
if (/keyword/.test(input)) {
input = "https://search.youku.com/api/search?appScene=show_episode&showIds=" + json.pageComponentList[0].commonData.showId;
json = JSON.parse(fetch(MY_URL, fetch_params));
}
let video_lists = json.serisesList;
var name = json.sourceName;
if (/优酷/.test(name) && video_lists.length > 0) {
let ourl = "https://v.youku.com/v_show/id_" + video_lists[0].videoId + ".html";
let _img = video_lists[0].thumbUrl;
let detailInfo = null;
try {
let detailUrl = "https://v.youku.com/v_getvideo_info/?showId=" + json.showId;
detailInfo = JSON.parse(fetch(detailUrl, {
headers: {
Referer: "https://v.youku.com/",
"User-Agent": PC_UA
}
}));
} catch (e) {
log("详情接口请求失败:" + e.message);
}
if (detailInfo && detailInfo.data) {
let v = detailInfo.data;
VOD.vod_type = v.showVideotype;
VOD.vod_year = v.lastUpdate;
VOD.vod_remarks = v.rc_title;
VOD.vod_actor = v._personNameStr;
VOD.vod_content = v.showdesc;
VOD.vod_pic = v.showLogo || _img;
VOD.vod_name = v.showTitle;
} else {
let html = fetch(ourl, {
headers: {
Referer: "https://v.youku.com/",
"User-Agent": PC_UA
}
});
let jsonData = /__INITIAL_DATA__/.test(html) ? html.split("window.__INITIAL_DATA__ =")[1].split(";")[0] : "{}";
if (jsonData === "{}") {
VOD.vod_remarks = ourl;
VOD.vod_pic = _img;
VOD.vod_name = video_lists[0].title.replace(/(\d+)/g, "");
VOD.vod_content = "触发了优酷人机验证,本次未获取详情,但不影响播放(" + ourl + ")";
} else {
try {
jsonData = JSON.parse(jsonData);
let data = jsonData.data.data;
let data_extra = data.data.extra;
let img = data_extra.showImgV;
let model = jsonData.data.model;
let m = model.detail.data.nodes[0].nodes[0].nodes[0].data;
VOD.vod_pic = img;
VOD.vod_name = m.introTitle;
VOD.vod_type = m.showGenre;
VOD.vod_remarks = m.updateInfo || m.subtitle;
VOD.vod_content = m.desc;
} catch (e) {
VOD.vod_remarks = name;
}
}
}
} else if (!/优酷/.test(name)) {
VOD.vod_content = "非自家播放源,暂无视频简介及海报";
VOD.vod_remarks = name;
}
play_url = play_url.replace("&play_url=", "&type=json&play_url=");
video_lists.forEach(function(it) {
let url = "https://v.youku.com/v_show/id_" + it.videoId + ".html";
if (it.thumbUrl) {
d.push({
desc: it.showVideoStage ? it.showVideoStage.replace("期", "集") : it.displayName,
pic_url: it.thumbUrl,
title: it.title,
url: play_url + urlencode(url)
});
} else if (name !== "优酷") {
d.push({
title: it.displayName ? it.displayName : it.title,
url: play_url + urlencode(it.url)
});
}
});
VOD.vod_play_from = name;
VOD.vod_play_url = d.map(function(it) {
return it.title + "$" + it.url;
}).join("#");`,
搜索: `js:
var d = [];
let html = request(input);
let json = JSON.parse(html);
json.pageComponentList.forEach(function(it) {
if (it.hasOwnProperty("commonData")) {
it = it.commonData;
d.push({
title: it.titleDTO.displayName,
img: it.posterDTO.vThumbUrl,
desc: it.stripeBottom,
content: it.updateNotice + " " + it.feature,
url: "https://search.youku.com/api/search?appScene=show_episode&showIds=" + it.showId + "&appCaller=h5"
});
}
});
setResult(d);`
};
+502 -240
View File
@@ -1,240 +1,502 @@
import re
import sys
from base64 import b64encode, b64decode
from urllib.parse import quote, unquote
from pyquery import PyQuery as pq
from requests import Session, adapters
from urllib3.util.retry import Retry
from concurrent.futures import ThreadPoolExecutor, as_completed
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.22a5.com"
self.session = Session()
adapter = adapters.HTTPAdapter(max_retries=Retry(total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504]), pool_connections=20, pool_maxsize=50)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
self.headers = {"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"}
self.session.headers.update(self.headers)
def getName(self): return "爱听音乐"
def isVideoFormat(self, url): return bool(re.search(r'\.(m3u8|mp4|mp3|m4a|flv)(\?|$)', url or "", re.I))
def manualVideoCheck(self): return False
def destroy(self): self.session.close()
def homeContent(self, filter):
classes = [{"type_name": n, "type_id": i} for n, i in [("歌手","/singerlist/index/index/index/index.html"), ("TOP榜单","/list/top.html"), ("新歌榜","/list/new.html"), ("电台","/radiolist/index.html"), ("高清MV","/mvlist/oumei.html"), ("专辑","/albumlist/index.html"), ("歌单","/playtype/index.html")]]
filters = {p: d for p in [c["type_id"] for c in classes if "singer" not in c["type_id"]] if (d := self._fetch_filters(p))}
if "/radiolist/index.html" not in filters:
filters["/radiolist/index.html"] = [{"key": "id", "name": "分类", "value": [{"n": n, "v": v} for n,v in zip(["最新","最热","有声小说","相声","音乐","情感","国漫","影视","脱口秀","历史","儿童","教育","八卦","推理","头条"], ["index","hot","novel","xiangyi","music","emotion","game","yingshi","talkshow","history","children","education","gossip","tuili","headline"])]}]
filters["/singerlist/index/index/index/index.html"] = [
{"key": "area", "name": "地区", "value": [{"n": n, "v": v} for n,v in [("全部","index"),("华语","huayu"),("欧美","oumei"),("韩国","hanguo"),("日本","ribrn")]]},
{"key": "sex", "name": "性别", "value": [{"n": n, "v": v} for n,v in [("全部","index"),("","male"),("","girl"),("组合","band")]]},
{"key": "genre", "name": "流派", "value": [{"n": n, "v": v} for n,v in [("全部","index"),("流行","liuxing"),("电子","dianzi"),("摇滚","yaogun"),("嘻哈","xiha"),("R&B","rb"),("民谣","minyao"),("爵士","jueshi"),("古典","gudian")]]},
{"key": "char", "name": "字母", "value": [{"n": n, "v": v} for n,v in [("全部","index")] + [{"n": chr(i), "v": chr(i).lower()} for i in range(65, 91)]]}
]
return {"class": classes, "filters": filters, "list": []}
def homeVideoContent(self): return {"list": []}
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg or 1)
url = tid
if "/singerlist/" in tid:
p = tid.split('/')
if len(p) >= 6:
url = "/".join(p[:2] + [extend.get(k, p[i]) for i, k in enumerate(["area", "sex", "genre"], 2)] + [f"{extend.get('char', 'index')}.html"])
elif "id" in extend and extend["id"] not in ["index", "top"]:
url = tid.replace("index.html", f"{extend['id']}.html").replace("top.html", f"{extend['id']}.html")
if url == tid: url = f"{tid.rsplit('/', 1)[0]}/{extend['id']}.html"
if pg > 1:
sep = "/" if any(x in url for x in ["/singerlist/", "/radiolist/", "/mvlist/", "/playtype/", "/list/"]) else "_"
url = re.sub(r'(_\d+|/\d+)?\.html$', f'{sep}{pg}.html', url)
doc = self.getpq(url)
return {"list": self._parse_list(doc(".play_list li, .video_list li, .pic_list li, .singer_list li, .ali li, .layui-row li, .base_l li"), tid), "page": pg, "pagecount": 9999, "limit": 90, "total": 999999}
def searchContent(self, key, quick, pg="1"):
return {"list": self._parse_list(self.getpq(f"/so/{quote(key)}/{pg}.html")(".base_l li, .play_list li"), "search"), "page": int(pg)}
def detailContent(self, ids):
url = self._abs(ids[0])
doc = self.getpq(url)
vod = {"vod_id": url, "vod_name": self._clean(doc("h1").text() or doc("title").text()), "vod_pic": self._abs(doc(".djpg img, .pic img, .djpic img").attr("src")), "vod_play_from": "爱听音乐", "vod_content": ""}
if any(x in url for x in ["/playlist/", "/album/", "/list/", "/singer/", "/special/", "/radio/", "/radiolist/"]):
eps = self._get_eps(doc)
page_urls = {self._abs(a.attr("href")) for a in doc(".page a, .dede_pages a, .pagelist a").items() if a.attr("href") and "javascript" not in a.attr("href")} - {url}
if page_urls:
with ThreadPoolExecutor(max_workers=5) as ex:
for r in as_completed([ex.submit(lambda u: self._get_eps(self.getpq(u)), u) for u in sorted(page_urls, key=lambda x: int(re.search(r'[_\/](\d+)\.html', x).group(1)) if re.search(r'[_\/](\d+)\.html', x) else 0)]):
eps.extend(r.result() or [])
if eps:
vod.update({"vod_play_from": "播放列表", "vod_play_url": "#".join(eps)})
return {"list": [vod]}
play_list = []
if mid := re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', url):
lrc_url = f"{self.host}/plug/down.php?ac=music&lk=lrc&id={mid.group(2)}"
play_list = [f"播放${self.e64('0@@@@' + url + '|||' + lrc_url)}"]
elif vid := re.search(r'/(video|mp4)/([^/]+)\.html', url):
with ThreadPoolExecutor(max_workers=3) as ex:
fs = {ex.submit(self._api, "/plug/down.php", {"ac": "vplay", "id": vid.group(2), "q": q}): n for n, q in [("蓝光", 1080), ("超清", 720), ("高清", 480)]}
play_list = [f"{fs[f]}${self.e64('0@@@@'+u)}" for f in as_completed(fs) if (u := f.result())]
play_list.sort(key=lambda x: {"":0, "":1, "":2}.get(x[0], 3))
vod["vod_play_url"] = "#".join(play_list) if play_list else f"解析失败${self.e64('1@@@@'+url)}"
return {"list": [vod]}
def playerContent(self, flag, id, vipFlags):
raw = self.d64(id).split("@@@@")[-1]
url, subt = raw.split("|||") if "|||" in raw else (raw, "")
url = url.replace(r"\/", "/")
if ".html" in url and not self.isVideoFormat(url):
if mid := re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', url):
if r_url := self._api("/js/play.php", method="POST", data={"id": mid.group(2), "type": "music"}, headers={"Referer": url.replace("http://","https://"), "X-Requested-With": "XMLHttpRequest"}):
url = r_url if ".php" not in r_url else url
elif vid := re.search(r'/(video|mp4)/([^/]+)\.html', url):
with ThreadPoolExecutor(max_workers=3) as ex:
for f in as_completed([ex.submit(self._api, "/plug/down.php", {"ac": "vplay", "id": vid.group(2), "q": q}) for q in [1080, 720, 480]]):
if v_url := f.result():
url = v_url; break
result = {"parse": 0, "url": url, "header": {"User-Agent": self.headers["User-Agent"]}}
if "22a5.com" in url: result["header"]["Referer"] = self.host + "/"
# OK影视3.6.5+支持LRC格式滚动歌词
if subt:
try:
r = self.session.get(subt, headers={"Referer": self.host + "/"}, timeout=5)
lrc_content = r.text
if lrc_content:
# 过滤广告内容
lrc_content = self._filter_lrc_ads(lrc_content)
result["lrc"] = lrc_content
except:
pass
return result
def _filter_lrc_ads(self, lrc_text):
"""过滤LRC歌词中的广告内容"""
lines = lrc_text.splitlines()
filtered_lines = []
# 广告关键词模式
ad_patterns = [
r'欢迎来访.*',
r'本站.*',
r'.*广告.*',
r'QQ群.*',
r'.*www\..*',
r'.*http.*',
r'.*\.com.*',
r'.*\.cn.*',
r'.*\.net.*',
r'.*音乐网.*',
r'.*提供.*',
r'.*下载.*',
]
for line in lines:
# 保留时间标签行,但过滤掉广告文本
if re.match(r'\[\d{2}:\d{2}', line):
# 检查是否包含广告
is_ad = False
for pattern in ad_patterns:
if re.search(pattern, line, re.IGNORECASE):
is_ad = True
break
if not is_ad:
filtered_lines.append(line)
else:
# 非时间标签行(可能是元数据),保留
filtered_lines.append(line)
return '\n'.join(filtered_lines)
def localProxy(self, param):
url = unquote(param.get("url", ""))
type_ = param.get("type")
if type_ == "img":
return [200, "image/jpeg", self.session.get(url, headers={"Referer": self.host + "/"}, timeout=5).content, {}]
elif type_ == "lrc":
try:
r = self.session.get(url, headers={"Referer": self.host + "/"}, timeout=5)
# 同时过滤代理中的广告
lrc_content = r.text
lrc_content = self._filter_lrc_ads(lrc_content)
return [200, "application/octet-stream", lrc_content.encode('utf-8'), {}]
except:
return [404, "text/plain", "Error", {}]
return None
def _parse_list(self, items, tid=""):
res = []
for li in items.items():
a = li("a").eq(0)
if not (href := a.attr("href")) or href == "/" or any(x in href for x in ["/user/", "/login/", "javascript"]): continue
if not (name := self._clean(li(".name").text() or a.attr("title") or a.text())): continue
pic = self._abs((li("img").attr("src") or "").replace('120', '500'))
res.append({"vod_id": self._abs(href), "vod_name": name, "vod_pic": f"{self.getProxyUrl()}&url={pic}&type=img" if pic else "", "style": {"type": "oval" if "/singer/" in href else ("list" if any(x in tid for x in ["/list/", "/playtype/", "/albumlist/"]) else "rect"), "ratio": 1 if "/singer/" in href else 1.33}})
return res
def _get_eps(self, doc):
eps = []
for li in doc(".play_list li, .song_list li, .music_list li").items():
if not (a := li("a").eq(0)).attr("href") or not re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', a.attr("href")): continue
full_url = self._abs(a.attr("href"))
lrc_part = ""
mid = re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', full_url)
if mid:
lrc_url = f"{self.host}/plug/down.php?ac=music&lk=lrc&id={mid.group(2)}"
lrc_part = f"|||{lrc_url}"
eps.append(f"{self._clean(a.text() or li('.name').text())}${self.e64('0@@@@' + full_url + lrc_part)}")
return eps
def _clean(self, text): return re.sub(r'(爱玩音乐网|视频下载说明|视频下载地址|www\.2t58\.com|MP3免费下载|LRC歌词下载|全部歌曲|\[第\d+页\]|刷新|每日推荐|最新|热门|推荐|MV|高清|无损)', '', text or "", flags=re.I).strip()
def _fetch_filters(self, url):
doc, filters = self.getpq(url), []
for i, group in enumerate([doc(s) for s in [".ilingku_fl", ".class_list", ".screen_list", ".box_list", ".nav_list"] if doc(s)]):
opts, seen = [{"n": "全部", "v": "top" if "top" in url else "index"}], set()
for a in group("a").items():
if (v := (a.attr("href") or "").split("?")[0].rstrip('/').split('/')[-1].replace('.html','')) and v not in seen:
opts.append({"n": a.text().strip(), "v": v}); seen.add(v)
if len(opts) > 1: filters.append({"key": f"id{i}" if i else "id", "name": "分类", "value": opts})
return filters
def _api(self, path, params=None, method="GET", headers=None, data=None):
try:
h = self.headers.copy()
if headers: h.update(headers)
r = (self.session.post if method == "POST" else self.session.get)(f"{self.host}{path}", params=params, data=data, headers=h, timeout=10, allow_redirects=False)
if loc := r.headers.get("Location"): return self._abs(loc.strip())
return self._abs(r.json().get("url", "").replace(r"\/", "/")) or (r.text.strip() if r.text.strip().startswith("http") else "")
except: return ""
def getpq(self, url):
import time
for _ in range(2):
try: return pq(self.session.get(self._abs(url), timeout=5).text)
except: time.sleep(0.1)
return pq("<html></html>")
def _abs(self, url): return url if url.startswith("http") else (f"{self.host}{'/' if not url.startswith('/') else ''}{url}" if url else "")
def e64(self, text): return b64encode(text.encode("utf-8")).decode("utf-8")
def d64(self, text): return b64decode(text.encode("utf-8")).decode("utf-8")
# -*- coding: utf-8 -*-
# 修复:歌手不显示歌手图片
# by:垃圾星河
# 代码指导:嗷呜呜呜呜
# 增加:自动人机验证绕过
import re
import sys
import time
from base64 import b64encode, b64decode
from urllib.parse import quote, unquote
from pyquery import PyQuery as pq
from requests import Session, adapters
from urllib3.util.retry import Retry
from concurrent.futures import ThreadPoolExecutor, as_completed
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.22a5.com"
self.session = Session()
adapter = adapters.HTTPAdapter(
max_retries=Retry(total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504]),
pool_connections=20,
pool_maxsize=50
)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
self.headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"
}
self.session.headers.update(self.headers)
def getName(self):
return "爱听音乐"
def isVideoFormat(self, url):
return bool(re.search(r'\.(m3u8|mp4|mp3|m4a|flv)(\?|$)', url or "", re.I))
def manualVideoCheck(self):
return False
def destroy(self):
self.session.close()
# ==================== 新增人机验证绕过 ====================
def _bypass_verification(self, url, response_text):
"""若当前页面是人机验证,自动提交勾选"""
if '安全人机验证' not in response_text and 'human_check' not in response_text:
return None
# 提取 csrf_token(支持多种格式)
token_match = re.search(r'name="csrf_token"\s+value="([^"]+)"', response_text)
if not token_match:
print("[爱听音乐] 未找到 csrf_token,跳过绕过")
return None
token = token_match.group(1)
# 构造提交数据
data = {
'csrf_token': token,
'human_check': 'on'
}
try:
# 发送 POST 请求,自动跟随重定向
post_resp = self.session.post(url, data=data, allow_redirects=True, timeout=10)
print("[爱听音乐] 人机验证已绕过")
return post_resp
except Exception as e:
print(f"[爱听音乐] 人机验证提交失败: {e}")
return None
def getpq(self, url):
"""获取页面并自动处理人机验证(重试3次)"""
full_url = self._abs(url)
for attempt in range(3):
try:
resp = self.session.get(full_url, timeout=5)
# 若遇到验证页,尝试绕过
if '安全人机验证' in resp.text or 'human_check' in resp.text:
print("[爱听音乐] 检测到人机验证,尝试自动绕过...")
bypass_resp = self._bypass_verification(full_url, resp.text)
if bypass_resp:
# 验证成功后,返回最终页面(可能是重定向后的目标)
return pq(bypass_resp.text)
else:
# 绕过失败,等待后重试
time.sleep(1)
continue
else:
# 正常页面直接返回
return pq(resp.text)
except Exception as e:
print(f"[爱听音乐] 请求失败 (尝试 {attempt+1}/3): {e}")
time.sleep(0.5 * (attempt + 1))
# 全部失败,返回空文档
return pq("<html></html>")
# ==================== 原有功能(保持不变) ====================
def homeContent(self, filter):
classes = [
{"type_name": n, "type_id": i}
for n, i in [
("歌手", "/singerlist/index/index/index/index.html"),
("TOP榜单", "/list/top.html"),
("新歌榜", "/list/new.html"),
("电台", "/radiolist/index.html"),
("高清MV", "/mvlist/oumei.html"),
("专辑", "/albumlist/index.html"),
("歌单", "/playtype/index.html")
]
]
filters = {}
for p in [c["type_id"] for c in classes if "singer" not in c["type_id"]]:
d = self._fetch_filters(p)
if d:
filters[p] = d
if "/radiolist/index.html" not in filters:
filters["/radiolist/index.html"] = [{
"key": "id",
"name": "分类",
"value": [
{"n": n, "v": v}
for n, v in zip(
["最新", "最热", "有声小说", "相声", "音乐", "情感", "国漫", "影视", "脱口秀", "历史", "儿童", "教育", "八卦", "推理", "头条"],
["index", "hot", "novel", "xiangyi", "music", "emotion", "game", "yingshi", "talkshow", "history",
"children", "education", "gossip", "tuili", "headline"]
)
]
}]
filters["/singerlist/index/index/index/index.html"] = [
{
"key": "area",
"name": "地区",
"value": [
{"n": "全部", "v": "index"},
{"n": "华语", "v": "huayu"},
{"n": "欧美", "v": "oumei"},
{"n": "韩国", "v": "hanguo"},
{"n": "日本", "v": "ribrn"}
]
},
{
"key": "sex",
"name": "性别",
"value": [
{"n": "全部", "v": "index"},
{"n": "", "v": "male"},
{"n": "", "v": "girl"},
{"n": "组合", "v": "band"}
]
},
{
"key": "genre",
"name": "流派",
"value": [
{"n": "全部", "v": "index"},
{"n": "流行", "v": "liuxing"},
{"n": "电子", "v": "dianzi"},
{"n": "摇滚", "v": "yaogun"},
{"n": "嘻哈", "v": "xiha"},
{"n": "R&B", "v": "rb"},
{"n": "民谣", "v": "minyao"},
{"n": "爵士", "v": "jueshi"},
{"n": "古典", "v": "gudian"}
]
}
]
return {"class": classes, "filters": filters, "list": []}
def homeVideoContent(self):
return {"list": []}
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg or 1)
url = tid
if "/singerlist/" in tid:
parts = tid.split('/')
if len(parts) >= 6:
url = "/".join(parts[:2] + [extend.get(k, parts[i]) for i, k in enumerate(["area", "sex", "genre"], 2)] +
[f"{extend.get('char', 'index')}.html"])
elif "id" in extend and extend["id"] not in ["index", "top"]:
url = tid.replace("index.html", f"{extend['id']}.html").replace("top.html", f"{extend['id']}.html")
if url == tid:
url = f"{tid.rsplit('/', 1)[0]}/{extend['id']}.html"
if pg > 1:
sep = "/" if any(x in url for x in ["/singerlist/", "/radiolist/", "/mvlist/", "/playtype/", "/list/"]) else "_"
url = re.sub(r'(_\d+|/\d+)?\.html$', f'{sep}{pg}.html', url)
doc = self.getpq(url)
items = doc(".play_list li, .video_list li, .pic_list li, .singer_list li, .ali li, .layui-row li, .base_l li")
return {
"list": self._parse_list(items, tid),
"page": pg,
"pagecount": 9999,
"limit": 90,
"total": 999999
}
def searchContent(self, key, quick, pg="1"):
doc = self.getpq(f"/so/{quote(key)}/{pg}.html")
items = doc(".base_l li, .play_list li")
return {
"list": self._parse_list(items, "search"),
"page": int(pg)
}
def detailContent(self, ids):
url = self._abs(ids[0])
doc = self.getpq(url)
vod = {
"vod_id": url,
"vod_name": self._clean(doc("h1").text() or doc("title").text()),
"vod_pic": self._abs(doc(".djpg img, .pic img, .djpic img").attr("src")),
"vod_play_from": "爱听音乐",
"vod_content": ""
}
if any(x in url for x in ["/playlist/", "/album/", "/list/", "/singer/", "/special/", "/radio/", "/radiolist/"]):
eps = self._get_eps(doc)
page_urls = {
self._abs(a.attr("href"))
for a in doc(".page a, .dede_pages a, .pagelist a").items()
if a.attr("href") and "javascript" not in a.attr("href")
} - {url}
if page_urls:
with ThreadPoolExecutor(max_workers=5) as ex:
futures = []
for u in sorted(page_urls, key=lambda x: int(re.search(r'[_/](\d+)\.html', x).group(1)) if re.search(
r'[_/](\d+)\.html', x) else 0):
futures.append(ex.submit(lambda uu: self._get_eps(self.getpq(uu)), u))
for f in as_completed(futures):
eps.extend(f.result() or [])
if eps:
vod.update({
"vod_play_from": "播放列表",
"vod_play_url": "#".join(eps)
})
return {"list": [vod]}
play_list = []
if mid := re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', url):
lrc_url = f"{self.host}/plug/down.php?ac=music&lk=lrc&id={mid.group(2)}"
play_list = [f"播放${self.e64('0@@@@' + url + '|||' + lrc_url)}"]
elif vid := re.search(r'/(video|mp4)/([^/]+)\.html', url):
with ThreadPoolExecutor(max_workers=3) as ex:
fs = {
ex.submit(self._api, "/plug/down.php", {"ac": "vplay", "id": vid.group(2), "q": q}): n
for n, q in [("蓝光", 1080), ("超清", 720), ("高清", 480)]
}
play_list = [f"{fs[f]}${self.e64('0@@@@'+u)}" for f in as_completed(fs) if (u := f.result())]
play_list.sort(key=lambda x: {"": 0, "": 1, "": 2}.get(x[0], 3))
vod["vod_play_url"] = "#".join(play_list) if play_list else f"解析失败${self.e64('1@@@@'+url)}"
return {"list": [vod]}
def playerContent(self, flag, id, vipFlags):
raw = self.d64(id).split("@@@@")[-1]
url, subt = raw.split("|||") if "|||" in raw else (raw, "")
url = url.replace(r"\/", "/")
if ".html" in url and not self.isVideoFormat(url):
if mid := re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', url):
if r_url := self._api("/js/play.php", method="POST", data={"id": mid.group(2), "type": "music"},
headers={"Referer": url.replace("http://", "https://"),
"X-Requested-With": "XMLHttpRequest"}):
url = r_url if ".php" not in r_url else url
elif vid := re.search(r'/(video|mp4)/([^/]+)\.html', url):
with ThreadPoolExecutor(max_workers=3) as ex:
for f in as_completed(
[ex.submit(self._api, "/plug/down.php", {"ac": "vplay", "id": vid.group(2), "q": q}) for q in
[1080, 720, 480]]):
if v_url := f.result():
url = v_url
break
result = {"parse": 0, "url": url, "header": {"User-Agent": self.headers["User-Agent"]}}
if "22a5.com" in url:
result["header"]["Referer"] = self.host + "/"
# OK影视3.6.5+支持LRC格式滚动歌词
if subt:
try:
r = self.session.get(subt, headers={"Referer": self.host + "/"}, timeout=5)
lrc_content = r.text
if lrc_content:
lrc_content = self._filter_lrc_ads(lrc_content)
result["lrc"] = lrc_content
except:
pass
return result
def _filter_lrc_ads(self, lrc_text):
"""过滤LRC歌词中的广告内容"""
lines = lrc_text.splitlines()
filtered_lines = []
# 广告关键词模式
ad_patterns = [
r'欢迎来访.*',
r'本站.*',
r'.*广告.*',
r'QQ群.*',
r'.*www\..*',
r'.*http.*',
r'.*\.com.*',
r'.*\.cn.*',
r'.*\.net.*',
r'.*音乐网.*',
r'.*提供.*',
r'.*下载.*',
]
for line in lines:
if re.match(r'\[\d{2}:\d{2}', line):
is_ad = False
for pattern in ad_patterns:
if re.search(pattern, line, re.IGNORECASE):
is_ad = True
break
if not is_ad:
filtered_lines.append(line)
else:
filtered_lines.append(line)
return '\n'.join(filtered_lines)
def localProxy(self, param):
url = unquote(param.get("url", ""))
type_ = param.get("type")
if type_ == "img":
try:
headers = {
"Referer": "https://www.baidu.com/",
"User-Agent": self.headers["User-Agent"],
"Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9"
}
resp = self.session.get(url, headers=headers, timeout=10)
return [200, "image/jpeg", resp.content, {}]
except Exception as e:
print(f"图片代理失败: {e}")
return [404, "text/plain", b"", {}]
elif type_ == "lrc":
try:
r = self.session.get(url, headers={"Referer": self.host + "/"}, timeout=5)
lrc_content = r.text
lrc_content = self._filter_lrc_ads(lrc_content)
return [200, "application/octet-stream", lrc_content.encode('utf-8'), {}]
except:
return [404, "text/plain", "Error", {}]
return None
# ==================== 辅助方法 ====================
def _parse_list(self, items, tid=""):
"""解析列表项,修复歌手头像 - 直接返回原始图片URL"""
res = []
for li in items.items():
a = li("a").eq(0)
if not (href := a.attr("href")) or href == "/" or any(x in href for x in ["/user/", "/login/", "javascript"]):
continue
if not (name := self._clean(li(".name").text() or a.attr("title") or a.text())):
continue
is_singer = "/singer/" in href or "/singerlist" in tid
pic = ""
src = ""
if is_singer:
img = li(".pic img").eq(0)
src = img.attr("src") or ""
if not src:
img = li("img").eq(0)
src = img.attr("src") or ""
else:
img = li("img").eq(0)
src = img.attr("src") or ""
if not src:
img = li(".pic img").eq(0)
src = img.attr("src") or ""
if src:
if src.startswith('//'):
src = 'https:' + src
elif src.startswith('/'):
src = self.host + src
pic = src
res.append({
"vod_id": self._abs(href),
"vod_name": name,
"vod_pic": pic,
"style": {
"type": "oval" if is_singer else ("list" if any(
x in tid for x in ["/list/", "/playtype/", "/albumlist/"]) else "rect"),
"ratio": 1 if is_singer else 1.33
}
})
return res
def _get_eps(self, doc):
eps = []
for li in doc(".play_list li, .song_list li, .music_list li").items():
a = li("a").eq(0)
href = a.attr("href")
if not href or not re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', href):
continue
full_url = self._abs(href)
lrc_part = ""
mid = re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', full_url)
if mid:
lrc_url = f"{self.host}/plug/down.php?ac=music&lk=lrc&id={mid.group(2)}"
lrc_part = f"|||{lrc_url}"
eps.append(f"{self._clean(a.text() or li('.name').text())}${self.e64('0@@@@' + full_url + lrc_part)}")
return eps
def _clean(self, text):
return re.sub(
r'(爱玩音乐网|视频下载说明|视频下载地址|www\.2t58\.com|MP3免费下载|LRC歌词下载|全部歌曲|\[第\d+页\]|刷新|每日推荐|最新|热门|推荐|MV|高清|无损)',
'',
text or '',
flags=re.I
).strip()
def _fetch_filters(self, url):
doc = self.getpq(url)
filters = []
for i, group in enumerate([
doc(".ilingku_fl"),
doc(".class_list"),
doc(".screen_list"),
doc(".box_list"),
doc(".nav_list")
]):
if group:
opts = [{"n": "全部", "v": "top" if "top" in url else "index"}]
seen = set()
for a in group("a").items():
v = (a.attr("href") or "").split("?")[0].rstrip('/').split('/')[-1].replace('.html', '')
if v and v not in seen:
opts.append({"n": a.text().strip(), "v": v})
seen.add(v)
if len(opts) > 1:
filters.append({"key": f"id{i}" if i else "id", "name": "分类", "value": opts})
return filters
def _api(self, path, params=None, method="GET", headers=None, data=None):
try:
h = self.headers.copy()
if headers:
h.update(headers)
r = (self.session.post if method == "POST" else self.session.get)(
f"{self.host}{path}",
params=params,
data=data,
headers=h,
timeout=10,
allow_redirects=False
)
if loc := r.headers.get("Location"):
return self._abs(loc.strip())
return self._abs(r.json().get("url", "").replace(r"\/", "/")) or (r.text.strip() if r.text.strip().startswith(
"http") else "")
except:
return ""
def _abs(self, url):
if not url:
return ""
if url.startswith("http"):
return url
if url.startswith("//"):
return "https:" + url
return f"{self.host}{'/' if not url.startswith('/') else ''}{url}"
def e64(self, text):
return b64encode(text.encode("utf-8")).decode("utf-8")
def d64(self, text):
return b64decode(text.encode("utf-8")).decode("utf-8")
+240
View File
@@ -0,0 +1,240 @@
import re
import sys
from base64 import b64encode, b64decode
from urllib.parse import quote, unquote
from pyquery import PyQuery as pq
from requests import Session, adapters
from urllib3.util.retry import Retry
from concurrent.futures import ThreadPoolExecutor, as_completed
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.22a5.com"
self.session = Session()
adapter = adapters.HTTPAdapter(max_retries=Retry(total=3, backoff_factor=0.5, status_forcelist=[429, 500, 502, 503, 504]), pool_connections=20, pool_maxsize=50)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
self.headers = {"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"}
self.session.headers.update(self.headers)
def getName(self): return "爱听音乐"
def isVideoFormat(self, url): return bool(re.search(r'\.(m3u8|mp4|mp3|m4a|flv)(\?|$)', url or "", re.I))
def manualVideoCheck(self): return False
def destroy(self): self.session.close()
def homeContent(self, filter):
classes = [{"type_name": n, "type_id": i} for n, i in [("歌手","/singerlist/index/index/index/index.html"), ("TOP榜单","/list/top.html"), ("新歌榜","/list/new.html"), ("电台","/radiolist/index.html"), ("高清MV","/mvlist/oumei.html"), ("专辑","/albumlist/index.html"), ("歌单","/playtype/index.html")]]
filters = {p: d for p in [c["type_id"] for c in classes if "singer" not in c["type_id"]] if (d := self._fetch_filters(p))}
if "/radiolist/index.html" not in filters:
filters["/radiolist/index.html"] = [{"key": "id", "name": "分类", "value": [{"n": n, "v": v} for n,v in zip(["最新","最热","有声小说","相声","音乐","情感","国漫","影视","脱口秀","历史","儿童","教育","八卦","推理","头条"], ["index","hot","novel","xiangyi","music","emotion","game","yingshi","talkshow","history","children","education","gossip","tuili","headline"])]}]
filters["/singerlist/index/index/index/index.html"] = [
{"key": "area", "name": "地区", "value": [{"n": n, "v": v} for n,v in [("全部","index"),("华语","huayu"),("欧美","oumei"),("韩国","hanguo"),("日本","ribrn")]]},
{"key": "sex", "name": "性别", "value": [{"n": n, "v": v} for n,v in [("全部","index"),("","male"),("","girl"),("组合","band")]]},
{"key": "genre", "name": "流派", "value": [{"n": n, "v": v} for n,v in [("全部","index"),("流行","liuxing"),("电子","dianzi"),("摇滚","yaogun"),("嘻哈","xiha"),("R&B","rb"),("民谣","minyao"),("爵士","jueshi"),("古典","gudian")]]},
{"key": "char", "name": "字母", "value": [{"n": n, "v": v} for n,v in [("全部","index")] + [{"n": chr(i), "v": chr(i).lower()} for i in range(65, 91)]]}
]
return {"class": classes, "filters": filters, "list": []}
def homeVideoContent(self): return {"list": []}
def categoryContent(self, tid, pg, filter, extend):
pg = int(pg or 1)
url = tid
if "/singerlist/" in tid:
p = tid.split('/')
if len(p) >= 6:
url = "/".join(p[:2] + [extend.get(k, p[i]) for i, k in enumerate(["area", "sex", "genre"], 2)] + [f"{extend.get('char', 'index')}.html"])
elif "id" in extend and extend["id"] not in ["index", "top"]:
url = tid.replace("index.html", f"{extend['id']}.html").replace("top.html", f"{extend['id']}.html")
if url == tid: url = f"{tid.rsplit('/', 1)[0]}/{extend['id']}.html"
if pg > 1:
sep = "/" if any(x in url for x in ["/singerlist/", "/radiolist/", "/mvlist/", "/playtype/", "/list/"]) else "_"
url = re.sub(r'(_\d+|/\d+)?\.html$', f'{sep}{pg}.html', url)
doc = self.getpq(url)
return {"list": self._parse_list(doc(".play_list li, .video_list li, .pic_list li, .singer_list li, .ali li, .layui-row li, .base_l li"), tid), "page": pg, "pagecount": 9999, "limit": 90, "total": 999999}
def searchContent(self, key, quick, pg="1"):
return {"list": self._parse_list(self.getpq(f"/so/{quote(key)}/{pg}.html")(".base_l li, .play_list li"), "search"), "page": int(pg)}
def detailContent(self, ids):
url = self._abs(ids[0])
doc = self.getpq(url)
vod = {"vod_id": url, "vod_name": self._clean(doc("h1").text() or doc("title").text()), "vod_pic": self._abs(doc(".djpg img, .pic img, .djpic img").attr("src")), "vod_play_from": "爱听音乐", "vod_content": ""}
if any(x in url for x in ["/playlist/", "/album/", "/list/", "/singer/", "/special/", "/radio/", "/radiolist/"]):
eps = self._get_eps(doc)
page_urls = {self._abs(a.attr("href")) for a in doc(".page a, .dede_pages a, .pagelist a").items() if a.attr("href") and "javascript" not in a.attr("href")} - {url}
if page_urls:
with ThreadPoolExecutor(max_workers=5) as ex:
for r in as_completed([ex.submit(lambda u: self._get_eps(self.getpq(u)), u) for u in sorted(page_urls, key=lambda x: int(re.search(r'[_\/](\d+)\.html', x).group(1)) if re.search(r'[_\/](\d+)\.html', x) else 0)]):
eps.extend(r.result() or [])
if eps:
vod.update({"vod_play_from": "播放列表", "vod_play_url": "#".join(eps)})
return {"list": [vod]}
play_list = []
if mid := re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', url):
lrc_url = f"{self.host}/plug/down.php?ac=music&lk=lrc&id={mid.group(2)}"
play_list = [f"播放${self.e64('0@@@@' + url + '|||' + lrc_url)}"]
elif vid := re.search(r'/(video|mp4)/([^/]+)\.html', url):
with ThreadPoolExecutor(max_workers=3) as ex:
fs = {ex.submit(self._api, "/plug/down.php", {"ac": "vplay", "id": vid.group(2), "q": q}): n for n, q in [("蓝光", 1080), ("超清", 720), ("高清", 480)]}
play_list = [f"{fs[f]}${self.e64('0@@@@'+u)}" for f in as_completed(fs) if (u := f.result())]
play_list.sort(key=lambda x: {"":0, "":1, "":2}.get(x[0], 3))
vod["vod_play_url"] = "#".join(play_list) if play_list else f"解析失败${self.e64('1@@@@'+url)}"
return {"list": [vod]}
def playerContent(self, flag, id, vipFlags):
raw = self.d64(id).split("@@@@")[-1]
url, subt = raw.split("|||") if "|||" in raw else (raw, "")
url = url.replace(r"\/", "/")
if ".html" in url and not self.isVideoFormat(url):
if mid := re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', url):
if r_url := self._api("/js/play.php", method="POST", data={"id": mid.group(2), "type": "music"}, headers={"Referer": url.replace("http://","https://"), "X-Requested-With": "XMLHttpRequest"}):
url = r_url if ".php" not in r_url else url
elif vid := re.search(r'/(video|mp4)/([^/]+)\.html', url):
with ThreadPoolExecutor(max_workers=3) as ex:
for f in as_completed([ex.submit(self._api, "/plug/down.php", {"ac": "vplay", "id": vid.group(2), "q": q}) for q in [1080, 720, 480]]):
if v_url := f.result():
url = v_url; break
result = {"parse": 0, "url": url, "header": {"User-Agent": self.headers["User-Agent"]}}
if "22a5.com" in url: result["header"]["Referer"] = self.host + "/"
# OK影视3.6.5+支持LRC格式滚动歌词
if subt:
try:
r = self.session.get(subt, headers={"Referer": self.host + "/"}, timeout=5)
lrc_content = r.text
if lrc_content:
# 过滤广告内容
lrc_content = self._filter_lrc_ads(lrc_content)
result["lrc"] = lrc_content
except:
pass
return result
def _filter_lrc_ads(self, lrc_text):
"""过滤LRC歌词中的广告内容"""
lines = lrc_text.splitlines()
filtered_lines = []
# 广告关键词模式
ad_patterns = [
r'欢迎来访.*',
r'本站.*',
r'.*广告.*',
r'QQ群.*',
r'.*www\..*',
r'.*http.*',
r'.*\.com.*',
r'.*\.cn.*',
r'.*\.net.*',
r'.*音乐网.*',
r'.*提供.*',
r'.*下载.*',
]
for line in lines:
# 保留时间标签行,但过滤掉广告文本
if re.match(r'\[\d{2}:\d{2}', line):
# 检查是否包含广告
is_ad = False
for pattern in ad_patterns:
if re.search(pattern, line, re.IGNORECASE):
is_ad = True
break
if not is_ad:
filtered_lines.append(line)
else:
# 非时间标签行(可能是元数据),保留
filtered_lines.append(line)
return '\n'.join(filtered_lines)
def localProxy(self, param):
url = unquote(param.get("url", ""))
type_ = param.get("type")
if type_ == "img":
return [200, "image/jpeg", self.session.get(url, headers={"Referer": self.host + "/"}, timeout=5).content, {}]
elif type_ == "lrc":
try:
r = self.session.get(url, headers={"Referer": self.host + "/"}, timeout=5)
# 同时过滤代理中的广告
lrc_content = r.text
lrc_content = self._filter_lrc_ads(lrc_content)
return [200, "application/octet-stream", lrc_content.encode('utf-8'), {}]
except:
return [404, "text/plain", "Error", {}]
return None
def _parse_list(self, items, tid=""):
res = []
for li in items.items():
a = li("a").eq(0)
if not (href := a.attr("href")) or href == "/" or any(x in href for x in ["/user/", "/login/", "javascript"]): continue
if not (name := self._clean(li(".name").text() or a.attr("title") or a.text())): continue
pic = self._abs((li("img").attr("src") or "").replace('120', '500'))
res.append({"vod_id": self._abs(href), "vod_name": name, "vod_pic": f"{self.getProxyUrl()}&url={pic}&type=img" if pic else "", "style": {"type": "oval" if "/singer/" in href else ("list" if any(x in tid for x in ["/list/", "/playtype/", "/albumlist/"]) else "rect"), "ratio": 1 if "/singer/" in href else 1.33}})
return res
def _get_eps(self, doc):
eps = []
for li in doc(".play_list li, .song_list li, .music_list li").items():
if not (a := li("a").eq(0)).attr("href") or not re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', a.attr("href")): continue
full_url = self._abs(a.attr("href"))
lrc_part = ""
mid = re.search(r'/(song|mp3|radio|radiolist|radioplay)/([^/]+)\.html', full_url)
if mid:
lrc_url = f"{self.host}/plug/down.php?ac=music&lk=lrc&id={mid.group(2)}"
lrc_part = f"|||{lrc_url}"
eps.append(f"{self._clean(a.text() or li('.name').text())}${self.e64('0@@@@' + full_url + lrc_part)}")
return eps
def _clean(self, text): return re.sub(r'(爱玩音乐网|视频下载说明|视频下载地址|www\.2t58\.com|MP3免费下载|LRC歌词下载|全部歌曲|\[第\d+页\]|刷新|每日推荐|最新|热门|推荐|MV|高清|无损)', '', text or "", flags=re.I).strip()
def _fetch_filters(self, url):
doc, filters = self.getpq(url), []
for i, group in enumerate([doc(s) for s in [".ilingku_fl", ".class_list", ".screen_list", ".box_list", ".nav_list"] if doc(s)]):
opts, seen = [{"n": "全部", "v": "top" if "top" in url else "index"}], set()
for a in group("a").items():
if (v := (a.attr("href") or "").split("?")[0].rstrip('/').split('/')[-1].replace('.html','')) and v not in seen:
opts.append({"n": a.text().strip(), "v": v}); seen.add(v)
if len(opts) > 1: filters.append({"key": f"id{i}" if i else "id", "name": "分类", "value": opts})
return filters
def _api(self, path, params=None, method="GET", headers=None, data=None):
try:
h = self.headers.copy()
if headers: h.update(headers)
r = (self.session.post if method == "POST" else self.session.get)(f"{self.host}{path}", params=params, data=data, headers=h, timeout=10, allow_redirects=False)
if loc := r.headers.get("Location"): return self._abs(loc.strip())
return self._abs(r.json().get("url", "").replace(r"\/", "/")) or (r.text.strip() if r.text.strip().startswith("http") else "")
except: return ""
def getpq(self, url):
import time
for _ in range(2):
try: return pq(self.session.get(self._abs(url), timeout=5).text)
except: time.sleep(0.1)
return pq("<html></html>")
def _abs(self, url): return url if url.startswith("http") else (f"{self.host}{'/' if not url.startswith('/') else ''}{url}" if url else "")
def e64(self, text): return b64encode(text.encode("utf-8")).decode("utf-8")
def d64(self, text): return b64decode(text.encode("utf-8")).decode("utf-8")
+7
View File
@@ -9,6 +9,13 @@
"paichu": "1,2,3,4"
},
{
"name": "TV-大众资源站",
"api": "https://cdn.dzzyapi.com/api.php/provide/vod/",
"detail": "https://cdn.dzzyapi.com",
"bz": "1",
"paichu": "1,2,3,4"
},
{
"name": "TV-98资源站",
"api": "https://98zy.vip/api.php/provide/vod/",
"detail": "https://98zy.vip",
+7
View File
@@ -21,6 +21,13 @@
"detail": "https://98zy.vip",
"bz": "1",
"paichu": "1,2,3,4"
},
{
"name": "TV-大众资源站",
"api": "https://cdn.dzzyapi.com/api.php/provide/vod/",
"detail": "https://cdn.dzzyapi.com",
"bz": "1",
"paichu": "1,2,3,4"
},
{
"name": "TV-1080资源",
+118
View File
@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
import json
import time
from base.spider import Spider
class Spider(Spider):
def getName(self):
return "Cam4直播"
def init(self, extend=""):
self.base = "https://zh.cam4.com"
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
return self
def homeContent(self, filter):
classes = [
{"type_id": "all", "type_name": "全部"},
{"type_id": "female", "type_name": "女性"},
{"type_id": "male", "type_name": "男性"},
{"type_id": "couples", "type_name": "情侣"},
{"type_id": "shemale", "type_name": "变性"},
]
return {"class": classes}
def categoryContent(self, tid, pg, filter, extend):
if not pg:
pg = 1
params = f"?directoryJson=true&online=true&url=true&page={pg}"
if tid == "female":
params += "&gender=female"
elif tid == "male":
params += "&gender=male"
elif tid == "couples":
params += "&broadcastType=male_female_group"
elif tid == "shemale":
params += "&gender=shemale"
url = f"{self.base}/directoryCams{params}"
rsp = self.fetch(url, headers=self.headers)
data = rsp.text
try:
jRoot = json.loads(data)
except:
return {"list": []}
videos = []
for u in jRoot.get("users", []):
title = f"{u.get('username')} ({u.get('countryCode', '')})"
if "age" in u:
title += f" - {u['age']}"
if "resolution" in u:
res = u["resolution"].split(":")[-1]
title += f" [HD:{res}]"
video = {
"vod_id": u.get("hlsPreviewUrl"),
"vod_name": title,
"vod_pic": u.get("snapshotImageLink", ""),
"vod_remarks": u.get("statusMessage", ""),
}
videos.append(video)
result = {
"list": videos,
"page": int(pg),
"pagecount": 9999,
"limit": 90,
"total": len(videos)
}
return result
def detailContent(self, ids):
id = ids[0]
vod = {
"vod_id": id,
"vod_name": "Cam4直播",
"vod_pic": "",
"vod_play_from": "Cam4",
"vod_play_url": f"直播源${id}",
}
return {"list": [vod]}
def playerContent(self, flag, id, vipFlags):
play_url = id
return {
"parse": 0,
"playUrl": "",
"url": play_url,
"header": self.headers
}
def searchContent(self, key, quick, pg="1"):
url = f"{self.base}/directoryCams?directoryJson=true&online=true&url=true&showTag={key}&page={pg}"
rsp = self.fetch(url, headers=self.headers)
data = rsp.text
try:
jRoot = json.loads(data)
except:
return {"list": []}
videos = []
for u in jRoot.get("users", []):
title = f"{u.get('username')} ({u.get('countryCode', '')})"
video = {
"vod_id": u.get("hlsPreviewUrl"),
"vod_name": title,
"vod_pic": u.get("snapshotImageLink", ""),
"vod_remarks": u.get("statusMessage", ""),
}
videos.append(video)
return {"list": videos}
def isVideoFormat(self, url):
return ".m3u8" in url
def manualVideoCheck(self):
return True
+335
View File
@@ -0,0 +1,335 @@
import requests
from bs4 import BeautifulSoup
import re
from base.spider import Spider
import sys
import json
import base64
import urllib.parse
from Crypto.Cipher import ARC4
from Crypto.Util.Padding import unpad
import binascii
sys.path.append('..')
xurl = "https://www.fullhd.xxx/zh/"
headerx = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
}
pm = ''
class Spider(Spider):
global xurl
global headerx
def getName(self):
return "首页"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def extract_middle_text(self, text, start_str, end_str, pl, start_index1: str = '', end_index2: str = ''):
if pl == 3:
plx = []
while True:
start_index = text.find(start_str)
if start_index == -1:
break
end_index = text.find(end_str, start_index + len(start_str))
if end_index == -1:
break
middle_text = text[start_index + len(start_str):end_index]
plx.append(middle_text)
text = text.replace(start_str + middle_text + end_str, '')
if len(plx) > 0:
purl = ''
for i in range(len(plx)):
matches = re.findall(start_index1, plx[i])
output = ""
for match in matches:
match3 = re.search(r'(?:^|[^0-9])(\d+)(?:[^0-9]|$)', match[1])
if match3:
number = match3.group(1)
else:
number = 0
if 'http' not in match[0]:
output += f"#{'📽️' + match[1]}${number}{xurl}{match[0]}"
else:
output += f"#{'📽️' + match[1]}${number}{match[0]}"
output = output[1:]
purl = purl + output + "$$$"
purl = purl[:-3]
return purl
else:
return ""
else:
start_index = text.find(start_str)
if start_index == -1:
return ""
end_index = text.find(end_str, start_index + len(start_str))
if end_index == -1:
return ""
if pl == 0:
middle_text = text[start_index + len(start_str):end_index]
return middle_text.replace("\\", "")
if pl == 1:
middle_text = text[start_index + len(start_str):end_index]
matches = re.findall(start_index1, middle_text)
if matches:
jg = ' '.join(matches)
return jg
if pl == 2:
middle_text = text[start_index + len(start_str):end_index]
matches = re.findall(start_index1, middle_text)
if matches:
new_list = [f'{item}' for item in matches]
jg = '$$$'.join(new_list)
return jg
def homeContent(self, filter):
result = {}
result = {"class": [{"type_id": "latest-updates", "type_name": "最新视频🌠"},
{"type_id": "top-rated", "type_name": "最佳视频🌠"},
{"type_id": "most-popular", "type_name": "热门影片🌠"}],
}
return result
def homeVideoContent(self):
videos = []
try:
detail = requests.get(url=xurl, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
# Get videos from different sections
sections = {
"latest-updates": "最新视频",
"top-rated": "最佳视频",
"most-popular": "热门影片"
}
for section_id, section_name in sections.items():
section = doc.find('div', id=f"list_videos_videos_watched_right_now_items")
if not section:
continue
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else section_name
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
pics = vod.find('img', class_="lazyload")
pic = pics['data-src'] if pics and 'data-src' in pics.attrs else ""
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
result = {'list': videos}
return result
except Exception as e:
print(f"Error in homeVideoContent: {str(e)}")
return {'list': []}
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
try:
if pg and int(pg) > 1:
url = f'{xurl}/{cid}/{pg}/'
else:
url = f'{xurl}/{cid}/'
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
section = doc.find('div', class_="list-videos")
if section:
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else ""
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
pics = vod.find('img', class_="lazyload")
pic = pics['data-src'] if pics and 'data-src' in pics.attrs else ""
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
except Exception as e:
print(f"Error in categoryContent: {str(e)}")
result = {
'list': videos,
'page': pg,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
return result
def detailContent(self, ids):
global pm
did = ids[0]
result = {}
videos = []
playurl = ''
if 'http' not in did:
did = xurl + did
res1 = requests.get(url=did, headers=headerx)
res1.encoding = "utf-8"
res = res1.text
content = '👉' + self.extract_middle_text(res,'<h1>','</h1>', 0)
yanuan = self.extract_middle_text(res, '<span>Pornstars:</span>','</div>',1, 'href=".*?">(.*?)</a>')
bofang = did
videos.append({
"vod_id": did,
"vod_actor": yanuan,
"vod_director": '',
"vod_content": content,
"vod_play_from": '💗4K💗',
"vod_play_url": bofang
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
parts = id.split("http")
xiutan = 0
if xiutan == 0:
if len(parts) > 1:
before_https, after_https = parts[0], 'http' + parts[1]
res = requests.get(url=after_https, headers=headerx)
res = res.text
url2 = self.extract_middle_text(res, '<video', '</video>', 0).replace('\\', '')
soup = BeautifulSoup(url2, 'html.parser')
first_source = soup.find('source')
src_value = first_source.get('src')
response = requests.head(src_value, allow_redirects=False)
if response.status_code == 302:
redirect_url = response.headers['Location']
response = requests.head(redirect_url, allow_redirects=False)
if response.status_code == 302:
redirect_url = response.headers['Location']
result = {}
result["parse"] = xiutan
result["playUrl"] = ''
result["url"] = redirect_url
result["header"] = headerx
return result
def searchContentPage(self, key, quick, page):
result = {}
videos = []
if not page:
page = '1'
if page == '1':
url = f'{xurl}/search/{key}/'
else:
url = f'{xurl}/search/{key}/{str(page)}/'
try:
detail = requests.get(url=url, headers=headerx)
detail.encoding = "utf-8"
res = detail.text
doc = BeautifulSoup(res, "lxml")
section = doc.find('div', class_="list-videos")
if section:
vods = section.find_all('div', class_="item")
for vod in vods:
names = vod.find_all('a')
name = names[0]['title'] if names and 'title' in names[0].attrs else ""
ids = vod.find_all('a')
id = ids[0]['href'] if ids else ""
pics = vod.find('img', class_="lazyload")
pic = pics['data-src'] if pics and 'data-src' in pics.attrs else ""
if pic and 'http' not in pic:
pic = xurl + pic
remarks = vod.find('span', class_="duration")
remark = remarks.text.strip() if remarks else ""
video = {
"vod_id": id,
"vod_name": name,
"vod_pic": pic,
"vod_remarks": remark
}
videos.append(video)
except Exception as e:
print(f"Error in searchContentPage: {str(e)}")
result = {
'list': videos,
'page': page,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
return result
def searchContent(self, key, quick):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None
+232
View File
@@ -0,0 +1,232 @@
# -*- coding: utf-8 -*-
import requests
import urllib.parse
import json
class Spider:
def init(self, extend=""):
self.host = "https://5721004.xyz"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Referer': 'https://www.pandalive.co.kr/',
'Origin': 'https://www.pandalive.co.kr'
}
print("PandaLive 專業版 (僅 PandaTV) 初始化成功")
def getName(self):
return "PandaLive"
def getDependence(self):
return []
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
pass
def destroy(self):
pass
def localProxy(self, param):
return None
def homeContent(self, filter):
"""首頁 - 僅保留 PandaTV 分類與對應篩選器"""
try:
# 只保留 PandaTV
classes = [
{'type_id': 'pandalive', 'type_name': '🐼 PandaTV'}
]
filters = {
"pandalive": [
{
"key": "type",
"name": "類型",
"value": [
{"n": "全部", "v": "all"},
{"n": "🔞 19+", "v": "adult"},
{"n": "🔐 密碼房", "v": "pw"},
{"n": "💎 粉絲房", "v": "fan"}
]
},
{
"key": "sort",
"name": "排序",
"value": [
{"n": "觀眾量 ↓", "v": "user-desc"},
{"n": "實時熱度 ↓", "v": "totalScoreCnt-desc"},
{"n": "關注量 ↓", "v": "bookmarkCnt-desc"}
]
}
]
}
# 獲取首頁推薦數據 (從 JSON 獲取以保證有圖片)
all_data = self._fetch_json_data()
return {
'class': classes,
'list': all_data[:30],
'filters': filters
}
except Exception as e:
print(f"homeContent錯誤: {e}")
return {'class': [], 'list': []}
def homeVideoContent(self):
try:
return {'list': self._fetch_json_data()[:20]}
except:
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
"""分類頁 - 基於 JSON 的篩選排序邏輯"""
try:
all_list = self._fetch_json_data()
filtered = all_list
# 1. 執行篩選
f_type = extend.get('type', 'all')
if f_type == 'adult':
filtered = [v for v in filtered if v.get('_isAdult')]
elif f_type == 'pw':
filtered = [v for v in filtered if v.get('_isPw')]
elif f_type == 'fan':
filtered = [v for v in filtered if v.get('_type') == 'fan']
# 2. 執行排序
sort_type = extend.get('sort', 'user-desc')
if sort_type == 'user-desc':
filtered.sort(key=lambda x: x.get('_user_count', 0), reverse=True)
elif sort_type == 'totalScoreCnt-desc':
filtered.sort(key=lambda x: x.get('_score', 0), reverse=True)
elif sort_type == 'bookmarkCnt-desc':
filtered.sort(key=lambda x: x.get('_bookmark', 0), reverse=True)
# 3. 分頁
pg = int(pg)
limit = 30
start = (pg - 1) * limit
end = start + limit
page_list = filtered[start:end] if start < len(filtered) else []
return {
'list': page_list,
'page': pg,
'pagecount': (len(filtered) + limit - 1) // limit if filtered else 1,
'limit': limit,
'total': len(filtered)
}
except Exception as e:
print(f"categoryContent錯誤: {e}")
return {'list': [], 'page': int(pg)}
def _fetch_json_data(self):
"""抓取 list.json 數據,確保 vod_pic 獲取正確"""
try:
url = f"{self.host}/player/list.json"
res = requests.get(url, headers=self.headers, timeout=10)
if res.status_code != 200:
return []
data = res.json()
raw_list = data.get('list', [])
processed = []
for item in raw_list:
user_id = item.get('userId', '')
nick = item.get('userNick', '未知主播')
title = item.get('title', '無標題')
is_adult = item.get('isAdult', False)
is_pw = item.get('isPw', False)
v_type = item.get('type', '')
processed.append({
'vod_id': f"live_{user_id}",
'vod_name': f"📺 {nick}",
'vod_pic': item.get('thumbUrl', 'https://tupian.li/images/2024/03/30/660769b1ba623.png'),
'vod_remarks': f"👤 {item.get('user', 0)} {'🔞' if is_adult else ''}",
'vod_content': title,
'vod_actor': user_id,
'_isAdult': is_adult,
'_isPw': is_pw,
'_type': v_type,
'_user_count': item.get('user', 0),
'_score': item.get('totalScoreCnt', 0),
'_bookmark': item.get('bookmarkCnt', 0)
})
return processed
except Exception as e:
print(f"JSON抓取失敗: {e}")
return []
def detailContent(self, ids):
"""詳情頁 - 保持對接 list.m3u 獲取真實流地址的邏輯"""
try:
first_id = ids[0] if isinstance(ids, list) else ids
user_id = first_id.replace("live_", "")
stream_url = ""
m3u_res = requests.get(f"{self.host}/player/list.m3u", headers=self.headers, timeout=10)
if m3u_res.status_code == 200:
lines = m3u_res.text.split('\n')
for i, line in enumerate(lines):
# 匹配格式: #EXTINF:0,主播ID,主播名稱
if f",{user_id}," in line and i + 1 < len(lines):
stream_url = lines[i+1].strip()
break
if not stream_url:
# 如果 M3U 匹配不到,嘗試模糊匹配主播 ID
for i, line in enumerate(lines):
if user_id in line and i + 1 < len(lines):
stream_url = lines[i+1].strip()
break
proxies = [
"https://hubu.515355.xyz/proxy/?",
"https://flank.515355.xyz/proxy/",
"https://uae2.515355.xyz/proxy/",
"https://pol.515355.xyz/proxy/",
"https://f00.515355.xyz/proxy/",
"https://ce2.515355.xyz/proxy/?",
]
# 1. 首先创建列表,并将“直连”作为第一个元素添加进去
play_links = [f"直連${stream_url}"]
# 2. 然后通过 extend() 方法或循环,将生成的代理链接追加到列表中
play_links.extend([f"代理{i}${p}{stream_url}" for i, p in enumerate(proxies, 1)])
vod = {
'vod_id': first_id,
'vod_name': f"PandaTV - {user_id}",
'vod_pic': 'https://tupian.li/images/2024/03/30/660769b1ba623.png',
'vod_content': f'主播: {user_id}',
'vod_play_from': 'PandaLive',
'vod_play_url': '#'.join(play_links)
}
return {'list': [vod]}
except Exception as e:
print(f"detailContent錯誤: {e}")
return {'list': []}
def searchContent(self, key, quick, pg="1"):
"""搜索 - 帶 pg="1" 修復"""
try:
all_v = self._fetch_json_data()
key_l = key.lower()
res = [v for v in all_v if key_l in v['vod_name'].lower() or key_l in v['vod_actor'].lower()]
return {'list': res[:50], 'page': int(pg)}
except:
return {'list': [], 'page': int(pg)}
def playerContent(self, flag, id, vipFlags):
return {
'parse': 0,
'url': id,
'header': self.headers
}
+167
View File
@@ -0,0 +1,167 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import concurrent.futures
import json
import sys
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.ihost=self.imgsite()
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
host='https://api.zxfmj.com'
headers={
'User-Agent': 'Mozilla/5.0 (Linux; Android 11; M2012K10C Build/RP1A.200720.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36;webank/h5face;webank/1.0;netType:NETWORK_WIFI;appVersion:416;packageName:com.jp3.xg3',
'Accept': 'application/json, text/plain, */*',
'x-requested-with': 'com.jp3.xg3',
'accept-language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
}
# def imgsite(self):
# data=self.fetch(f"{self.host}/api/appAuthConfig",headers=self.headers).json()
# host=data['data']['imgDomain']
# return host if host.startswith('http') else f"https://{host}"
def imgsite(self):
response = self.fetch(f"{self.host}/api/v2/settings/resourceDomainConfig", headers=self.headers)
data = response.json()
if data.get('code') != 1 or 'data' not in data or 'imgDomain' not in data['data']:
return [] # 或者可以抛出异常,取决于你的错误处理策略
domains = data['data']['imgDomain'].split(',')
processed_domains = []
for domain in domains:
domain = domain.strip() # 去除可能的空格
if not domain: # 跳过空域名
continue
if not domain.startswith('http'):
domain = f"https://{domain}"
processed_domains.append(domain)
return processed_domains[0]
def getfts(self,id):
data=self.fetch(f"{self.host}/api/crumb/filterOptions",params={'fcate_pid':id},headers=self.headers).json()
fts=[{
'key': i['key'],
'name':i['key'],
'value': [{
'n': j['name'],
'v': j['id']
} for j in i['data']]
} for i in data['data']]
return id,fts
def build_cl(self,data,tid=''):
videos=[]
for i in data:
text=json.dumps(i.get('res_categories',[]))
videos.append({
'vod_id': f"{i.get('id')}@{'67' if json.dumps('短剧') in text and '67' in text else tid}",
'vod_name': i.get('title'),
'vod_pic': f"{self.ihost}{i.get('path') or i.get('cover_image') or i.get('thumbnail')}",
'vod_remarks': i.get('mask'),
'vod_year': i.get('score'),
})
return videos
def homeContent(self, filter):
result = {}
cdata=self.fetch(f"{self.host}/api/term/home_fenlei",headers=self.headers).json()
hdata=self.fetch(f"{self.host}/api/dyTag/hand_data",params={'category_id':cdata['data'][0]['id']},headers=self.headers).json()
classes = []
filters = {}
for k in cdata['data']:
if 'abbr' in k:
classes.append({
'type_name': k['name'],
'type_id': k['id']
})
with concurrent.futures.ThreadPoolExecutor(max_workers=len(classes)) as executor:
future_to_aid = {
executor.submit(self.getfts, aid['type_id']): aid['type_id']
for aid in classes
}
for future in concurrent.futures.as_completed(future_to_aid):
aid = future_to_aid[future]
try:
aid_id, fts = future.result()
filters[aid_id] = fts
except Exception as e:
print(f"Error processing aid {aid}: {e}")
result['class'] = classes
result['filters'] = filters
result['list'] = [item for i in hdata['data'].values() for item in self.build_cl(i)]
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
params={**{'fcate_pid': tid, 'page': pg}, **extend}
path= '/api/crumb/shortList' if tid=='67' else '/api/crumb/list'
data=self.fetch(f"{self.host}{path}",params=params,headers=self.headers).json()
result = {}
result['list'] = self.build_cl(data['data'],tid)
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
ids=ids[0].split('@')
path, ikey = ('/api/detail', 'vid') if ids[-1] == '67' else ('/api/video/detailv2', 'id')
data=self.fetch(f"{self.host}{path}",params={ikey:ids[0]},headers=self.headers).json()
v=data['data']
if ids[-1]=='67':
pdata=v.get('playlist',[])
n,p=[pdata[0].get('source_config_name')],['#'.join([f"{i.get('title')}${i['url']}" for i in pdata])]
else:
n,p=[],[]
for i in v.get('source_list_source',[]):
n.append(i.get('name'))
p.append('#'.join([f"{j.get('source_name') or j.get('weight')}${j['url']}" for j in i.get('source_list',[])]))
vod = {
'type_name': '/'.join([i.get('name') for i in v.get('types',[])]),
'vod_year': v.get('year'),
'vod_area': v.get('area'),
'vod_remarks': v.get('update_cycle'),
'vod_actor': '/'.join([i.get('name') for i in v.get('actors',[])]),
'vod_content': v.get('description'),
'vod_play_from': '$$$'.join(n),
'vod_play_url': '$$$'.join(p)
}
return {'list':[vod]}
def searchContent(self, key, quick, pg="1"):
data=self.fetch(f"{self.host}/api/v2/search/videoV2",params={'key':key,'page':pg,'pageSize':20},headers=self.headers).json()
return {'list':self.build_cl(data['data']),'page':pg}
def playerContent(self, flag, id, vipFlags):
return {'parse': 0, 'url': id, 'header': {'User-Agent':self.headers['User-Agent']}}
def localProxy(self, param):
pass
def liveContent(self, url):
pass
+278
View File
@@ -0,0 +1,278 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import concurrent.futures
import json
import re
import sys
import time
from base64 import b64decode, b64encode
import requests
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://vip.wwgz.cn:5200"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36',
'Referer': self.host + '/',
'Accept': 'text/html'
}
self.cateConfig = {
"12": [{"key": "cateId", "name": "类型", "value": [{"n": "国产剧", "v": "12"}]}],
"4": [{"key": "cateId", "name": "类型", "value": [{"n": "动漫", "v": "4"}]}],
"1": [{"key": "cateId", "name": "类型", "value": [{"n": "电影", "v": "1"}]}],
"2": [{"key": "cateId", "name": "类型", "value": [{"n": "电视剧", "v": "2"}]}],
"3": [{"key": "cateId", "name": "类型", "value": [{"n": "综艺", "v": "3"}]}],
"26": [{"key": "cateId", "name": "类型", "value": [{"n": "短剧", "v": "26"}]}]
}
self.filterConfig = {}
def getName(self):
return "农民影视"
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
def homeContent(self, filter):
result = {}
classes = [
{'type_name': '国产剧', 'type_id': '12'},
{'type_name': '动漫', 'type_id': '4'},
{'type_name': '电影', 'type_id': '1'},
{'type_name': '电视剧', 'type_id': '2'},
{'type_name': '综艺', 'type_id': '3'},
{'type_name': '短剧', 'type_id': '26'}
]
try:
data = self.fetch(self.host, headers=self.headers).text
doc = pq(data)
videos = []
# 修改选择器并添加去重逻辑
seen_ids = set() # 用于记录已处理的影片ID
for item in doc('.globalPicList li:has(img)').items():
vod_id = self.host + item('a').attr('href')
if vod_id not in seen_ids: # 检查是否已处理过
seen_ids.add(vod_id) # 记录已处理的ID
pic_url = item('img').attr('data-echo') or item('img').attr('data-src') or item('img').attr('src')
# 替换图片域名
if pic_url and 'pic.lzzypic.com' in pic_url:
pic_url = pic_url.replace('https://pic.lzzypic.com', 'https://img.lzzyimg.com')
videos.append({
'vod_id': vod_id,
'vod_name': item('.sTit').text(),
'vod_pic': pic_url,
'vod_remarks': item('.sBottom').text()
})
result['class'] = classes
result['filters'] = self.cateConfig
result['list'] = videos
except Exception as e:
print(f"首页数据获取失败: {str(e)}")
result['class'] = classes
result['filters'] = self.cateConfig
result['list'] = []
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
result = {}
try:
if tid == "4-dm":
# 处理大陆人气动漫分类
url = "https://www.wwgz.cn/vod-list-id-4-pg-{}-order--by-hits-class-0-year-0-letter--area-大陆-lang-.html".format(pg)
else:
cateId = tid
url = f"{self.host}/vod-list-id-{cateId}-pg-{pg}.html"
data = self.fetch(url, headers=self.headers).text
doc = pq(data)
videos = []
for item in doc('.globalPicList li').items():
pic_url = item('img').attr('data-echo') or item('img').attr('data-src') or item('img').attr('src')
# 替换图片域名
if pic_url and 'pic.lzzypic.com' in pic_url:
pic_url = pic_url.replace('https://pic.lzzypic.com', 'https://img.lzzyimg.com')
videos.append({
'vod_id': self.host + item('a').attr('href'),
'vod_name': item('.sTit').text(),
'vod_pic': pic_url,
'vod_remarks': item('.sBottom').text()
})
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
except Exception as e:
print(f"分类数据获取失败: {str(e)}")
result['list'] = []
result['page'] = pg
result['pagecount'] = 1
result['limit'] = 90
result['total'] = 0
return result
def detailContent(self, ids):
result = {}
try:
url = ids[0]
data = self.fetch(url, headers=self.headers).text
doc = pq(data)
# 获取播放线路和剧集
play_from = []
play_url = []
tab_box = doc('#leftTabBox')
if tab_box:
for tab in tab_box('ul li').items():
play_from.append(tab.text())
play_lists = []
for num_list in tab_box('.numList').items():
episodes = []
# 修改这里:将items()转换为列表后反转顺序
for ep in list(num_list('li').items())[::-1]: # 反转列表顺序
episodes.append(f"{ep('a').text()}${self.host}{ep('a').attr('href')}")
play_lists.append('#'.join(episodes))
play_url = play_lists
# 获取详情信息
vod = {
'vod_name': doc('h1 a').text(),
'vod_year': doc('span:contains("年代:")').text().replace('年代:', ''),
'vod_area': '',
'vod_actor': doc('.sDes:contains("主演:")').text().replace('主演:', ''),
'vod_director': '',
'vod_content': doc('.detail-con p').text().replace('简介:', ''),
'vod_play_from': '$$$'.join(play_from),
'vod_play_url': '$$$'.join(play_url)
}
result['list'] = [vod]
except Exception as e:
print(f"详情数据获取失败: {str(e)}")
result['list'] = []
return result
def searchContent(self, key, quick, pg="1"):
result = {}
try:
url = f"{self.host}/index.php?m=vod-search"
data = {'wd': key}
headers = {
'User-Agent': self.headers['User-Agent'],
'Referer': self.host + '/'
}
html = self.post(url, data=data, headers=headers).text
doc = pq(html)
videos = []
for item in doc('#data_list li').items():
pic_url = item('.lazyload').attr('data-src')
# 替换图片域名
if pic_url and 'pic.lzzypic.com' in pic_url:
pic_url = pic_url.replace('https://pic.lzzypic.com', 'https://img.lzzyimg.com')
videos.append({
'vod_id': self.host + item('a').attr('href'),
'vod_name': item('.sTit').text(),
'vod_pic': pic_url,
'vod_remarks': item('.sDes').eq(-1).text()
})
result['list'] = videos
result['page'] = pg
except Exception as e:
print(f"搜索数据获取失败: {str(e)}")
result['list'] = []
result['page'] = pg
return result
def playerContent(self, flag, id, vipFlags):
result = {}
try:
if '@' in id:
ids = id.split('@')
if not ids[0]:
raise Exception('未找到播放地址')
js_url = f"{self.host}/player/{ids[0]}.js"
js_data = self.fetch(js_url, headers=self.headers).text
jxurl = re.search(r'http.*?url=', js_data).group()
data = self.fetch(f"{jxurl}{ids[1]}", headers=self.headers).text
matches = re.findall(r'http.*?url=', data)
if matches:
url = []
for i, x in enumerate(matches):
js = {'jx': x, 'id': ids[1]}
purl = f"{self.getProxyUrl()}&wdict={self.e64(json.dumps(js))}"
url.extend([f'线路{i + 1}', purl])
else:
url = re.search(r"url='(.*?)'", data).group(1)
if not url:
raise Exception('未找到播放地址')
p = 0
else:
p, url = 1, id
result['parse'] = p
result['url'] = url
result['header'] = self.headers
except Exception as e:
print(f"播放数据获取失败: {str(e)}")
result['parse'] = 1
result['url'] = id
result['header'] = self.headers
return result
def localProxy(self, param):
try:
wdict = json.loads(self.d64(param['wdict']))
url = f"{wdict['jx']}{wdict['id']}"
data = self.fetch(url, headers=self.headers).text
doc = pq(data)
html = doc('script').eq(-1).text()
url = re.search(r'src="(.*?)"', html).group(1)
return [302, 'text/html', None, {'Location': url}]
except Exception as e:
print(f"代理处理失败: {str(e)}")
return [500, 'text/plain', str(e).encode('utf-8')]
def liveContent(self, url):
pass
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self, encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
+222 -280
View File
@@ -1,30 +1,27 @@
# -*- coding: utf-8 -*-
# 恒轩:https://www.jubaba.vip/
import json
import random
import requests
from bs4 import BeautifulSoup
import re
import sys
from base64 import b64decode, b64encode
import requests
from Crypto.Hash import MD5
from pyquery import PyQuery as pq
import json
import urllib.parse
from base.spider import Spider
from urllib.parse import quote, urljoin
sys.path.append('..')
from base.spider import Spider
xurl = "http://www.dgpengcheng.com"
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 13; M2102J2SC Build/TKQ1.221114.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/144.0.7559.31 Mobile Safari/537.36',
'Referer': xurl,
}
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.jubaba.cc"
self.headers.update({
'referer': f'{self.host}/',
'origin': self.host,
})
self.session = requests.Session()
self.session.headers.update(self.headers)
self.session.get(self.host)
global xurl, headers
def getName(self):
return "星辰影院"
def init(self, extend):
pass
def isVideoFormat(self, url):
@@ -33,275 +30,220 @@ class Spider(Spider):
def manualVideoCheck(self):
pass
def destroy(self):
pass
headers = {
'User-Agent': 'Mozilla/5.0 (iPad; CPU OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="134", "Google Chrome";v="134"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"macOS"',
'sec-fetch-site': 'same-origin',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
}
config = {
"1": [
{"key": "by", "name": "排序",
"value": [{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"}, {"n": "评分", "v": "score"}]}],
"2": [
{"key": "by", "name": "排序",
"value": [{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"}, {"n": "评分", "v": "score"}]}],
"3": [
{"key": "by", "name": "排序",
"value": [{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"}, {"n": "评分", "v": "score"}]}],
"4": [
{"key": "by", "name": "排序",
"value": [{"n": "时间", "v": "time"}, {"n": "人气", "v": "hits"}, {"n": "评分", "v": "score"}]}],
}
def clean_vod_name(self, raw_name):
clean_name = re.sub(r'年番\d+$|第[一二三四五六七八九十\d]+季$|\d+$|:.*$', '', raw_name).strip()
return clean_name
def homeContent(self, filter):
data = self.getpq()
result = {}
classes = []
for k in data('ul.swiper-wrapper').eq(0)('li').items():
i = k('a').attr('href')
if i and 'type' in i:
type_name = k.text().strip()
classes = [
{"type_id": "1", "name": "电影"},
{"type_id": "2", "name": "电视剧"},
{"type_id": "3", "name": "综艺"},
{"type_id": "4", "name": "动漫"},
{"type_id": "28", "name": "纪录片"}
]
return {'class': classes}
if type_name == '推荐':
type_name = '恒轩'
classes.append({
'type_name': type_name,
'type_id': re.findall(r'\d+', i)[0],
})
result['class'] = classes
result['list'] = self.getlist(data('.tab-content.ewave-pannel_bd li'))
result['filters'] = self.config
return result
def homeVideoContent(self):
pass
def categoryContent(self, tid, pg, filter, extend):
path = f"/vodshow/{tid}-{extend.get('area', '')}-{extend.get('by', '')}-{extend.get('class', '')}-----{pg}---{extend.get('year', '')}.html"
data = self.getpq(path)
result = {}
result['list'] = self.getlist(data('ul.ewave-vodlist.clearfix li'))
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data = self.getpq(f"/voddetail/{ids[0]}.html")
v = data('.ewave-content__detail')
c = data('p')
raw_vod_name = v('h1').text()
clean_vod_name = self.clean_vod_name(raw_vod_name)
vod = {
'type_name': c.eq(0)('a').text(),
'vod_year': v('.data.hidden-sm').text(),
'vod_remarks': clean_vod_name,
'vod_actor': c.eq(1)('a').text(),
'vod_director': c.eq(2)('a').text(),
'vod_content': v('.desc.hidden-xs').text(),
'vod_play_from': '',
'vod_play_url': ''
}
nd = list(data('ul.nav-tabs.swiper-wrapper li').items())
pd = list(data('ul.ewave-content__playlist').items())
line_priority = ['自营b', '自营e', '自营c', '自营c', '自营d', 'LZ有广', 'BF有广', 'YZ有广']
play_url = ''
for line in line_priority:
for idx, line_name_ele in enumerate(nd):
current_line = line_name_ele.text().strip()
if current_line == line and pd[idx]('li').items():
play_url = '#'.join([f"{j.text()}${j('a').attr('href')}" for j in pd[idx]('li').items()])
break
if play_url:
break
vod['vod_play_from'] = '剧爸爸专线'
vod['vod_play_url'] = play_url if play_url else ''
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
if pg == "1":
p = f"-------------.html?wd={key}"
else:
p = f"{key}----------{pg}---.html"
data = self.getpq(f"/vodsearch/{p}")
return {'list': self.getlist(data('ul.ewave-vodlist__media.clearfix li')), 'page': pg}
def playerContent(self, flag, id, vipFlags):
try:
data = self.getpq(id)
jstr = json.loads(data('.ewave-player__video script').eq(0).text().split('=', 1)[-1])
jxpath = '/bbplayer/api.php'
data = self.session.post(f"{self.host}{jxpath}", data={'vid': jstr['url']}).json()['data']
if re.search(r'\.m3u8|\.mp4', data['url']):
url = data['url']
elif data['urlmode'] == 1:
url = self.decode1(data['url'])
elif data['urlmode'] == 2:
url = self.decode2(data['url'])
elif re.search(r'\.m3u8|\.mp4', jstr['url']):
url = jstr['url']
else:
url = None
if not url: raise Exception('未找到播放地址')
p, c = 0, ''
except Exception as e:
self.log(f"解析失败: {e}")
p, url, c = 1, f"{self.host}{id}", 'document.querySelector("#playleft iframe").contentWindow.document.querySelector("#start").click()'
return {'parse': p, 'url': url, 'header': {'User-Agent': 'okhttp/3.12.1'}, 'click': c}
def localProxy(self, param):
wdict = json.loads(self.d64(param['wdict']))
url = f"{wdict['jx']}{wdict['id']}"
data = pq(self.fetch(url, headers=self.headers).text)
html = data('script').eq(-1).text()
url = re.search(r'src="(.*?)"', html).group(1)
return [302, 'text/html', None, {'Location': url}]
def liveContent(self, url):
pass
def getpq(self, path='', min=0, max=3):
data = self.session.get(f"{self.host}{path}")
data = data.text
try:
if '人机验证' in data:
print(f"{min}次尝试人机验证")
jstr = pq(data)('script').eq(-1).html()
token, tpath, stt = self.extract(jstr)
body = {'value': self.encrypt(self.host, stt), 'token': self.encrypt(token, stt)}
cd = self.session.post(f"{self.host}{tpath}", data=body)
if min > max: raise Exception('人机验证失败')
return self.getpq(path, min + 1, max)
return pq(data)
except:
return pq(data.encode('utf-8'))
def encrypt(self, input_str, staticchars):
encodechars = ""
for char in input_str:
num0 = staticchars.find(char)
if num0 == -1:
code = char
else:
code = staticchars[(num0 + 3) % 62]
num1 = random.randint(0, 61)
num2 = random.randint(0, 61)
encodechars += staticchars[num1] + code + staticchars[num2]
return self.e64(encodechars)
def extract(self, js_code):
token_match = re.search(r'var token = encrypt\("([^"]+)"\);', js_code)
token_value = token_match.group(1) if token_match else None
url_match = re.search(r'var url = \'([^\']+)\';', js_code)
url_value = url_match.group(1) if url_match else None
staticchars_match = re.search(r'var\s+staticchars\s*=\s*["\']([^"\']+)["\'];', js_code)
staticchars = staticchars_match.group(1) if staticchars_match else None
return token_value, url_value, staticchars
def decode1(self, val):
url = self._custom_str_decode(val)
parts = url.split("/")
result = "/".join(parts[2:])
key1 = json.loads(self.d64(parts[1]))
key2 = json.loads(self.d64(parts[0]))
decoded = self.d64(result)
return self._de_string(key1, key2, decoded)
def _custom_str_decode(self, val):
decoded = self.d64(val)
key = self.md5("test")
result = ""
for i in range(len(decoded)):
result += chr(ord(decoded[i]) ^ ord(key[i % len(key)]))
return self.d64(result)
def _de_string(self, key_array, value_array, input_str):
result = ""
for char in input_str:
if re.match(r'^[a-zA-Z]$', char):
if char in key_array:
index = key_array.index(char)
result += value_array[index]
continue
result += char
return result
def decode2(self, url):
key = "PXhw7UT1B0a9kQDKZsjIASmOezxYG4CHo5Jyfg2b8FLpEvRr3WtVnlqMidu6cN"
url = self.d64(url)
result = ""
i = 1
while i < len(url):
try:
index = key.find(url[i])
if index == -1:
char = url[i]
else:
char = key[(index + 59) % 62]
result += char
except IndexError:
break
i += 3
return result
def getlist(self, data):
def _parse_video_items(self, soup):
videos = []
for k in data.items():
j = k('.ewave-vodlist__thumb')
h = k('.text-overflow a')
if not h.attr('href'): h = j
seen_ids = set()
for li in soup.select('li'):
a_tag = li.select_one('a.stui-vodlist__thumb')
if not a_tag:
continue
href = a_tag.get('href', '')
if not href:
continue
vod_id = href if 'http' in href else xurl + href
if vod_id in seen_ids:
continue
seen_ids.add(vod_id)
img = a_tag.select_one('img')
title = a_tag.get('title', '')
if not title and img:
title = img.get('alt', '')
if not title:
title = a_tag.get_text(strip=True)
if not title:
continue
pic = a_tag.get('data-original', '')
if not pic and img:
pic = img.get('data-original') or img.get('src', '')
if pic and not pic.startswith('http'):
pic = xurl + ('' if pic.startswith('/') else '/') + pic
remark = ''
remark_span = a_tag.select_one('span.pic-text')
if remark_span:
remark = remark_span.get_text(strip=True)
raw_name = j.attr('title')
clean_name = self.clean_vod_name(raw_name)
videos.append({
'vod_id': re.findall(r'\d+', h.attr('href'))[0],
'vod_name': clean_name,
'vod_pic': j.attr('data-original'),
'vod_remarks': k('.pic-text').text(),
"vod_id": vod_id,
"vod_name": title,
"vod_pic": pic,
"vod_remarks": remark
})
return videos
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def homeVideoContent(self):
return {'list': self._parse_video_items(BeautifulSoup(requests.get(xurl, headers=headers).text, 'lxml'))}
def d64(self, encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
def categoryContent(self, cid, pg, filter, ext):
page = int(pg) if pg else 1
url = f"{xurl}/vtype/{cid}-{page}.html"
videos = self._parse_video_items(BeautifulSoup(requests.get(url, headers=headers).text, 'lxml'))
return {
'list': videos,
'page': page,
'pagecount': 999,
'limit': 90,
'total': 9999
}
def md5(self, text):
h = MD5.new()
h.update(text.encode('utf-8'))
return h.hexdigest()
def detailContent(self, ids):
did = ids[0]
if not did.startswith('http'):
did = urljoin(xurl, did)
resp = requests.get(did, headers=headers, timeout=10)
soup = BeautifulSoup(resp.text, 'lxml')
info = {'vod_id': did}
thumb = soup.select_one('.stui-content__thumb img')
if thumb:
pic = thumb.get('data-original') or thumb.get('src', '')
if pic and pic.startswith('//'):
pic = 'https:' + pic
elif pic and not pic.startswith('http'):
pic = urljoin(xurl, pic)
info['vod_pic'] = pic
detail_div = soup.select_one('.stui-content')
if detail_div:
title_h1 = detail_div.select_one('h3.title')
if title_h1:
raw_title = title_h1.get_text(strip=True).replace('\n', '')
info['vod_name'] = raw_title
for p in detail_div.select('p.data'):
text = p.get_text(strip=True)
if '主演:' in text:
info['vod_actor'] = text.split('主演:')[-1].strip()
if '类型:' in text:
info['type_name'] = text.split('类型:')[-1].strip()
if '导演:' in text:
info['vod_director'] = text.split('导演:')[-1].strip()
if '状态:' in text:
info['vod_remarks'] = text.split('状态:')[-1].strip()
if '年代:' in text:
info['vod_year'] = text.split('年代:')[-1].strip()
if '地区:' in text:
info['vod_area'] = text.split('地区:')[-1].strip()
content_div = soup.select_one('.detail-content')
if content_div:
p = content_div.find('p')
if p:
text = p.get_text(strip=True)
else:
text = content_div.get_text(strip=True)
text = re.sub(r'^简介[:]\s*', '', text)
info['vod_content'] = text
else:
info['vod_content'] = ''
filter_lines = ['猜您喜欢', '同类型']
filter_titles = ['1080P', 'дрр滈凊']
ktabs = []
klists = []
seen_lines = set()
line_items = soup.select('.stui-pannel__head h3')
playlist_containers = soup.select('.stui-content__playlist')
for idx, tab in enumerate(line_items):
if idx >= len(playlist_containers):
break
line_name = tab.get_text(strip=True)
if not line_name or line_name in filter_lines:
continue
if line_name in seen_lines:
continue
container = playlist_containers[idx]
episode_links = container.select('li a')
if not episode_links:
continue
klist = []
for ep in episode_links:
ep_name = ep.get_text(strip=True)
ep_link = ep.get('href', '')
if ep_name in filter_titles:
continue
if ep_name and ep_link:
klist.append(f'{ep_name}${ep_link}')
if klist:
seen_lines.add(line_name)
ktabs.append(line_name)
klists.append('#'.join(klist))
info["vod_play_from"] = '$$$'.join(ktabs)
info["vod_play_url"] = '$$$'.join(klists)
return {'list': [info]}
def playerContent(self, flag, id, vipFlags):
try:
play_url = id if id.startswith(('http://', 'https://')) else xurl + id
html = requests.get(play_url, headers=headers, timeout=10).text
pattern = r'var\s+player_\w+\s*=\s*(\{[^;]+?\})\s*(?:;|</script)'
match = re.search(pattern, html, re.DOTALL)
video_url = ''
if match:
try:
json_str = match.group(1).strip()
json_str = re.sub(r',\s*}', '}', json_str)
data = json.loads(json_str)
video_url = data.get('url', '')
except Exception:
pass
if not video_url:
m3u8_match = re.search(r'["\']url["\']\s*:\s*["\'](https?://[^"\']+\.m3u8[^"\']*)["\']', html)
if m3u8_match:
video_url = m3u8_match.group(1)
if video_url:
parse = 0 if re.search(r'\.(m3u8|mp4|mkv|flv|ts)$', video_url, re.I) else 1
if parse:
video_url = play_url
else:
parse = 1
video_url = play_url
return {"parse": parse, "playUrl": "", "url": video_url, "header": headers}
except Exception as e:
print(f"player error: {e}")
return {"parse": 1, "playUrl": "", "url": xurl + id, "header": headers}
def searchContent(self, key, quick, page='1'):
page = int(page) if page else 1
encoded_key = urllib.parse.quote(key, safe='')
url = f"{xurl}/vodsearch/{encoded_key}----------{page}---.html"
soup = BeautifulSoup(requests.get(url, headers=headers).text, 'lxml')
videos = self._parse_video_items(soup)
return {
'list': videos,
'page': page,
'pagecount': 60,
'limit': 30,
'total': 999999
}
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None
+546 -552
View File
@@ -1,552 +1,546 @@
# coding=utf-8
# !/usr/bin/python
"""
作者 精彩一瞬间 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
====================Diudiumiao====================
"""
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from urllib.parse import urlparse
from urllib.parse import unquote
from Crypto.Cipher import ARC4
from urllib.parse import quote
from base.spider import Spider
from Crypto.Cipher import AES
from datetime import datetime
from bs4 import BeautifulSoup
from base64 import b64decode
import concurrent.futures
import urllib.request
import urllib.parse
import datetime
import binascii
import requests
import hashlib
import base64
import json
import time
import hmac
import sys
import re
import os
sys.path.append('..')
xurl = "https://www.wasu.cn"
headerx = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
}
headerz = {
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'origin': xurl,
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': xurl,
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
}
class Spider(Spider):
def getName(self):
return "精彩"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeVideoContent(self):
pass
def homeContent(self, filter):
result = {}
result = {"class": [{"type_id": "961", "type_name": "电影"},
{"type_id": "962", "type_name": "剧集"},
{"type_id": "963", "type_name": "少儿"},
{"type_id": "965", "type_name": "栏目"},
{"type_id": "966", "type_name": "新闻"}],
"list": [],
"filters": {"961": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "港台", "v": "港台"},
{"n": "欧美", "v": "欧美"},
{"n": "日韩", "v": "日韩"},
{"n": "泰国", "v": "泰国"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "动作", "v": "动作"},
{"n": "科幻", "v": "科幻"},
{"n": "惊悚", "v": "惊悚"},
{"n": "冒险", "v": "冒险"},
{"n": "剧情", "v": "剧情"},
{"n": "励志", "v": "励志"},
{"n": "爱情", "v": "爱情"},
{"n": "喜剧", "v": "喜剧"},
{"n": "家庭", "v": "家庭"},
{"n": "历史", "v": "历史"},
{"n": "魔幻", "v": "魔幻"},
{"n": "恐怖", "v": "恐怖"},
{"n": "战争", "v": "战争"},
{"n": "武侠", "v": "武侠"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"962": [{"key": "地区",
"name": "",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "港台", "v": "港台"},
{"n": "日韩", "v": "日韩"},
{"n": "欧美", "v": "欧美"},
{"n": "泰国", "v": "泰国"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "都市", "v": "都市"},
{"n": "爱情", "v": "爱情"},
{"n": "短剧", "v": "短剧"}, {"n": "战争", "v": "战争"},
{"n": "家庭", "v": "家庭"},
{"n": "悬疑", "v": "悬疑"},
{"n": "古装", "v": "古装"}, {"n": "谍战", "v": "谍战"},
{"n": "喜剧", "v": "喜剧"},
{"n": "农村", "v": "农村"},
{"n": "刑侦", "v": "刑侦"},
{"n": "武侠", "v": "武侠"},
{"n": "历史", "v": "历史"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"963": [{"key": "",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "日韩", "v": "日韩"},
{"n": "欧美", "v": "欧美"},
{"n": "港台", "v": "港台"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "动作", "v": "动作"},
{"n": "冒险", "v": "冒险"},
{"n": "益智", "v": "益智"},
{"n": "亲子", "v": "亲子"},
{"n": "热血", "v": "热血"},
{"n": "剧情", "v": "剧情"},
{"n": "魔幻", "v": "魔幻"},
{"n": "励志", "v": "励志"},
{"n": "机战", "v": "机战"},
{"n": "搞笑", "v": "搞笑"},
{"n": "科幻", "v": "科幻"},
{"n": "治愈", "v": "治愈"},
{"n": "儿歌", "v": "儿歌"},
{"n": "教育", "v": "教育"},
{"n": "校园", "v": "校园"},
{"n": "童话", "v": "童话"},
{"n": "推理", "v": "推理"},
{"n": "怀旧", "v": "怀旧"},
{"n": "宠物", "v": "宠物"},
{"n": "舞蹈", "v": "舞蹈"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"965": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "欧美", "v": "欧美"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "文化", "v": "文化"},
{"n": "纪实", "v": "纪实"},
{"n": "访谈", "v": "访谈"},
{"n": "历史", "v": "历史"},
{"n": "美食", "v": "美食"},
{"n": "旅游", "v": "旅游"},
{"n": "时尚", "v": "时尚"},
{"n": "情感", "v": "情感"},
{"n": "生活", "v": "生活"},
{"n": "真人秀", "v": "真人秀"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2026", "v": "2026"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"966": [{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "国内视野", "v": "国内视野"},
{"n": "国际纵览", "v": "国际纵览"},
{"n": "军事话题", "v": "军事话题"},
{"n": "社会百态", "v": "社会百态"},
{"n": "央视频", "v": "央视频"}]}]}}
return result
def generate_x_sign(self, secret_b64):
secret_key = self.decode_secret_key(secret_b64)
data = self.get_data_string()
signature = self.compute_signature(secret_key, data)
x_sign = self.encode_signature(signature)
return x_sign
def decode_secret_key(self, secret_b64):
return base64.b64decode(secret_b64)
def get_data_string(self):
return "{}"
def compute_signature(self, secret_key, data):
return hmac.new(secret_key, data.encode('utf-8'), hashlib.sha256).digest()
def encode_signature(self, signature):
return base64.b64encode(signature).decode('utf-8')
def get_current_app_key(self, xurl, headerx):
resp = self.fetch_index_page(xurl, headerx)
js_path = self.extract_js_path(resp.text)
js_url = f"{xurl}{js_path}"
js_content = self.fetch_js_content(js_url, headerx)
target_key_b64 = self.extract_target_key(js_content)
return target_key_b64
def fetch_index_page(self, xurl, headerx):
resp = requests.get(xurl, headers=headerx)
resp.raise_for_status()
return resp
def extract_js_path(self, html_content):
js_path_pattern = r'src="(/[\d\.]+/assets/js/index-[\w\.-]+\.js)"'
match = re.search(js_path_pattern, html_content)
return match.group(1)
def fetch_js_content(self, js_url, headerx):
js_resp = requests.get(js_url, headers=headerx)
js_resp.raise_for_status()
return js_resp.text
def extract_target_key(self, js_content):
key_pattern = r'const \w+="([^"]+)",\w+="([^"]+)",\w+="([^"]+)";'
key_match = re.search(key_pattern, js_content)
return key_match.group(2)
def get_headers(self, sign1, xurl):
return {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'launchchannel': 'web_channel',
'origin': xurl,
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': xurl,
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'siteid': '1000101',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
'x-sign': sign1,
}
def categoryContent(self, cid, pg, filter, ext):
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
page = self.get_page_number(pg)
NdType, DqType, LxType = self.extract_filter_types(ext)
headers = self.get_headers(sign1, xurl)
params = self.build_category_params(cid, page, NdType, DqType, LxType)
data = self.fetch_category_data(params, headers)
videos = self.parse_category_videos(data)
return self.build_category_result(videos, pg)
def get_page_number(self, pg):
return int(pg) if pg else 1
def extract_filter_types(self, ext):
NdType = ext.get('年代', '全部')
DqType = ext.get('地区', '全部')
LxType = ext.get('类型', '全部')
return NdType, DqType, LxType
def build_category_params(self, cid, page, NdType, DqType, LxType):
return {'functionName': 'getNewsSearchedByCondition','nodeId': cid,'nodeTag': LxType,'yearTag': NdType,'countryTag': DqType,'orderType': '0','pageSize': '40','page': page,'keyword': '','siteId': '1000101',}
def fetch_category_data(self, params, headers):
detail = requests.get('https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet', params=params,headers=headers)
detail.encoding = "utf-8"
return detail.json()
def parse_category_videos(self, data):
videos = []
for vod in data['data']:
videos.append(self.parse_category_video(vod))
return videos
def parse_category_video(self, vod):
return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}","vod_name": vod['title'],"vod_pic": vod['hPic'],"vod_year": vod.get('pubTime', '暂无日期'),"vod_remarks": vod.get('episodeDesc', '暂无备注')}
def build_category_result(self, videos, pg):
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
did = ids[0]
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
fenge = did.split("@")
headers = self.get_headers(sign1, xurl)
params = self.build_detail_params(fenge)
data = self.fetch_detail_data(params, headers)
content = self.build_content(data)
director = self.extract_detail_field(data, 'director')
actor = self.extract_detail_field(data, 'actor')
remarks = self.extract_detail_field(data, 'episodeDesc')
year = self.extract_detail_field(data, 'pubTime')
area = self.extract_detail_field(data, 'countryTag')
bofang = self.build_play_url(data)
videos = [self.build_video_data(did, director, actor, remarks, year, area, content, bofang)]
return self.build_result(videos)
def build_detail_params(self, fenge):
return {'siteId': '1000101', 'functionName': 'getCurrentNews', 'nodeId': fenge[0], 'newsId': fenge[1],'platform': 'web',}
def fetch_detail_data(self, params, headers):
detail = requests.get('https://mcspapp.5g.wasu.tv/bvradio_app/hzhs/newsServlet', params=params, headers=headers)
detail.encoding = "utf-8"
return detail.json()
def build_content(self, data):
return '介绍剧情👉' + data.get('data', {}).get('newsAbstract', '')
def extract_detail_field(self, data, field_name):
return data.get('data', {}).get(field_name, '')
def build_play_url(self, data):
bofang = ''
for vod in data['data']['vodList']:
name = vod['title']
if len(vod['fileList']) > 1:
id = vod['fileList'][1]['playUrl']
else:
id = vod['fileList'][0]['playUrl']
bofang += name + '$' + id + '#'
return bofang[:-1]
def build_video_data(self, did, director, actor, remarks, year, area, content, bofang):
return {"vod_id": did, "vod_director": director, "vod_actor": actor, "vod_remarks": remarks, "vod_year": year, "vod_area": area, "vod_content": content, "vod_play_from": "华数", "vod_play_url": bofang}
def build_result(self, videos):
result = {}
result['list'] = videos
return result
def get_x_sign_for_post(self, secret_b64, playUrl):
secret_key = self.decode_secret_key(secret_b64)
payload_dict = self.build_payload_dict(playUrl)
data_string = self.serialize_payload(payload_dict)
signature = self.compute_hmac_signature(secret_key, data_string)
x_sign = self.encode_signature(signature)
return x_sign
def decode_secret_key(self, secret_b64):
return base64.b64decode(secret_b64)
def build_payload_dict(self, playUrl):
return {"playUrl": playUrl, "platform": "web"}
def serialize_payload(self, payload_dict):
return json.dumps(payload_dict, separators=(',', ':'), ensure_ascii=False)
def compute_hmac_signature(self, secret_key, data_string):
return hmac.new(secret_key, data_string.encode('utf-8'), hashlib.sha256).digest()
def encode_signature(self, signature):
return base64.b64encode(signature).decode('utf-8')
def playerContent(self, flag, id, vipFlags):
secret_b64 = self.get_current_app_key(xurl, headerx)
id = self.normalize_play_url(id)
sign2 = self.get_x_sign_for_post(secret_b64, id)
headers = self.get_headers(sign2, xurl)
json_data = self.build_request_json(id)
result = self.post_play_url_request(headers, json_data)
play_url = result['data']['playUrl']
return self.build_player_result(play_url)
def normalize_play_url(self, id):
return id.replace('.mp4', '/playlist.m3u8')
def build_request_json(self, playUrl):
return {'playUrl': playUrl, 'platform': 'web', }
def post_play_url_request(self, headers, json_data):
response = requests.post('https://mcspapp.5g.wasu.tv/thirdApiFile/file/getPlayUrl', headers=headers, json=json_data)
return response.json()
def build_player_result(self, play_url):
result = {}
result["parse"] = 0
result["playUrl"] = ''
result["url"] = play_url
result["header"] = headerz
return result
def searchContentPage(self, key, quick, pg):
page = self.get_page_number(pg)
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
headers = self.get_headers(sign1, xurl)
params = self.build_search_params(key, page)
data = self.fetch_search_data(params, headers)
videos = self.parse_search_videos(data)
return self.build_search_result(videos, pg)
def get_page_number(self, pg):
return int(pg) if pg else 1
def build_search_params(self, key, page):
return {'functionName': 'getNewsSearched', 'searchNewsType': '3,4,5', 'keyword': key, 'pageSize': 10, 'page': page, 'siteId': 1000101}
def fetch_search_data(self, params, headers):
detail = requests.get("https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet", params=params, headers=headers)
detail.encoding = "utf-8"
return detail.json()
def parse_search_videos(self, data):
videos = []
for vod in data['data']:
videos.append(self.parse_search_video(vod))
return videos
def parse_search_video(self, vod):
return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}", "vod_name": vod['title'], "vod_pic": vod['hPic'], "vod_year": vod.get('pubTime', '暂无日期'), "vod_remarks": vod.get('episodeDesc', '暂无备注')}
def build_search_result(self, videos, pg):
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None
# coding=utf-8
# !/usr/bin/python
"""
作者 精彩一瞬间 内容均从互联网收集而来 仅供交流学习使用 严禁用于商业用途 请于24小时内删除
====================Diudiumiao====================
"""
from Crypto.Util.Padding import unpad
from Crypto.Util.Padding import pad
from urllib.parse import urlparse
from urllib.parse import unquote
from Crypto.Cipher import ARC4
from urllib.parse import quote
from base.spider import Spider
from Crypto.Cipher import AES
from datetime import datetime
from bs4 import BeautifulSoup
from base64 import b64decode
import concurrent.futures
import urllib.request
import urllib.parse
import datetime
import binascii
import requests
import hashlib
import base64
import json
import time
import hmac
import sys
import re
import os
sys.path.append('..')
xurl = "https://www.wasu.cn"
headerx = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
}
headerz = {
'accept': '*/*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'origin': xurl,
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': xurl,
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
}
class Spider(Spider):
def getName(self):
return "精彩"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeVideoContent(self):
pass
def homeContent(self, filter):
result = {}
result = {"class": [{"type_id": "961", "type_name": "电影"},
{"type_id": "962", "type_name": "剧集"},
{"type_id": "963", "type_name": "少儿"},
{"type_id": "965", "type_name": "栏目"},
{"type_id": "966", "type_name": "新闻"}],
"list": [],
"filters": {"961": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "港台", "v": "港台"},
{"n": "欧美", "v": "欧美"},
{"n": "日韩", "v": "日韩"},
{"n": "泰国", "v": "泰国"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "动作", "v": "动作"},
{"n": "科幻", "v": "科幻"},
{"n": "惊悚", "v": "惊悚"},
{"n": "冒险", "v": "冒险"},
{"n": "剧情", "v": "剧情"},
{"n": "励志", "v": "励志"},
{"n": "爱情", "v": "爱情"},
{"n": "喜剧", "v": "喜剧"},
{"n": "家庭", "v": "家庭"},
{"n": "历史", "v": "历史"},
{"n": "魔幻", "v": "魔幻"},
{"n": "恐怖", "v": "恐怖"},
{"n": "战争", "v": "战争"},
{"n": "武侠", "v": "武侠"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"962": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": ""},
{"n": "港台", "v": "港台"},
{"n": "日韩", "v": "日韩"},
{"n": "欧美", "v": "欧美"},
{"n": "泰国", "v": "泰国"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "都市", "v": "都市"},
{"n": "爱情", "v": "爱情"},
{"n": "战争", "v": "战争"},
{"n": "家庭", "v": "家庭"},
{"n": "悬疑", "v": "悬疑"},
{"n": "古装", "v": "古装"},
{"n": "短剧", "v": "短剧"},
{"n": "谍战", "v": "谍战"},
{"n": "喜剧", "v": "喜剧"},
{"n": "农村", "v": "农村"},
{"n": "刑侦", "v": "刑侦"},
{"n": "武侠", "v": "武侠"},
{"n": "历史", "v": "历史"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"963": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": ""},
{"n": "日韩", "v": "日韩"},
{"n": "欧美", "v": "欧美"},
{"n": "港台", "v": "港台"},
{"n": "其他", "v": "其他"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "动作", "v": "动作"},
{"n": "冒险", "v": "冒险"},
{"n": "益智", "v": "益智"},
{"n": "亲子", "v": "亲子"},
{"n": "热血", "v": "热血"},
{"n": "剧情", "v": "剧情"},
{"n": "魔幻", "v": "魔幻"},
{"n": "励志", "v": "励志"},
{"n": "机战", "v": "机战"},
{"n": "搞笑", "v": "搞笑"},
{"n": "科幻", "v": "科幻"},
{"n": "治愈", "v": "治愈"},
{"n": "儿歌", "v": "儿歌"},
{"n": "教育", "v": "教育"},
{"n": "校园", "v": "校园"},
{"n": "童话", "v": "童话"},
{"n": "推理", "v": "推理"},
{"n": "怀旧", "v": "怀旧"},
{"n": "宠物", "v": "宠物"},
{"n": "舞蹈", "v": "舞蹈"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"965": [{"key": "地区",
"name": "地区",
"value": [{"n": "全部", "v": ""},
{"n": "内地", "v": "内地"},
{"n": "欧美", "v": "欧美"}]},
{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "文化", "v": "文化"},
{"n": "纪实", "v": "纪实"},
{"n": "访谈", "v": "访谈"},
{"n": "历史", "v": "历史"},
{"n": "美食", "v": "美食"},
{"n": "旅游", "v": "旅游"},
{"n": "时尚", "v": "时尚"},
{"n": "情感", "v": "情感"},
{"n": "生活", "v": "生活"},
{"n": "真人秀", "v": "真人秀"}]},
{"key": "年代",
"name": "年代",
"value": [{"n": "全部", "v": ""},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"}]}],
"966": [{"key": "类型",
"name": "类型",
"value": [{"n": "全部", "v": ""},
{"n": "国内视野", "v": "国内视野"},
{"n": "国际纵览", "v": "国际纵览"},
{"n": "军事话题", "v": "军事话题"},
{"n": "社会百态", "v": "社会百态"},
{"n": "央视频", "v": "央视频"}]}]}}
return result
def generate_x_sign(self, secret_b64):
secret_key = self.decode_secret_key(secret_b64)
data = self.get_data_string()
signature = self.compute_signature(secret_key, data)
x_sign = self.encode_signature(signature)
return x_sign
def decode_secret_key(self, secret_b64):
return base64.b64decode(secret_b64)
def get_data_string(self):
return "{}"
def compute_signature(self, secret_key, data):
return hmac.new(secret_key, data.encode('utf-8'), hashlib.sha256).digest()
def encode_signature(self, signature):
return base64.b64encode(signature).decode('utf-8')
def get_current_app_key(self, xurl, headerx):
resp = self.fetch_index_page(xurl, headerx)
js_path = self.extract_js_path(resp.text)
js_url = f"{xurl}{js_path}"
js_content = self.fetch_js_content(js_url, headerx)
target_key_b64 = self.extract_target_key(js_content)
return target_key_b64
def fetch_index_page(self, xurl, headerx):
resp = requests.get(xurl, headers=headerx)
resp.raise_for_status()
return resp
def extract_js_path(self, html_content):
js_path_pattern = r'src="(/[\d\.]+/assets/js/index-[\w\.-]+\.js)"'
match = re.search(js_path_pattern, html_content)
return match.group(1)
def fetch_js_content(self, js_url, headerx):
js_resp = requests.get(js_url, headers=headerx)
js_resp.raise_for_status()
return js_resp.text
def extract_target_key(self, js_content):
key_pattern = r'const \w+="([^"]+)",\w+="([^"]+)",\w+="([^"]+)";'
key_match = re.search(key_pattern, js_content)
return key_match.group(2)
def get_headers(self, sign1, xurl):
return {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'launchchannel': 'web_channel',
'origin': xurl,
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': xurl,
'sec-ch-ua': '"Microsoft Edge";v="143", "Chromium";v="143", "Not A(Brand";v="24"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'cross-site',
'siteid': '1000101',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0',
'x-sign': sign1,
}
def categoryContent(self, cid, pg, filter, ext):
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
page = self.get_page_number(pg)
NdType, DqType, LxType = self.extract_filter_types(ext)
headers = self.get_headers(sign1, xurl)
params = self.build_category_params(cid, page, NdType, DqType, LxType)
data = self.fetch_category_data(params, headers)
videos = self.parse_category_videos(data)
return self.build_category_result(videos, pg)
def get_page_number(self, pg):
return int(pg) if pg else 1
def extract_filter_types(self, ext):
NdType = ext.get('年代', '全部')
DqType = ext.get('地区', '全部')
LxType = ext.get('类型', '全部')
return NdType, DqType, LxType
def build_category_params(self, cid, page, NdType, DqType, LxType):
return {'functionName': 'getNewsSearchedByCondition','nodeId': cid,'nodeTag': LxType,'yearTag': NdType,'countryTag': DqType,'orderType': '0','pageSize': '40','page': page,'keyword': '','siteId': '1000101',}
def fetch_category_data(self, params, headers):
detail = requests.get('https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet', params=params,headers=headers)
detail.encoding = "utf-8"
return detail.json()
def parse_category_videos(self, data):
videos = []
for vod in data['data']:
videos.append(self.parse_category_video(vod))
return videos
def parse_category_video(self, vod):
return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}","vod_name": vod['title'],"vod_pic": vod['hPic'],"vod_year": vod.get('pubTime', '暂无日期'),"vod_remarks": vod.get('episodeDesc', '暂无备注')}
def build_category_result(self, videos, pg):
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
did = ids[0]
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
fenge = did.split("@")
headers = self.get_headers(sign1, xurl)
params = self.build_detail_params(fenge)
data = self.fetch_detail_data(params, headers)
content = self.build_content(data)
director = self.extract_detail_field(data, 'director')
actor = self.extract_detail_field(data, 'actor')
remarks = self.extract_detail_field(data, 'episodeDesc')
year = self.extract_detail_field(data, 'pubTime')
area = self.extract_detail_field(data, 'countryTag')
bofang = self.build_play_url(data)
videos = [self.build_video_data(did, director, actor, remarks, year, area, content, bofang)]
return self.build_result(videos)
def build_detail_params(self, fenge):
return {'siteId': '1000101', 'functionName': 'getCurrentNews', 'nodeId': fenge[0], 'newsId': fenge[1],'platform': 'web',}
def fetch_detail_data(self, params, headers):
detail = requests.get('https://mcspapp.5g.wasu.tv/bvradio_app/hzhs/newsServlet', params=params, headers=headers)
detail.encoding = "utf-8"
return detail.json()
def build_content(self, data):
return '介绍剧情📢' + data.get('data', {}).get('newsAbstract', '')
def extract_detail_field(self, data, field_name):
return data.get('data', {}).get(field_name, '')
def build_play_url(self, data):
bofang = ''
for vod in data['data']['vodList']:
name = vod['title']
if len(vod['fileList']) > 1:
id = vod['fileList'][1]['playUrl']
else:
id = vod['fileList'][0]['playUrl']
bofang += name + '$' + id + '#'
return bofang[:-1]
def build_video_data(self, did, director, actor, remarks, year, area, content, bofang):
return {"vod_id": did, "vod_director": director, "vod_actor": actor, "vod_remarks": remarks, "vod_year": year, "vod_area": area, "vod_content": content, "vod_play_from": "华数专线", "vod_play_url": bofang}
def build_result(self, videos):
result = {}
result['list'] = videos
return result
def get_x_sign_for_post(self, secret_b64, playUrl):
secret_key = self.decode_secret_key(secret_b64)
payload_dict = self.build_payload_dict(playUrl)
data_string = self.serialize_payload(payload_dict)
signature = self.compute_hmac_signature(secret_key, data_string)
x_sign = self.encode_signature(signature)
return x_sign
def decode_secret_key(self, secret_b64):
return base64.b64decode(secret_b64)
def build_payload_dict(self, playUrl):
return {"playUrl": playUrl, "platform": "web"}
def serialize_payload(self, payload_dict):
return json.dumps(payload_dict, separators=(',', ':'), ensure_ascii=False)
def compute_hmac_signature(self, secret_key, data_string):
return hmac.new(secret_key, data_string.encode('utf-8'), hashlib.sha256).digest()
def encode_signature(self, signature):
return base64.b64encode(signature).decode('utf-8')
def playerContent(self, flag, id, vipFlags):
secret_b64 = self.get_current_app_key(xurl, headerx)
id = self.normalize_play_url(id)
sign2 = self.get_x_sign_for_post(secret_b64, id)
headers = self.get_headers(sign2, xurl)
json_data = self.build_request_json(id)
result = self.post_play_url_request(headers, json_data)
play_url = result['data']['playUrl']
return self.build_player_result(play_url)
def normalize_play_url(self, id):
return id.replace('.mp4', '/playlist.m3u8')
def build_request_json(self, playUrl):
return {'playUrl': playUrl, 'platform': 'web', }
def post_play_url_request(self, headers, json_data):
response = requests.post('https://mcspapp.5g.wasu.tv/thirdApiFile/file/getPlayUrl', headers=headers, json=json_data)
return response.json()
def build_player_result(self, play_url):
result = {}
result["parse"] = 0
result["playUrl"] = ''
result["url"] = play_url
result["header"] = headerz
return result
def searchContentPage(self, key, quick, pg):
page = self.get_page_number(pg)
secret_b64 = self.get_current_app_key(xurl, headerx)
sign1 = self.generate_x_sign(secret_b64)
headers = self.get_headers(sign1, xurl)
params = self.build_search_params(key, page)
data = self.fetch_search_data(params, headers)
videos = self.parse_search_videos(data)
return self.build_search_result(videos, pg)
def get_page_number(self, pg):
return int(pg) if pg else 1
def build_search_params(self, key, page):
return {'functionName': 'getNewsSearched', 'searchNewsType': '3,4,5', 'keyword': key, 'pageSize': 10, 'page': page, 'siteId': 1000101}
def fetch_search_data(self, params, headers):
detail = requests.get("https://ups.5g.wasu.tv/rmp-user-suggest/1000101/hzhs/searchServlet", params=params, headers=headers)
detail.encoding = "utf-8"
return detail.json()
def parse_search_videos(self, data):
videos = []
for vod in data['data']:
videos.append(self.parse_search_video(vod))
return videos
def parse_search_video(self, vod):
return {"vod_id": f"{vod['nodeId']}@{vod['newsId']}", "vod_name": vod['title'], "vod_pic": vod['hPic'], "vod_year": vod.get('pubTime', '暂无日期'), "vod_remarks": vod.get('episodeDesc', '暂无备注')}
def build_search_result(self, videos, pg):
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick, pg="1"):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None
+53 -38
View File
@@ -3,7 +3,7 @@
"""
作者 丢丢喵 🚓 内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
作者内容均从互联网收集而来 仅供交流学习使用 版权归原创者所有 如侵犯了您的权益 请通知作者 将及时删除侵权内容
====================Diudiumiao====================
"""
@@ -161,8 +161,13 @@ class Spider(Spider):
names = vod.find('h3', class_="bili-live-card__info--tit")
name = names.text.strip().replace('直播中', '')
id = names.find('a')['href']
id = self.extract_middle_text(id, 'bilibili.com/', '?', 0)
href = names.find('a')['href']
id = self.extract_middle_text(href, 'bilibili.com/', '?', 0)
# 兜底:如果链接没有 ? 参数,用正则直接提取房间号
if not id:
m = re.search(r'bilibili\.com/(\d+)', href)
if m:
id = m.group(1)
pic = vod.find('img')['src']
if 'http' not in pic:
@@ -190,7 +195,6 @@ class Spider(Spider):
did = ids[0]
result = {}
videos = []
xianlu = ''
bofang = ''
url = f'{xurl1}/xlive/web-room/v2/index/getRoomPlayInfo?room_id={did}&platform=web&protocol=0,1&format=0,1,2&codec=0,1'
@@ -200,30 +204,39 @@ class Spider(Spider):
content = '欢迎观看哔哩直播'
setup = data['data']['playurl_info']['playurl']['stream']
try:
setup = data['data']['playurl_info']['playurl']['stream']
except (KeyError, TypeError):
setup = []
nam = 0
line_count = 0
for stream in setup:
for fmt in stream.get('format', []):
for codec in fmt.get('codec', []):
base_url = codec.get('base_url', '')
url_info_list = codec.get('url_info', [])
if not base_url or not url_info_list:
continue
# 遍历所有CDN节点,取第一个可用的
for uinfo in url_info_list:
host = uinfo.get('host', '')
extra = uinfo.get('extra', '')
if not host or not extra:
continue
# 处理 host 和 base_url 之间可能出现的双斜杠
if host.endswith('/') and base_url.startswith('/'):
base_url = base_url[1:]
play_url = host + base_url + extra
line_count += 1
namc = f"{line_count}号线路"
bofang += f"{namc}${play_url}#"
break # 每个 codec 只取第一个可用 CDN
for vod in setup:
try:
host = vod['format'][nam]['codec'][0]['url_info'][1]['host']
except (KeyError, IndexError):
continue
base = vod['format'][nam]['codec'][0]['base_url']
extra = vod['format'][nam]['codec'][0]['url_info'][1]['extra']
id = host + base + extra
nam = nam + 1
namc = f"{nam}号线路"
bofang = bofang + namc + '$' + id + '#'
bofang = bofang[:-1]
if bofang:
bofang = bofang[:-1]
xianlu = '哔哩专线'
@@ -243,7 +256,12 @@ class Spider(Spider):
result["parse"] = 0
result["playUrl"] = ''
result["url"] = id
result["header"] = headerx
# B站直播流必须带 Referer,否则 403
result["header"] = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0',
'Referer': 'https://live.bilibili.com/',
'Origin': 'https://live.bilibili.com'
}
return result
def searchContentPage(self, key, quick, pg):
@@ -268,8 +286,13 @@ class Spider(Spider):
names = vod.find('h3', class_="bili-live-card__info--tit")
name = names.text.strip().replace('直播中', '')
id = names.find('a')['href']
id = self.extract_middle_text(id, 'bilibili.com/', '?', 0)
href = names.find('a')['href']
id = self.extract_middle_text(href, 'bilibili.com/', '?', 0)
# 兜底:如果链接没有 ? 参数,用正则直接提取房间号
if not id:
m = re.search(r'bilibili\.com/(\d+)', href)
if m:
id = m.group(1)
pic = vod.find('img')['src']
if 'http' not in pic:
@@ -286,7 +309,7 @@ class Spider(Spider):
}
videos.append(video)
result['list'] = videos
result = {'list': videos}
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
@@ -304,11 +327,3 @@ class Spider(Spider):
elif params['type'] == "ts":
return self.proxyTs(params)
return None
+279
View File
@@ -0,0 +1,279 @@
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
# junyouyun
import re
import sys
import json
import base64
import os
from Crypto.Cipher import AES
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Referer': 'https://www.aowu.tv/'
}
HOST = 'https://www.aowu.tv'
API = 'https://www.aowu.tv/api/site/secure'
def init(self, extend=''):
self.host = self.HOST
self._aes_key = None
# ===================== 密钥与加解密 =====================
def _get_key(self):
if self._aes_key:
return self._aes_key
try:
resp = self.fetch(self.host, headers=self.headers)
html = resp.text
except Exception as e:
raise RuntimeError(f"获取首页失败: {e}")
meta = re.search(r'<meta[^>]+name=["\']?fk-p["\']?[^>]+content=["\']?([^"\'\s>]+)', html)
meta = meta.group(1) if meta else ''
fks = re.search(r'<html[^>]+data-fk-s=["\']?([^"\'\s>]+)', html)
fks = fks.group(1) if fks else ''
fkc = re.search(r'--fk-c:\s*"([^"]*)"', html)
fkc = fkc.group(1) if fkc else ''
fkm_match = re.search(r'__FKM\s*=\s*(\[.*?\])', html, re.DOTALL)
try:
fkm = json.loads(fkm_match.group(1)) if fkm_match else ['', '']
except (json.JSONDecodeError, TypeError):
fkm = ['', '']
parts = [meta, fks, fkm[0] if len(fkm) > 0 else '', fkc, fkm[1] if len(fkm) > 1 else '']
cleaned = [p.strip().strip("'\"") for p in parts]
combined = ''.join(cleaned)
try:
key = base64.b64decode(combined)
except Exception:
key = combined.encode('latin-1')
else:
if len(key) not in (16, 24, 32):
key = combined.encode('latin-1')
if len(key) not in (16, 24, 32):
if len(key) > 32:
key = key[:32]
elif len(key) > 24:
key = key[:24]
elif len(key) > 16:
key = key[:16]
else:
key = key + b'\x00' * (16 - len(key))
self._aes_key = key
return key
def _encrypt(self, plaintext):
key = self._get_key()
iv = os.urandom(12)
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode('utf-8'))
return json.dumps({
'n': base64.b64encode(iv).decode(),
'd': base64.b64encode(ciphertext + tag).decode()
})
def _decrypt(self, enc_data):
if isinstance(enc_data, str):
enc_data = json.loads(enc_data)
key = self._get_key()
iv = base64.b64decode(enc_data['n'])
ct_with_tag = base64.b64decode(enc_data['d'])
tag = ct_with_tag[-16:]
ciphertext = ct_with_tag[:-16]
cipher = AES.new(key, AES.MODE_GCM, nonce=iv)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext.decode('utf-8')
def _post_api(self, body_dict):
"""发送加密 POST 请求并返回解密后的 dict"""
enc_payload = self._encrypt(json.dumps(body_dict))
# 部分框架的 fetch 支持 method/data 参数,否则回退到 requests
try:
resp = self.fetch(self.API, method='POST', data=enc_payload,
headers={'Content-Type': 'application/json'})
except TypeError:
import requests
resp = requests.post(self.API, data=enc_payload,
headers={**self.headers, 'Content-Type': 'application/json'},
timeout=15)
if hasattr(resp, 'json'):
data = resp.json()
else:
data = json.loads(resp.text)
decrypted = self._decrypt(data)
return json.loads(decrypted)
# ===================== 框架必需方法 =====================
def homeContent(self, filter):
"""返回分类菜单"""
classes = [
{'type_name': '新番', 'type_id': '2'},
{'type_name': '番剧', 'type_id': '1'},
{'type_name': '剧场', 'type_id': '3'}
]
return {'class': classes, 'filters': {}}
def homeVideoContent(self):
"""首页推荐(取新番第一页)"""
data = self.categoryContent('2', '1', False, {})
return {'list': data.get('list', [])}
def categoryContent(self, tid, pg, filter, extend):
"""分类影片列表"""
body = {
'action': 'bundle',
'params': {
'key': str(tid),
'type': '',
'year': '',
'sort': 'latest',
'page': int(pg) if pg else 1,
'limit': 12,
'bundle_page': 'category'
}
}
data = self._post_api(body)
items = data.get('data', {}).get('data', {}).get('list', [])
result = []
for it in items:
result.append({
'vod_id': str(it['id']),
'vod_name': it['name'],
'vod_pic': it.get('pic', ''),
'vod_remarks': it.get('remarks', ''),
'vod_year': it.get('year', ''),
'vod_area': it.get('area', ''),
'vod_actor': it.get('actor', ''),
'vod_director': it.get('director', ''),
'vod_content': it.get('content', ''),
})
return {'list': result, 'page': pg}
def searchContent(self, key, quick, pg="1"):
"""搜索"""
body = {
'action': 'bundle',
'params': {
'anime': key,
'page': int(pg) if pg else 1,
'limit': 21,
'bundle_page': 'search'
}
}
try:
data = self._post_api(body)
except Exception:
return {'list': [], 'page': pg}
lst = data.get('data', {}).get('list') or data.get('data', {}).get('data', {}).get('list', [])
result = []
for it in lst:
result.append({
'vod_id': str(it['id']),
'vod_name': it['name'],
'vod_pic': it.get('pic', ''),
'vod_remarks': it.get('remarks', ''),
'vod_year': it.get('year', ''),
})
return {'list': result, 'page': pg}
def detailContent(self, ids):
"""视频详情"""
vid = ids[0] if isinstance(ids, list) else ids
body = {
'action': 'bundle',
'params': {
'id': int(vid),
'bundle_page': 'video'
}
}
data = self._post_api(body)
detail = data.get('data', {}).get('data', {}).get('video', {})
sources = data.get('data', {}).get('data', {}).get('sources', [])
vod = {
'vod_id': str(vid),
'vod_name': detail.get('name', ''),
'vod_pic': detail.get('pic', ''),
'vod_actor': detail.get('actor', ''),
'vod_director': detail.get('director', ''),
'vod_remarks': detail.get('remarks', ''),
'vod_year': detail.get('year', ''),
'vod_area': detail.get('area', ''),
'vod_content': detail.get('content', ''),
'vod_play_from': [],
'vod_play_url': []
}
for source in sources:
if not source.get('episodes'):
continue
vod['vod_play_from'].append(source['name'])
eps = []
for ep in source['episodes']:
param = f"{vid}|{source['id']}|{ep['no']}"
eps.append(f"{ep['name']}${param}")
vod['vod_play_url'].append('#'.join(eps))
vod['vod_play_from'] = '$$$'.join(vod['vod_play_from'])
vod['vod_play_url'] = '$$$'.join(vod['vod_play_url'])
return {'list': [vod]}
def playerContent(self, flag, video_id, vipFlags):
"""解析播放地址"""
try:
parts = video_id.split('|')
if len(parts) != 3:
return {'parse': 0, 'url': ''}
vod_id, source_id, ep_no = parts
# 第一步:获取 token
body1 = {
'action': 'bundle',
'params': {
'id': int(vod_id),
'episode': int(ep_no),
'source_id': int(source_id),
'bundle_page': 'play'
}
}
data1 = self._post_api(body1)
token = data1.get('data', {}).get('data', {}).get('play_token', {}).get('token')
if not token:
return {'parse': 0, 'url': ''}
# 第二步:获取真实播放地址
body2 = {
'action': 'play',
'params': {
'id': int(vod_id),
'token': token
}
}
data2 = self._post_api(body2)
if data2.get('code') == 200 and data2.get('data', {}).get('url'):
return {
'parse': 0,
'url': data2['data']['url'],
'header': self.headers
}
return {'parse': 0, 'url': ''}
except Exception as e:
print(f'[嗷呜动漫] 播放解析失败: {e}')
return {'parse': 0, 'url': ''}
# ===================== 辅助(可选) =====================
def getName(self):
return "嗷呜动漫"
def destroy(self):
pass
+255
View File
@@ -0,0 +1,255 @@
# -*- coding: utf-8 -*-
"""
tvbox 插件 - 蛋挞TV(兼容基类修正版)
站点: https://www.dantatv.cc
"""
import json
import sys
from base64 import b64encode, b64decode
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.host = "https://www.dantatv.cc"
pass
def getName(self):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def destroy(self):
pass
# ---------- 辅助函数 ----------
def getheader(self, content_type=None):
headers = {
'Unique-Origin': 'B9A378A8C39BDA1277D2D6185FCE2695',
'User-Agent': 'okhttp/4.1.0/luob.app',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip',
}
if content_type:
headers['Content-Type'] = content_type
return headers
def e64(self, text):
try:
text_bytes = text.encode('utf-8')
encoded_bytes = b64encode(text_bytes)
return encoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64编码错误: {str(e)}")
return ""
def d64(self, encoded_text):
try:
encoded_bytes = encoded_text.encode('utf-8')
decoded_bytes = b64decode(encoded_bytes)
return decoded_bytes.decode('utf-8')
except Exception as e:
print(f"Base64解码错误: {str(e)}")
return ""
# ---------- 首页(固定分类) ----------
def homeContent(self, filter):
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": "5"},
{"type_name": "体育赛事", "type_id": "29"},
]
return {"class": classes, "filters": {}}
# ---------- 首页推荐视频 ----------
def homeVideoContent(self):
url = f"{self.host}/api/index"
headers = self.getheader()
try:
resp = self.fetch(url, headers=headers)
data = resp.json()
vod_list = []
if data.get('data'):
for item in data['data'][0].get('vodList', []):
vod_list.append(self._vod_to_common(item))
except Exception as e:
print(f"首页推荐请求失败: {e}")
vod_list = []
return {'list': vod_list}
# ---------- 分类页(POST请求改用 self.post ----------
def categoryContent(self, tid, pg, filter, extend=None):
if extend is None:
extend = {}
body = {
"typeId1": int(tid),
"pageNum": int(pg),
"pageSize": 12,
"sortField": "vod_time",
"vodClass": extend.get('class', ''),
"vodArea": extend.get('area', ''),
"vodYear": extend.get('year', '')
}
url = f"{self.host}/api/search/type"
headers = self.getheader(content_type='application/json')
try:
# 使用基类的 post 方法(若基类无 post,可改为 requests.post 自行实现)
resp = self.post(url, headers=headers, data=json.dumps(body).encode('utf-8'))
data = resp.json()
vod_list = [self._vod_to_common(item) for item in data.get('data', [])]
except Exception as e:
print(f"分类页请求失败: {e}")
vod_list = []
return {
'list': vod_list,
'page': pg,
'pagecount': 9999,
'limit': 12,
'total': 999999
}
# ---------- 详情页 ----------
def detailContent(self, ids):
vod_id = ids[0]
url = f"{self.host}/api/vod/play?vodId={vod_id}"
headers = self.getheader()
try:
resp = self.fetch(url, headers=headers)
data = resp.json()
vod = data.get('data', {}).get('dantaVod', {})
except Exception as e:
print(f"详情请求失败: {e}")
return {'list': []}
if not vod:
return {'list': []}
info = {
'vod_id': vod.get('vodId'),
'vod_name': vod.get('vodName'),
'vod_pic': self._fix_pic(vod.get('vodPic')),
'vod_actor': vod.get('vodActor'),
'vod_director': vod.get('vodDirector'),
'vod_blurb': vod.get('vodContent') or vod.get('vodBlurb'),
'vod_area': vod.get('vodArea'),
'vod_year': vod.get('vodYear'),
'vod_remarks': vod.get('vodRemarks'),
'vod_lang': vod.get('vodLang'),
'vod_class': vod.get('vodClass'),
}
sources = vod.get('sources', [])
vod_play_from = []
vod_play_url = []
for idx, src in enumerate(sources):
collect_id = src.get('collectId')
raw_url = src.get('vodPlayUrl', '')
if not raw_url:
continue
items = raw_url.split('#')
encoded_items = []
for part in items:
if '$' not in part:
continue
name, link = part.split('$', 1)
payload = {"collectId": collect_id, "url": link}
enc = self.e64(json.dumps(payload, ensure_ascii=False))
encoded_items.append(f"{name}${enc}")
if encoded_items:
line_name = src.get('collectName') or src.get('vodPlayFrom') or f"线路{idx+1}"
vod_play_from.append(line_name)
vod_play_url.append('#'.join(encoded_items))
info['vod_play_from'] = '$$$'.join(vod_play_from)
info['vod_play_url'] = '$$$'.join(vod_play_url)
return {'list': [info]}
# ---------- 搜索 ----------
def searchContent(self, key, quick, pg="1"):
url = f"{self.host}/api/search/keyword"
params = {"keyword": key, "pageNum": pg, "pageSize": 10}
headers = self.getheader()
try:
resp = self.fetch(url, headers=headers, params=params)
data = resp.json()
vod_list = [self._vod_to_common(item) for item in data.get('data', [])]
except Exception as e:
print(f"搜索失败: {e}")
vod_list = []
return {'list': vod_list, 'page': pg}
# ---------- 播放解析 ----------
def playerContent(self, flag, id, vipFlags):
try:
payload = json.loads(self.d64(id))
collect_id = payload.get('collectId')
raw_url = payload.get('url')
if not collect_id or not raw_url:
raise ValueError("缺少参数")
except:
return {
'parse': 1,
'url': '',
'header': {'User-Agent': 'okhttp/4.1.0/luob.app'}
}
parse_url = f"{self.host}/api/vod/parse"
params = {"collectId": collect_id, "url": raw_url}
headers = self.getheader()
try:
resp = self.fetch(parse_url, headers=headers, params=params)
if resp.status_code == 200:
result = resp.json()
final_url = result.get('data') or result.get('url')
if final_url:
return {
'parse': 0,
'url': final_url,
'header': {'User-Agent': 'okhttp/4.1.0/luob.app'}
}
except Exception as e:
print(f"解析失败: {e}")
return {
'parse': 1,
'url': raw_url,
'header': {'User-Agent': 'okhttp/4.1.0/luob.app'}
}
# ---------- 内部辅助:字段映射 ----------
def _vod_to_common(self, item):
remark = item.get('vodRemarks', '')
color = item.get('vodColor')
if color:
remark = f"[{color}] {remark}" if remark else color
return {
'vod_id': item.get('vodId'),
'vod_name': item.get('vodName'),
'vod_pic': self._fix_pic(item.get('vodPic')),
'vod_remarks': remark,
'vod_year': item.get('vodYear'),
'vod_area': item.get('vodArea'),
'vod_actor': item.get('vodActor'),
'vod_director': item.get('vodDirector'),
'vod_lang': item.get('vodLang'),
'vod_class': item.get('vodClass'),
}
def _fix_pic(self, pic):
"""补全相对路径图片"""
if pic and pic.startswith('/'):
return self.host + pic
return pic
+250
View File
@@ -0,0 +1,250 @@
# -*- coding: utf-8 -*-
# by @嗷呜
import re
import sys
from pyquery import PyQuery as pq
sys.path.append('..')
from base.spider import Spider
class Spider(Spider):
def init(self, extend=""):
self.extend = extend
def getName(self):
return "毒舌影视"
def isVideoFormat(self, url):
return False
def manualVideoCheck(self):
return False
def destroy(self):
pass
host = 'https://www.xnhrsb.com/'
headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; Mobile) '
'AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/114.0.0.0 Mobile Safari/537.36',
'Referer': host,
}
# 对应 js 里的 class_name / class_url
classes_config = [
("电影", "1"),
("电视剧", "2"),
("综艺", "3"),
("动漫", "4"),
("短剧", "5"),
("豆瓣", "duoban"),
]
def homeContent(self, filter):
result = {}
classes = []
vlist = []
# 静态分类
for name, tid in self.classes_config:
classes.append({
"type_name": name,
"type_id": tid
})
# 首页推荐,沿用一级规则里的 .mrb ul li
rsp = self.fetch(self.host, headers=self.headers)
data = pq(rsp.text)
# 一级: '.mrb&&ul li;.dytit&&Text;.lazy&&data-original;.hdinfo&&Text;a&&href'
vlist.extend(self.getlist(data('.mrb ul li')))
result['class'] = classes
result['list'] = vlist
return result
def homeVideoContent(self):
return {}
def categoryContent(self, tid, pg, filter, extend):
# js: url: '/dsshiyisw/fyclass--------fypage---.html'
url = f'{self.host}dsshiyisw/{tid}--------{pg}---.html'
rsp = self.fetch(url, headers=self.headers)
data = pq(rsp.text)
videos = self.getlist(data('.mrb ul li'))
result = {
'list': videos,
'page': pg,
'pagecount': 9999,
'limit': 90,
'total': 999999
}
return result
def detailContent(self, ids):
vid = ids[0]
if not vid.startswith('http'):
if vid.startswith('/'):
url = self.host.rstrip('/') + vid
else:
url = self.host.rstrip('/') + '/' + vid
else:
url = vid
rsp = self.fetch(url, headers=self.headers)
data = pq(rsp.text)
# 二级: title/img/desc/content/tabs/lists
# title: 'h1&&Text;.moviedteail_list li&&a&&Text'
name = data('h1').text()
info_list = data('.moviedteail_list li')
def li_text(idx):
return info_list.eq(idx).text() if idx < len(info_list) else ''
# 参考 desc 顺序,大致映射
# desc: '.moviedteail_list li:eq(3)&&Text;
# .moviedteail_list li:eq(2)&&Text;
# .moviedteail_list li:eq(1)&&Text;
# .moviedteail_list li:eq(6)&&Text;
# .moviedteail_list li:eq(4)&&Text'
type_name = li_text(3)
director = li_text(2)
actor = li_text(1)
remarks = li_text(6)
year_or_area = li_text(4)
pic = data('div.dyimg img').attr('src') or ''
if pic and not pic.startswith('http'):
if pic.startswith('/'):
pic = self.host.rstrip('/') + pic
else:
pic = self.host.rstrip('/') + '/' + pic
content = data('.yp_context').text()
vod = {
'vod_id': vid,
'vod_name': name,
'vod_pic': pic,
'type_name': type_name,
'vod_year': year_or_area,
'vod_area': '',
'vod_remarks': remarks,
'vod_actor': actor,
'vod_director': director,
'vod_content': content,
'vod_play_from': '',
'vod_play_url': ''
}
# tabs: '.mi_paly_box .ypxingq_t'
tabs = [i.text() for i in data('.mi_paly_box .ypxingq_t').items()]
# lists: '.paly_list_btn:eq(#id) a:gt(0)'
# 对应每个 tab 一组播放列表
play_lists = []
play_uls = list(data('.paly_list_btn').items())
for idx, ul in enumerate(play_uls):
items = []
for i, a in enumerate(ul('a').items()):
# a:gt(0) => 跳过第一个
if i == 0:
continue
title = a.text()
href = a.attr('href') or ''
if href and not href.startswith('http'):
if href.startswith('/'):
href = self.host.rstrip('/') + href
else:
href = self.host.rstrip('/') + '/' + href
items.append(f'{title}${href}')
play_lists.append('#'.join(items))
vod['vod_play_from'] = '$$$'.join(tabs)
vod['vod_play_url'] = '$$$'.join(play_lists)
return {'list': [vod]}
def searchContent(self, key, quick, pg="1"):
# js: searchUrl: '/dsshiyisc/**----------fypage---.html'
url = f'{self.host}dsshiyisc/{key}----------{pg}---.html'
rsp = self.fetch(url, headers=self.headers)
data = pq(rsp.text)
videos = self.getlist(data('.mrb ul li'))
return {
'list': videos,
'page': pg
}
def playerContent(self, flag, id, vipFlags):
"""
对应 js 里的 lazy 解析:
- 如果页面中存在 player_aaaa 且脚本里有 "url":"xxx.m3u8" 就直接取 m3u8
- 否则返回原始地址
"""
if id.startswith('http'):
play_url = id
else:
if id.startswith('/'):
play_url = self.host.rstrip('/') + id
else:
play_url = self.host.rstrip('/') + '/' + id
p = 0 # 直接返回真实地址,不再二级解析
# 如果本身就是 m3u8
if '.m3u8' in play_url:
return {'parse': p, 'url': play_url, 'header': self.headers}
rsp = self.fetch(play_url, headers=self.headers)
html = rsp.text
# 查找包含 player_aaaa 的脚本并提取 m3u8
# js 里正则: /\"url\"\\s*:\\s*\"([^\"]+\\.m3u8[^\"]*)\"/
m = re.search(r'"url"\s*:\s*"([^"]+\.m3u8[^"]*)"', html)
if m:
real = m.group(1).replace('\\/', '/')
return {'parse': p, 'url': real, 'header': self.headers}
# 兜底,返回原地址
return {'parse': p, 'url': play_url, 'header': self.headers}
def localProxy(self, param):
return None
def getlist(self, data):
"""
对应 js 一级/搜索 规则:
'.mrb&&ul li;.dytit&&Text;.lazy&&data-original;.hdinfo&&Text;a&&href'
"""
vlist = []
for j in data.items():
name = j('.dytit').text()
pic = j('.lazy').attr('data-original') or ''
remark = j('.hdinfo').text()
href = j('a').attr('href') or ''
if pic and not pic.startswith('http'):
if pic.startswith('/'):
pic = self.host.rstrip('/') + pic
else:
pic = self.host.rstrip('/') + '/' + pic
vod_id = href
vlist.append({
'vod_id': vod_id,
'vod_name': name,
'vod_pic': pic,
'vod_remarks': remark
})
return vlist
+253
View File
@@ -0,0 +1,253 @@
# coding=utf-8
# !/usr/bin/python
import sys
import requests
from bs4 import BeautifulSoup
import re
from base.spider import Spider
import json
sys.path.append('..')
xurl = "https://ee55ff.com/video.html"
headerx = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'
}
class Spider(Spider):
global xurl
global headerx
def getName(self):
return "首页"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeContent(self, filter):
# https://yaselulu.autos/?page_id=9
data = {"name": "John", "age": 31, "city": "New York"}
res = requests.post('https://spiderscloudcn2.51111666.com/getDataInit', headers=headerx, json=data)
res.encoding = "utf-8"
json_dict = json.loads(res.text)
menu0ListMap = json_dict["data"]["menu0ListMap"]
result = {}
result['class'] = []
for item in menu0ListMap:
if item['typeName'] == "传媒" or item['typeName'] == "视频" or item['typeName'] == "电影":
for item1 in item['menu2List']:
result['class'].append({'type_id': item1['typeId2'], 'type_name': item1['typeName2']})
return result
def homeVideoContent(self):
videos = []
try:
data = {
"command": "WEB_GET_INFO",
"pageNumber": 1,
"RecordsPage": 20,
"typeId": "24",
"typeMid": "1",
"languageType": "CN",
"content": ""
}
res = requests.post('https://spiderscloudcn2.51111666.com/forward', headers=headerx, json=data)
res.encoding = "utf-8"
json_dict = json.loads(res.text)
menu0ListMap = json_dict["data"]["resultList"]
for item in menu0ListMap:
name1 = item['vod_name'].replace("yy8ycom", "")
pattern = r'(.*?)-(.*?)-\d+\s+'
name = re.sub(pattern, '', name1)
id = item['id']
pic = item['vod_pic']
id2 = item['vod_server_id']
video = {
"vod_id": str(id) + '#' + str(id2),
"vod_name": name,
"vod_pic": pic,
"vod_remarks": ''
}
videos.append(video)
result = {'list': videos}
return result
except:
pass
def categoryContent(self, cid, pg, filter, ext):
result = {}
videos = []
if not pg:
pg = 1
# https://yaselulu.autos/?cat=3754&paged=1
videos = []
try:
data = {
"command": "WEB_GET_INFO",
"pageNumber": pg,
"RecordsPage": 20,
"typeId": cid,
"typeMid": "1",
"languageType": "CN",
"content": ""
}
res = requests.post('https://spiderscloudcn2.51111666.com/forward', headers=headerx, json=data)
res.encoding = "utf-8"
json_dict = json.loads(res.text)
menu0ListMap = json_dict["data"]["resultList"]
for item in menu0ListMap:
name1 = item['vod_name'].replace("yy8ycom", "")
pattern = r'(.*?)-(.*?)-\d+\s+'
name = re.sub(pattern, '', name1)
id = item['id']
pic = item['vod_pic']
id2 = item['vod_server_id']
video = {
"vod_id": str(id) + '#' + str(id2),
"vod_name": name,
"vod_pic": pic,
"vod_remarks": ''
}
videos.append(video)
except:
pass
result['list'] = videos
result['page'] = pg
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def detailContent(self, ids):
data2 = {"name": "John", "age": 31, "city": "New York"}
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36',
'Content-Type': 'application/json'
}
res1 = requests.post('https://spiderscloudcn2.51111666.com/getDataInit', headers=headers, json=data2)
js1 = json.loads(res1.text)
did = ids[0]
cid, svid = did.split("#")
videos = []
result = {}
data = {
"command": "WEB_GET_INFO_DETAIL",
"type_Mid": "1",
"id": cid,
"languageType": "CN"
}
res = requests.post('https://spiderscloudcn2.51111666.com/forward', headers=headerx, json=data)
res.encoding = "utf-8"
json_dict = json.loads(res.text)
if svid:
purl = js1['data']['macVodLinkMap'][svid]['LINK_2'] + json_dict['data']["result"]["vod_url"]
else:
purl = json_dict['data']["result"]["vod_url"]
videos.append({
"vod_id": '',
"vod_name": '',
"vod_pic": "",
"type_name": "ぃぅおか🍬 คิดถึง",
"vod_year": "",
"vod_area": "",
"vod_remarks": "",
"vod_actor": "",
"vod_director": "",
"vod_content": "",
"vod_play_from": "直链播放",
"vod_play_url": purl
})
result['list'] = videos
return result
def playerContent(self, flag, id, vipFlags):
result = {}
result["parse"] = 0
result["playUrl"] = ''
result["url"] = id
result["header"] = headerx
return result
def searchContentPage(self, key, quick, page):
# https://yaselulu.autos/?s=%E6%88%91%E7%9A%84&paged=2
result = {}
videos = []
if not page:
page = 1
data = {
"command": "WEB_GET_INFO",
"pageNumber": page,
"RecordsPage": 20,
"typeId": "0",
"typeMid": "1",
"languageType": "CN",
"content": key,
"type": "1"
}
res = requests.post('https://spiderscloudcn2.51111666.com/forward', headers=headerx, json=data)
res.encoding = "utf-8"
json_dict = json.loads(res.text)
menu0ListMap = json_dict["data"]["resultList"]
for item in menu0ListMap:
name = item['vod_name'].replace("yy8ycom", "")
id = item['id']
pic = item['vod_pic']
id2 = item['vod_server_id']
video = {
"vod_id": str(id) + '#' + str(id2),
"vod_name": name,
"vod_pic": pic,
"vod_remarks": ''
}
videos.append(video)
result['list'] = videos
result['page'] = page
result['pagecount'] = 9999
result['limit'] = 90
result['total'] = 999999
return result
def searchContent(self, key, quick):
return self.searchContentPage(key, quick, '1')
def localProxy(self, params):
if params['type'] == "m3u8":
return self.proxyM3u8(params)
elif params['type'] == "media":
return self.proxyMedia(params)
elif params['type'] == "ts":
return self.proxyTs(params)
return None
+471
View File
@@ -0,0 +1,471 @@
# coding = utf-8
#!/usr/bin/python
import re
import sys
import json
import time
import base64
import hashlib
import urllib.parse
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from base.spider import Spider
sys.path.append('..')
class Spider(Spider):
def __init__(self):
self.name = "瓜子"
self.host = 'https://api.w32z7vtd.com'
self.token = '1be86e8e18a9fa18b2b8d5432699dad0.ac008ed650fd087bfbecf2fda9d82e9835253ef24843e6b18fcd128b10763497bcf9d53e959f5377cde038c20ccf9d17f604c9b8bb6e61041def86729b2fc7408bd241e23c213ac57f0226ee656e2bb0a583ae0e4f3bf6c6ab6c490c9a6f0d8cdfd366aacf5d83193671a8f77cd1af1ff2e9145de92ec43ec87cf4bdc563f6e919fe32861b0e93b118ec37d8035fbb3c.59dd05c5d9a8ae726528783128218f15fe6f2c0c8145eddab112b374fcfe3d79'
self.header = {
'Cache-Control': 'no-cache',
'Version': '2406025',
'PackageName': 'com.uf076bf0c246.qe439f0d5e.m8aaf56b725a.ifeb647346f',
'Ver': '1.9.2',
'Referer': self.host,
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'okhttp/3.12.0'
}
# 添加缓存机制
self.cache = {}
self.cache_timeout = 300 # 5分钟缓存
def getName(self):
return self.name
def init(self, extend=''):
pass
def homeContent(self, filter):
result = {}
classes = [
{"type_name": "电影", "type_id": "1"},
{"type_name": "电视剧", "type_id": "2"},
{"type_name": "动漫", "type_id": "4"},
{"type_name": "综艺", "type_id": "3"},
{"type_name": "短剧", "type_id": "64"}
]
result['class'] = classes
# 设置筛选条件 - 为所有分类添加筛选
filters = {}
for cate in classes:
tid = cate['type_id']
filters[tid] = [
{"key": "area", "name": "地区", "value": [
{"n": "全部", "v": "0"},
{"n": "大陆", "v": "大陆"},
{"n": "香港", "v": "香港"},
{"n": "台湾", "v": "台湾"},
{"n": "美国", "v": "美国"},
{"n": "韩国", "v": "韩国"},
{"n": "日本", "v": "日本"},
{"n": "英国", "v": "英国"},
{"n": "法国", "v": "法国"},
{"n": "泰国", "v": "泰国"},
{"n": "印度", "v": "印度"},
{"n": "其他", "v": "其他"}
]},
{"key": "year", "name": "年份", "value": [
{"n": "全部", "v": "0"},
{"n": "2025", "v": "2025"},
{"n": "2024", "v": "2024"},
{"n": "2023", "v": "2023"},
{"n": "2022", "v": "2022"},
{"n": "2021", "v": "2021"},
{"n": "2020", "v": "2020"},
{"n": "2019", "v": "2019"},
{"n": "2018", "v": "2018"},
{"n": "2017", "v": "2017"},
{"n": "2016", "v": "2016"},
{"n": "2015", "v": "2015"},
{"n": "2014", "v": "2014"},
{"n": "2013", "v": "2013"},
{"n": "2012", "v": "2012"},
{"n": "2011", "v": "2011"},
{"n": "2010", "v": "2010"},
{"n": "2009", "v": "2009"},
{"n": "2008", "v": "2008"},
{"n": "2007", "v": "2007"},
{"n": "2006", "v": "2006"},
{"n": "2005", "v": "2005"},
{"n": "更早", "v": "2004"}
]},
{"key": "sort", "name": "排序", "value": [
{"n": "最新", "v": "d_id"},
{"n": "最热", "v": "d_hits"},
{"n": "推荐", "v": "d_score"}
]}
]
result['filters'] = filters
return result
def homeVideoContent(self):
# 首页推荐直接返回空列表,避免加载问题
return {'list': []}
def categoryContent(self, tid, pg, filter, extend):
videos = []
try:
body = {
"area": extend.get('area', '0'),
"year": extend.get('year', '0'),
"pageSize": "30",
"sort": extend.get('sort', 'd_id'),
"page": str(pg),
"tid": tid
}
cache_key = f"category_{tid}_{pg}_{hash(str(body))}"
data = self.get_cached_data(cache_key, body, '/App/IndexList/indexList')
if data and 'list' in data:
for item in data['list']:
vod_continu = item.get('vod_continu', 0)
remarks = '电影' if vod_continu == 0 else f'更新至{vod_continu}'
video = {
"vod_id": f"{item.get('vod_id', '')}/{vod_continu}",
"vod_name": item.get('vod_name', ''),
"vod_pic": item.get('vod_pic', ''),
"vod_remarks": remarks
}
videos.append(video)
except Exception as e:
print(f"获取分类内容失败: {e}")
return {
'list': videos,
'page': int(pg),
'pagecount': 9999,
'limit': 30,
'total': 999999
}
def detailContent(self, ids):
try:
vod_id = ids[0].split('/')[0]
# 获取视频详情
t = str(int(time.time()))
body1 = {
"token_id": "1649412",
"vod_id": vod_id,
"mobile_time": t,
"token": self.token
}
qdata = self.get_data(body1, '/App/IndexPlay/playInfo')
# 获取播放列表
body2 = {
"vurl_cloud_id": "2",
"vod_d_id": vod_id
}
jdata = self.get_data(body2, '/App/Resource/Vurl/show')
if not qdata or 'vodInfo' not in qdata:
return {'list': []}
vod = qdata['vodInfo']
# 构建视频信息
video_detail = {
"vod_id": vod_id,
"vod_name": vod.get('vod_name', ''),
"vod_pic": vod.get('vod_pic', ''),
"vod_year": vod.get('vod_year', ''),
"vod_area": vod.get('vod_area', ''),
"vod_actor": vod.get('vod_actor', ''),
"vod_director": vod.get('vod_director', ''),
"vod_content": vod.get('vod_use_content', '').strip(),
"vod_play_from": "拾光请你看瓜子"
}
# 构建播放列表
play_list = []
if jdata and 'list' in jdata:
for index, item in enumerate(jdata['list']):
if 'play' in item:
n = [] # 播放源名称
p = [] # 播放参数
for key, value in item['play'].items():
if 'param' in value and value['param']:
n.append(key)
p.append(value['param'])
if p:
play_name = str(index + 1)
if len(jdata['list']) == 1:
play_name = vod.get('vod_name', '')
play_url = f"{p[-1]}||{'@'.join(n)}"
play_list.append(f"{play_name}${play_url}")
video_detail["vod_play_url"] = "#".join(play_list)
return {'list': [video_detail]}
except Exception as e:
print(f"获取详情失败: {e}")
return {'list': []}
def searchContent(self, key, quick, pg=1):
videos = []
try:
body = {
"keywords": key,
"order_val": "1",
"page": str(pg)
}
# 搜索不使用缓存,确保实时性
start_time = time.time()
data = self.get_data(body, '/App/Index/findMoreVod', use_cache=False)
end_time = time.time()
print(f"搜索请求耗时: {end_time - start_time:.2f}")
if data and 'list' in data:
for item in data['list']:
vod_continu = item.get('vod_continu', 0)
remarks = '电影' if vod_continu == 0 else f'更新至{vod_continu}'
video = {
"vod_id": f"{item.get('vod_id', '')}/{vod_continu}",
"vod_name": item.get('vod_name', ''),
"vod_pic": item.get('vod_pic', ''),
"vod_remarks": remarks
}
videos.append(video)
except Exception as e:
print(f"搜索失败: {e}")
return {
'list': videos,
'page': int(pg),
'pagecount': 9999,
'limit': 30,
'total': 999999
}
def playerContent(self, flag, id, vipFlags):
try:
# 解析播放信息
parts = id.split('||')
if len(parts) < 2:
return {"parse": 0, "playUrl": "", "url": ""}
param_str = parts[0]
resolutions = parts[1].split('@') if len(parts) > 1 else []
# 解析参数
params = {}
for pair in param_str.split('&'):
if '=' in pair:
key, value = pair.split('=', 1)
params[key] = value
# 获取播放链接
if resolutions:
# 分辨率从大到小排序
resolutions.sort(key=lambda x: int(x) if x.isdigit() else 0, reverse=True)
# 使用最大分辨率
params['resolution'] = resolutions[0]
body = params
start_time = time.time()
data = self.get_data(body, '/App/Resource/VurlDetail/showOne', use_cache=False)
end_time = time.time()
print(f"播放链接获取耗时: {end_time - start_time:.2f}")
if data and 'url' in data:
return {
"parse": 0,
"playUrl": "",
"url": data['url'],
"header": json.dumps({"User-Agent": "Lavf/57.83.100"})
}
return {"parse": 0, "playUrl": "", "url": ""}
except Exception as e:
print(f"播放解析失败: {e}")
return {"parse": 0, "playUrl": "", "url": ""}
def isVideoFormat(self, url):
video_formats = ['.m3u8', '.mp4', '.avi', '.mkv', '.flv', '.ts']
return any(url.lower().endswith(fmt) for fmt in video_formats)
def manualVideoCheck(self):
pass
def localProxy(self, params):
return None
def aes_encrypt(self, text, key, iv):
"""AES加密"""
try:
key_bytes = key.encode('utf-8')
iv_bytes = iv.encode('utf-8')
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
encrypted = cipher.encrypt(pad(text.encode('utf-8'), AES.block_size))
return encrypted.hex().upper()
except Exception as e:
print(f"AES加密失败: {e}")
return ""
def aes_decrypt(self, text, key, iv):
"""AES解密"""
try:
key_bytes = key.encode('utf-8')
iv_bytes = iv.encode('utf-8')
cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
encrypted_bytes = bytes.fromhex(text)
decrypted = unpad(cipher.decrypt(encrypted_bytes), AES.block_size)
return decrypted.decode('utf-8')
except Exception as e:
print(f"AES解密失败: {e}")
return ""
def rsa_decrypt(self, encrypted_data, private_key):
"""RSA解密"""
try:
# 解码base64数据
encrypted_bytes = base64.b64decode(encrypted_data)
# 导入私钥
rsa_key = RSA.import_key(private_key)
cipher = PKCS1_v1_5.new(rsa_key)
# 解密
decrypted = cipher.decrypt(encrypted_bytes, None)
return decrypted.decode('utf-8') if decrypted else ""
except Exception as e:
print(f"RSA解密失败: {e}")
return ""
def get_cached_data(self, cache_key, data, path):
"""带缓存的数据获取"""
current_time = time.time()
if cache_key in self.cache:
cached_data, timestamp = self.cache[cache_key]
if current_time - timestamp < self.cache_timeout:
return cached_data
# 缓存不存在或已过期,重新获取
result = self.get_data(data, path)
if result:
self.cache[cache_key] = (result, current_time)
return result
def get_data(self, data, path, use_cache=True):
"""获取数据的主要方法"""
try:
# 构建缓存键
cache_key = f"{path}_{hash(str(data))}" if use_cache else None
if use_cache and cache_key in self.cache:
cached_data, timestamp = self.cache[cache_key]
if time.time() - timestamp < self.cache_timeout:
return cached_data
start_time = time.time()
# AES加密请求数据
request_key = self.aes_encrypt(json.dumps(data), 'mvXBSW7ekreItNsT', '2U3IrJL8szAKp0Fj')
if not request_key:
return None
# 生成签名
t = str(int(time.time()))
keys = "Qmxi5ciWXbQzkr7o+SUNiUuQxQEf8/AVyUWY4T/BGhcXBIUz4nOyHBGf9A4KbM0iKF3yp9M7WAY0rrs5PzdTAOB45plcS2zZ0wUibcXuGJ29VVGRWKGwE9zu2vLwhfgjTaaDpXo4rby+7GxXTktzJmxvneOUdYeHi+PZsThlvPI="
sign_str = f"token_id=,token={self.token},phone_type=1,request_key={request_key},app_id=1,time={t},keys={keys}*&zvdvdvddbfikkkumtmdwqppp?|4Y!s!2br"
signature = hashlib.md5(sign_str.encode()).hexdigest()
# 构建请求体
body = {
'token': self.token,
'token_id': '',
'phone_type': '1',
'time': t,
'phone_model': 'xiaomi-22021211rc',
'keys': keys,
'request_key': request_key,
'signature': signature,
'app_id': '1',
'ad_version': '1'
}
# 发送请求 - 设置超时时间
url = f"{self.host}{path}"
response = self.post(url, headers=self.header, data=body, timeout=10)
if response.status_code != 200:
print(f"API请求失败: {response.status_code}, 路径: {path}")
return None
response_data = response.json()
if 'data' not in response_data:
print(f"API返回数据格式错误, 路径: {path}")
return None
data_response = response_data['data']
# RSA解密响应密钥
private_key = """-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGAe6hKrWLi1zQmjTT1
ozbE4QdFeJGNxubxld6GrFGximxfMsMB6BpJhpcTouAqywAFppiKetUBBbXwYsYU
1wNr648XVmPmCMCy4rY8vdliFnbMUj086DU6Z+/oXBdWU3/b1G0DN3E9wULRSwcK
ZT3wj/cCI1vsCm3gj2R5SqkA9Y0CAwEAAQKBgAJH+4CxV0/zBVcLiBCHvSANm0l7
HetybTh/j2p0Y1sTXro4ALwAaCTUeqdBjWiLSo9lNwDHFyq8zX90+gNxa7c5EqcW
V9FmlVXr8VhfBzcZo1nXeNdXFT7tQ2yah/odtdcx+vRMSGJd1t/5k5bDd9wAvYdI
DblMAg+wiKKZ5KcdAkEA1cCakEN4NexkF5tHPRrR6XOY/XHfkqXxEhMqmNbB9U34
saTJnLWIHC8IXys6Qmzz30TtzCjuOqKRRy+FMM4TdwJBAJQZFPjsGC+RqcG5UvVM
iMPhnwe/bXEehShK86yJK/g/UiKrO87h3aEu5gcJqBygTq3BBBoH2md3pr/W+hUM
WBsCQQChfhTIrdDinKi6lRxrdBnn0Ohjg2cwuqK5zzU9p/N+S9x7Ck8wUI53DKm8
jUJE8WAG7WLj/oCOWEh+ic6NIwTdAkEAj0X8nhx6AXsgCYRql1klbqtVmL8+95KZ
K7PnLWG/IfjQUy3pPGoSaZ7fdquG8bq8oyf5+dzjE/oTXcByS+6XRQJAP/5ciy1b
L3NhUhsaOVy55MHXnPjdcTX0FaLi+ybXZIfIQ2P4rb19mVq1feMbCXhz+L1rG8oa
t5lYKfpe8k83ZA==
-----END PRIVATE KEY-----"""
bodyki_json = self.rsa_decrypt(data_response['keys'], private_key)
if not bodyki_json:
print("RSA解密失败")
return None
bodyki = json.loads(bodyki_json)
# AES解密响应数据
decrypted_data = self.aes_decrypt(data_response['response_key'], bodyki['key'], bodyki['iv'])
if not decrypted_data:
print("AES解密失败")
return None
result = json.loads(decrypted_data)
end_time = time.time()
print(f"数据获取耗时: {end_time - start_time:.2f}秒, 路径: {path}")
# 缓存结果
if use_cache and cache_key:
self.cache[cache_key] = (result, time.time())
return result
except Exception as e:
print(f"获取数据失败: {e}, 路径: {path}")
return None
def get_md5(self, text):
"""计算MD5"""
return hashlib.md5(text.encode()).hexdigest()
if __name__ == '__main__':
pass
+296
View File
@@ -0,0 +1,296 @@
# 本资源来源于互联网公开渠道,仅可用于个人学习爬虫技术。
# 严禁将其用于任何商业用途,下载后请于 24 小时内删除,搜索结果均来自源站,本人不承担任何责任。
# junyouyun
import re
import json
from urllib.parse import quote, urljoin, parse_qs
import requests
from bs4 import BeautifulSoup
from base.spider import Spider
BASE = "https://anime.xifanacg.com"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
"Referer": BASE,
}
CLASS_OPTIONS = {
"1": ["搞笑", "原创", "轻小说改", "恋爱", "百合", "漫改", "校园", "战斗", "治愈", "奇幻",
"日常", "青春", "乙女向", "悬疑", "后宫", "科幻", "冒险", "热血", "异世界", "游戏改",
"音乐", "偶像", "美食", "耽美"],
"2": ["搞笑", "原创", "轻小说改", "恋爱", "百合", "漫改", "校园", "战斗", "治愈", "奇幻",
"日常", "青春", "乙女向", "悬疑", "后宫", "科幻", "冒险", "热血", "异世界", "游戏改",
"音乐", "偶像", "美食", "耽美", "2026年1月", "2025年10月"],
"3": [],
"21": [],
}
AREA_OPTIONS = {
"1": ["日本"],
"2": [],
"3": ["日本"],
"21": [],
}
YEAR_RANGE = [str(y) for y in range(2026, 2004, -1)]
ORDER_OPTIONS = [
{"n": "按最新", "v": "time"},
{"n": "按最热", "v": "hits"},
{"n": "按评分", "v": "score"},
]
class Spider(Spider):
def init(self, extend=""):
self.session = requests.Session()
self.session.headers.update(HEADERS)
def _get_decoded_html(self, url):
r = self.session.get(url, timeout=15)
r.encoding = r.apparent_encoding if r.apparent_encoding else 'utf-8'
return r.text
def _parse_vod_cards(self, html):
soup = BeautifulSoup(html, 'html.parser')
vod_list = []
for box in soup.select('.public-list-box.public-pic-b'):
link = box.select_one('a.public-list-exp')
if not link:
continue
href = link.get('href', '')
vid = href.split('/')[-1].replace('.html', '')
title = link.get('title', '')
img = box.select_one('img.gen-movie-img')
pic = img.get('data-src', '') if img else ''
prb = box.select_one('.public-list-prb')
remarks = prb.get_text(strip=True) if prb else ''
vod_list.append({
"vod_id": vid,
"vod_name": title,
"vod_pic": pic,
"vod_remarks": remarks
})
return vod_list
def homeContent(self, filter):
classes = [
{"type_name": "连载新番", "type_id": "1"},
{"type_name": "完结旧番", "type_id": "2"},
{"type_name": "剧场版", "type_id": "3"},
{"type_name": "美漫", "type_id": "21"},
]
filters = {}
for tid in ["1", "2", "3", "21"]:
f_list = []
if CLASS_OPTIONS[tid]:
f_list.append({
"key": "class", "name": "类型",
"value": [{"n": "全部", "v": ""}] + [{"n": c, "v": c} for c in CLASS_OPTIONS[tid]]
})
if AREA_OPTIONS[tid]:
f_list.append({
"key": "area", "name": "地区",
"value": [{"n": "全部", "v": ""}] + [{"n": a, "v": a} for a in AREA_OPTIONS[tid]]
})
if tid != "21":
f_list.append({
"key": "year", "name": "年份",
"value": [{"n": "全部", "v": ""}] + [{"n": y, "v": y} for y in YEAR_RANGE]
})
f_list.append({"key": "by", "name": "排序", "value": ORDER_OPTIONS})
filters[tid] = f_list
return {"class": classes, "filters": filters}
def homeVideoContent(self):
try:
html = self._get_decoded_html(BASE)
return {"list": self._parse_vod_cards(html)}
except Exception:
data = self.categoryContent("1", "1", False, {})
return {"list": data.get("list", [])}
def categoryContent(self, tid, pg, filter, extend):
"""
分类列表:POST 请求 /index.php/ds_api/vod
传递完整表单参数(包含空字段 lang/version/state/letter/time/level/weekday
"""
# 2. 构建完整表单数据(必须包含所有字段,空值用空字符串)
form_data = {
'type': tid,
'class': extend.get("class", ""),
'area': extend.get("area", ""),
'year': extend.get("year", ""),
'lang': extend.get("lang", ""),
'version': extend.get("version", ""),
'state': extend.get("state", ""),
'letter': extend.get("letter", ""),
'time': extend.get("time", ""),
'level': extend.get("level", "0"),
'weekday': extend.get("weekday", ""),
'by': extend.get("by", "time"),
'page': pg,
}
url = f"{BASE}/index.php/ds_api/vod"
try:
r = self.session.post(url, data=form_data, timeout=15)
data = r.json()
except Exception:
return {"list": [], "page": int(pg), "pagecount": 1, "limit": 40, "total": 0}
vod_list = []
for item in data.get("list", []):
vod_id = str(item.get("vod_id", ""))
actor_raw = item.get("vod_actor", "")
actor = ','.join([a for a in actor_raw.split(',') if a.strip()]) if actor_raw else ''
vod_list.append({
"vod_id": vod_id,
"vod_name": item.get("vod_name", ""),
"vod_pic": item.get("vod_pic", ""),
"vod_remarks": item.get("vod_remarks", ""),
"vod_actor": actor,
"vod_douban_score": str(item.get("vod_douban_score", "")) if item.get("vod_douban_score") else "",
})
return {
"list": vod_list,
"page": int(data.get("page", pg)),
"pagecount": int(data.get("pagecount", 1)),
"limit": int(data.get("limit", 40)),
"total": int(data.get("total", 0)),
}
def detailContent(self, ids):
vid = ids[0] if isinstance(ids, list) else ids
url = f"{BASE}/bangumi/{vid}.html"
try:
html = self._get_decoded_html(url)
except Exception:
return {"list": [{"vod_id": vid, "vod_name": "获取失败"}]}
soup = BeautifulSoup(html, 'html.parser')
name = soup.select_one('h3.slide-info-title')
name = name.text.strip() if name else ''
pic_tag = soup.select_one('.detail-pic img.lazy')
pic = pic_tag.get('data-src', '') if pic_tag else ''
score_tag = soup.select_one('.fraction')
score = score_tag.text.strip() if score_tag else ''
remarks_tag = soup.select_one('.slide-info-remarks')
remarks = remarks_tag.text.strip() if remarks_tag else ''
director = actor = area = year = desc = ''
param_box = soup.select_one('.info-parameter')
if param_box:
for li in param_box.find_all('li'):
em = li.find('em')
if not em:
continue
key = em.get_text(strip=True).rstrip('').rstrip(':')
li_clone = BeautifulSoup(str(li), 'html.parser')
for e in li_clone.find_all('em'):
e.decompose()
value = li_clone.get_text(strip=True)
if '导演' in key:
director = value
elif '主演' in key:
actor = value
elif '地区' in key:
area = value
elif '年份' in key:
year = value
elif '简介' in key:
desc = value
if not director:
d_tag = soup.find('strong', string=re.compile('导演'))
if d_tag and d_tag.find_next('a'):
director = d_tag.find_next('a').text.strip()
if not actor:
a_tag = soup.find('strong', string=re.compile('演员'))
if a_tag:
actor_links = a_tag.find_next_siblings('a')
actor = ','.join([a.text.strip() for a in actor_links]) if actor_links else ''
if not desc:
desc_div = soup.select_one('#height_limit')
desc = desc_div.text.strip() if desc_div else ''
play_from, play_url = [], []
tab_links = soup.select('.anthology-tab .swiper-slide')
list_boxes = soup.select('.anthology-list-box')
for idx, box in enumerate(list_boxes):
line_name = f"线路{idx+1}"
if idx < len(tab_links):
raw = tab_links[idx].get_text(strip=True)
badge = tab_links[idx].find('span', class_='badge')
if badge:
raw = raw.replace(badge.text, '').strip()
if raw:
line_name = raw
play_from.append(line_name)
eps = [f"{a.text.strip()}${urljoin(BASE, a['href'])}" for a in box.select('li a.this-link')]
play_url.append("#".join(eps))
vod = {
"vod_id": str(vid), "vod_name": name, "vod_pic": pic,
"vod_score": score, "vod_remarks": remarks,
"vod_year": year, "vod_area": area, "vod_actor": actor,
"vod_director": director, "vod_content": desc,
"vod_play_from": "$$$".join(play_from) if play_from else "",
"vod_play_url": "$$$".join(play_url) if play_url else "",
}
return {"list": [vod]}
def searchContent(self, key, quick, pg="1"):
# 优先使用 AJAX suggest 接口
try:
url = f"{BASE}/index.php/ajax/suggest?mid=1&wd={quote(key)}"
r = self.session.get(url, timeout=10)
data = r.json()
vod_list = []
for item in data.get("list", []):
vod_list.append({
"vod_id": str(item.get("id", "")),
"vod_name": item.get("name", ""),
"vod_pic": item.get("pic", ""),
})
if vod_list:
return {"list": vod_list, "page": int(pg)}
except Exception:
pass # 接口失败则回退到页面解析
# 回退:静态搜索页面
try:
search_url = f"{BASE}/search.html?wd={quote(key)}"
html = self._get_decoded_html(search_url)
vod_list = self._parse_vod_cards(html)
return {"list": vod_list, "page": int(pg)}
except Exception:
return {"list": [], "page": int(pg)}
def playerContent(self, flag, video_id, vipFlags):
try:
html = self._get_decoded_html(video_id)
except Exception:
return {"parse": 0, "url": ""}
m = re.search(r'var\s+player_\w+\s*=\s*({[^;]+})', html, re.DOTALL)
if not m:
direct = re.search(r'"url":"(https?://[^"]+\.(?:mp4|m3u8)[^"]*)"', html)
return {"parse": 0, "url": direct.group(1)} if direct else {"parse": 0, "url": ""}
try:
data = json.loads(m.group(1))
except json.JSONDecodeError:
raw = m.group(1)
fallback = re.search(r'"url":"(https?://[^"]+)"', raw)
return {"parse": 0, "url": fallback.group(1)} if fallback else {"parse": 0, "url": ""}
mp4_url = data.get("url", "")
if data.get("encrypt") == 1 and data.get("from") in ("xfy2", "CS"):
mp4_url = f"https://player.moedot.net/player/index.php?code=xfdm1&from=cf&url={mp4_url}"
return {"parse": 1, "url": mp4_url}
return {"parse": 0, "url": mp4_url}
def getName(self):
return "xifanacg"
def destroy(self):
if hasattr(self, "session"):
self.session.close()
+271
View File
@@ -0,0 +1,271 @@
from base.spider import Spider
import requests
import re
import urllib.parse
host = "https://mov.cenguigui.cn"
base_url = host + "/duanju/api.php"
quality_host = "https://mov.cenguigui.cn"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'
}
timeout = 10
class Spider(Spider):
def getName(self):
return "小心儿悠悠"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeContent(self, filter):
class_names = "推荐榜&热播榜&新剧榜&漫剧榜大唐&大秦&大明&擦边&逆袭&霸总&豪门恩怨&神豪&都市日常&大女主&都市修仙&强者回归&重生&闪婚&赘婿逆袭&追妻&萌宝&奇幻脑洞&传承觉醒&奇幻爱情&乡村&历史古代&王妃&娱乐圈&暗恋成真&系统&真假千金&穿书&女帝&团宠&年代爱情&玄幻仙侠&皇后&逆袭&霸总&现代言情&打脸虐渣&豪门恩怨&神豪&马甲&都市日常&战神归来&小人物&女性成长&大女主&穿越&都市修仙&强者回归&亲情&古装&重生&闪婚&赘婿逆袭&虐恋&追妻&天下无敌&家庭伦理&萌宝&古风权谋&职场&奇幻脑洞&异能&无敌神医&古风言情&传承觉醒&现言甜宠&奇幻爱情&乡村&历史古代&王妃&高手下山&娱乐圈&强强联合&破镜重圆&暗恋成真&民国&欢喜冤家&系统&真假千金&龙王&校园&穿书&女帝&团宠&年代爱情&玄幻仙侠&青梅竹马&悬疑推理&皇后&替身&大叔&喜剧&剧情"
class_list = class_names.split('&')
classes = []
for class_name in class_list:
classes.append({
"type_id": class_name,
"type_name": class_name
})
return {"class": classes}
def homeVideoContent(self):
return {'list': []}
def categoryContent(self, cid, pg, filter, ext):
videos = []
page = int(pg) if pg else 1
params = f"page={page}&name={urllib.parse.quote(cid)}"
tab_type = "19"
if ext and 'tab_type' in ext:
tab_type = ext['tab_type']
params += f"&tab_type={tab_type}"
url = f"{base_url}?{params}"
try:
response = requests.get(url=url, headers=headers, timeout=timeout)
if response.status_code != 200:
return {'list': []}
response.encoding = "utf-8"
data = response.json()
if data.get('code') == 200 and data.get('data'):
for vod in data['data']:
vod_id = f"book_id={vod.get('book_id', '')}&actor={vod.get('author', '')}&type={vod.get('type', '')}"
videos.append({
"vod_id": vod_id,
"vod_name": vod.get('title', ''),
"vod_pic": vod.get('cover', ''),
"vod_remarks": vod.get('type', ''),
"vod_content": vod.get('intro', '')
})
except Exception:
return {'list': []}
return {
'list': videos,
'page': pg,
'pagecount': 9999,
'limit': 20,
'total': 999999
}
def detailContent(self, ids):
did = ids[0]
params = {}
queryString = did.split('?')[1] if '?' in did else did
pairs = queryString.split('&')
for i in range(len(pairs)):
pair = pairs[i].split('=')
if len(pair) == 2:
params[pair[0]] = pair[1]
book_id = params.get('book_id', '')
actor = params.get('actor', '')
fullType = params.get('type', '')
if not book_id:
match = re.search(r'book_id=([^&]*)', did)
if match and match[1]:
book_id = match[1]
if not book_id:
return {'list': []}
apiUrl = f"{base_url}?book_id={book_id}"
try:
response = requests.get(url=apiUrl, headers=headers, timeout=timeout)
if response.status_code != 200:
return {'list': []}
data = response.json()
if data.get('code') == 200 and data.get('data'):
vod_list = data['data']
play_from = []
play_url = []
quality_options = [
("超清", "2160p"),
("高清", "1080p"),
("标清", "720p"),
("低清", "480p"),
("流畅", "360p")
]
for quality_name, quality_value in quality_options:
urls = []
for item in vod_list:
chapterName = item.get('title', '')
videoId = item.get('video_id', '')
playUrl = f"{quality_host}/duanju/api.php?video_id={videoId}&type=json&level={quality_value}"
urls.append(f"{chapterName}${playUrl}")
play_from.append(quality_name)
play_url.append("#".join(urls))
actors = []
try:
actor_api_url = f"{base_url}?series_id={book_id}&showRawParams=false"
actor_response = requests.get(url=actor_api_url, headers=headers, timeout=timeout)
if actor_response.status_code == 200:
actor_data = actor_response.json()
if actor_data.get('code') == 200 and 'celebrities' in actor_data:
celebrities = actor_data['celebrities']
if isinstance(celebrities, list):
for celeb in celebrities:
actor_name = celeb.get('user_name') or celeb.get('name') or celeb.get('actor_name') or ''
if actor_name and actor_name.strip():
if actor_name not in actors:
actors.append(actor_name)
except Exception as e:
print(f"获取演员信息失败: {e}")
if actor:
actors = [actor]
actor_str = ", ".join(actors) if actors else (actor or "")
categories = []
if 'category_names' in data and isinstance(data['category_names'], list):
categories = data['category_names'][:3] # 只取前3个
elif 'category' in data:
categories = [data['category']][:1]
type_str = ""
if categories:
type_str = ", ".join(categories)
remarks_str = f"{len(vod_list)}"
content_str = data.get('desc', '')
VOD = {
"vod_id": did,
"vod_name": data.get('book_name', ''),
"vod_pic": data.get('book_pic', ''),
"vod_actor": actor_str,
"type_name": type_str or fullType,
"vod_remarks": remarks_str,
"vod_content": content_str,
"vod_play_from": "$$$".join(play_from),
"vod_play_url": "$$$".join(play_url)
}
return {'list': [VOD]}
else:
return {'list': []}
except Exception as e:
print(f"获取详情失败: {e}")
return {'list': []}
def playerContent(self, flag, id, vipFlags):
max_retries = 3
for i in range(max_retries):
try:
response = requests.get(url=id, headers=headers, timeout=timeout)
if response.status_code == 200:
data = response.json()
if data.get('code') == 200 and data.get('data'):
play_url = data['data'].get('url', '')
if play_url:
return {
"parse": 0,
"playUrl": '',
"url": play_url,
"header": headers
}
break
except:
if i < max_retries - 1:
continue
else:
break
return {
"parse": 0,
"playUrl": '',
"url": 'about:blank',
"header": headers
}
def searchContent(self, key, quick, pg=1):
try:
page = int(pg)
except:
page = 1
params = f"page={page}&name={urllib.parse.quote(key)}&tab_type=19"
search_url = f"{base_url}?{params}"
try:
response = requests.get(search_url, headers=headers, timeout=timeout)
if response.status_code != 200:
return {'list': []}
response.encoding = "utf-8"
data = response.json()
if data.get('code') != 200 or not data.get('data'):
return {'list': []}
videos = []
for vod in data['data']:
vod_id = f"book_id={vod.get('book_id', '')}&actor={vod.get('author', '')}&type={vod.get('type', '')}"
videos.append({
"vod_id": vod_id,
"vod_name": vod.get('title', ''),
"vod_pic": vod.get('cover', ''),
"vod_remarks": vod.get('type', ''),
"vod_content": vod.get('intro', '')
})
return {
'list': videos,
'page': page,
'pagecount': 9999,
'limit': len(videos),
'total': 999999
}
except Exception:
return {'list': []}
+271
View File
@@ -0,0 +1,271 @@
from base.spider import Spider
import requests
import re
import urllib.parse
host = "https://mov.cenguigui.cn"
base_url = host + "/duanju/api.php"
quality_host = "https://mov.cenguigui.cn"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'
}
timeout = 10
class Spider(Spider):
def getName(self):
return "小心儿悠悠"
def init(self, extend):
pass
def isVideoFormat(self, url):
pass
def manualVideoCheck(self):
pass
def homeContent(self, filter):
class_names = "推荐榜&热播榜&新剧榜&漫剧榜大唐&大秦&大明&擦边&逆袭&霸总&豪门恩怨&神豪&都市日常&大女主&都市修仙&强者回归&重生&闪婚&赘婿逆袭&追妻&萌宝&奇幻脑洞&传承觉醒&奇幻爱情&乡村&历史古代&王妃&娱乐圈&暗恋成真&系统&真假千金&穿书&女帝&团宠&年代爱情&玄幻仙侠&皇后&逆袭&霸总&现代言情&打脸虐渣&豪门恩怨&神豪&马甲&都市日常&战神归来&小人物&女性成长&大女主&穿越&都市修仙&强者回归&亲情&古装&重生&闪婚&赘婿逆袭&虐恋&追妻&天下无敌&家庭伦理&萌宝&古风权谋&职场&奇幻脑洞&异能&无敌神医&古风言情&传承觉醒&现言甜宠&奇幻爱情&乡村&历史古代&王妃&高手下山&娱乐圈&强强联合&破镜重圆&暗恋成真&民国&欢喜冤家&系统&真假千金&龙王&校园&穿书&女帝&团宠&年代爱情&玄幻仙侠&青梅竹马&悬疑推理&皇后&替身&大叔&喜剧&剧情"
class_list = class_names.split('&')
classes = []
for class_name in class_list:
classes.append({
"type_id": class_name,
"type_name": class_name
})
return {"class": classes}
def homeVideoContent(self):
return {'list': []}
def categoryContent(self, cid, pg, filter, ext):
videos = []
page = int(pg) if pg else 1
params = f"page={page}&name={urllib.parse.quote(cid)}"
tab_type = "19"
if ext and 'tab_type' in ext:
tab_type = ext['tab_type']
params += f"&tab_type={tab_type}"
url = f"{base_url}?{params}"
try:
response = requests.get(url=url, headers=headers, timeout=timeout)
if response.status_code != 200:
return {'list': []}
response.encoding = "utf-8"
data = response.json()
if data.get('code') == 200 and data.get('data'):
for vod in data['data']:
vod_id = f"book_id={vod.get('book_id', '')}&actor={vod.get('author', '')}&type={vod.get('type', '')}"
videos.append({
"vod_id": vod_id,
"vod_name": vod.get('title', ''),
"vod_pic": vod.get('cover', ''),
"vod_remarks": vod.get('type', ''),
"vod_content": vod.get('intro', '')
})
except Exception:
return {'list': []}
return {
'list': videos,
'page': pg,
'pagecount': 9999,
'limit': 20,
'total': 999999
}
def detailContent(self, ids):
did = ids[0]
params = {}
queryString = did.split('?')[1] if '?' in did else did
pairs = queryString.split('&')
for i in range(len(pairs)):
pair = pairs[i].split('=')
if len(pair) == 2:
params[pair[0]] = pair[1]
book_id = params.get('book_id', '')
actor = params.get('actor', '')
fullType = params.get('type', '')
if not book_id:
match = re.search(r'book_id=([^&]*)', did)
if match and match[1]:
book_id = match[1]
if not book_id:
return {'list': []}
apiUrl = f"{base_url}?book_id={book_id}"
try:
response = requests.get(url=apiUrl, headers=headers, timeout=timeout)
if response.status_code != 200:
return {'list': []}
data = response.json()
if data.get('code') == 200 and data.get('data'):
vod_list = data['data']
play_from = []
play_url = []
quality_options = [
("超清", "2160p"),
("高清", "1080p"),
("标清", "720p"),
("低清", "480p"),
("流畅", "360p")
]
for quality_name, quality_value in quality_options:
urls = []
for item in vod_list:
chapterName = item.get('title', '')
videoId = item.get('video_id', '')
playUrl = f"{quality_host}/duanju/api.php?video_id={videoId}&type=json&level={quality_value}"
urls.append(f"{chapterName}${playUrl}")
play_from.append(quality_name)
play_url.append("#".join(urls))
actors = []
try:
actor_api_url = f"{base_url}?series_id={book_id}&showRawParams=false"
actor_response = requests.get(url=actor_api_url, headers=headers, timeout=timeout)
if actor_response.status_code == 200:
actor_data = actor_response.json()
if actor_data.get('code') == 200 and 'celebrities' in actor_data:
celebrities = actor_data['celebrities']
if isinstance(celebrities, list):
for celeb in celebrities:
actor_name = celeb.get('user_name') or celeb.get('name') or celeb.get('actor_name') or ''
if actor_name and actor_name.strip():
if actor_name not in actors:
actors.append(actor_name)
except Exception as e:
print(f"获取演员信息失败: {e}")
if actor:
actors = [actor]
actor_str = ", ".join(actors) if actors else (actor or "")
categories = []
if 'category_names' in data and isinstance(data['category_names'], list):
categories = data['category_names'][:3] # 只取前3个
elif 'category' in data:
categories = [data['category']][:1]
type_str = ""
if categories:
type_str = ", ".join(categories)
remarks_str = f"{len(vod_list)}"
content_str = data.get('desc', '')
VOD = {
"vod_id": did,
"vod_name": data.get('book_name', ''),
"vod_pic": data.get('book_pic', ''),
"vod_actor": actor_str,
"type_name": type_str or fullType,
"vod_remarks": remarks_str,
"vod_content": content_str,
"vod_play_from": "$$$".join(play_from),
"vod_play_url": "$$$".join(play_url)
}
return {'list': [VOD]}
else:
return {'list': []}
except Exception as e:
print(f"获取详情失败: {e}")
return {'list': []}
def playerContent(self, flag, id, vipFlags):
max_retries = 3
for i in range(max_retries):
try:
response = requests.get(url=id, headers=headers, timeout=timeout)
if response.status_code == 200:
data = response.json()
if data.get('code') == 200 and data.get('data'):
play_url = data['data'].get('url', '')
if play_url:
return {
"parse": 0,
"playUrl": '',
"url": play_url,
"header": headers
}
break
except:
if i < max_retries - 1:
continue
else:
break
return {
"parse": 0,
"playUrl": '',
"url": 'about:blank',
"header": headers
}
def searchContent(self, key, quick, pg=1):
try:
page = int(pg)
except:
page = 1
params = f"page={page}&name={urllib.parse.quote(key)}&tab_type=19"
search_url = f"{base_url}?{params}"
try:
response = requests.get(search_url, headers=headers, timeout=timeout)
if response.status_code != 200:
return {'list': []}
response.encoding = "utf-8"
data = response.json()
if data.get('code') != 200 or not data.get('data'):
return {'list': []}
videos = []
for vod in data['data']:
vod_id = f"book_id={vod.get('book_id', '')}&actor={vod.get('author', '')}&type={vod.get('type', '')}"
videos.append({
"vod_id": vod_id,
"vod_name": vod.get('title', ''),
"vod_pic": vod.get('cover', ''),
"vod_remarks": vod.get('type', ''),
"vod_content": vod.get('intro', '')
})
return {
'list': videos,
'page': page,
'pagecount': 9999,
'limit': len(videos),
'total': 999999
}
except Exception:
return {'list': []}
+2 -1
View File
@@ -45,7 +45,8 @@ class Spider(Spider):
's32': {'name': '🍃飘零', 'api': 'https://p2100.net/api.php/provide/vod'},
's33': {'name': '🐾淘片', 'api': 'https://www.taopianzy.com/cjapi/mc/vod/json.html'},
's34': {'name': '🐾98', 'api': 'https://98zy.vip/api.php/provide/vod/'},
's35': {'name': '📺魔都', 'api': 'https://www.mdzyapi.com/api.php/provide/vod'},
's35': {'name': '🐾大众', 'api': 'https://cdn.dzzyapi.com/api.php/provide/vod/'},
's36': {'name': '📺魔都', 'api': 'https://www.mdzyapi.com/api.php/provide/vod'},
}
headers = {
+302
View File
@@ -0,0 +1,302 @@
# -*- coding: utf-8 -*-
# 专属全网聚合 Python版
# 适配常见 Cat/TVBox Python Spider
#本地py适配 😂
import json
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
from base.spider import Spider
class Spider(Spider):
sources = {
's1': {'name': '🎬电影天堂', 'api': 'http://caiji.dyttzyapi.com/api.php/provide/vod/from/dyttm3u8/at/json'},
's2': {'name': '💧无水印', 'api': 'https://api.wsyzy.net/api.php/provide/vod'},
's3': {'name': '🧸量子', 'api': 'https://cj.lziapi.com/api.php/provide/vod'},
's4': {'name': '📺1080资源', 'api': 'https://api.1080zyku.com/inc/api_mac10.php'},
's5': {'name': '🔥155资源', 'api': 'https://155api.com/api.php/provide/vod'},
's6': {'name': '📺天涯', 'api': 'https://tyyszy.com/api.php/provide/vod'},
's7': {'name': '📺暴风', 'api': 'https://bfzyapi.com/api.php/provide/vod'},
's8': {'name': '⚡索尼闪电', 'api': 'https://xsd.sdzyapi.com/api.php/provide/vod'},
's9': {'name': '📺索尼', 'api': 'https://suoniapi.com/api.php/provide/vod'},
's10': {'name': '📺红牛', 'api': 'https://www.hongniuzy2.com/api.php/provide/vod'},
's11': {'name': '📺茅台', 'api': 'https://caiji.maotaizy.cc/api.php/provide/vod'},
's12': {'name': '🐯虎牙', 'api': 'https://www.huyaapi.com/api.php/provide/vod'},
's13': {'name': '📺豆瓣', 'api': 'https://caiji.dbzy.tv/api.php/provide/vod'},
's14': {'name': '📺豆瓣2', 'api': 'https://dbzy.tv/api.php/provide/vod'},
's15': {'name': '📺豪华', 'api': 'https://hhzyapi.com/api.php/provide/vod'},
's16': {'name': '📺CK资源', 'api': 'https://ckzy.me/api.php/provide/vod'},
's17': {'name': '📺U酷', 'api': 'https://api.ukuapi.com/api.php/provide/vod'},
's18': {'name': '📺ikun', 'api': 'https://ikunzyapi.com/api.php/provide/vod'},
's19': {'name': '📺无尽', 'api': 'https://api.wujinapi.cc/api.php/provide/vod'},
's20': {'name': '🌕光速', 'api': 'https://api.guangsuapi.com/api.php/provide/vod'},
's21': {'name': '📺卧龙', 'api': 'https://collect.wolongzyw.com/api.php/provide/vod'},
's22': {'name': '📺新浪', 'api': 'https://api.xinlangapi.com/xinlangapi.php/provide/vod'},
's23': {'name': '📺旺旺', 'api': 'https://api.wwzy.tv/api.php/provide/vod'},
's24': {'name': '📺最大', 'api': 'https://api.zuidapi.com/api.php/provide/vod'},
's25': {'name': '🌸樱花', 'api': 'https://m3u8.apiyhzy.com/api.php/provide/vod'},
's26': {'name': '🐮牛牛', 'api': 'https://api.niuniuzy.me/api.php/provide/vod'},
's27': {'name': '☁️百度云', 'api': 'https://api.apibdzy.com/api.php/provide/vod'},
's28': {'name': '🏎速播', 'api': 'https://subocaiji.com/api.php/provide/vod'},
's29': {'name': '🦅金鹰', 'api': 'https://jinyingzy.com/api.php/provide/vod'},
's30': {'name': '⚡闪电', 'api': 'https://sdzyapi.com/api.php/provide/vod'},
's31': {'name': '👑非凡', 'api': 'https://cj.ffzyapi.com/api.php/provide/vod'},
's32': {'name': '🍃飘零', 'api': 'https://p2100.net/api.php/provide/vod'},
's33': {'name': '🐾魔爪', 'api': 'https://mozhuazy.com/api.php/provide/vod'},
's34': {'name': '📺魔都', 'api': 'https://www.mdzyapi.com/api.php/provide/vod'},
}
headers = {
"User-Agent": "Mozilla/5.0"
}
def getName(self):
return "影视+专属全网聚合"
def init(self, extend=""):
pass
def fetch(self, url, timeout=8):
try:
r = requests.get(
url,
headers=self.headers,
timeout=timeout,
verify=False
)
return r.text
except Exception:
return ""
def clean_item(self, item, source_key, source_name, is_detail=False):
item = dict(item)
if not is_detail:
item["vod_id"] = f"{source_key}@@{item.get('vod_id', '')}"
remarks = item.get("vod_remarks", "")
item["vod_remarks"] = f"{source_name} | {remarks}"
if item.get("vod_play_from"):
froms = item["vod_play_from"].split("$$$")
froms = [f"{source_name}-{x}" for x in froms]
item["vod_play_from"] = "$$$".join(froms)
item.pop("vod_down_from", None)
item.pop("vod_down_url", None)
return item
def homeContent(self, filter):
classes = []
filters = {}
def load_class(key, source):
url = f"{source['api']}?ac=list"
html = self.fetch(url, 4)
try:
data = json.loads(html)
except:
data = {}
vals = [{"n": "全部(最新)", "v": ""}]
for c in data.get("class", []):
vals.append({
"n": c.get("type_name", ""),
"v": c.get("type_id", "")
})
return key, vals
with ThreadPoolExecutor(max_workers=16) as executor:
futures = []
for key, source in self.sources.items():
classes.append({
"type_id": key,
"type_name": source["name"]
})
futures.append(executor.submit(load_class, key, source))
for future in as_completed(futures):
try:
key, vals = future.result()
filters[key] = [{
"key": "cateId",
"name": "分类",
"value": vals
}]
except:
pass
return {
"class": classes,
"filters": filters,
"list": []
}
def categoryContent(self, tid, pg, filter, extend):
if tid not in self.sources:
return {"list": []}
source = self.sources[tid]
cate_id = ""
if isinstance(extend, dict):
cate_id = extend.get("cateId", "")
url = f"{source['api']}?ac=detail&pg={pg}"
if cate_id:
url += f"&t={cate_id}"
html = self.fetch(url)
try:
data = json.loads(html)
except:
data = {}
result = []
for item in data.get("list", []):
result.append(
self.clean_item(
item,
tid,
source["name"],
False
)
)
return {
"list": result,
"page": data.get("page", pg),
"pagecount": data.get("pagecount", 1),
"limit": data.get("limit", 20),
"total": data.get("total", len(result))
}
def detailContent(self, ids):
if isinstance(ids, list):
ids = ids[0]
if "@@" not in ids:
return {"list": []}
source_key, real_id = ids.split("@@", 1)
if source_key not in self.sources:
return {"list": []}
source = self.sources[source_key]
url = f"{source['api']}?ac=detail&ids={real_id}"
html = self.fetch(url)
try:
data = json.loads(html)
except:
data = {}
result = []
for item in data.get("list", []):
cleaned = self.clean_item(
item,
source_key,
source["name"],
True
)
cleaned["vod_id"] = ids
result.append(cleaned)
return {"list": result}
def search_one(self, source_key, source, keyword, pg):
url = f"{source['api']}?ac=detail&wd={keyword}&pg={pg}"
html = self.fetch(url, 6)
try:
data = json.loads(html)
except:
data = {}
result = []
for item in data.get("list", []):
result.append(
self.clean_item(
item,
source_key,
source["name"],
False
)
)
return {
"list": result,
"pagecount": data.get("pagecount", 1)
}
def searchContent(self, key, quick=False, pg=1):
result = []
max_page = 1
with ThreadPoolExecutor(max_workers=20) as executor:
futures = []
for source_key, source in self.sources.items():
futures.append(
executor.submit(
self.search_one,
source_key,
source,
key,
pg
)
)
for future in as_completed(futures):
try:
data = future.result()
result.extend(data["list"])
if data["pagecount"] > max_page:
max_page = data["pagecount"]
except:
pass
return {
"list": result,
"page": pg,
"pagecount": max_page,
"limit": 40,
"total": 9999
}
def playerContent(self, flag, id, vipFlags):
return {
"parse": 0,
"playUrl": "",
"url": id,
"header": self.headers
}
def localProxy(self, param):
return [200, "text/plain", "ok"]
if __name__ == "__main__":
Spider().run()