import req from '../../util/req.js'; import { formatPlayUrl, randDeviceWithId, jsonParse, randUUID } from '../../util/misc.js'; import dayjs from 'dayjs'; import NodeRSA from 'node-rsa'; import CryptoJS from 'crypto-js'; let url = 'https://api.tyun77.cn'; let device = {}; let timeOffset = 0; const appVer = '2.2.9'; const rsa = NodeRSA( `-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7QHUVAUM7yghB0/3qz5C bWX5YYD0ss+uDtbDz5VkTclop6YnCY+1U4aw4z134ljkp/jL0mWnYioZHTTqxXMf R5q15FcMZnnn/gMZNj1ZR67/c9ti6WTG0VEr9IdcJgwHwwGak/xQK1Z9htl7TR3Q WA45MmpCSSgjVvX4bbV43IjdjSZNm8s5efdlLl1Z+7uJTR024xizhK5NH0/uPmR4 O8QEtxO9ha3LMmTYTfERzfNmpfDVdV3Rok4eoTzhHmxgqQ0/S0S+FgjHiwrCTFlv NCiDhSemnJT+NIzAnMQX4acL5AYNb5PiDD06ZMrtklTua+USY0gSIrG9LctaYvHR swIDAQAB -----END PUBLIC KEY-----`, 'pkcs8-public-pem', { encryptionScheme: 'pkcs1', } ); async function request(reqUrl, ua) { let sj = dayjs().unix() - timeOffset; let uri = new URL(reqUrl); uri.searchParams.append('pcode', '010110010'); uri.searchParams.append('version', appVer); uri.searchParams.append('devid', device.id); uri.searchParams.append('package', 'com.sevenVideo.app.android'); // com.xiaoxiaoVideo.app.android uri.searchParams.append('sys', 'android'); uri.searchParams.append('sysver', device.release); uri.searchParams.append('brand', device.brand); uri.searchParams.append('state', 'on'); uri.searchParams.append('model', device.model.replaceAll(' ', '_')); uri.searchParams.append('sj', sj); let keys = []; for (const k of uri.searchParams.keys()) { keys.push(k); } keys.sort(); let tkSrc = uri.pathname; for (let k of keys) { let v = uri.searchParams.get(k); v = encodeURIComponent(v); tkSrc += v; } tkSrc += sj; tkSrc += 'XSpeUFjJ'; console.log(tkSrc); let tk = CryptoJS.enc.Hex.stringify(CryptoJS.MD5(tkSrc)).toString().toLowerCase(); console.log(tk); let header = { 'User-Agent': ua || 'okhttp/3.12.0', T: sj, TK: tk, }; if (reqUrl.indexOf('getVideoPlayAuth') > 0) { header['TK-VToken'] = rsa.encrypt(`{"videoId":"${uri.searchParams.get('videoId')}","timestamp":"${sj}"}`, 'base64'); } else if (reqUrl.indexOf('parserUrl') > 0) { header['TK-VToken'] = rsa.encrypt(`{"url":"${uri.searchParams.get('url')}","timestamp":"${sj}"}`, 'base64'); } let resp = await req.get(uri.toString(), { headers: header, }); let serverTime = resp.headers.date; // dart all response header key is lowercase let serverTimeS = dayjs(serverTime).unix(); timeOffset = dayjs().unix() - serverTimeS; return resp.data; } async function init(inReq, _outResp) { console.log(inReq.server.config.kunyu77.testcfg); const deviceKey = inReq.server.prefix + '/device'; device = await inReq.server.db.getObjectDefault(deviceKey, {}); if (!device.id) { device = randDeviceWithId(32); device.id = device.id.toLowerCase(); device.ua = 'Dalvik/2.1.0 (Linux; U; Android ' + device.release + '; ' + device.model + ' Build/' + device.buildId + ')'; await inReq.server.db.push(deviceKey, device); } await request(url + '/api.php/provide/getDomain'); await request(url + '/api.php/provide/config'); await request(url + '/api.php/provide/checkUpgrade'); await request(url + '/api.php/provide/channel'); return {}; } async function home(_inReq, _outResp) { let data = (await request(url + '/api.php/provide/filter')).data; let classes = []; let filterObj = {}; let filterAll = []; for (const key in data) { classes.push({ type_id: key, type_name: data[key][0].cat, }); try { let typeId = key.toString(); if (filterAll.length === 0) { let filterData = (await request(url + '/api.php/provide/searchFilter?type_id=0&pagenum=1&pagesize=24')).data.conditions; // 年份 let year = { key: 'year', name: '年份', init: '', }; let yearValues = []; yearValues.push({ n: '全部', v: '' }); // yearValues.push({ n: '2022', v: '2022' }); // yearValues.push({ n: '2021', v: '2021' }); filterData.y.forEach((e) => { yearValues.push({ n: e.name, v: e.value }); }); year['value'] = yearValues; // 地区 let area = { key: 'area', name: '地区', init: '', }; let areaValues = []; areaValues.push({ n: '全部', v: '' }); filterData.a.forEach((e) => { areaValues.push({ n: e.name, v: e.value }); }); area['value'] = areaValues; // 类型 let type = { key: 'category', name: '类型', init: '', }; let typeValues = []; typeValues.push({ n: '全部', v: '' }); filterData.scat.forEach((e) => { typeValues.push({ n: e.name, v: e.value }); }); type['value'] = typeValues; filterAll.push(year, area, type); } if (filterAll.length !== 0) { filterObj[typeId] = filterAll; } } catch (e) { console.log(e); } } return { class: classes, filters: filterObj, }; } async function category(inReq, _outResp) { const tid = inReq.body.id; const pg = inReq.body.page; const extend = inReq.body.filters; let reqUrl = url + '/api.php/provide/searchFilter?type_id=' + tid + '&pagenum=' + pg + '&pagesize=24&'; reqUrl += `year=${extend.year || ''}&category=${extend.category || ''}&area=${extend.area || ''}`; let data = (await request(reqUrl)).data; let videos = []; for (const vod of data.result) { videos.push({ vod_id: vod.id, vod_name: vod.title, vod_pic: vod.videoCover, vod_remarks: vod.msg, }); } return { page: parseInt(data.page), pagecount: data.pagesize, limit: 24, 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) { let data = (await request(url + '/api.php/provide/videoDetail?ids=' + id)).data; console.log(data); let vod = { vod_id: data.id, vod_name: data.videoName, vod_pic: data.videoCover, type_name: data.subCategory, vod_year: data.year, vod_area: data.area, vod_remarks: data.msg, vod_actor: data.actor, vod_director: data.director, vod_content: data.brief.trim(), }; let episodes = (await request(url + '/api.php/provide/videoPlaylist?ids=' + id)).data.episodes; let playlist = {}; for (const episode of episodes) { let playurls = episode.playurls; for (const playurl of playurls) { let from = playurl.playfrom; let t = formatPlayUrl(vod.vod_name, playurl.title); if (t.length == 0) t = playurl.title.trim(); if (!playlist.hasOwnProperty(from)) { playlist[from] = []; } playlist[from].push(t + '$' + playurl.playurl); } } vod.vod_play_from = Object.keys(playlist).join('$$$'); let urls = Object.values(playlist); let vod_play_url = []; for (const urlist of urls) { vod_play_url.push(urlist.join('#')); } vod.vod_play_url = vod_play_url.join('$$$'); videos.push(vod); } return { list: videos, }; } async function play(inReq, _outResp) { const flag = inReq.body.flag; const id = inReq.body.id; if (flag == 'alivc') { const ua = `Dalvik/2.1.0(sevenVideo android)${device.release} ${appVer} ${device.brand}`; let data = (await request(url + '/api.php/provide/getVideoPlayAuth?videoId=' + id)).data; var s = CryptoJS.enc.Utf8.stringify(CryptoJS.enc.Base64.parse(data.playAuth)); s = JSON.parse(s); const e = { AccessKeyId: s.AccessKeyId, Action: 'GetPlayInfo', AuthInfo: s.AuthInfo, AuthTimeout: 3600, Channel: 'Android', Format: 'JSON', Formats: '', PlayerVersion: '', Rand: randUUID(), SecurityToken: s.SecurityToken, SignatureMethod: 'HMAC-SHA1', SignatureNonce: randUUID(), SignatureVersion: '1.0', Version: '2017-03-21', VideoId: id, }; let keys = Object.keys(e); keys.sort(); let param = keys.map((k) => k + '=' + encodeURIComponent(e[k])).join('&'); let signSrc = 'GET&%2F&' + encodeURIComponent(param); let sign = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA1(signSrc, s.AccessKeySecret + '&')); param += '&Signature=' + encodeURIComponent(sign); const aliurl = 'https://vod.cn-shanghai.aliyuncs.com/?' + param; const res = await req.get(aliurl, { header: { 'User-Agent': ua, }, }); if (res.status == 200) { const p = res.data; return { parse: 0, url: p.PlayInfoList.PlayInfo[0].PlayURL, header: { 'User-Agent': ua, }, }; } else { return { parse: 0, url: id, }; } } let data = (await request(url + '/api.php/provide/parserUrl?url=' + id + '&retryNum=0')).data; let playHeader = data.playHeader; let jxUrl = data.url; if (jxUrl.indexOf(url) >= 0) { let result = jsonParse(id, await request(jxUrl)); result['parse'] = 0; if (playHeader) { result.header = Object.assign(result.header, playHeader); } return result; } else { let res = await req.get(jxUrl, { headers: { 'user-agent': 'okhttp/3.12.0', }, }); let result = jsonParse(id, res.data); result['parse'] = 0; if (playHeader) { result.header = Object.assign(result.header, playHeader); } return result; } } async function search(inReq, _outResp) { const pg = inReq.body.page; const wd = inReq.body.wd; let page = pg || 1; if (page == 0) page = 1; let data = await request(url + '/api.php/provide/searchVideo?searchName=' + wd + '&pg=' + page, 'okhttp/3.12.0'); let videos = []; for (const vod of data.data) { videos.push({ vod_id: vod.id, vod_name: vod.videoName, vod_pic: vod.videoCover, vod_remarks: vod.msg, }); } return { page: page, pagecount: data.pages, list: videos, }; } async function test(inReq, outResp) { try { const prefix = inReq.server.prefix; const dataResult = {}; let resp = await inReq.server.inject().post(`${prefix}/init`); dataResult.init = resp.json(); resp = await inReq.server.inject().post(`${prefix}/home`); dataResult.home = 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(); 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(); if (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(); return dataResult; } catch (err) { console.error(err); outResp.code(500); return { err: err.message, tip: 'check debug console output' }; } } export default { meta: { key: 'kunyu77', 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('/test', test); }, };