永乐视频

https://www.ylsp.lv

zpccool (13551) 2天前 下载:651

视频
永乐视频影视书源,HTML 解析详情、目录和播放直链,播放页变量解密兜底。
二维码导入(APP尚未完成该功能)
// @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(/&lt;/g, '<')
    .replace(/&gt;/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
  });
}
广告