采集之王.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. globalThis.getRandomItem = function (items) {
  2. return items[Math.random() * items.length | 0];
  3. }
  4. var rule = {
  5. title: '采集之王[合]',
  6. author: '道长',
  7. version: '20240706 beta17',
  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: 10,
  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. cate_excludes: it.cate_excludes || [],
  83. };
  84. _classes.push(_obj);
  85. try {
  86. let json1 = [];
  87. if (it.class_name && it.class_url) {
  88. json1 = getClasses(it);
  89. } else {
  90. json1 = JSON.parse(request(urljoin(_obj.type_id, _obj.api || rule.classUrl))).class;
  91. }
  92. if (_obj.cate_excludes && Array.isArray(_obj.cate_excludes) && _obj.cate_excludes.length > 0) {
  93. json1 = json1.filter(cl => !_obj.cate_excludes.includes(cl.type_name));
  94. } else if (_obj.cate_exclude) {
  95. json1 = json1.filter(cl => !new RegExp(_obj.cate_exclude, 'i').test(cl.type_name));
  96. }
  97. rule.filter[_obj.type_id] = [{
  98. "key": "类型",
  99. "name": "类型",
  100. "value": json1.map(i => {
  101. return {
  102. "n": i.type_name,
  103. 'v': i.type_id
  104. }
  105. })
  106. }
  107. ];
  108. if (json1.length > 0) {
  109. rule.filter_def[it.url] = {
  110. "类型": json1[0].type_id
  111. };
  112. }
  113. } catch (e) {
  114. rule.filter[it.url] = [{
  115. "key": "类型",
  116. "name": "类型",
  117. "value": [{
  118. "n": "全部",
  119. "v": ""
  120. }
  121. ]
  122. }
  123. ];
  124. }
  125. });
  126. rule.classes = _classes;
  127. }
  128. }),
  129. class_parse: $js.toString(() => {
  130. input = rule.classes;
  131. }),
  132. 推荐: $js.toString(() => {
  133. VODS = [];
  134. if (rule.classes) {
  135. let randomClass = getRandomItem(rule.classes);
  136. let _url = urljoin(randomClass.type_id, input);
  137. if (randomClass.api) {
  138. _url = _url.replace('/api.php/provide/vod/', randomClass.api)
  139. }
  140. try {
  141. let html = request(_url, {
  142. timeout: rule.timeout
  143. });
  144. let json = JSON.parse(html);
  145. VODS = json.list;
  146. VODS.forEach(it => {
  147. it.vod_id = randomClass.type_id + '$' + it.vod_id;
  148. it.vod_remarks = it.vod_remarks + '|' + randomClass.type_name;
  149. });
  150. } catch (e) {}
  151. }
  152. }),
  153. 一级: $js.toString(() => {
  154. VODS = [];
  155. if (rule.classes) {
  156. let _url = urljoin(MY_CATE, input);
  157. let current_vod = rule.classes.find(item => item.type_id === MY_CATE);
  158. if (current_vod && current_vod.api) {
  159. _url = _url.replace('/api.php/provide/vod/', current_vod.api)
  160. }
  161. let html = request(_url);
  162. let json = JSON.parse(html);
  163. VODS = json.list;
  164. VODS.forEach(it => {
  165. it.vod_id = MY_CATE + '$' + it.vod_id
  166. });
  167. }
  168. }),
  169. 二级: $js.toString(() => {
  170. VOD = {};
  171. if (orId === 'update_info') {
  172. VOD = {
  173. vod_content: rule.update_info.trim(),
  174. vod_name: '更新日志',
  175. type_name: '更新日志',
  176. vod_pic: 'https://resource-cdn.tuxiaobei.com/video/FtWhs2mewX_7nEuE51_k6zvg6awl.png',
  177. vod_remarks: `版本:${rule.version}`,
  178. vod_play_from: '道长在线',
  179. vod_play_url: '随机小视频$http://api.yujn.cn/api/zzxjj.php',
  180. };
  181. } else {
  182. if (rule.classes) {
  183. let _url = urljoin(fyclass, input);
  184. let current_vod = rule.classes.find(item => item.type_id === fyclass);
  185. if (current_vod && current_vod.api) {
  186. _url = _url.replace('/api.php/provide/vod/', current_vod.api)
  187. }
  188. let html = request(_url);
  189. let json = JSON.parse(html);
  190. let data = json.list;
  191. VOD = data[0];
  192. if (current_vod && current_vod.type_name) {
  193. VOD.vod_play_from = VOD.vod_play_from.split('$$$').map(it => current_vod.type_name + '|' + it).join('$$$')
  194. }
  195. }
  196. }
  197. }),
  198. 搜索: $js.toString(() => {
  199. VODS = [];
  200. if (rule.classes) {
  201. let canSearch = rule.classes.filter(it => it.searchable);
  202. let page = Number(MY_PAGE);
  203. page = (MY_PAGE - 1) % Math.ceil(canSearch.length / rule.search_limit) + 1;
  204. let truePage = Math.ceil(MY_PAGE / Math.ceil(canSearch.length / rule.search_limit));
  205. if (rule.search_limit) {
  206. let start = (page - 1) * rule.search_limit;
  207. let end = page * rule.search_limit;
  208. let t1 = new Date().getTime();
  209. let searchMode = typeof(batchFetch) === 'function' ? '批量' : '单个';
  210. log('start:' + start);
  211. log('end:' + end);
  212. log('搜索模式:' + searchMode);
  213. log('精准搜索:' + rule.search_match);
  214. log('强制获取图片:' + rule.search_pic);
  215. if (start < canSearch.length) {
  216. let search_classes = canSearch.slice(start, end);
  217. let urls = [];
  218. search_classes.forEach(it => {
  219. let _url = urljoin(it.type_id, input);
  220. if (it.api) {
  221. _url = _url.replace('/api.php/provide/vod/', it.api)
  222. }
  223. _url = _url.replace("#TruePage#", "" + truePage);
  224. urls.push(_url);
  225. });
  226. let results_list = [];
  227. let results = [];
  228. if (typeof(batchFetch) === 'function') {
  229. let reqUrls = urls.map(it => {
  230. return {
  231. url: it,
  232. options: {
  233. timeout: rule.timeout
  234. }
  235. }
  236. });
  237. let rets = batchFetch(reqUrls);
  238. let detailUrls = [];
  239. let detailUrlCount = 0;
  240. rets.forEach((ret, idx) => {
  241. let it = search_classes[idx];
  242. if (ret) {
  243. try {
  244. let json = JSON.parse(ret);
  245. let data = json.list;
  246. data.forEach(i => {
  247. i.site_name = it.type_name;
  248. i.vod_id = it.type_id + '$' + i.vod_id;
  249. i.vod_remarks = i.vod_remarks + '|' + it.type_name;
  250. });
  251. if (rule.search_match) {
  252. data = data.filter(item => item.vod_name && (new RegExp(KEY, 'i')).test(item.vod_name))
  253. }
  254. if (data.length > 0) {
  255. if (rule.search_pic && !data[0].vod_pic) {
  256. log(`当前搜索站点【${it.type_name}】没图片,尝试访问二级去获取图片`);
  257. let detailUrl = urls[idx].split('wd=')[0] + 'ac=detail&ids=' + data.map(k => k.vod_id.split('$')[1]).join(',');
  258. detailUrls.push(detailUrl);
  259. results_list.push({
  260. data: data,
  261. has_pic: false,
  262. detailUrlCount: detailUrlCount
  263. });
  264. detailUrlCount++;
  265. } else {
  266. results_list.push({
  267. data: data,
  268. has_pic: true
  269. });
  270. }
  271. }
  272. } catch (e) {
  273. log(`请求:${it.type_id}发生错误:${e.message}`)
  274. }
  275. }
  276. });
  277. let reqUrls2 = detailUrls.map(it => {
  278. return {
  279. url: it,
  280. options: {
  281. timeout: rule.timeout
  282. }
  283. }
  284. });
  285. let rets2 = reqUrls2.length > 0 ? batchFetch(reqUrls2) : [];
  286. for (let k = 0; k < results_list.length; k++) {
  287. let result_data = results_list[k].data;
  288. if (!results_list[k].has_pic) {
  289. try {
  290. let detailJson = JSON.parse(rets2[results_list[k].detailUrlCount]);
  291. log('二级数据列表元素数:' + detailJson.list.length);
  292. result_data.forEach((d, _seq) => {
  293. let detailVodPic = detailJson.list.find(vod => vod.vod_id.toString() === d.vod_id.split('$')[1]);
  294. if (detailVodPic) {
  295. Object.assign(d, {
  296. vod_pic: detailVodPic.vod_pic
  297. });
  298. }
  299. });
  300. } catch (e) {
  301. log(`强制获取网站${result_data[0].site_name}的搜索图片失败:${e.message}`);
  302. }
  303. }
  304. results = results.concat(result_data);
  305. }
  306. } else {
  307. urls.forEach((_url, idx) => {
  308. let it = search_classes[idx];
  309. try {
  310. let html = request(_url);
  311. let json = JSON.parse(html);
  312. let data = json.list;
  313. data.forEach(i => {
  314. i.vod_id = it.type_id + '$' + i.vod_id;
  315. i.vod_remarks = i.vod_remarks + '|' + it.type_name;
  316. });
  317. if (rule.search_match) {
  318. data = data.filter(item => item.vod_name && (new RegExp(KEY, 'i')).test(item.vod_name))
  319. }
  320. if (data.length > 0) {
  321. if (rule.search_pic && !data[0].vod_pic) {
  322. log(`当前搜索站点【${it.type_name}】没图片,尝试访问二级去获取图片`);
  323. let detailUrl = urls[idx].split('wd=')[0] + 'ac=detail&ids=' + data.map(k => k.vod_id.split('$')[1]).join(',');
  324. try {
  325. let detailJson = JSON.parse(request(detailUrl));
  326. log('二级数据列表元素数:' + detailJson.list.length);
  327. data.forEach((d, _seq) => {
  328. let detailVodPic = detailJson.list.find(vod => vod.vod_id.toString() === d.vod_id.split('$')[1]);
  329. if (detailVodPic) {
  330. Object.assign(d, {
  331. vod_pic: detailVodPic.vod_pic
  332. });
  333. }
  334. });
  335. } catch (e) {
  336. log(`强制获取网站${it.type_id}的搜索图片失败:${e.message}`);
  337. }
  338. }
  339. results = results.concat(data);
  340. }
  341. results = results.concat(data);
  342. } catch (e) {
  343. log(`请求:${it.type_id}发生错误:${e.message}`)
  344. }
  345. });
  346. }
  347. VODS = results;
  348. let t2 = new Date().getTime();
  349. log(`${searchMode}搜索:${urls.length}个站耗时:${(Number(t2) - Number(t1))}ms`)
  350. }
  351. }
  352. }
  353. }),
  354. lazy: $js.toString(() => {
  355. let parse_url = '';
  356. if (flag && flag.includes('|')) {
  357. let type_name = flag.split('|')[0];
  358. let current_vod = rule.classes.find(item => item.type_name === type_name);
  359. if (current_vod && current_vod.parse_url) {
  360. parse_url = current_vod.parse_url
  361. }
  362. }
  363. if (/\.(m3u8|mp4)/.test(input)) {
  364. input = {
  365. parse: 0,
  366. url: input
  367. }
  368. } else {
  369. if (parse_url.startsWith('json:')) {
  370. let purl = parse_url.replace('json:', '') + input;
  371. let html = request(purl);
  372. input = {
  373. parse: 0,
  374. url: JSON.parse(html).url
  375. }
  376. } else {
  377. input = parse_url + input;
  378. }
  379. }
  380. }),
  381. }