123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- /**
- * live2cms.js
- * 配置设置 {"key":"Live2CMS","name":"直播转点播V2","type":3,"api":"{{host}}/libs/live2cms.js","searchable":2,"quickSearch":0,"filterable":0,"ext":"{{host}}/txt/json/live2mv_data.json"}
- * live2mv_data.json
- * 支持m3u类直播,支持线路归并。支持筛选切换显示模式
- [
- {"name": "甜蜜", "url": "http://zdir.kebedd69.repl.co/public/live.txt"},
- {"name": "俊于", "url": "http://home.jundie.top:81/Cat/tv/live.txt"},
- {"name": "菜妮丝", "url": "http://xn--ihqu10cn4c.xn--z7x900a.love:63/TV/tvzb.txt"},
- {"name": "布里m3u", "url": "http://jiexi.bulisite.top/m3u.php"},
- {"name": "吾爱", "url": "http://52bsj.vip:81/api/v3/file/get/763/live.txt?sign=87BTGT1_6AOry7FPwy_uuxFTv2Wcb9aDMj46rDdRTD8%3D%3A0"},
- {"name": "饭太硬", "url": "http://ftyyy.tk/live.txt"}
- ]
- * 提示 ext文件格式为json列表,name,url参数
- * 取消加密,减少性能问题
- */
- String.prototype.rstrip = function (chars) {
- let regex = new RegExp(chars + "$");
- return this.replace(regex, "");
- };
- const request_timeout = 5000;
- const RKEY = 'live2cms'; // 源的唯一标识
- const VERSION = 'live2cms 20230619';
- const UA = 'Mozilla/5.0'; //默认请求ua
- const __ext = {data_dict:{}};
- const tips = `\n道长直播转点播js-当前版本${VERSION}`;
- const def_pic = 'https://avatars.githubusercontent.com/u/97389433?s=120&v=4';
- /**
- * 存在数据库配置表里, key字段对应值value,没有就新增,有就更新,调用此方法会清除key对应的内存缓存
- * @param k 键
- * @param v 值
- */
- function setItem(k,v){
- local.set(RKEY,k,v);
- console.log(`规则${RKEY}设置${k} => ${v}`)
- }
- /**
- * 获取数据库配置表对应的key字段的value,没有这个key就返回value默认传参.需要有缓存,第一次获取后会存在内存里
- * @param k 键
- * @param v 值
- * @returns {*}
- */
- function getItem(k,v){
- return local.get(RKEY,k) || v;
- }
- /**
- * 删除数据库key对应的一条数据,并清除此key对应的内存缓存
- * @param k
- */
- function clearItem(k){
- local.delete(RKEY,k);
- }
- var showMode = getItem('showMode','groups'); // groups按组分类显示 all全部一条线路展示
- var groupDict = JSON.parse(getItem('groupDict','{}')); // 搜索分组字典
- /**
- * 打印日志
- * @param any 任意变量
- */
- function print(any){
- any = any||'';
- if(typeof(any)=='object'&&Object.keys(any).length>0){
- try {
- any = JSON.stringify(any);
- console.log(any);
- }catch (e) {
- // console.log('print:'+e.message);
- console.log(typeof(any)+':'+any.length);
- }
- }else if(typeof(any)=='object'&&Object.keys(any).length<1){
- console.log('null object');
- }else{
- console.log(any);
- }
- }
- /*** js自封装的方法 ***/
- /**
- * 获取链接的host(带http协议的完整链接)
- * @param url 任意一个正常完整的Url,自动提取根
- * @returns {string}
- */
- 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
- }
- /**
- * m3u直播格式转一般直播格式
- * @param m3u
- * @returns {string}
- */
- function convertM3uToNormal(m3u) {
- try {
- const lines = m3u.split('\n');
- let result = '';
- let TV='';
- // let flag='#genre#';
- let flag='#m3u#';
- let currentGroupTitle = '';
- lines.forEach((line) => {
- if (line.startsWith('#EXTINF:')) {
- const groupTitle = line.split('"')[1].trim();
- TV= line.split('"')[2].substring(1);
- if (currentGroupTitle !== groupTitle) {
- currentGroupTitle = groupTitle;
- result += `\n${currentGroupTitle},${flag}\n`;
- }
- } else if (line.startsWith('http')) {
- const splitLine = line.split(',');
- result += `${TV}\,${splitLine[0]}\n`;
- }
- });
- return result.trim();
- }catch (e) {
- print(`m3u直播转普通直播发生错误:${e.message}`);
- return m3u
- }
- }
- /**
- * 线路归类
- * @param arr
- * @returns {*[][]}
- */
- function merge(arr) {
- var parse = arguments[1] ? arguments[1] : '';
- var p = [];
- if (parse !== '' && typeof(parse)=="function") {
- p = arr.map(parse);
- }
- const createEmptyArrays = (length) => Array.from({
- length
- }, () => []);
- let lists = createEmptyArrays(arr.length);
- let sl = createEmptyArrays(arr.length);
- (p.length ? p : arr).forEach((k, index) => {
- var i = 0;
- while (sl[i].includes(k)) {
- i = i + 1
- }
- sl[i].push(k);
- lists[i].push(arr[index]);
- })
- lists=lists.filter(x=>x.some(k=>k.length));
- return lists
- }
- /**
- * 线路归类/小棉袄算法
- * @param arr 数组
- * @param parse 解析式
- * @returns {[[*]]}
- */
- function splitArray(arr,parse) {
- parse = parse&&typeof(parse)=='function'?parse:'';
- let result = [[arr[0]]];
- for (let i = 1; i < arr.length; i++) {
- let index = -1;
- for (let j = 0; j < result.length; j++) {
- if (parse&&result[j].map(parse).includes(parse(arr[i]))) {
- index = j;
- }else if((!parse) && result[j].includes(arr[i])){
- index = j;
- }
- }
- if (index >= result.length - 1) {
- result.push([]);
- result[result.length - 1].push(arr[i]);
- } else {
- result[index + 1].push(arr[i]);
- }
- }
- return result;
- }
- /**
- * 搜索结果生成分组字典
- * @param arr
- * @param parse x=>x.split(',')[0]
- * @returns {{}}
- */
- function gen_group_dict(arr,parse){
- let dict = {};
- arr.forEach((it)=>{
- let k = it.split(',')[0];
- if(parse && typeof(parse)==='function'){
- k = parse(k);
- }
- if(!dict[k]){
- dict[k] = [it]
- }else{
- dict[k].push(it);
- }
- });
- return dict
- }
- const http = function (url, options = {}) {
- if(options.method ==='POST' && options.data){
- options.body = JSON.stringify(options.data);
- options.headers = Object.assign({'content-type':'application/json'}, options.headers);
- }
- options.timeout = request_timeout;
- if(!options.headers){
- options.headers = {};
- }
- let keys = Object.keys(options.headers).map(it=>it.toLowerCase());
- if(!keys.includes('referer')){
- options.headers['Referer'] = getHome(url);
- }
- if(!keys.includes('user-agent')){
- options.headers['User-Agent'] = UA;
- }
- console.log(JSON.stringify(options.headers));
- try {
- const res = req(url, options);
- // if(options.headers['Authorization']){
- // console.log(res.content);
- // }
- res.json = () => res&&res.content ? JSON.parse(res.content) : null;
- res.text = () => res&&res.content ? res.content:'';
- return res
- }catch (e) {
- return {
- json() {
- return null
- }, text() {
- return ''
- }
- }
- }
- };
- ["get", "post"].forEach(method => {
- http[method] = function (url, options = {}) {
- return http(url, Object.assign(options, {method: method.toUpperCase()}));
- }
- });
- function init(ext) {
- console.log("当前版本号:"+VERSION);
- let data;
- if (typeof ext == 'object'){
- data = ext;
- print('live ext:object');
- } else if (typeof ext == 'string') {
- if (ext.startsWith('http')) {
- let ext_paramas = ext.split(';');
- let data_url = ext_paramas[0];
- print(data_url);
- data = http.get(data_url).json();
- }
- }
- print(data);
- __ext.data = data;
- print('init执行完毕');
- }
- function home(filter) {
- let classes = __ext.data.map(it => ({
- type_id: it.url,
- type_name: it.name,
- }));
- print("----home----");
- let filter_dict = {};
- let filters = [
- {'key': 'show', 'name': '播放展示', 'value': [{'n': '多线路分组', 'v': 'groups'},{'n': '单线路', 'v': 'all'}]}
- ];
- classes.forEach(it=>{
- filter_dict[it.type_id] = filters;
- });
- print(classes);
- return JSON.stringify({ 'class': classes,'filters': filter_dict});
- }
- function homeVod(params) {
- let _get_url = __ext.data[0].url;
- let html;
- if(__ext.data_dict[_get_url]){
- html = __ext.data_dict[_get_url];
- }else{
- html = http.get(_get_url).text();
- if(/#EXTM3U/.test(html)){
- html = convertM3uToNormal(html);
- }
- __ext.data_dict[_get_url] = html;
- }
- // let arr = html.match(/.*?,#[\s\S].*?#/g);
- let arr = html.match(/.*?[,,]#[\s\S].*?#/g); // 可能存在中文逗号
- let _list = [];
- try {
- arr.forEach(it=>{
- let vname = it.split(/[,,]/)[0];
- let vtab = it.match(/#(.*?)#/)[0];
- _list.push({
- vod_name:vname,
- vod_id:_get_url+'$'+vname,
- vod_pic:def_pic,
- vod_remarks:vtab,
- });
- });
- }catch (e) {
- print('Live2cms获取首页推荐发送错误:'+e.message);
- }
- return JSON.stringify({ 'list': _list });
- }
- function category(tid, pg, filter, extend) {
- let fl = filter?extend:{};
- if(fl.show){
- showMode = fl.show;
- setItem('showMode',showMode);
- }
- if(parseInt(pg)>1){
- return JSON.stringify({
- 'list': [],
- });
- }
- let _get_url = tid;
- let html;
- if(__ext.data_dict[_get_url]){
- html = __ext.data_dict[_get_url];
- }else{
- html = http.get(_get_url).text();
- if(/#EXTM3U/.test(html)){
- html = convertM3uToNormal(html);
- }
- __ext.data_dict[_get_url] = html;
- }
- // let arr = html.match(/.*?[,,]#[\s\S].*?#/g);
- let arr = html.match(/.*?[,,]#[\s\S].*?#/g); // 可能存在中文逗号
- let _list = [];
- try {
- arr.forEach(it=>{
- let vname = it.split(/[,,]/)[0];
- let vtab = it.match(/#(.*?)#/)[0];
- _list.push({
- // vod_name:it.split(',')[0],
- vod_name:vname,
- vod_id:_get_url+'$'+vname,
- vod_pic:def_pic,
- vod_remarks:vtab,
- });
- });
- }catch (e) {
- print('Live2cms获取一级分类页发生错误:'+e.message);
- }
- return JSON.stringify({
- 'page': 1,
- 'pagecount': 1,
- 'limit': _list.length,
- 'total': _list.length,
- 'list': _list,
- });
- }
- function detail(tid) { // ⛵ 港•澳•台
- let _get_url = tid.split('$')[0];
- let _tab = tid.split('$')[1];
- if(tid.includes('#search#')){
- let vod_name = _tab.replace('#search#','');
- let vod_play_from = '来自搜索';
- vod_play_from+=`:${_get_url}`;
- // let vod_play_url = vod_name+'$'+_get_url;
- // print(vod_play_url);
- let vod_play_url = groupDict[_get_url].map(x=>x.replace(',','$')).join('#');
- return JSON.stringify({
- list: [{
- vod_id: tid,
- vod_name: '搜索:'+vod_name,
- type_name: "直播列表",
- vod_pic: def_pic,
- vod_content: tid,
- vod_play_from: vod_play_from,
- vod_play_url: vod_play_url,
- vod_director: tips,
- vod_remarks: `道长直播转点播js-当前版本${VERSION}`,
- }]
- });
- }
- let html;
- if(__ext.data_dict[_get_url]){
- html = __ext.data_dict[_get_url];
- }else{
- html = http.get(_get_url).text();
- if(/#EXTM3U/.test(html)){
- html = convertM3uToNormal(html);
- }
- __ext.data_dict[_get_url] = html;
- }
- // let a = new RegExp(`.*?${_tab},#[\\s\\S].*?#`);
- let a = new RegExp(`.*?${_tab.replace('(','\\(').replace(')','\\)')}[,,]#[\\s\\S].*?#`);
- let b = html.match(a)[0];
- let c = html.split(b)[1];
- if(c.match(/.*?[,,]#[\s\S].*?#/)){
- let d = c.match(/.*?[,,]#[\s\S].*?#/)[0];
- c = c.split(d)[0];
- }
- let arr = c.trim().split('\n');
- let _list = [];
- arr.forEach((it)=>{
- if(it.trim()){
- let t = it.trim().split(',')[0];
- let u = it.trim().split(',')[1];
- _list.push(t+'$'+u);
- }
- });
- let vod_name = __ext.data.find(x=>x.url===_get_url).name;
- let vod_play_url;
- let vod_play_from;
- if(showMode==='groups'){
- let groups = splitArray(_list,x=>x.split('$')[0]);
- let tabs = [];
- for(let i=0;i<groups.length;i++){
- if(i===0){
- tabs.push(vod_name+'1')
- }else{
- tabs.push(` ${i+1} `)
- }
- }
- vod_play_url = groups.map(it=>it.join('#')).join('$$$');
- vod_play_from = tabs.join('$$$');
- }else{
- vod_play_url = _list.join('#');
- vod_play_from = vod_name;
- }
- let vod = {
- vod_id: tid,
- vod_name: vod_name+'|'+_tab,
- type_name: "直播列表",
- vod_pic: def_pic,
- vod_content: tid,
- vod_play_from: vod_play_from,
- vod_play_url: vod_play_url,
- vod_director: tips,
- vod_remarks: `道长直播转点播js-当前版本${VERSION}`,
- };
- return JSON.stringify({
- list: [vod]
- });
- }
- function play(flag, id, flags) {
- let vod = {
- 'parse': /m3u8/.test(id)?0:1,
- 'playUrl': '',
- 'url': id
- };
- print(vod);
- return JSON.stringify(vod);
- }
- function search(wd, quick) {
- let _get_url = __ext.data[0].url;
- let html;
- if(__ext.data_dict[_get_url]){
- html = __ext.data_dict[_get_url];
- }else{
- html = http.get(_get_url).text();
- if(/#EXTM3U/.test(html)){
- html = convertM3uToNormal(html);
- }
- __ext.data_dict[_get_url] = html;
- }
- let str='';
- Object.keys(__ext.data_dict).forEach(()=>{
- str+=__ext.data_dict[_get_url];
- });
- let links = str.split('\n').filter(it=>it.trim() && it.includes(',') && it.split(',')[1].trim().startsWith('http'));
- links = links.map(it=>it.trim());
- let plays = Array.from(new Set(links));
- print('搜索关键词:'+wd);
- print('过滤前:'+plays.length);
- plays = plays.filter(it=>it.includes(wd));
- print('过滤后:'+plays.length);
- print(plays);
- let new_group = gen_group_dict(plays);
- groupDict = Object.assign(groupDict,new_group);
- // 搜索分组结果存至本地方便二级调用
- setItem('groupDict',JSON.stringify(groupDict));
- let _list = [];
- // plays.forEach((it)=>{
- // _list.push({
- // 'vod_name':it.split(',')[0],
- // 'vod_id':it.split(',')[1].trim()+'$'+it.split(',')[0].trim()+'#search#',
- // 'vod_pic':def_pic,
- // })
- // });
- Object.keys(groupDict).forEach((it)=>{
- _list.push({
- 'vod_name':it,
- 'vod_id':it+'$'+wd+'#search#',
- 'vod_pic':def_pic,
- });
- });
- return JSON.stringify({
- 'list': _list
- });
- }
- // 导出函数对象
- export default {
- init: init,
- home: home,
- homeVod: homeVod,
- category: category,
- detail: detail,
- play: play,
- search: search
- }
|