1028 dokiss
8 months ago
1 changed files with 534 additions and 0 deletions
@ -0,0 +1,534 @@ |
|||
/** |
|||
* 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://pan.shangui.cc/f/N0qlIj/9qMWp7i0D892ebDM820p.jpg'; |
|||
|
|||
/** |
|||
* 存在数据库配置表里, 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 |
|||
} |
Loading…
Reference in new issue