467 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			467 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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:')) {
 | ||
|                 line = line.replace(/'/g, '"');
 | ||
|                 let groupTitle = '未知频道';
 | ||
|                 let tvg_name = '';
 | ||
|                 let tvg_logo = '';
 | ||
|                 try {
 | ||
|                     groupTitle = line.match(/group-title="(.*?)"/)[1].trim();
 | ||
|                 } catch (e) {
 | ||
|                 }
 | ||
|                 try {
 | ||
|                     tvg_name = line.match(/tvg-name="(.*?)"/)[1].trim();
 | ||
|                 } catch (e) {
 | ||
|                 }
 | ||
|                 try {
 | ||
|                     tvg_logo = line.match(/tvg-logo="(.*?)"/)[1].trim();
 | ||
|                 } catch (e) {
 | ||
|                 }
 | ||
|                 TV = line.split(',').slice(-1)[0].trim();
 | ||
|                 if (currentGroupTitle !== groupTitle) {
 | ||
|                     currentGroupTitle = groupTitle;
 | ||
|                     let ret_list = [currentGroupTitle, flag];
 | ||
|                     // if(tvg_name){
 | ||
|                     //     ret_list.push(tvg_name);
 | ||
|                     // }
 | ||
|                     // if(tvg_logo){
 | ||
|                     //     ret_list.push(tvg_logo);
 | ||
|                     // }
 | ||
|                     result += `\n${ret_list.join(",")}\n`;
 | ||
|                 }
 | ||
|             } else if (line.startsWith('http')) {
 | ||
|                 const splitLine = line.split(',');
 | ||
|                 result += `${TV}\,${splitLine[0]}\n`;
 | ||
|             }
 | ||
|         });
 | ||
|         // result = result.trim();
 | ||
|         result = mergeChannels(result);
 | ||
|         // log(result);
 | ||
|         return result
 | ||
|     } catch (e) {
 | ||
|         log(`m3u直播转普通直播发生错误:${e.message}`);
 | ||
|         return m3u
 | ||
|     }
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * 线路归类/小棉袄算法
 | ||
|  * @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
 | ||
| }
 | ||
| 
 | ||
| /**
 | ||
|  * txt格式直播自动合并频道链接
 | ||
|  * @param text
 | ||
|  * @returns {string}
 | ||
|  */
 | ||
| function mergeChannels(text) {
 | ||
|     const lines = text.split('\n');
 | ||
|     const channelMap = new Map();
 | ||
|     let currentChannel = ''; // 当前处理的频道
 | ||
| 
 | ||
|     lines.forEach(line => {
 | ||
|         // 使用正则表达式匹配频道行,假设频道行包含",#"即可识别为频道行
 | ||
|         if (/,#/.test(line)) {
 | ||
|             // 如果是频道名称,作为键值存储,初始化为空数组
 | ||
|             currentChannel = line;
 | ||
|             if (!channelMap.has(line)) {
 | ||
|                 channelMap.set(line, []);
 | ||
|             }
 | ||
|         } else if (line) { // 忽略空行
 | ||
|             // 将当前行(链接)添加到当前频道数组中
 | ||
|             if (currentChannel) {
 | ||
|                 channelMap.get(currentChannel).push(line);
 | ||
|             }
 | ||
|         }
 | ||
|     });
 | ||
| 
 | ||
|     // 构建结果字符串
 | ||
|     let result = '';
 | ||
|     channelMap.forEach((value, key) => {
 | ||
|         result += key + '\n' + value.join('\n') + '\n\n';
 | ||
|     });
 | ||
| 
 | ||
|     return result.trim(); // 移除尾部的多余换行符
 | ||
| }
 | ||
| 
 | ||
| globalThis.mergeChannels = mergeChannels;
 | ||
| globalThis.convertM3uToNormal = convertM3uToNormal;
 | ||
| globalThis.splitArray = splitArray;
 | ||
| globalThis.gen_group_dict = gen_group_dict;
 | ||
| globalThis.getRandomItem = function (items) {//从列表随机取出一个元素
 | ||
|     return items[Math.random() * items.length | 0];
 | ||
| }
 | ||
| globalThis.__ext = {data_dict: {}};
 | ||
