永乐视频
https://www.ylsp.lv
zpccool (13551) 2天前 下载:651
视频
永乐视频影视书源,HTML 解析详情、目录和播放直链,播放页变量解密兜底。
// @name 永乐视频
// @uuid yongleshipin
// @version 1.0.0
// @author AI
// @url https://www.ylsp.lv
// @type video
// @enabled true
// @description 永乐视频影视书源,HTML 解析详情、目录和播放直链,播放页变量解密兜底。
var BASE = 'https://www.ylsp.lv';
var UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36';
var PAGE_HEADERS = {
'User-Agent': UA,
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': BASE + '/'
};
var PLAY_HEADERS = {
'User-Agent': UA,
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Referer': BASE + '/',
'Origin': BASE
};
var CATEGORIES = {
'首页': '/',
'电影': '/vodtype/1/',
'剧集': '/vodtype/2/',
'综艺': '/vodtype/3/',
'动漫': '/vodtype/4/',
'更新': '/label/new/',
'热榜': '/label/hot/'
};
function init() {
legado.log('[init] 永乐视频 ready');
}
function trim(s) {
return String(s || '').replace(/^\s+|\s+$/g, '');
}
function htmlDecode(s) {
s = String(s || '');
return s
.replace(/ /g, ' ')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/</g, '<')
.replace(/>/g, '>');
}
function stripHtml(s) {
return trim(htmlDecode(String(s || '')
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')));
}
function absoluteUrl(url) {
url = trim(htmlDecode(url));
if (!url) return '';
if (/^https?:\/\//i.test(url)) return url;
if (url.indexOf('//') === 0) return 'https:' + url;
if (url.charAt(0) === '/') return BASE + url;
return BASE + '/' + url;
}
function uniquePush(list, seen, item) {
if (!item || !item.bookUrl || seen[item.bookUrl]) return;
seen[item.bookUrl] = true;
list.push(item);
}
function attr(block, name) {
var re = new RegExp("\\s" + name + "=[\"']([^\"']*)[\"']", 'i');
var m = re.exec(block || '');
return m ? htmlDecode(m[1]) : '';
}
function makeBookItem(name, url, cover, note, kind, intro, author) {
name = stripHtml(name);
var bookUrl = absoluteUrl(url);
return {
name: name,
author: stripHtml(author || ''),
bookUrl: bookUrl,
tocUrl: bookUrl,
coverUrl: absoluteUrl(cover),
intro: stripHtml(intro || ''),
kind: stripHtml(kind || ''),
latestChapter: stripHtml(note || ''),
latestChapterUrl: bookUrl,
status: stripHtml(note || '')
};
}
function parseCardItems(html) {
html = String(html || '');
var list = [];
var seen = {};
var re = /<a\b[^>]*href=["'](\/voddetail\/\d+\/)["'][^>]*class=["'][^"']*(?:module-poster-item|module-card-item-poster)[^"']*["'][\s\S]*?(?=<a\b[^>]*href=["']\/voddetail\/\d+\/["'][^>]*class=["'][^"']*(?:module-poster-item|module-card-item-poster)|<\/body>)/gi;
var m;
while ((m = re.exec(html)) !== null) {
var block = m[0];
var url = m[1];
var name = attr(block, 'title');
if (!name) {
var tm = /class=["'][^"']*module-poster-item-title[^"']*["'][^>]*>([\s\S]*?)<\/div>/i.exec(block);
name = tm ? stripHtml(tm[1]) : '';
}
if (!name) {
var am = /<img\b[^>]*\salt=["']([^"']+)["']/i.exec(block);
name = am ? am[1] : '';
}
var cm = /(?:data-original|src)=["']([^"']*(?:\/upload\/vod\/|\/upload\/)[^"']+)["']/i.exec(block);
var nm = /class=["'][^"']*module-item-note[^"']*["'][^>]*>([\s\S]*?)<\/div>/i.exec(block);
if (name && url) uniquePush(list, seen, makeBookItem(name, url, cm ? cm[1] : '', nm ? nm[1] : '', '', '', ''));
}
var cardRe = /<div class=["'][^"']*module-card-item module-item[^"']*["'][\s\S]*?(?=<div class=["'][^"']*module-card-item module-item|<\/body>)/gi;
while ((m = cardRe.exec(html)) !== null) {
var b = m[0];
var um = /href=["'](\/voddetail\/\d+\/)["']/i.exec(b);
var nm2 = /class=["'][^"']*module-card-item-title[^"']*["'][\s\S]*?<a[^>]*>([\s\S]*?)<\/a>/i.exec(b);
var im = /(?:data-original|src)=["']([^"']*(?:\/upload\/vod\/|\/upload\/)[^"']+)["']/i.exec(b);
var note = /class=["'][^"']*module-item-note[^"']*["'][^>]*>([\s\S]*?)<\/div>/i.exec(b);
var kind = /class=["'][^"']*module-card-item-class[^"']*["'][^>]*>([\s\S]*?)<\/div>/i.exec(b);
var info = /class=["'][^"']*module-info-item-content[^"']*["'][^>]*>([\s\S]*?)<\/div>/i.exec(b);
if (um && nm2) uniquePush(list, seen, makeBookItem(nm2[1], um[1], im ? im[1] : '', note ? note[1] : '', kind ? kind[1] : '', '', info ? info[1] : ''));
}
return list;
}
async function httpGet(url, headers) {
return await legado.http.get(url, headers || PAGE_HEADERS);
}
function pageUrl(path, page) {
page = Number(page || 1);
if (page <= 1) return BASE + path;
if (path === '/') return BASE + '/index-' + page + '.html';
if (/\/vodtype\/\d+\/$/i.test(path)) return BASE + path.replace(/\/$/, '') + '-' + page + '/';
if (/\/label\/[^\/]+\/$/i.test(path)) return BASE + path.replace(/\/$/, '') + '-' + page + '/';
return BASE + path;
}
async function explore(page, category) {
legado.log('[explore] page=' + page + ' category=' + category);
if (page === 'GETALL' || category === 'GETALL' || page === undefined) {
return ['首页', '电影', '剧集', '综艺', '动漫', '更新', '热榜'];
}
if (typeof page === 'string' && !/^\d+$/.test(page) && category === undefined) {
category = page;
page = 1;
}
category = category || '首页';
var path = CATEGORIES[category] || CATEGORIES['首页'];
var html = await httpGet(pageUrl(path, page || 1), PAGE_HEADERS);
return parseCardItems(html);
}
async function search(keyword, page) {
legado.log('[search] keyword=' + keyword + ' page=' + page);
page = page || 1;
var url = BASE + '/vodsearch/' + encodeURIComponent(keyword || '') + '-------------/';
if (page > 1) url = BASE + '/vodsearch/' + encodeURIComponent(keyword || '') + '----------' + page + '---/';
var html = await httpGet(url, PAGE_HEADERS);
return parseCardItems(html);
}
function pickInfo(html, label) {
var re = new RegExp("<span class=[\"']module-info-item-title[\"']>" + label + "[::]<\\/span>\\s*<div class=[\"']module-info-item-content[\"']>([\\s\\S]*?)<\\/div>", 'i');
var m = re.exec(html || '');
return m ? stripHtml(m[1]).replace(/\s*\/\s*/g, ',') : '';
}
async function bookInfo(bookUrl) {
legado.log('[bookInfo] url=' + bookUrl);
bookUrl = absoluteUrl(bookUrl);
var html = await httpGet(bookUrl, PAGE_HEADERS);
var name = '';
var m = /<div class=["']module-info-heading["'][\s\S]*?<h1[^>]*>([\s\S]*?)<\/h1>/i.exec(html);
if (m) name = stripHtml(m[1]);
if (!name) {
m = /<meta property=["']og:title["'] content=["']([^"']+)/i.exec(html);
name = m ? String(m[1]).replace(/-免费在线观看.*/g, '') : '';
}
var cover = '';
m = /<meta property=["']og:image["'] content=["']([^"']+)/i.exec(html);
if (m) cover = m[1];
if (!cover) {
m = /class=["']module-info-poster["'][\s\S]*?(?:data-original|src)=["']([^"']+)/i.exec(html);
cover = m ? m[1] : '';
}
var intro = '';
m = /class=["']module-info-introduction-content["'][^>]*>([\s\S]*?)<\/div>/i.exec(html);
if (m) intro = stripHtml(m[1]);
var director = pickInfo(html, '导演');
var actor = pickInfo(html, '主演');
var updateTime = pickInfo(html, '更新');
var status = pickInfo(html, '集数');
var tags = [];
var tagRe = /class=["']module-info-tag-link["'][\s\S]*?<a[^>]*>([\s\S]*?)<\/a>/gi;
while ((m = tagRe.exec(html)) !== null) tags.push(stripHtml(m[1]));
return {
name: name,
author: director || actor,
bookUrl: bookUrl,
tocUrl: bookUrl,
coverUrl: absoluteUrl(cover),
intro: intro,
kind: tags.join(','),
latestChapter: status,
updateTime: updateTime,
status: status
};
}
async function chapterList(tocUrl) {
legado.log('[chapterList] url=' + tocUrl);
tocUrl = absoluteUrl(tocUrl);
var html = await httpGet(tocUrl, PAGE_HEADERS);
var routeNames = [];
var rm;
var routeRe = /<div class=["']module-tab-item tab-item["'][^>]*data-dropdown-value=["']([^"']+)["'][\s\S]*?<\/div>/gi;
while ((rm = routeRe.exec(html)) !== null) routeNames.push(stripHtml(rm[1]));
if (!routeNames.length) {
routeRe = /<a class=["']module-tab-item tab-item["'][^>]*href=["'][^"']+["'][^>]*>\s*<span>([\s\S]*?)<\/span>/gi;
while ((rm = routeRe.exec(html)) !== null) routeNames.push(stripHtml(rm[1]));
}
var chapters = [];
var panelRe = /<div class=["'][^"']*module-list[^"']*(?:his-tab-list|play-tab-list)[^"']*["'][\s\S]*?(?=<div class=["'][^"']*module-list[^"']*(?:his-tab-list|play-tab-list)|<style>|<\/body>)/gi;
var pm;
var groupIndex = 0;
while ((pm = panelRe.exec(html)) !== null) {
var panel = pm[0];
var group = routeNames[groupIndex] || ('线路' + (groupIndex + 1));
var linkRe = /<a class=["']module-play-list-link["'][^>]*href=["']([^"']+)["'][^>]*title=["']([^"']*)["'][\s\S]*?<span>([\s\S]*?)<\/span>/gi;
var lm;
var beforeCount = chapters.length;
while ((lm = linkRe.exec(panel)) !== null) {
var name = stripHtml(lm[3]) || stripHtml(lm[2]).replace(/^播放[^0-9第]*/, '');
chapters.push({
name: name,
url: absoluteUrl(lm[1]),
group: group
});
}
if (chapters.length > beforeCount) groupIndex++;
}
return chapters;
}
function decodePlayUrl(url, encrypt) {
url = String(url || '');
if (String(encrypt) === '1') {
try { url = unescape(url); } catch (e) {}
} else if (String(encrypt) === '2') {
try { url = atob(url); } catch (e2) {}
try { url = decodeURIComponent(url); } catch (e3) {}
}
return htmlDecode(url.replace(/\\\//g, '/'));
}
async function chapterContent(chapterUrl) {
legado.log('[chapterContent] url=' + chapterUrl);
chapterUrl = absoluteUrl(chapterUrl);
var html = await httpGet(chapterUrl, PAGE_HEADERS);
var m = /var\s+player_[a-zA-Z0-9_]+\s*=\s*(\{[\s\S]*?\})\s*<\/script>/i.exec(html);
if (!m) {
m = /(https?:\\?\/\\?\/[^"'\s<>]+\.m3u8[^"'\s<>]*)/i.exec(html);
if (m) return m[1].replace(/\\\//g, '/');
throw new Error('未找到播放变量');
}
var data = JSON.parse(m[1]);
var url = decodePlayUrl(data.url || '', data.encrypt);
if (!url) throw new Error('播放地址为空');
if (url.indexOf('http') !== 0) url = absoluteUrl(url);
return JSON.stringify({
url: url,
type: /\.m3u8(?:$|\?)/i.test(url) ? 'hls' : 'mp4',
headers: PLAY_HEADERS
});
}