Sync all projects
This commit is contained in:
@@ -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
@@ -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
@@ -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,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
||||
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,'"') + '"' +
|
||||
' data-video="' + (m.video||'').replace(/"/g,'"') + '"' +
|
||||
' 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,'&').replace(/</g,'<').replace(/>/g,'>')
|
||||
.replace(/"/g,'"').replace(/'/g,''');
|
||||
}
|
||||
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>';
|
||||
}
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+3201
@@ -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
|
||||
};
|
||||
@@ -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}}®ion={{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(/®ion=([^&]+)/);if(rm&&rm[1]){input=input.replace(/three_category_id=[^&]*/,"three_category_id="+rm[1])}input=input.replace(/®ion=[^&]*/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',
|
||||
}
|
||||
@@ -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
@@ -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"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
};
|
||||
})
|
||||
};
|
||||
@@ -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¶ms=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
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
@@ -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",
|
||||
|
||||
@@ -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资源",
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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': []}
|
||||
@@ -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': []}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user