采集之王.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. globalThis.getRandomItem = function (items) {
  2. return items[Math.random() * items.length | 0];
  3. }
  4. var rule = {
  5. title: '采集之王[合]',
  6. author: '道长',
  7. version: '20240705 beta16',
  8. update_info: ``.trim(),
  9. host: '',
  10. homeTid: '',
  11. homeUrl: '/api.php/provide/vod/?ac=detail&t={{rule.homeTid}}',
  12. detailUrl: '/api.php/provide/vod/?ac=detail&ids=fyid',
  13. searchUrl: '/api.php/provide/vod/?wd=**&pg=#TruePage##page=fypage',
  14. classUrl: '/api.php/provide/vod/',
  15. url: '/api.php/provide/vod/?ac=detail&pg=fypage&t=fyfilter',
  16. filter_url: '{{fl.类型}}',
  17. headers: {
  18. 'User-Agent': 'MOBILE_UA'
  19. },
  20. timeout: 5000,
  21. limit: 20,
  22. search_limit: 5,
  23. searchable: 1,
  24. quickSearch: 0,
  25. filterable: 1,
  26. play_parse: true,
  27. parse_url: '',
  28. search_match: false,
  29. search_pic: true,
  30. 预处理: $js.toString(() => {
  31. function getClasses(item) {
  32. let classes = [];
  33. if (item.class_name && item.class_url) {
  34. if (!/&|电影|电视剧|综艺|动漫[\u4E00-\u9FA5]+/.test(item.class_name)) {
  35. try {
  36. item.class_name = ungzip(item.class_name)
  37. } catch (e) {
  38. log(`不识别的class_name导致gzip解码失败:${e}`)
  39. return classes
  40. }
  41. }
  42. let names = item.class_name.split('&');
  43. let urls = item.class_url.split('&');
  44. let cnt = Math.min(names.length, urls.length);
  45. for (let i = 0; i < cnt; i++) {
  46. classes.push({
  47. 'type_id': urls[i],
  48. 'type_name': names[i]
  49. });
  50. }
  51. }
  52. return classes
  53. }
  54. if (typeof(batchFetch) === 'function') {
  55. rule.search_limit = 16;
  56. log('当前程序支持批量请求[batchFetch],搜索限制已设置为16');
  57. }
  58. let _url = rule.params;
  59. log(`传入参数:${_url}`);
  60. if (_url && typeof(_url) === 'string' && /^(http|file)/.test(_url)) {
  61. if (_url.includes('$')) {
  62. let _url_params = _url.split('$');
  63. _url = _url_params[0];
  64. rule.search_match = !!(_url_params[1]);
  65. if (_url_params.length > 2) {
  66. rule.search_pic = !!(_url_params[2]);
  67. }
  68. }
  69. let html = request(_url);
  70. let json = JSON.parse(html);
  71. let _classes = [];
  72. rule.filter = {};
  73. rule.filter_def = {};
  74. json.forEach(it => {
  75. let _obj = {
  76. type_name: it.name,
  77. type_id: it.url,
  78. parse_url: it.parse_url || '',
  79. searchable: it.searchable !== 0,
  80. api: it.api || '',
  81. cate_exclude: it.cate_exclude || '',
  82. };
  83. _classes.push(_obj);
  84. try {
  85. let json1 = [];
  86. if (it.class_name && it.class_url) {
  87. json1 = getClasses(it);
  88. } else {
  89. json1 = JSON.parse(request(urljoin(_obj.type_id, _obj.api || rule.classUrl))).class;
  90. }
  91. if (_obj.cate_exclude) {
  92. json1 = json1.filter(cl => !new RegExp(_obj.cate_exclude, 'i').test(cl.type_name));
  93. }
  94. rule.filter[_obj.type_id] = [{
  95. "key": "类型",
  96. "name": "类型",
  97. "value": json1.map(i => {
  98. return {
  99. "n": i.type_name,
  100. 'v': i.type_id
  101. }
  102. })
  103. }
  104. ];
  105. if (json1.length > 0) {
  106. rule.filter_def[it.url] = {
  107. "类型": json1[0].type_id
  108. };
  109. }
  110. } catch (e) {
  111. rule.filter[it.url] = [{
  112. "key": "类型",
  113. "name": "类型",
  114. "value": [{
  115. "n": "全部",
  116. "v": ""
  117. }
  118. ]
  119. }
  120. ];
  121. }
  122. });
  123. rule.classes = _classes;
  124. }
  125. }),
  126. class_parse: $js.toString(() => {
  127. input = rule.classes;
  128. }),
  129. 推荐: $js.toString(() => {
  130. VODS = [];
  131. if (rule.classes) {
  132. let randomClass = getRandomItem(rule.classes);
  133. let _url = urljoin(randomClass.type_id, input);
  134. if (randomClass.api) {
  135. _url = _url.replace('/api.php/provide/vod/', randomClass.api)
  136. }
  137. try {
  138. let html = request(_url, {
  139. timeout: rule.timeout
  140. });
  141. let json = JSON.parse(html);
  142. VODS = json.list;
  143. VODS.forEach(it => {
  144. it.vod_id = randomClass.type_id + '$' + it.vod_id;
  145. it.vod_remarks = it.vod_remarks + '|' + randomClass.type_name;
  146. });
  147. } catch (e) {}
  148. }
  149. }),
  150. 一级: $js.toString(() => {
  151. VODS = [];
  152. if (rule.classes) {
  153. let _url = urljoin(MY_CATE, input);
  154. let current_vod = rule.classes.find(item => item.type_id === MY_CATE);
  155. if (current_vod && current_vod.api) {
  156. _url = _url.replace('/api.php/provide/vod/', current_vod.api)
  157. }
  158. let html = request(_url);
  159. let json = JSON.parse(html);
  160. VODS = json.list;
  161. VODS.forEach(it => {
  162. it.vod_id = MY_CATE + '$' + it.vod_id
  163. });
  164. }
  165. }),
  166. 二级: $js.toString(() => {
  167. VOD = {};
  168. if (orId === 'update_info') {
  169. VOD = {
  170. vod_content: rule.update_info.trim(),
  171. vod_name: '更新日志',
  172. type_name: '更新日志',
  173. vod_pic: 'https://resource-cdn.tuxiaobei.com/video/FtWhs2mewX_7nEuE51_k6zvg6awl.png',
  174. vod_remarks: `版本:${rule.version}`,
  175. vod_play_from: '道长在线',
  176. vod_play_url: '随机小视频$http://api.yujn.cn/api/zzxjj.php',
  177. };
  178. } else {
  179. if (rule.classes) {
  180. let _url = urljoin(fyclass, input);
  181. let current_vod = rule.classes.find(item => item.type_id === fyclass);
  182. if (current_vod && current_vod.api) {
  183. _url = _url.replace('/api.php/provide/vod/', current_vod.api)
  184. }
  185. let html = request(_url);
  186. let json = JSON.parse(html);
  187. let data = json.list;
  188. VOD = data[0];
  189. if (current_vod && current_vod.type_name) {
  190. VOD.vod_play_from = VOD.vod_play_from.split('$$$').map(it => current_vod.type_name + '|' + it).join('$$$')
  191. }
  192. }
  193. }
  194. }),
  195. 搜索: $js.toString(() => {
  196. VODS = [];
  197. if (rule.classes) {
  198. let canSearch = rule.classes.filter(it => it.searchable);
  199. let page = Number(MY_PAGE);
  200. page = (MY_PAGE - 1) % Math.ceil(canSearch.length / rule.search_limit) + 1;
  201. let truePage = Math.ceil(MY_PAGE / Math.ceil(canSearch.length / rule.search_limit));
  202. if (rule.search_limit) {
  203. let start = (page - 1) * rule.search_limit;
  204. let end = page * rule.search_limit;
  205. let t1 = new Date().getTime();
  206. let searchMode = typeof(batchFetch) === 'function' ? '批量' : '单个';
  207. log('start:' + start);
  208. log('end:' + end);
  209. log('搜索模式:' + searchMode);
  210. log('精准搜索:' + rule.search_match);
  211. if (start < canSearch.length) {
  212. let search_classes = canSearch.slice(start, end);
  213. let urls = [];
  214. search_classes.forEach(it => {
  215. let _url = urljoin(it.type_id, input);
  216. if (it.api) {
  217. _url = _url.replace('/api.php/provide/vod/', it.api)
  218. }
  219. _url = _url.replace("#TruePage#", "" + truePage);
  220. urls.push(_url);
  221. });
  222. let results_list = [];
  223. let results = [];
  224. if (typeof(batchFetch) === 'function') {
  225. let reqUrls = urls.map(it => {
  226. return {
  227. url: it,
  228. options: {
  229. timeout: rule.timeout
  230. }
  231. }
  232. });
  233. let rets = batchFetch(reqUrls);
  234. let detailUrls = [];
  235. let detailUrlCount = 0;
  236. rets.forEach((ret, idx) => {
  237. let it = search_classes[idx];
  238. if (ret) {
  239. try {
  240. let json = JSON.parse(ret);
  241. let data = json.list;
  242. data.forEach(i => {
  243. i.site_name = it.type_name;
  244. i.vod_id = it.type_id + '$' + i.vod_id;
  245. i.vod_remarks = i.vod_remarks + '|' + it.type_name;
  246. });
  247. if (rule.search_match) {
  248. data = data.filter(item => item.vod_name && (new RegExp(KEY, 'i')).test(item.vod_name))
  249. }
  250. if (data.length > 0) {
  251. if (rule.search_pic && !data[0].vod_pic) {
  252. log(`当前搜索站点【${it.type_name}】没图片,尝试访问二级去获取图片`);
  253. let detailUrl = urls[idx].split('wd=')[0] + 'ac=detail&ids=' + data.map(k => k.vod_id.split('$')[1]).join(',');
  254. detailUrls.push(detailUrl);
  255. results_list.push({
  256. data: data,
  257. has_pic: false,
  258. detailUrlCount: detailUrlCount
  259. });
  260. detailUrlCount++;
  261. } else {
  262. results_list.push({
  263. data: data,
  264. has_pic: true
  265. });
  266. }
  267. }
  268. } catch (e) {
  269. log(`请求:${it.type_id}发生错误:${e.message}`)
  270. }
  271. }
  272. });
  273. let reqUrls2 = detailUrls.map(it => {
  274. return {
  275. url: it,
  276. options: {
  277. timeout: rule.timeout
  278. }
  279. }
  280. });
  281. let rets2 = batchFetch(reqUrls2);
  282. for (let k = 0; k < results_list.length; k++) {
  283. let result_data = results_list[k].data;
  284. if (!results_list[k].has_pic) {
  285. try {
  286. let detailJson = JSON.parse(rets2[results_list[k].detailUrlCount]);
  287. log('二级数据列表元素数:' + detailJson.list.length);
  288. result_data.forEach((d, _seq) => {
  289. let detailVodPic = detailJson.list.find(vod => vod.vod_id.toString() === d.vod_id.split('$')[1]);
  290. if (detailVodPic) {
  291. Object.assign(d, {
  292. vod_pic: detailVodPic.vod_pic
  293. });
  294. }
  295. });
  296. } catch (e) {
  297. log(`强制获取网站${result_data[0].site_name}的搜索图片失败:${e.message}`);
  298. }
  299. }
  300. results = results.concat(result_data);
  301. }
  302. } else {
  303. urls.forEach((_url, idx) => {
  304. let it = search_classes[idx];
  305. try {
  306. let html = request(_url);
  307. let json = JSON.parse(html);
  308. let data = json.list;
  309. data.forEach(i => {
  310. i.vod_id = it.type_id + '$' + i.vod_id;
  311. i.vod_remarks = i.vod_remarks + '|' + it.type_name;
  312. });
  313. if (rule.search_match) {
  314. data = data.filter(item => item.vod_name && (new RegExp(KEY, 'i')).test(item.vod_name))
  315. }
  316. if (data.length > 0) {
  317. if (rule.search_pic && !data[0].vod_pic) {
  318. log(`当前搜索站点【${it.type_name}】没图片,尝试访问二级去获取图片`);
  319. let detailUrl = urls[idx].split('wd=')[0] + 'ac=detail&ids=' + data.map(k => k.vod_id.split('$')[1]).join(',');
  320. try {
  321. let detailJson = JSON.parse(request(detailUrl));
  322. log('二级数据列表元素数:' + detailJson.list.length);
  323. data.forEach((d, _seq) => {
  324. let detailVodPic = detailJson.list.find(vod => vod.vod_id.toString() === d.vod_id.split('$')[1]);
  325. if (detailVodPic) {
  326. Object.assign(d, {
  327. vod_pic: detailVodPic.vod_pic
  328. });
  329. }
  330. });
  331. } catch (e) {
  332. log(`强制获取网站${it.type_id}的搜索图片失败:${e.message}`);
  333. }
  334. }
  335. results = results.concat(data);
  336. }
  337. results = results.concat(data);
  338. } catch (e) {
  339. log(`请求:${it.type_id}发生错误:${e.message}`)
  340. }
  341. });
  342. }
  343. VODS = results;
  344. let t2 = new Date().getTime();
  345. log(`${searchMode}搜索:${urls.length}个站耗时:${(Number(t2) - Number(t1))}ms`)
  346. }
  347. }
  348. }
  349. }),
  350. lazy: $js.toString(() => {
  351. let parse_url = '';
  352. if (flag && flag.includes('|')) {
  353. let type_name = flag.split('|')[0];
  354. let current_vod = rule.classes.find(item => item.type_name === type_name);
  355. if (current_vod && current_vod.parse_url) {
  356. parse_url = current_vod.parse_url
  357. }
  358. }
  359. if (/\.(m3u8|mp4)/.test(input)) {
  360. input = {
  361. parse: 0,
  362. url: input
  363. }
  364. } else {
  365. if (parse_url.startsWith('json:')) {
  366. let purl = parse_url.replace('json:', '') + input;
  367. let html = request(purl);
  368. input = {
  369. parse: 0,
  370. url: JSON.parse(html).url
  371. }
  372. } else {
  373. input = parse_url + input;
  374. }
  375. }
  376. }),
  377. }