import * as HLS from 'hls-parser'; import req from '../../util/req.js'; let url = ''; let categories = []; async function request(reqUrl) { let res = await req(reqUrl, { method: 'get', }); return res.data; } async function init(inReq, _outResp) { url = inReq.server.config.ffm3u8.url; categories = inReq.server.config.ffm3u8.categories; return {}; } async function home(_inReq, _outResp) { const data = await request(url); let classes = []; for (const cls of data.class) { const n = cls.type_name.toString().trim(); if (categories && categories.length > 0) { if (categories.indexOf(n) < 0) continue; } classes.push({ type_id: cls.type_id.toString(), type_name: n, }); } if (categories && categories.length > 0) { classes = classes.sort((a, b) => { return categories.indexOf(a.type_name) - categories.indexOf(b.type_name); }); } return { class: classes, }; } async function category(inReq, _outResp) { const tid = inReq.body.id; const pg = inReq.body.page; let page = pg || 1; if (page == 0) page = 1; const data = await request(url + `?ac=detail&t=${tid}&pg=${page}`); let videos = []; for (const vod of data.list) { videos.push({ vod_id: vod.vod_id.toString(), vod_name: vod.vod_name.toString(), vod_pic: vod.vod_pic, vod_remarks: vod.vod_remarks, }); } return { page: parseInt(data.page), pagecount: data.pagecount, total: data.total, list: videos, }; } async function detail(inReq, _outResp) { const ids = !Array.isArray(inReq.body.id) ? [inReq.body.id] : inReq.body.id; const videos = []; for (const id of ids) { const data = (await request(url + `?ac=detail&ids=${id}`)).list[0]; let vod = { vod_id: data.vod_id, vod_name: data.vod_name, vod_pic: data.vod_pic, type_name: data.type_name, vod_year: data.vod_year, vod_area: data.vod_area, vod_remarks: data.vod_remarks, vod_actor: data.vod_actor, vod_director: data.vod_director, vod_content: data.vod_content.trim(), vod_play_from: data.vod_play_from, vod_play_url: data.vod_play_url, }; videos.push(vod); } return { list: videos, }; } async function proxy(inReq, outResp) { const what = inReq.params.what; const purl = decodeURIComponent(inReq.params.ids); if (what == 'hls') { const resp = await req(purl, { method: 'get', }); const plist = HLS.parse(resp.data); if (plist.variants) { for (const v of plist.variants) { if (!v.uri.startsWith('http')) { v.uri = new URL(v.uri, purl).toString(); } } plist.variants.map((variant) => { variant.uri = inReq.server.prefix + '/proxy/hls/' + encodeURIComponent(variant.uri) + '/.m3u8'; }); } if (plist.segments) { for (const s of plist.segments) { if (!s.uri.startsWith('http')) { s.uri = new URL(s.uri, purl).toString(); } if (s.key && s.key.uri && !s.key.uri.startsWith('http')) { s.key.uri = new URL(s.key.uri, purl).toString(); } } plist.segments.map((seg) => { seg.uri = inReq.server.prefix + '/proxy/ts/' + encodeURIComponent(seg.uri) + '/.ts'; }); } const hls = HLS.stringify(plist); let hlsHeaders = {}; if (resp.headers['content-length']) { Object.assign(hlsHeaders, resp.headers, { 'content-length': hls.length.toString() }); } else { Object.assign(hlsHeaders, resp.headers); } delete hlsHeaders['transfer-encoding']; delete hlsHeaders['cache-control']; if (hlsHeaders['content-encoding'] == 'gzip') { delete hlsHeaders['content-encoding']; } outResp.code(resp.status).headers(hlsHeaders); return hls; } else { outResp.redirect(purl); return; } } async function play(inReq, _outResp) { const id = inReq.body.id; if (id.indexOf('.m3u8') < 0) { const sniffer = await inReq.server.messageToDart({ action: 'sniff', opt: { url: id, timeout: 10000, rule: 'http((?!http).){12,}?\\.m3u8(?!\\?)', }, }); if (sniffer && sniffer.url) { const hds = {}; if (sniffer.headers) { if (sniffer.headers['user-agent']) { hds['User-Agent'] = sniffer.headers['user-agent']; } if (sniffer.headers['referer']) { hds['Referer'] = sniffer.headers['referer']; } } return { parse: 0, url: sniffer.url, header: hds, }; } } return { parse: 0, url: inReq.server.address().dynamic + inReq.server.prefix + '/proxy/hls/' + encodeURIComponent(id) + '/.m3u8', }; } async function search(inReq, _outResp) { const wd = inReq.body.wd; const data = await request(url + `?ac=detail&wd=${wd}`); let videos = []; for (const vod of data.list) { videos.push({ vod_id: vod.vod_id.toString(), vod_name: vod.vod_name.toString(), vod_pic: vod.vod_pic, vod_remarks: vod.vod_remarks, }); } return { page: parseInt(data.page), pagecount: data.pagecount, total: data.total, list: videos, }; } async function test(inReq, outResp) { try { const printErr = function (json) { if (json.statusCode && json.statusCode == 500) { console.error(json); } }; const prefix = inReq.server.prefix; const dataResult = {}; let resp = await inReq.server.inject().post(`${prefix}/init`); dataResult.init = resp.json(); printErr(resp.json()); resp = await inReq.server.inject().post(`${prefix}/home`); dataResult.home = resp.json(); printErr(resp.json()); if (dataResult.home.class.length > 0) { resp = await inReq.server.inject().post(`${prefix}/category`).payload({ id: dataResult.home.class[0].type_id, page: 1, filter: true, filters: {}, }); dataResult.category = resp.json(); printErr(resp.json()); if (dataResult.category.list.length > 0) { resp = await inReq.server.inject().post(`${prefix}/detail`).payload({ id: dataResult.category.list[0].vod_id, // dataResult.category.list.map((v) => v.vod_id), }); dataResult.detail = resp.json(); printErr(resp.json()); if (dataResult.detail.list && dataResult.detail.list.length > 0) { dataResult.play = []; for (const vod of dataResult.detail.list) { const flags = vod.vod_play_from.split('$$$'); const ids = vod.vod_play_url.split('$$$'); for (let j = 0; j < flags.length; j++) { const flag = flags[j]; const urls = ids[j].split('#'); for (let i = 0; i < urls.length && i < 2; i++) { resp = await inReq.server .inject() .post(`${prefix}/play`) .payload({ flag: flag, id: urls[i].split('$')[1], }); dataResult.play.push(resp.json()); } } } } } } resp = await inReq.server.inject().post(`${prefix}/search`).payload({ wd: '爱', page: 1, }); dataResult.search = resp.json(); printErr(resp.json()); return dataResult; } catch (err) { console.error(err); outResp.code(500); return { err: err.message, tip: 'check debug console output' }; } } export default { meta: { key: 'ffm3u8', name: '非凡采集', type: 3, }, api: async (fastify) => { fastify.post('/init', init); fastify.post('/home', home); fastify.post('/category', category); fastify.post('/detail', detail); fastify.post('/play', play); fastify.post('/search', search); fastify.get('/proxy/:what/:ids/:end', proxy); fastify.get('/test', test); }, };