酷云77.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. import req from '../../util/req.js';
  2. import { formatPlayUrl, randDeviceWithId, jsonParse, randUUID } from '../../util/misc.js';
  3. import dayjs from 'dayjs';
  4. import NodeRSA from 'node-rsa';
  5. import CryptoJS from 'crypto-js';
  6. let url = 'https://api.tyun77.cn';
  7. let device = {};
  8. let timeOffset = 0;
  9. const appVer = '2.2.9';
  10. const rsa = NodeRSA(
  11. `-----BEGIN PUBLIC KEY-----
  12. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7QHUVAUM7yghB0/3qz5C
  13. bWX5YYD0ss+uDtbDz5VkTclop6YnCY+1U4aw4z134ljkp/jL0mWnYioZHTTqxXMf
  14. R5q15FcMZnnn/gMZNj1ZR67/c9ti6WTG0VEr9IdcJgwHwwGak/xQK1Z9htl7TR3Q
  15. WA45MmpCSSgjVvX4bbV43IjdjSZNm8s5efdlLl1Z+7uJTR024xizhK5NH0/uPmR4
  16. O8QEtxO9ha3LMmTYTfERzfNmpfDVdV3Rok4eoTzhHmxgqQ0/S0S+FgjHiwrCTFlv
  17. NCiDhSemnJT+NIzAnMQX4acL5AYNb5PiDD06ZMrtklTua+USY0gSIrG9LctaYvHR
  18. swIDAQAB
  19. -----END PUBLIC KEY-----`,
  20. 'pkcs8-public-pem',
  21. {
  22. encryptionScheme: 'pkcs1',
  23. }
  24. );
  25. async function request(reqUrl, ua) {
  26. let sj = dayjs().unix() - timeOffset;
  27. let uri = new URL(reqUrl);
  28. uri.searchParams.append('pcode', '010110010');
  29. uri.searchParams.append('version', appVer);
  30. uri.searchParams.append('devid', device.id);
  31. uri.searchParams.append('package', 'com.sevenVideo.app.android'); // com.xiaoxiaoVideo.app.android
  32. uri.searchParams.append('sys', 'android');
  33. uri.searchParams.append('sysver', device.release);
  34. uri.searchParams.append('brand', device.brand);
  35. uri.searchParams.append('state', 'on');
  36. uri.searchParams.append('model', device.model.replaceAll(' ', '_'));
  37. uri.searchParams.append('sj', sj);
  38. let keys = [];
  39. for (const k of uri.searchParams.keys()) {
  40. keys.push(k);
  41. }
  42. keys.sort();
  43. let tkSrc = uri.pathname;
  44. for (let k of keys) {
  45. let v = uri.searchParams.get(k);
  46. v = encodeURIComponent(v);
  47. tkSrc += v;
  48. }
  49. tkSrc += sj;
  50. tkSrc += 'XSpeUFjJ';
  51. console.log(tkSrc);
  52. let tk = CryptoJS.enc.Hex.stringify(CryptoJS.MD5(tkSrc)).toString().toLowerCase();
  53. console.log(tk);
  54. let header = {
  55. 'User-Agent': ua || 'okhttp/3.12.0',
  56. T: sj,
  57. TK: tk,
  58. };
  59. if (reqUrl.indexOf('getVideoPlayAuth') > 0) {
  60. header['TK-VToken'] = rsa.encrypt(`{"videoId":"${uri.searchParams.get('videoId')}","timestamp":"${sj}"}`, 'base64');
  61. } else if (reqUrl.indexOf('parserUrl') > 0) {
  62. header['TK-VToken'] = rsa.encrypt(`{"url":"${uri.searchParams.get('url')}","timestamp":"${sj}"}`, 'base64');
  63. }
  64. let resp = await req.get(uri.toString(), {
  65. headers: header,
  66. });
  67. let serverTime = resp.headers.date; // dart all response header key is lowercase
  68. let serverTimeS = dayjs(serverTime).unix();
  69. timeOffset = dayjs().unix() - serverTimeS;
  70. return resp.data;
  71. }
  72. async function init(inReq, _outResp) {
  73. console.log(inReq.server.config.kunyu77.testcfg);
  74. const deviceKey = inReq.server.prefix + '/device';
  75. device = await inReq.server.db.getObjectDefault(deviceKey, {});
  76. if (!device.id) {
  77. device = randDeviceWithId(32);
  78. device.id = device.id.toLowerCase();
  79. device.ua = 'Dalvik/2.1.0 (Linux; U; Android ' + device.release + '; ' + device.model + ' Build/' + device.buildId + ')';
  80. await inReq.server.db.push(deviceKey, device);
  81. }
  82. await request(url + '/api.php/provide/getDomain');
  83. await request(url + '/api.php/provide/config');
  84. await request(url + '/api.php/provide/checkUpgrade');
  85. await request(url + '/api.php/provide/channel');
  86. return {};
  87. }
  88. async function home(_inReq, _outResp) {
  89. let data = (await request(url + '/api.php/provide/filter')).data;
  90. let classes = [];
  91. let filterObj = {};
  92. let filterAll = [];
  93. for (const key in data) {
  94. classes.push({
  95. type_id: key,
  96. type_name: data[key][0].cat,
  97. });
  98. try {
  99. let typeId = key.toString();
  100. if (filterAll.length === 0) {
  101. let filterData = (await request(url + '/api.php/provide/searchFilter?type_id=0&pagenum=1&pagesize=24')).data.conditions;
  102. // 年份
  103. let year = {
  104. key: 'year',
  105. name: '年份',
  106. init: '',
  107. };
  108. let yearValues = [];
  109. yearValues.push({ n: '全部', v: '' });
  110. // yearValues.push({ n: '2022', v: '2022' });
  111. // yearValues.push({ n: '2021', v: '2021' });
  112. filterData.y.forEach((e) => {
  113. yearValues.push({ n: e.name, v: e.value });
  114. });
  115. year['value'] = yearValues;
  116. // 地区
  117. let area = {
  118. key: 'area',
  119. name: '地区',
  120. init: '',
  121. };
  122. let areaValues = [];
  123. areaValues.push({ n: '全部', v: '' });
  124. filterData.a.forEach((e) => {
  125. areaValues.push({ n: e.name, v: e.value });
  126. });
  127. area['value'] = areaValues;
  128. // 类型
  129. let type = {
  130. key: 'category',
  131. name: '类型',
  132. init: '',
  133. };
  134. let typeValues = [];
  135. typeValues.push({ n: '全部', v: '' });
  136. filterData.scat.forEach((e) => {
  137. typeValues.push({ n: e.name, v: e.value });
  138. });
  139. type['value'] = typeValues;
  140. filterAll.push(year, area, type);
  141. }
  142. if (filterAll.length !== 0) {
  143. filterObj[typeId] = filterAll;
  144. }
  145. } catch (e) {
  146. console.log(e);
  147. }
  148. }
  149. return {
  150. class: classes,
  151. filters: filterObj,
  152. };
  153. }
  154. async function category(inReq, _outResp) {
  155. const tid = inReq.body.id;
  156. const pg = inReq.body.page;
  157. const extend = inReq.body.filters;
  158. let reqUrl = url + '/api.php/provide/searchFilter?type_id=' + tid + '&pagenum=' + pg + '&pagesize=24&';
  159. reqUrl += `year=${extend.year || ''}&category=${extend.category || ''}&area=${extend.area || ''}`;
  160. let data = (await request(reqUrl)).data;
  161. let videos = [];
  162. for (const vod of data.result) {
  163. videos.push({
  164. vod_id: vod.id,
  165. vod_name: vod.title,
  166. vod_pic: vod.videoCover,
  167. vod_remarks: vod.msg,
  168. });
  169. }
  170. return {
  171. page: parseInt(data.page),
  172. pagecount: data.pagesize,
  173. limit: 24,
  174. total: data.total,
  175. list: videos,
  176. };
  177. }
  178. async function detail(inReq, _outResp) {
  179. const ids = !Array.isArray(inReq.body.id) ? [inReq.body.id] : inReq.body.id;
  180. const videos = [];
  181. for (const id of ids) {
  182. let data = (await request(url + '/api.php/provide/videoDetail?ids=' + id)).data;
  183. console.log(data);
  184. let vod = {
  185. vod_id: data.id,
  186. vod_name: data.videoName,
  187. vod_pic: data.videoCover,
  188. type_name: data.subCategory,
  189. vod_year: data.year,
  190. vod_area: data.area,
  191. vod_remarks: data.msg,
  192. vod_actor: data.actor,
  193. vod_director: data.director,
  194. vod_content: data.brief.trim(),
  195. };
  196. let episodes = (await request(url + '/api.php/provide/videoPlaylist?ids=' + id)).data.episodes;
  197. let playlist = {};
  198. for (const episode of episodes) {
  199. let playurls = episode.playurls;
  200. for (const playurl of playurls) {
  201. let from = playurl.playfrom;
  202. let t = formatPlayUrl(vod.vod_name, playurl.title);
  203. if (t.length == 0) t = playurl.title.trim();
  204. if (!playlist.hasOwnProperty(from)) {
  205. playlist[from] = [];
  206. }
  207. playlist[from].push(t + '$' + playurl.playurl);
  208. }
  209. }
  210. vod.vod_play_from = Object.keys(playlist).join('$$$');
  211. let urls = Object.values(playlist);
  212. let vod_play_url = [];
  213. for (const urlist of urls) {
  214. vod_play_url.push(urlist.join('#'));
  215. }
  216. vod.vod_play_url = vod_play_url.join('$$$');
  217. videos.push(vod);
  218. }
  219. return {
  220. list: videos,
  221. };
  222. }
  223. async function play(inReq, _outResp) {
  224. const flag = inReq.body.flag;
  225. const id = inReq.body.id;
  226. if (flag == 'alivc') {
  227. const ua = `Dalvik/2.1.0(sevenVideo android)${device.release} ${appVer} ${device.brand}`;
  228. let data = (await request(url + '/api.php/provide/getVideoPlayAuth?videoId=' + id)).data;
  229. var s = CryptoJS.enc.Utf8.stringify(CryptoJS.enc.Base64.parse(data.playAuth));
  230. s = JSON.parse(s);
  231. const e = {
  232. AccessKeyId: s.AccessKeyId,
  233. Action: 'GetPlayInfo',
  234. AuthInfo: s.AuthInfo,
  235. AuthTimeout: 3600,
  236. Channel: 'Android',
  237. Format: 'JSON',
  238. Formats: '',
  239. PlayerVersion: '',
  240. Rand: randUUID(),
  241. SecurityToken: s.SecurityToken,
  242. SignatureMethod: 'HMAC-SHA1',
  243. SignatureNonce: randUUID(),
  244. SignatureVersion: '1.0',
  245. Version: '2017-03-21',
  246. VideoId: id,
  247. };
  248. let keys = Object.keys(e);
  249. keys.sort();
  250. let param = keys.map((k) => k + '=' + encodeURIComponent(e[k])).join('&');
  251. let signSrc = 'GET&%2F&' + encodeURIComponent(param);
  252. let sign = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA1(signSrc, s.AccessKeySecret + '&'));
  253. param += '&Signature=' + encodeURIComponent(sign);
  254. const aliurl = 'https://vod.cn-shanghai.aliyuncs.com/?' + param;
  255. const res = await req.get(aliurl, {
  256. header: {
  257. 'User-Agent': ua,
  258. },
  259. });
  260. if (res.status == 200) {
  261. const p = res.data;
  262. return {
  263. parse: 0,
  264. url: p.PlayInfoList.PlayInfo[0].PlayURL,
  265. header: {
  266. 'User-Agent': ua,
  267. },
  268. };
  269. } else {
  270. return {
  271. parse: 0,
  272. url: id,
  273. };
  274. }
  275. }
  276. let data = (await request(url + '/api.php/provide/parserUrl?url=' + id + '&retryNum=0')).data;
  277. let playHeader = data.playHeader;
  278. let jxUrl = data.url;
  279. if (jxUrl.indexOf(url) >= 0) {
  280. let result = jsonParse(id, await request(jxUrl));
  281. result['parse'] = 0;
  282. if (playHeader) {
  283. result.header = Object.assign(result.header, playHeader);
  284. }
  285. return result;
  286. } else {
  287. let res = await req.get(jxUrl, {
  288. headers: {
  289. 'user-agent': 'okhttp/3.12.0',
  290. },
  291. });
  292. let result = jsonParse(id, res.data);
  293. result['parse'] = 0;
  294. if (playHeader) {
  295. result.header = Object.assign(result.header, playHeader);
  296. }
  297. return result;
  298. }
  299. }
  300. async function search(inReq, _outResp) {
  301. const pg = inReq.body.page;
  302. const wd = inReq.body.wd;
  303. let page = pg || 1;
  304. if (page == 0) page = 1;
  305. let data = await request(url + '/api.php/provide/searchVideo?searchName=' + wd + '&pg=' + page, 'okhttp/3.12.0');
  306. let videos = [];
  307. for (const vod of data.data) {
  308. videos.push({
  309. vod_id: vod.id,
  310. vod_name: vod.videoName,
  311. vod_pic: vod.videoCover,
  312. vod_remarks: vod.msg,
  313. });
  314. }
  315. return {
  316. page: page,
  317. pagecount: data.pages,
  318. list: videos,
  319. };
  320. }
  321. async function test(inReq, outResp) {
  322. try {
  323. const prefix = inReq.server.prefix;
  324. const dataResult = {};
  325. let resp = await inReq.server.inject().post(`${prefix}/init`);
  326. dataResult.init = resp.json();
  327. resp = await inReq.server.inject().post(`${prefix}/home`);
  328. dataResult.home = resp.json();
  329. if (dataResult.home.class.length > 0) {
  330. resp = await inReq.server.inject().post(`${prefix}/category`).payload({
  331. id: dataResult.home.class[0].type_id,
  332. page: 1,
  333. filter: true,
  334. filters: {},
  335. });
  336. dataResult.category = resp.json();
  337. if (dataResult.category.list.length > 0) {
  338. resp = await inReq.server.inject().post(`${prefix}/detail`).payload({
  339. id: dataResult.category.list[0].vod_id, // dataResult.category.list.map((v) => v.vod_id),
  340. });
  341. dataResult.detail = resp.json();
  342. if (dataResult.detail.list.length > 0) {
  343. dataResult.play = [];
  344. for (const vod of dataResult.detail.list) {
  345. const flags = vod.vod_play_from.split('$$$');
  346. const ids = vod.vod_play_url.split('$$$');
  347. for (let j = 0; j < flags.length; j++) {
  348. const flag = flags[j];
  349. const urls = ids[j].split('#');
  350. for (let i = 0; i < urls.length && i < 2; i++) {
  351. resp = await inReq.server
  352. .inject()
  353. .post(`${prefix}/play`)
  354. .payload({
  355. flag: flag,
  356. id: urls[i].split('$')[1],
  357. });
  358. dataResult.play.push(resp.json());
  359. }
  360. }
  361. }
  362. }
  363. }
  364. }
  365. resp = await inReq.server.inject().post(`${prefix}/search`).payload({
  366. wd: '爱',
  367. page: 1,
  368. });
  369. dataResult.search = resp.json();
  370. return dataResult;
  371. } catch (err) {
  372. console.error(err);
  373. outResp.code(500);
  374. return { err: err.message, tip: 'check debug console output' };
  375. }
  376. }
  377. export default {
  378. meta: {
  379. key: 'kunyu77',
  380. name: '琨娱七七',
  381. type: 3,
  382. },
  383. api: async (fastify) => {
  384. fastify.post('/init', init);
  385. fastify.post('/home', home);
  386. fastify.post('/category', category);
  387. fastify.post('/detail', detail);
  388. fastify.post('/play', play);
  389. fastify.post('/search', search);
  390. fastify.get('/test', test);
  391. },
  392. };