| var rule = {
 | ||
|     title: '直播转点播[合]',
 | ||
|     author: '道长',
 | ||
|     version: '20240628 beta6',
 | ||
|     update_info: `
 | ||
| `,
 | ||
|     host: '',
 | ||
|     homeUrl: '',
 | ||
|     searchUrl: '#wd=**&pg=#TruePage##page=fypage',
 | ||
|     url: 'fyclass#pg=fypage&t=fyfilter',
 | ||
|     filter_url: '{{fl.show}}',
 | ||
|     headers: {'User-Agent': 'MOBILE_UA'},
 | ||
|     timeout: 5000, // class_name: '电影&电视剧&综艺&动漫',
 | ||
|     limit: 20,
 | ||
|     search_limit: 5, // 搜索限制取前5个,可以注释掉,就不限制搜索
 | ||
|     searchable: 1,//是否启用全局搜索,
 | ||
|     quickSearch: 0,//是否启用快速搜索,
 | ||
|     filterable: 1,//是否启用分类筛选,
 | ||
|     play_parse: true,
 | ||
|     // params: 'http://127.0.0.1:5707/files/json/live2cms.json',
 | ||
|     // 下面自定义一些源的配置
 | ||
|     // def_pic: 'https://avatars.githubusercontent.com/u/97389433?s=120&v=4', //默认列表图片
 | ||
|     def_pic: 'https://gitee.com/wyygj/gjbox/raw/master/ox/1.png', //默认列表图片
 | ||
|     showMode: 'groups',// groups按组分类显示 all全部一条线路展示
 | ||
|     groupDict: {},// 搜索分组字典
 | ||
|     tips: '', //二级提示信息
 | ||
|     预处理: $js.toString(() => {
 | ||
|         // 初始化保存的数据
 | ||
|         rule.showMode = getItem('showMode', 'groups');
 | ||
|         rule.groupDict = JSON.parse(getItem('groupDict', '{}'));
 | ||
|         rule.tips = `道长直播转点播js-当前版本${rule.version}`;
 | ||
| 
 | ||
|         if (typeof (batchFetch) === 'function') {
 | ||
|             // 支持批量请求直接放飞自我。搜索限制最大线程数量16
 | ||
|             rule.search_limit = 16;
 | ||
|             log('当前程序支持批量请求[batchFetch],搜索限制已设置为16');
 | ||
|         }
 | ||
|         let _url = rule.params;
 | ||
|         if (_url && typeof (_url) === 'string' && /^(http|file)/.test(_url)) {
 | ||
|             let html = request(_url);
 | ||
|             let json = JSON.parse(html);
 | ||
| 
 | ||
|             let _classes = [];
 | ||
|             rule.filter = {};
 | ||
|             rule.filter_def = {};
 | ||
|             json.forEach(it => {
 | ||
|                 if (it.url && !/^(http|file)/.test(it.url)) {
 | ||
|                     it.url = urljoin(_url, it.url);
 | ||
|                 }
 | ||
|                 let _obj = {
 | ||
|                     type_name: it.name,
 | ||
|                     type_id: it.url,
 | ||
|                     img: it.img,
 | ||
|                 };
 | ||
|                 _classes.push(_obj);
 | ||
|                 let json1 = [{'n': '多线路分组', 'v': 'groups'}, {'n': '单线路', 'v': 'all'}];
 | ||
|                 try {
 | ||
|                     rule.filter[_obj.type_id] = [
 | ||
|                         {'key': 'show', 'name': '播放展示', 'value': json1}
 | ||
|                     ];
 | ||
|                     if (json1.length > 0) {
 | ||
|                         rule.filter_def[it.url] = {"show": json1[0].v};
 | ||
|                     }
 | ||
|                 } catch (e) {
 | ||
|                     rule.filter[it.url] = json1
 | ||
|                 }
 | ||
|             });
 | ||
|             __ext.data = json;
 | ||
|             rule.classes = _classes;
 | ||
|         }
 | ||
|     }),
 | ||
|     class_parse: $js.toString(() => {
 | ||
|         input = rule.classes;
 | ||
|     }),
 | ||
|     推荐: $js.toString(() => {
 | ||
|         let update_info = [{
 | ||
|             vod_name: '别相信广告',
 | ||
|             vod_id: 'update_info',
 | ||
|             vod_remarks: `版本:${rule.version}`,
 | ||
|             vod_pic: 'https://gitee.com/wyygj/gjbox/raw/master/ox/2.png'
 | ||
|         }];
 | ||
|         VODS = [];
 | ||
|         if (rule.classes) {
 | ||
|             let randomClass = getRandomItem(rule.classes);
 | ||
|             let _get_url = randomClass.type_id;
 | ||
|             // let current_vod = rule.classes.find(item => item.type_id === _get_url);
 | ||
|             // let _pic = current_vod ? current_vod.img : '';
 | ||
|             let _pic = randomClass.img;
 | ||
|             let html;
 | ||
|             if (__ext.data_dict[_get_url]) {
 | ||
|                 html = __ext.data_dict[_get_url];
 | ||
|             } else {
 | ||
|                 html = request(_get_url);
 | ||
|                 if (/#EXTM3U/.test(html)) {
 | ||
|                     html = convertM3uToNormal(html);
 | ||
|                 } else {
 | ||
|                     html = mergeChannels(html);
 | ||
|                 }
 | ||
|                 __ext.data_dict[_get_url] = html;
 | ||
|             }
 | ||
|             let arr = html.match(/.*?[,,]#[\s\S].*?#/g); // 可能存在中文逗号
 | ||
|             try {
 | ||
|                 arr.forEach(it => {
 | ||
|                     let vname = it.split(/[,,]/)[0];
 | ||
|                     let vtab = it.match(/#(.*?)#/)[0];
 | ||
|                     VODS.push({
 | ||
|                         vod_name: vname,
 | ||
|                         vod_id: _get_url + '$' + vname,
 | ||
|                         vod_pic: _pic || rule.def_pic,
 | ||
|                         vod_remarks: vtab,
 | ||
|                     });
 | ||
|                 });
 | ||
|             } catch (e) {
 | ||
|                 log(`直播转点播获取首页推荐发送错误:${e.message}`);
 | ||
|             }
 | ||
|         }
 | ||
|         VODS = update_info.concat(VODS);
 | ||
|     }),
 | ||
|     一级: $js.toString(() => {
 | ||
|         VODS = [];
 | ||
|         // 一级限制页数不允许翻页
 | ||
|         if (rule.classes && MY_PAGE <= 1) {
 | ||
|             if (MY_FL.show) {
 | ||
|                 rule.showMode = MY_FL.show;
 | ||
|                 setItem('showMode', rule.showMode);
 | ||
|             }
 | ||
|             let _get_url = input.split('#')[0];
 | ||
|             let current_vod = rule.classes.find(item => item.type_id === MY_CATE);
 | ||
|             let _pic = current_vod ? current_vod.img : '';
 | ||
|             let html;
 | ||
|             if (__ext.data_dict[_get_url]) {
 | ||
|                 html = __ext.data_dict[_get_url];
 | ||
|             } else {
 | ||
|                 html = request(_get_url);
 | ||
|                 if (/#EXTM3U/.test(html)) {
 | ||
|                     html = convertM3uToNormal(html);
 | ||
|                 } else {
 | ||
|                     html = mergeChannels(html);
 | ||
|                 }
 | ||
|                 __ext.data_dict[_get_url] = html;
 | ||
|             }
 | ||
|             let arr = html.match(/.*?[,,]#[\s\S].*?#/g); // 可能存在中文逗号
 | ||
|             try {
 | ||
|                 arr.forEach(it => {
 | ||
|                     let vname = it.split(/[,,]/)[0];
 | ||
|                     let vtab = it.match(/#(.*?)#/)[0];
 | ||
|                     VODS.push({
 | ||
|                         // vod_name:it.split(',')[0],
 | ||
|                         vod_name: vname,
 | ||
|                         vod_id: _get_url + '$' + vname,
 | ||
|                         vod_pic: _pic || rule.def_pic,
 | ||
|                         vod_remarks: vtab,
 | ||
|                     });
 | ||
|                 });
 | ||
|             } catch (e) {
 | ||
|                 log(`直播转点播获取一级分类页发生错误:${e.message}`);
 | ||
|             }
 | ||
|         }
 | ||
|     }),
 | ||
|     二级: $js.toString(() => {
 | ||
|         VOD = {};
 | ||
|         if (orId === 'update_info') {
 | ||
|             VOD = {
 | ||
|                 vod_content: rule.update_info.trim(),
 | ||
|                 vod_name: '更新日志',
 | ||
|                 type_name: '更新日志',
 | ||
|                 vod_pic: 'https://gitee.com/wyygj/gjbox/raw/master/ox/1.png',
 | ||
|                 vod_remarks: `版本:${rule.version}`,
 | ||
|                 vod_play_from: '别信广告',
 | ||
|                 // vod_play_url: '嗅探播放$https://resource-cdn.tuxiaobei.com/video/10/8f/108fc9d1ac3f69d29a738cdc097c9018.mp4',
 | ||
|                 vod_play_url: '随机小视频$http://api.yujn.cn/api/zzxjj.php',
 | ||
|             };
 | ||
|         } else {
 | ||
|             if (rule.classes) {
 | ||
|                 let _get_url = orId.split('$')[0];
 | ||
|                 let _tab = orId.split('$')[1];
 | ||
|                 if (orId.includes('#search#')) {
 | ||
|                     let vod_name = _tab.replace('#search#', '');
 | ||
|                     let vod_play_from = '来自搜索';
 | ||
|                     vod_play_from += `:${_get_url}`;
 | ||
|                     let vod_play_url = rule.groupDict[_get_url].map(x => x.replace(',', '$')).join('#');
 | ||
|                     log(orId);
 | ||
|                     VOD = {
 | ||
|                         vod_name: '搜索:' + vod_name,
 | ||
|                         type_name: "直播列表",
 | ||
|                         vod_pic: rule.def_pic,
 | ||
|                         // vod_content: orId,
 | ||
|                         vod_content: orId.replace(getHome(orId), 'http://***'),
 | ||
|                         vod_play_from: vod_play_from,
 | ||
|                         vod_play_url: vod_play_url,
 | ||
|                         vod_director: rule.tips,
 | ||
|                         vod_remarks: rule.tips,
 | ||
|                     }
 | ||
|                 } else {
 | ||
|                     let current_vod = rule.classes.find(item => item.type_id === _get_url);
 | ||
|                     let _pic = current_vod ? current_vod.img : '';
 | ||
|                     let html;
 | ||
|                     if (__ext.data_dict[_get_url]) {
 | ||
|                         html = __ext.data_dict[_get_url];
 | ||
|                     } else {
 | ||
|                         html = request(_get_url);
 | ||
|                         if (/#EXTM3U/.test(html)) {
 | ||
|                             html = convertM3uToNormal(html);
 | ||
|                         } else {
 | ||
|                             html = mergeChannels(html);
 | ||
|                         }
 | ||
|                         __ext.data_dict[_get_url] = html;
 | ||
|                     }
 | ||
|                     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 (rule.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;
 | ||
|                     }
 | ||
|                     log(orId);
 | ||
|                     VOD = {
 | ||
|                         vod_id: orId,
 | ||
|                         vod_name: vod_name + '|' + _tab,
 | ||
|                         type_name: "直播列表",
 | ||
|                         vod_pic: _pic || rule.def_pic,
 | ||
|                         // vod_content: orId,
 | ||
|                         vod_content: orId.replace(getHome(orId), 'http://***'),
 | ||
|                         vod_play_from: vod_play_from,
 | ||
|                         vod_play_url: vod_play_url,
 | ||
|                         vod_director: rule.tips,
 | ||
|                         vod_remarks: rule.tips,
 | ||
|                     };
 | ||
| 
 | ||
|                 }
 | ||
|             }
 | ||
|         }
 | ||
|     }),
 | ||
|     搜索: $js.toString(() => {
 | ||
|         VODS = [];
 | ||
|         if (rule.classes && MY_PAGE <= 1) {
 | ||
|             let _get_url = __ext.data[0].url;
 | ||
|             let current_vod = rule.classes.find(item => item.type_id === _get_url);
 | ||
|             let _pic = current_vod ? current_vod.img : '';
 | ||
|             let html;
 | ||
|             if (__ext.data_dict[_get_url]) {
 | ||
|                 html = __ext.data_dict[_get_url];
 | ||
|             } else {
 | ||
|                 html = request(_get_url);
 | ||
|                 if (/#EXTM3U/.test(html)) {
 | ||
|                     html = convertM3uToNormal(html);
 | ||
|                 } else {
 | ||
|                     html = mergeChannels(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));
 | ||
|             log('搜索关键词:' + KEY);
 | ||
|             log('过滤前:' + plays.length);
 | ||
|             // plays = plays.filter(it => it.includes(KEY));
 | ||
|             plays = plays.filter(it => new RegExp(KEY, 'i').test(it));
 | ||
|             log('过滤后:' + plays.length);
 | ||
|             log(plays);
 | ||
|             let new_group = gen_group_dict(plays);
 | ||
|             rule.groupDict = Object.assign(rule.groupDict, new_group);
 | ||
|             // 搜索分组结果存至本地方便二级调用
 | ||
|             setItem('groupDict', JSON.stringify(rule.groupDict));
 | ||
|             // 返回的还是搜索的new_group
 | ||
|             Object.keys(new_group).forEach((it) => {
 | ||
|                 VODS.push({
 | ||
|                     'vod_name': it,
 | ||
|                     'vod_id': it + '$' + KEY + '#search#',
 | ||
|                     'vod_pic': _pic || rule.def_pic,
 | ||
|                 });
 | ||
|             });
 | ||
|         }
 | ||
|     }),
 | ||
|     lazy: $js.toString(() => {
 | ||
|         if (/\.(m3u8|mp4)/.test(input)) {
 | ||
|             if (input.includes('?') && typeof (playObj) == 'object' && playObj.url) {
 | ||
|                 input = playObj.url;
 | ||
|             }
 | ||
|             input = {parse: 0, url: input}
 | ||
|         } else if (/yangshipin|1905\.com/.test(input)) {
 | ||
|             input = {parse: 1, jx: 0, url: input, js: '', header: {'User-Agent': PC_UA}, parse_extra: '&is_pc=1'};
 | ||
|         } else {
 | ||
|             input
 | ||
|         }
 | ||
|     }),
 | ||
| } |