kuaikan.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. /*
  2. * @File : kuaikan.js
  3. * @Author : jade
  4. * @Date : 2024/3/19 11:12
  5. * @Email : jadehh@1ive.com
  6. * @Software : Samples
  7. * @Desc :
  8. */
  9. import {jinja2, _, dayjs, Crypto} from "../lib/cat.js";
  10. import {Spider} from "./spider.js";
  11. import {VodDetail, VodShort} from "../lib/vod.js";
  12. import * as Utils from "../lib/utils.js";
  13. const charStr = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789';
  14. function randStr(len, withNum) {
  15. let _str = '';
  16. let containsNum = withNum === undefined ? true : withNum;
  17. for (let i = 0; i < len; i++) {
  18. let idx = _.random(0, containsNum ? charStr.length - 1 : charStr.length - 11);
  19. _str += charStr[idx];
  20. }
  21. return _str;
  22. }
  23. function randDevice() {
  24. return {
  25. brand: 'Huawei',
  26. model: 'HUAWEI Mate 20',
  27. release: '10',
  28. buildId: randStr(3, false).toUpperCase() + _.random(11, 99) + randStr(1, false).toUpperCase(),
  29. };
  30. }
  31. function formatPlayUrl(src, name) {
  32. return name
  33. .trim()
  34. .replaceAll(src, '')
  35. .replace(/<|>|《|》/g, '')
  36. .replace(/\$|#/g, ' ')
  37. .trim();
  38. }
  39. function jsonParse(input, json) {
  40. try {
  41. let url = json.url ?? '';
  42. if (url.startsWith('//')) {
  43. url = 'https:' + url;
  44. }
  45. if (!url.startsWith('http')) {
  46. return {};
  47. }
  48. let headers = json['headers'] || {};
  49. let ua = (json['user-agent'] || '').trim();
  50. if (ua.length > 0) {
  51. headers['User-Agent'] = ua;
  52. }
  53. let referer = (json['referer'] || '').trim();
  54. if (referer.length > 0) {
  55. headers['Referer'] = referer;
  56. }
  57. _.keys(headers).forEach((hk) => {
  58. if (!headers[hk]) delete headers[hk];
  59. });
  60. return {
  61. header: headers, url: url,
  62. };
  63. } catch (error) {
  64. }
  65. return {};
  66. }
  67. class KuaiKanSpider extends Spider {
  68. constructor() {
  69. super();
  70. this.siteUrl = 'https://api1.baibaipei.com:8899';
  71. this.device = {}
  72. this.parse = []
  73. }
  74. getName() {
  75. return "🛥︎┃快看视频┃🛥︎"
  76. }
  77. getAppName() {
  78. return "快看视频"
  79. }
  80. getJSName() {
  81. return "kuaikan"
  82. }
  83. getType() {
  84. return 3
  85. }
  86. async init(cfg) {
  87. await super.init(cfg);
  88. this.danmuStaus = true
  89. await this.setDevice();
  90. }
  91. async request(reqUrl, postData, agentSp, get) {
  92. let ts = dayjs().valueOf().toString();
  93. let rand = randStr(32);
  94. let sign = Crypto.enc.Hex.stringify(Crypto.MD5('H58d2%gLbeingX*%D4Y8!C!!@G_' + ts + '_' + rand))
  95. .toString()
  96. .toLowerCase();
  97. let headers = {
  98. 'user-agent': agentSp || this.device.ua,
  99. };
  100. if (reqUrl.includes('baibaipei')) {
  101. headers['device-id'] = this.device.id;
  102. headers['sign'] = sign;
  103. headers['time'] = ts;
  104. headers['md5'] = rand;
  105. headers['version'] = '2.1.5';
  106. headers['system-model'] = this.device.model;
  107. headers['system-brand'] = this.device.brand;
  108. headers['system-version'] = this.device.release;
  109. headers["host"] = "api1.baibaipei.com:8899"
  110. }
  111. if (!get) {
  112. headers['Content-Type'] = 'application/x-www-form-urlencoded';
  113. }
  114. let res = await req(reqUrl, {
  115. method: get ? 'get' : 'post', headers: headers, data: postData || {}, postType: 'form'
  116. });
  117. await this.jadeLog.debug(`URL:${reqUrl},headers:${JSON.stringify(headers)},data:${[JSON.stringify(postData)]}`)
  118. let content = res.content;
  119. try {
  120. let key = Crypto.enc.Utf8.parse('IjhHsCB2B5^#%0Ag');
  121. let iv = Crypto.enc.Utf8.parse('y8_m.3rauW/>j,}.');
  122. let src = Crypto.enc.Base64.parse(content);
  123. let dst = Crypto.AES.decrypt({ciphertext: src}, key, {iv: iv, padding: Crypto.pad.Pkcs7});
  124. dst = Crypto.enc.Utf8.stringify(dst);
  125. await this.jadeLog.debug(`response:${dst}`)
  126. return JSON.parse(dst);
  127. } catch (e) {
  128. return JSON.parse(content)
  129. }
  130. }
  131. async setDevice() {
  132. let deviceKey = 'device';
  133. let deviceInfo = await local.get(this.siteKey, deviceKey);
  134. if (deviceInfo.length > 0) {
  135. try {
  136. this.device = JSON.parse(deviceInfo);
  137. } catch (error) {
  138. }
  139. }
  140. if (_.isEmpty(this.device)) {
  141. this.device = randDevice();
  142. this.device.id = randStr(13).toLowerCase();
  143. this.device.ua = 'okhttp/3.14.9';
  144. await local.set(this.siteKey, deviceKey, JSON.stringify(this.device));
  145. }
  146. }
  147. async setClasses() {
  148. await this.setDevice()
  149. let response = await this.request(this.siteUrl + '/api.php/Index/getTopVideoCategory');
  150. for (const type of response.data) {
  151. let typeName = type["nav_name"];
  152. if (typeName === '推荐') continue;
  153. let typeId = type["nav_type_id"].toString();
  154. this.classes.push({
  155. type_id: typeId, type_name: typeName,
  156. });
  157. }
  158. }
  159. async getFilter(filterData) {
  160. await this.jadeLog.debug(JSON.stringify(filterData))
  161. let filterAll = []
  162. for (let key of Object.keys(filterData)) {
  163. let itemValues = filterData[key];
  164. if (key === 'plot') key = 'class';
  165. let typeExtendName = '';
  166. switch (key) {
  167. case 'class':
  168. typeExtendName = '类型';
  169. break;
  170. case 'area':
  171. typeExtendName = '地区';
  172. break;
  173. case 'lang':
  174. typeExtendName = '语言';
  175. break;
  176. case 'year':
  177. typeExtendName = '年代';
  178. break;
  179. case 'sort':
  180. typeExtendName = '排序';
  181. break;
  182. }
  183. if (typeExtendName.length === 0) continue;
  184. let newTypeExtend = {
  185. key: key, name: typeExtendName,
  186. };
  187. let newTypeExtendKV = [];
  188. for (let j = 0; j < itemValues.length; j++) {
  189. const name = itemValues[j];
  190. let value = key === 'sort' ? j + '' : name === '全部' ? '0' : name;
  191. newTypeExtendKV.push({n: name, v: value});
  192. }
  193. newTypeExtend['init'] = key === 'sort' ? '1' : newTypeExtendKV[0]['v'];
  194. newTypeExtend.value = newTypeExtendKV;
  195. filterAll.push(newTypeExtend);
  196. }
  197. return filterAll
  198. }
  199. async setFilterObj() {
  200. for (const typeDic of this.classes) {
  201. let typeId = typeDic["type_id"]
  202. if (typeId !== "最近更新") {
  203. let filterData = await this.request(this.siteUrl + '/api.php/Video/getFilterType', {type: typeId})
  204. this.filterObj[typeId] = await this.getFilter(filterData["data"])
  205. }
  206. }
  207. }
  208. async parseVodShortListFromJSONByHome(obj) {
  209. let vod_list = []
  210. for (const data of obj["video"]) {
  211. let video_vod_list = await this.parseVodShortListFromJson(data["list"])
  212. vod_list.push(...video_vod_list)
  213. }
  214. return vod_list
  215. }
  216. async parseVodShortListFromJson(obj) {
  217. let vod_list = []
  218. for (const data of obj) {
  219. let vodShort = new VodShort()
  220. vodShort.vod_id = data["vod_id"]
  221. vodShort.vod_name = data["vod_name"]
  222. vodShort.vod_pic = data["vod_pic"]
  223. vodShort.vod_remarks = data["vod_remarks"]
  224. vod_list.push(vodShort)
  225. }
  226. return vod_list
  227. }
  228. async parseVodDetailfromJson(obj) {
  229. let vodDetail = new VodDetail()
  230. vodDetail.load_dic(JSON.stringify(obj))
  231. vodDetail.vod_content = obj["vod_content"].trim()
  232. vodDetail.type_name = obj["vod_class"]
  233. let playlist = {};
  234. for (const item of obj["vod_play"]) {
  235. let from = item["playerForm"];
  236. if (from === 'jp' && this.catOpenStatus) continue;
  237. if (from === 'xg' && this.catOpenStatus) continue;
  238. let urls = [];
  239. for (const u of item.url) {
  240. urls.push(formatPlayUrl(vodDetail.vod_name, u.title) + '$' + u.play_url);
  241. }
  242. if (!playlist.hasOwnProperty(from) && urls.length > 0) {
  243. playlist[from] = urls;
  244. }
  245. }
  246. this.parse = obj.parse || [];
  247. vodDetail.vod_play_from = _.keys(playlist).join('$$$');
  248. let urls = _.values(playlist);
  249. let vod_play_url = [];
  250. for (const urlist of urls) {
  251. vod_play_url.push(urlist.join('#'));
  252. }
  253. vodDetail.vod_play_url = vod_play_url.join('$$$');
  254. return vodDetail
  255. }
  256. async setHomeVod() {
  257. let data = await this.request(this.siteUrl + "/api.php/Index/getHomePage", {"p": "1", "type": "1"})
  258. this.homeVodList = await this.parseVodShortListFromJSONByHome(data.data)
  259. }
  260. async setCategory(tid, pg, filter, extend) {
  261. if (pg === 0) pg = 1;
  262. let reqUrl = this.siteUrl + '/api.php/Video/getFilterVideoList';
  263. let formData = JSON.parse(jinja2(`{
  264. "type": "{{tid}}",
  265. "p": "{{pg}}",
  266. "area": "{{ext.area|default(0)}}",
  267. "year": "{{ext.year|default(0)}}",
  268. "sort": "{{ext.sort|default(0)}}",
  269. "class": "{{ext.class|default(0)}}"}`, {ext: extend, tid: tid, pg: pg}));
  270. console.log(formData);
  271. let data = await this.request(reqUrl, formData);
  272. this.vodList = await this.parseVodShortListFromJson(data["data"]["data"])
  273. }
  274. async setDetail(id) {
  275. let data = await this.request(this.siteUrl + '/api.php/Video/getVideoInfo', {video_id: id})
  276. this.vodDetail = await this.parseVodDetailfromJson(data["data"]["video"])
  277. }
  278. async setPlay(flag, id, flags) {
  279. this.result.jx = 0
  280. try {
  281. if (id.indexOf('youku') >= 0 || id.indexOf('iqiyi') >= 0 || id.indexOf('v.qq.com') >= 0 || id.indexOf('pptv') >= 0 || id.indexOf('le.com') >= 0 || id.indexOf('1905.com') >= 0 || id.indexOf('mgtv') >= 0)
  282. {
  283. if (this.parse.length > 0) {
  284. for (let index = 0; index < this.parse.length; index++) {
  285. try {
  286. const p = this.parse[index];
  287. let res = await req(p + id, {
  288. headers: {'user-agent': 'okhttp/4.1.0'},
  289. });
  290. await this.jadeLog.debug(`解析连接结果为:${JSON.stringify(res)}`)
  291. let result = jsonParse(id, JSON.parse(res.content)["data"]);
  292. if (result.url){
  293. this.playUrl = result.url // 这里可以直接返回弹幕,无法进行快进操作
  294. this.danmuUrl = await this.danmuSpider.getVideoUrl(id,0)
  295. this.result.jx = 1
  296. }
  297. } catch (error) {
  298. }
  299. }
  300. }
  301. } else if (id.indexOf('jqq-') >= 0) {
  302. let jqqHeaders = await this.request(this.siteUrl + '/jqqheader.json', null, null, true);
  303. let ids = id.split('-');
  304. let jxJqq = await req('https://api.juquanquanapp.com/app/drama/detail?dramaId=' + ids[1] + '&episodeSid=' + ids[2] + '&quality=LD', {headers: jqqHeaders});
  305. let jqqInfo = JSON.parse(jxJqq.content);
  306. if (jqqInfo.data["playInfo"]["url"]) {
  307. this.playUrl = jqqInfo.data["playInfo"]["url"]
  308. }
  309. } else if (id.startsWith("ftp")) {
  310. this.playUrl = id
  311. } else {
  312. let res = await this.request(this.siteUrl + '/video.php', {url: id});
  313. let result = jsonParse(id, res.data);
  314. if (result.url) {
  315. if (result.url.indexOf("filename=1.mp4") > -1) {
  316. this.playUrl = result.url
  317. } else {
  318. this.playUrl = await js2Proxy(true, this.siteType, this.siteKey, 'lzm3u8/' + Utils.base64Encode(result.url), {});
  319. }
  320. }
  321. }
  322. } catch (e) {
  323. await this.jadeLog.error(e)
  324. }
  325. }
  326. async setSearch(wd, quick) {
  327. let data = await this.request(this.siteUrl + '/api.php/Search/getSearch', {key: wd, type_id: 0, p: 1})
  328. this.vodList = await this.parseVodShortListFromJson(data["data"]["data"])
  329. }
  330. async proxy(segments, headers) {
  331. let what = segments[0];
  332. let url = Utils.base64Decode(segments[1]);
  333. if (what === 'lzm3u8') {
  334. await this.jadeLog.debug(`使用代理播放,播放连接为:${url}`)
  335. const resp = await req(url, {});
  336. let hls = resp.content;
  337. const jsBase = await js2Proxy(false, this.siteType, this.siteKey, 'lzm3u8/', {});
  338. const baseUrl = url.substr(0, url.lastIndexOf('/') + 1);
  339. await this.jadeLog.debug(hls.length)
  340. hls = hls.replace(/#EXT-X-DISCONTINUITY\r*\n*#EXTINF:6.433333,[\s\S]*?#EXT-X-DISCONTINUITY/, '');
  341. await this.jadeLog.debug(hls.length)
  342. hls = hls.replace(/(#EXT-X-KEY\S+URI=")(\S+)("\S+)/g, function (match, p1, p2, p3) {
  343. let up = (!p2.startsWith('http') ? baseUrl : '') + p2;
  344. return p1 + up + p3;
  345. });
  346. hls = hls.replace(/(#EXT-X-STREAM-INF:.*\n)(.*)/g, function (match, p1, p2) {
  347. let up = (!p2.startsWith('http') ? baseUrl : '') + p2;
  348. return p1 + jsBase + Utils.base64Decode(up);
  349. });
  350. hls = hls.replace(/(#EXTINF:.*\n)(.*)/g, function (match, p1, p2) {
  351. let up = (!p2.startsWith('http') ? baseUrl : '') + p2;
  352. return p1 + up;
  353. });
  354. return JSON.stringify({
  355. code: resp.code, content: hls, headers: resp.headers,
  356. });
  357. }
  358. return JSON.stringify({
  359. code: 500, content: '',
  360. });
  361. }
  362. }
  363. let spider = new KuaiKanSpider()
  364. async function init(cfg) {
  365. await spider.init(cfg)
  366. }
  367. async function home(filter) {
  368. return await spider.home(filter)
  369. }
  370. async function homeVod() {
  371. return await spider.homeVod()
  372. }
  373. async function category(tid, pg, filter, extend) {
  374. return await spider.category(tid, pg, filter, extend)
  375. }
  376. async function detail(id) {
  377. return await spider.detail(id)
  378. }
  379. async function play(flag, id, flags) {
  380. return await spider.play(flag, id, flags)
  381. }
  382. async function search(wd, quick) {
  383. return await spider.search(wd, quick)
  384. }
  385. async function proxy(segments, headers) {
  386. return await spider.proxy(segments, headers)
  387. }
  388. export function __jsEvalReturn() {
  389. return {
  390. init: init,
  391. home: home,
  392. homeVod: homeVod,
  393. category: category,
  394. detail: detail,
  395. play: play,
  396. search: search,
  397. proxy: proxy
  398. };
  399. }
  400. export {spider}