spider.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901
  1. /*
  2. * @File : spider.js
  3. * @Author : jade
  4. * @Date : 2023/12/25 17:19
  5. * @Email : jadehh@1ive.com
  6. * @Software : Samples
  7. * @Desc :
  8. */
  9. import {JadeLogging} from "../lib/log.js";
  10. import * as Utils from "../lib/utils.js";
  11. import {VodDetail, VodShort} from "../lib/vod.js";
  12. import {_, load, Uri} from "../lib/cat.js";
  13. import * as HLS from "../lib/hls.js";
  14. import {hlsCache, tsCache} from "../lib/ffm3u8_open.js";
  15. import {DanmuSpider} from "../lib/danmuSpider.js";
  16. import { initCloud } from "../lib/cloud.js";
  17. class Result {
  18. constructor() {
  19. this.class = []
  20. this.list = []
  21. this.filters = []
  22. this.header = {"User-Agent": Utils.CHROME};
  23. this.format = "";
  24. this.danmaku = "";
  25. this.url = "";
  26. this.subs = [];
  27. this.parse = 0
  28. this.jx = 0;
  29. this.page = 0
  30. this.pagecount = 0
  31. this.limit = 0;
  32. this.total = 0;
  33. this.extra = {}
  34. }
  35. get() {
  36. return new Result()
  37. }
  38. home(classes, list, filters) {
  39. return JSON.stringify({
  40. "class": classes, "list": list, "filters": filters
  41. })
  42. }
  43. homeVod(vod_list) {
  44. return JSON.stringify({"page": this.page, "list": vod_list, "pagecount": this.page, "total": this.page})
  45. }
  46. category(vod_list, page, count, limit, total) {
  47. return JSON.stringify({
  48. page: parseInt(page), pagecount: count, limit: limit, total: total, list: vod_list,
  49. });
  50. }
  51. search(vod_list) {
  52. return JSON.stringify({"list": vod_list,"page":this.page,"pagecount":this.pagecount,"total":this.total})
  53. }
  54. detail(vod_detail) {
  55. return JSON.stringify({"list": [vod_detail]})
  56. }
  57. play(url) {
  58. if (!_.isEmpty(this.danmaku)) {
  59. return JSON.stringify({
  60. "url": url,
  61. "parse": this.parse,
  62. "header": this.header,
  63. "format": this.format,
  64. "subs": this.subs,
  65. "danmaku": this.danmaku,
  66. "extra": this.extra,
  67. "jx": this.jx
  68. })
  69. } else {
  70. return JSON.stringify({
  71. "url": url,
  72. "parse": this.parse,
  73. "header": this.header,
  74. "format": this.format,
  75. "subs": this.subs,
  76. "extra": this.extra,
  77. "jx": this.jx
  78. })
  79. }
  80. }
  81. playTxt(url) {
  82. return url
  83. }
  84. errorCategory(error_message) {
  85. let vodShort = new VodShort()
  86. vodShort.vod_name = "错误:打开无效"
  87. vodShort.vod_id = "error"
  88. vodShort.vod_pic = Utils.RESOURCEURL + "/resources/error.png"
  89. vodShort.vod_remarks = error_message
  90. return JSON.stringify({
  91. page: parseInt(0), pagecount: 0, limit: 0, total: 0, list: [vodShort],
  92. })
  93. }
  94. setClass(classes) {
  95. this.class = classes;
  96. return this;
  97. }
  98. setVod(list) {
  99. if (typeof list === "object" && Array.isArray(list)) {
  100. this.list = list;
  101. } else if (list !== undefined) {
  102. this.list = [list]
  103. }
  104. return this;
  105. }
  106. setFilters(filters) {
  107. this.filters = filters;
  108. return this;
  109. }
  110. setHeader(header) {
  111. this.header = header;
  112. return this;
  113. }
  114. setParse(parse) {
  115. this.parse = parse;
  116. return this;
  117. }
  118. setJx() {
  119. this.jx = 1;
  120. return this;
  121. }
  122. setUrl(url) {
  123. this.url = url;
  124. return this;
  125. }
  126. danmu(danmaku) {
  127. this.danmaku = danmaku;
  128. return this;
  129. }
  130. setFormat(format) {
  131. this.format = format;
  132. return this;
  133. }
  134. setSubs(subs) {
  135. this.subs = subs;
  136. return this;
  137. }
  138. dash() {
  139. this.format = "application/dash+xml";
  140. return this;
  141. }
  142. m3u8() {
  143. this.format = "application/x-mpegURL";
  144. return this;
  145. }
  146. rtsp() {
  147. this.format = "application/x-rtsp";
  148. return this;
  149. }
  150. octet() {
  151. this.format = "application/octet-stream";
  152. return this;
  153. }
  154. setPage(page, count, limit, total) {
  155. this.page = page
  156. this.limit = limit
  157. this.total = total
  158. this.pagecount = count
  159. return this;
  160. }
  161. toString() {
  162. return JSON.stringify(this);
  163. }
  164. }
  165. class Spider {
  166. constructor() {
  167. this.siteKey = ""
  168. this.siteType = 0
  169. this.jadeLog = new JadeLogging(this.getAppName(), "DEBUG")
  170. this.classes = []
  171. this.filterObj = {}
  172. this.result = new Result()
  173. this.catOpenStatus = true
  174. this.danmuStaus = false
  175. this.reconnectTimes = 0
  176. this.maxReconnectTimes = 5
  177. this.siteUrl = ""
  178. this.vodList = []
  179. this.homeVodList = []
  180. this.count = 0
  181. this.limit = 0
  182. this.total = 0
  183. this.page = 0
  184. this.vodDetail = new VodDetail()
  185. this.playUrl = ""
  186. this.header = {}
  187. this.remove18 = false
  188. this.type_id_18 = 0
  189. this.type_name_18 = "伦理片"
  190. this.episodeObj = {}
  191. this.danmuUrl = ""
  192. this.cfgObj = {}
  193. }
  194. async reconnnect(reqUrl, params, headers, redirect_url, return_cookie, buffer) {
  195. await this.jadeLog.error("请求失败,请检查url:" + reqUrl + ",两秒后重试")
  196. Utils.sleep(2)
  197. if (this.reconnectTimes < this.maxReconnectTimes) {
  198. this.reconnectTimes = this.reconnectTimes + 1
  199. return await this.fetch(reqUrl, params, headers, redirect_url, return_cookie, buffer)
  200. } else {
  201. await this.jadeLog.error("请求失败,重连失败")
  202. return null
  203. }
  204. }
  205. getClassIdList() {
  206. let class_id_list = []
  207. for (const class_dic of this.classes) {
  208. class_id_list.push(class_dic["type_id"])
  209. }
  210. return class_id_list
  211. }
  212. getTypeDic(type_name, type_id) {
  213. return {"type_name": type_name, "type_id": type_id}
  214. }
  215. getFliterDic(type_name, type_id) {
  216. return {"n": type_name, "v": type_id}
  217. }
  218. async getHtml(url = this.siteUrl, proxy = false, headers = this.getHeader()) {
  219. let html = await this.fetch(url, null, headers, false, false, 0, proxy)
  220. if (!_.isEmpty(html)) {
  221. return load(html)
  222. } else {
  223. await this.jadeLog.error(`html获取失败`, true)
  224. }
  225. }
  226. getClassNameList() {
  227. let class_name_list = []
  228. for (const class_dic of this.classes) {
  229. class_name_list.push(class_dic["type_name"])
  230. }
  231. return class_name_list
  232. }
  233. async postReconnect(reqUrl, params, headers,postType,buffer) {
  234. await this.jadeLog.error("请求失败,请检查url:" + reqUrl + ",两秒后重试")
  235. Utils.sleep(2)
  236. if (this.reconnectTimes < this.maxReconnectTimes) {
  237. this.reconnectTimes = this.reconnectTimes + 1
  238. return await this.post(reqUrl, params, headers,postType,buffer)
  239. } else {
  240. await this.jadeLog.error("请求失败,重连失败")
  241. return null
  242. }
  243. }
  244. getHeader() {
  245. return {"User-Agent": Utils.CHROME, "Referer": this.siteUrl + "/"};
  246. }
  247. async getResponse(reqUrl, params, headers, redirect_url, return_cookie, buffer, response,proxy) {
  248. {
  249. if (response.headers["location"] !== undefined) {
  250. if (redirect_url) {
  251. await this.jadeLog.debug(`返回重定向连接:${response.headers["location"]}`)
  252. return response.headers["location"]
  253. } else {
  254. return this.fetch(response.headers["location"], params, headers, redirect_url, return_cookie, buffer,proxy)
  255. }
  256. } else if (response.content.length > 0) {
  257. this.reconnectTimes = 0
  258. if (return_cookie) {
  259. return {"cookie": response.headers["set-cookie"], "content": response.content}
  260. } else {
  261. return response.content
  262. }
  263. } else if (buffer === 1) {
  264. this.reconnectTimes = 0
  265. return response.content
  266. } else {
  267. await this.jadeLog.error(`请求失败,请求url为:${reqUrl},回复内容为:${JSON.stringify(response)}`)
  268. return await this.reconnnect(reqUrl, params, headers, redirect_url, return_cookie, buffer,proxy)
  269. }
  270. }
  271. }
  272. async fetch(reqUrl, params, headers, redirect_url = false, return_cookie = false, buffer = 0, proxy = false) {
  273. let data = Utils.objectToStr(params)
  274. let url = reqUrl
  275. if (!_.isEmpty(data)) {
  276. url = reqUrl + "?" + data
  277. }
  278. let uri = new Uri(url);
  279. let response;
  280. if (redirect_url) {
  281. response = await req(uri.toString(), {
  282. method: "get", headers: headers, buffer: buffer, data: null, redirect: 2, proxy: proxy
  283. })
  284. } else {
  285. response = await req(uri.toString(), {method: "get", headers: headers, buffer: buffer, data: null,proxy:proxy,timeout:10000});
  286. }
  287. if (response.code === 200 || response.code === 302 || response.code === 301 || return_cookie) {
  288. return await this.getResponse(reqUrl, params, headers, redirect_url, return_cookie, buffer, response,proxy)
  289. } else {
  290. await this.jadeLog.error(`请求失败,失败原因为:状态码出错,请求url为:${uri},回复内容为:${JSON.stringify(response)}`)
  291. return await this.reconnnect(reqUrl, params, headers, redirect_url, return_cookie, buffer)
  292. }
  293. }
  294. async redirect(response) {
  295. }
  296. async post(reqUrl, params, headers, postType = "form",buffer = 0) {
  297. let uri = new Uri(reqUrl);
  298. let response = await req(uri.toString(), {
  299. method: "post", headers: headers, data: params, postType: postType,buffer: buffer
  300. });
  301. if (response.code === 200 || response.code === undefined || response.code === 302) {
  302. // 重定向
  303. if (response.headers["location"] !== undefined) {
  304. return await this.redirect(response)
  305. } else if (!_.isEmpty(response.content)) {
  306. this.reconnectTimes = 0
  307. return response.content
  308. } else {
  309. return await this.postReconnect(reqUrl, params, headers,postType,buffer)
  310. }
  311. } else {
  312. await this.jadeLog.error(`请求失败,请求url为:${reqUrl},回复内容为${JSON.stringify(response)}`)
  313. return await this.postReconnect(reqUrl, params, headers,postType,buffer)
  314. }
  315. }
  316. getName() {
  317. return `🍥┃基础┃🍥`
  318. }
  319. getAppName() {
  320. return `基础`
  321. }
  322. getJSName() {
  323. return "base"
  324. }
  325. getType() {
  326. return 3
  327. }
  328. async parseVodShortListFromDoc($) {
  329. }
  330. async parseVodShortListFromJson(obj) {
  331. }
  332. parseVodShortFromElement($, element) {
  333. }
  334. async parseVodShortListFromDocByCategory($) {
  335. }
  336. async getFilter($) {
  337. }
  338. async setClasses() {
  339. }
  340. async setFilterObj() {
  341. }
  342. async parseVodShortListFromDocBySearch($) {
  343. return []
  344. }
  345. async parseVodDetailFromDoc($) {
  346. }
  347. async parseVodDetailfromJson(obj) {
  348. }
  349. async parseVodPlayFromUrl(flag, play_url) {
  350. }
  351. async parseVodPlayFromDoc(flag, $) {
  352. }
  353. async SpiderInit(cfg) {
  354. try {
  355. this.siteKey = cfg["skey"]
  356. this.siteType = parseInt(cfg["stype"].toString())
  357. let extObj = null;
  358. if (typeof cfg.ext === "string") {
  359. await this.jadeLog.info(`读取配置文件,ext为:${cfg.ext}`)
  360. extObj = JSON.parse(cfg.ext)
  361. } else if (typeof cfg.ext === "object") {
  362. await this.jadeLog.info(`读取配置文件,所有参数为:${JSON.stringify(cfg)}`)
  363. await this.jadeLog.info(`读取配置文件,ext为:${JSON.stringify(cfg.ext)}`)
  364. extObj = cfg.ext
  365. } else {
  366. await this.jadeLog.error(`不支持的数据类型,数据类型为${typeof cfg.ext}`)
  367. }
  368. let boxType = extObj["box"]
  369. extObj["CatOpenStatus"] = boxType === "CatOpen";
  370. return extObj
  371. } catch (e) {
  372. await this.jadeLog.error("初始化失败,失败原因为:" + e.message)
  373. return {"token": null, "CatOpenStatus": false, "code": 0}
  374. }
  375. }
  376. async initCloud(token) {
  377. await initCloud(token)
  378. }
  379. async spiderInit() {
  380. }
  381. async init(cfg) {
  382. this.danmuSpider = new DanmuSpider()
  383. this.cfgObj = await this.SpiderInit(cfg)
  384. await this.jadeLog.debug(`初始化参数为:${JSON.stringify(cfg)}`)
  385. this.catOpenStatus = this.cfgObj.CatOpenStatus
  386. this.danmuStaus = this.cfgObj["danmu"] ?? this.danmuStaus
  387. try {
  388. if (await this.loadFilterAndClasses()) {
  389. await this.jadeLog.debug(`读取缓存列表和二级菜单成功`)
  390. } else {
  391. await this.jadeLog.warning(`读取缓存列表和二级菜单失败`)
  392. await this.writeFilterAndClasses()
  393. }
  394. } catch (e) {
  395. await local.set(this.siteKey, "classes", JSON.stringify([]));
  396. await local.set(this.siteKey, "filterObj", JSON.stringify({}));
  397. await this.jadeLog.error("读取缓存失败,失败原因为:" + e)
  398. }
  399. this.jsBase = await js2Proxy(true, this.siteType, this.siteKey, 'img/', {});
  400. this.douBanjsBase = await js2Proxy(true, this.siteType, this.siteKey, 'douban/', {});
  401. this.baseProxy = await js2Proxy(true, this.siteType, this.siteKey, 'img/', this.getHeader());
  402. this.videoProxy = await js2Proxy(true, this.siteType, this.siteKey, 'm3u8/', {});
  403. this.detailProxy = await js2Proxy(true, this.siteType, this.siteKey, 'detail/', this.getHeader());
  404. }
  405. async loadFilterAndClasses() {
  406. // 强制清空
  407. // await local.set(this.siteKey, "classes", JSON.stringify([]));
  408. // await local.set(this.siteKey, "filterObj", JSON.stringify({}));
  409. this.classes = await this.getClassesCache()
  410. this.filterObj = await this.getFiletObjCache()
  411. if (this.classes.length > 0) {
  412. return true
  413. } else {
  414. await local.set(this.siteKey, "classes", JSON.stringify([]));
  415. await local.set(this.siteKey, "filterObj", JSON.stringify({}));
  416. return false
  417. }
  418. }
  419. async writeFilterAndClasses() {
  420. if (this.catOpenStatus) {
  421. this.classes.push({"type_name": "最近更新", "type_id": "最近更新"})
  422. }
  423. await this.setClasses()
  424. await this.setFilterObj()
  425. await local.set(this.siteKey, "classes", JSON.stringify(this.classes));
  426. await local.set(this.siteKey, "filterObj", JSON.stringify(this.filterObj));
  427. }
  428. async getClassesCache() {
  429. let cacheClasses = await local.get(this.siteKey, "classes")
  430. if (!_.isEmpty(cacheClasses)) {
  431. return JSON.parse(cacheClasses)
  432. } else {
  433. return this.classes
  434. }
  435. }
  436. async getFiletObjCache() {
  437. let cacheFilterObj = await local.get(this.siteKey, "filterObj")
  438. if (!_.isEmpty(cacheFilterObj)) {
  439. return JSON.parse(cacheFilterObj)
  440. } else {
  441. return this.filterObj
  442. }
  443. }
  444. async setHome(filter) {
  445. }
  446. async home(filter) {
  447. this.vodList = []
  448. await this.jadeLog.info("正在解析首页类别", true)
  449. await this.setHome(filter)
  450. await this.jadeLog.debug(`首页类别内容为:${this.result.home(this.classes, [], this.filterObj)}`)
  451. await this.jadeLog.info("首页类别解析完成", true)
  452. return this.result.home(this.classes, [], this.filterObj)
  453. }
  454. async setHomeVod() {
  455. }
  456. async homeVod() {
  457. await this.jadeLog.info("正在解析首页内容", true)
  458. await this.setHomeVod()
  459. await this.jadeLog.debug(`首页内容为:${this.result.homeVod(this.homeVodList)}`)
  460. await this.jadeLog.info("首页内容解析完成", true)
  461. return this.result.homeVod(this.homeVodList)
  462. }
  463. async setCategory(tid, pg, filter, extend) {
  464. }
  465. async category(tid, pg, filter, extend) {
  466. this.page = parseInt(pg)
  467. await this.jadeLog.info(`正在解析分类页面,tid = ${tid},pg = ${pg},filter = ${filter},extend = ${JSON.stringify(extend)}`)
  468. if (tid === "最近更新") {
  469. this.page = 0
  470. return await this.homeVod()
  471. } else {
  472. try {
  473. this.vodList = []
  474. await this.setCategory(tid, pg, filter, extend)
  475. await this.jadeLog.debug(`分类页面内容为:${this.result.category(this.vodList, this.page, this.count, this.limit, this.total)}`)
  476. await this.jadeLog.info("分类页面解析完成", true)
  477. return this.result.category(this.vodList, this.page, this.count, this.limit, this.total)
  478. } catch (e) {
  479. await this.jadeLog.error(`分类页解析失败,失败原因为:${e}`)
  480. }
  481. }
  482. }
  483. async setDetail(id) {
  484. }
  485. setEpisodeCache() {
  486. // 记录每个播放链接的集数
  487. let episodeObj = {
  488. "vodDetail": this.vodDetail.to_dict(),
  489. }
  490. let vod_url_channels_list = this.vodDetail.vod_play_url.split("$$$")
  491. for (const vodItemsStr of vod_url_channels_list) {
  492. let vodItems = vodItemsStr.split("#")
  493. for (const vodItem of vodItems) {
  494. let episodeName = vodItem.split("$")[0].split(" ")[0]
  495. let episodeUrl = vodItem.split("$")[1]
  496. let matchers = episodeName.match(/\d+/g)
  497. if (matchers !== null && matchers.length > 0) {
  498. episodeName = matchers[0]
  499. }
  500. episodeObj[episodeUrl] = {"episodeName": episodeName, "episodeId": episodeName}
  501. }
  502. }
  503. return episodeObj
  504. }
  505. async detail(id) {
  506. this.vodDetail = new VodDetail();
  507. await this.jadeLog.info(`正在获取详情页面,id为:${id}`)
  508. try {
  509. await this.setDetail(id)
  510. await this.jadeLog.debug(`详情页面内容为:${this.result.detail(this.vodDetail)}`)
  511. await this.jadeLog.info("详情页面解析完成", true)
  512. this.vodDetail.vod_id = id
  513. if (this.siteType === 3) {
  514. this.episodeObj = this.setEpisodeCache()
  515. }
  516. return this.result.detail(this.vodDetail)
  517. } catch (e) {
  518. await this.jadeLog.error("详情界面获取失败,失败原因为:" + e)
  519. }
  520. }
  521. async setPlay(flag, id, flags) {
  522. this.playUrl = id
  523. }
  524. async setDanmu(id) {
  525. await this.jadeLog.debug(`${JSON.stringify(this.episodeObj)}`)
  526. let episodeId = this.episodeObj[id]
  527. let vodDetail = JSON.parse(this.episodeObj["vodDetail"])
  528. delete vodDetail.vod_content;
  529. delete vodDetail.vod_play_from;
  530. delete vodDetail.vod_play_url;
  531. delete vodDetail.vod_pic;
  532. await this.jadeLog.debug(`正在加载弹幕,视频详情为:${JSON.stringify(vodDetail)},集数:${JSON.stringify(this.episodeObj[id])}`)
  533. //区分电影还是电视剧
  534. return await this.danmuSpider.getDammu(vodDetail, episodeId)
  535. }
  536. async play(flag, id, flags) {
  537. await this.jadeLog.info(`正在解析播放页面,flag:${flag},id:${id},flags:${flags}`, true)
  538. try {
  539. let return_result;
  540. await this.setPlay(flag, id, flags)
  541. if (this.playUrl["content"] !== undefined) {
  542. return_result = this.result.playTxt(this.playUrl)
  543. } else {
  544. if (this.danmuStaus && !this.catOpenStatus) {
  545. if (!_.isEmpty(this.danmuUrl)) {
  546. await this.jadeLog.debug("播放详情页面有弹幕,所以不需要再查找弹幕")
  547. return_result = this.result.danmu(this.danmuUrl).play(this.playUrl)
  548. } else {
  549. let danmuUrl;
  550. try {
  551. danmuUrl = await this.setDanmu(id)
  552. } catch (e) {
  553. await this.jadeLog.error(`弹幕加载失败,失败原因为:${e}`)
  554. }
  555. return_result = this.result.danmu(danmuUrl).play(this.playUrl)
  556. }
  557. } else {
  558. await this.jadeLog.debug("不需要加载弹幕", true)
  559. return_result = this.result.play(this.playUrl)
  560. }
  561. }
  562. await this.jadeLog.info("播放页面解析完成", true)
  563. await this.jadeLog.debug(`播放页面内容为:${return_result}`)
  564. return return_result;
  565. } catch (e) {
  566. await this.jadeLog.error("解析播放页面出错,失败原因为:" + e)
  567. }
  568. }
  569. async setSearch(wd, quick) {
  570. }
  571. async search(wd, quick) {
  572. this.vodList = []
  573. await this.jadeLog.info(`正在解析搜索页面,关键词为 = ${wd},quick = ${quick}`)
  574. await this.setSearch(wd, quick,1)
  575. if (this.vodList.length === 0) {
  576. if (wd.indexOf(" ") > -1) {
  577. await this.jadeLog.debug(`搜索关键词为:${wd},其中有空格,去除空格在搜索一次`)
  578. await this.search(wd.replaceAll(" ", "").replaceAll("", ""), quick)
  579. }
  580. }
  581. await this.jadeLog.debug(`搜索页面内容为:${this.result.search(this.vodList)}`)
  582. await this.jadeLog.info("搜索页面解析完成", true)
  583. return this.result.search(this.vodList)
  584. }
  585. async getImg(url, headers) {
  586. let resp;
  587. let vpn_proxy = headers["Proxy"] // 使用代理不需要加headers
  588. if (_.isEmpty(headers)) {
  589. headers = {Referer: url, 'User-Agent': Utils.CHROME}
  590. }
  591. resp = await req(url, {buffer: 2, headers: headers,proxy:vpn_proxy});
  592. try {
  593. //二进制文件是不能使用Base64编码格式的
  594. Utils.base64Decode(resp.content)
  595. if (vpn_proxy){
  596. await this.jadeLog.error(`使用VPN代理,图片地址为:${url},headers:${JSON.stringify(headers)},代理失败,准备重连,输出内容为:${JSON.stringify(resp)}`)
  597. }else {
  598. await this.jadeLog.error(`使用普通代理,图片地址为:${url},headers:${JSON.stringify(headers)},代理失败,准备重连,输出内容为:${JSON.stringify(resp)}`)
  599. }
  600. if (this.reconnectTimes < this.maxReconnectTimes){
  601. this.reconnectTimes = this.reconnectTimes + 1
  602. return await this.getImg(url,headers)
  603. }else{
  604. return {"code": 500, "headers": headers, "content": "加载失败"}
  605. }
  606. } catch (e) {
  607. await this.jadeLog.debug("图片代理成功", true)
  608. this.reconnectTimes = 0
  609. return resp
  610. }
  611. }
  612. async proxy(segments, headers) {
  613. await this.jadeLog.debug(`正在设置反向代理 segments = ${segments.join(",")},headers = ${JSON.stringify(headers)}`)
  614. let what = segments[0];
  615. let url = Utils.base64Decode(segments[1]);
  616. await this.jadeLog.debug(`反向代理参数为:${url}`)
  617. if (what === 'img') {
  618. await this.jadeLog.debug("通过代理获取图片", true)
  619. let resp = await this.getImg(url, headers)
  620. return JSON.stringify({
  621. code: resp.code, buffer: 2, content: resp.content, headers: resp.headers,
  622. });
  623. } else if (what === "douban") {
  624. let vod_list = await this.doubanSearch(url)
  625. if (vod_list !== null) {
  626. let vod_pic = vod_list[0].vod_pic
  627. let resp;
  628. if (!_.isEmpty(headers)) {
  629. resp = await req(vod_pic, {
  630. buffer: 2, headers: headers
  631. });
  632. } else {
  633. resp = await req(vod_pic, {
  634. buffer: 2, headers: {
  635. Referer: vod_pic, 'User-Agent': Utils.CHROME,
  636. },
  637. });
  638. }
  639. return JSON.stringify({
  640. code: resp.code, buffer: 2, content: resp.content, headers: resp.headers,
  641. });
  642. }
  643. } else if (what === "m3u8") {
  644. let content;
  645. if (!_.isEmpty(headers)) {
  646. content = await this.fetch(url, null, headers, false, false, 2)
  647. } else {
  648. content = await this.fetch(url, null, {"Referer": url, 'User-Agent': Utils.CHROME}, false, false, 2)
  649. }
  650. await this.jadeLog.debug(`m3u8返回内容为:${Utils.base64Decode(content)}`)
  651. if (!_.isEmpty(content)) {
  652. return JSON.stringify({
  653. code: 200, buffer: 2, content: content, headers: {},
  654. });
  655. } else {
  656. return JSON.stringify({
  657. code: 500, buffer: 2, content: content, headers: {},
  658. })
  659. }
  660. } else if (what === 'hls') {
  661. function hlsHeader(data, hls) {
  662. let hlsHeaders = {};
  663. if (data.headers['content-length']) {
  664. Object.assign(hlsHeaders, data.headers, {'content-length': hls.length.toString()});
  665. } else {
  666. Object.assign(hlsHeaders, data.headers);
  667. }
  668. delete hlsHeaders['transfer-encoding'];
  669. if (hlsHeaders['content-encoding'] == 'gzip') {
  670. delete hlsHeaders['content-encoding'];
  671. }
  672. return hlsHeaders;
  673. }
  674. const hlsData = await hlsCache(url, headers);
  675. if (hlsData.variants) {
  676. // variants -> variants -> .... ignore
  677. const hls = HLS.stringify(hlsData.plist);
  678. return {
  679. code: hlsData.code, content: hls, headers: hlsHeader(hlsData, hls),
  680. };
  681. } else {
  682. const hls = HLS.stringify(hlsData.plist, (segment) => {
  683. return js2Proxy(false, this.siteType, this.siteKey, 'ts/' + encodeURIComponent(hlsData.key + '/' + segment.mediaSequenceNumber.toString()), headers);
  684. });
  685. return {
  686. code: hlsData.code, content: hls, headers: hlsHeader(hlsData, hls),
  687. };
  688. }
  689. } else if (what === 'ts') {
  690. const info = url.split('/');
  691. const hlsKey = info[0];
  692. const segIdx = parseInt(info[1]);
  693. return await tsCache(hlsKey, segIdx, headers);
  694. } else if (what === "detail") {
  695. let $ = await this.getHtml(this.siteUrl + url)
  696. let vodDetail = await this.parseVodDetailFromDoc($)
  697. let resp = await this.getImg(vodDetail.vod_pic, headers)
  698. return JSON.stringify({
  699. code: resp.code, buffer: 2, content: resp.content, headers: resp.headers,
  700. });
  701. } else {
  702. return JSON.stringify({
  703. code: 500, content: '',
  704. });
  705. }
  706. }
  707. getSearchHeader() {
  708. const UserAgents = ["api-client/1 com.douban.frodo/7.22.0.beta9(231) Android/23 product/Mate 40 vendor/HUAWEI model/Mate 40 brand/HUAWEI rom/android network/wifi platform/AndroidPad", "api-client/1 com.douban.frodo/7.18.0(230) Android/22 product/MI 9 vendor/Xiaomi model/MI 9 brand/Android rom/miui6 network/wifi platform/mobile nd/1", "api-client/1 com.douban.frodo/7.1.0(205) Android/29 product/perseus vendor/Xiaomi model/Mi MIX 3 rom/miui6 network/wifi platform/mobile nd/1", "api-client/1 com.douban.frodo/7.3.0(207) Android/22 product/MI 9 vendor/Xiaomi model/MI 9 brand/Android rom/miui6 network/wifi platform/mobile nd/1"]
  709. let randomNumber = Math.floor(Math.random() * UserAgents.length); // 生成一个介于0到9之间的随机整数
  710. return {
  711. 'User-Agent': UserAgents[randomNumber]
  712. }
  713. }
  714. async parseDoubanVodShortListFromJson(obj) {
  715. let vod_list = []
  716. for (const item of obj) {
  717. let vod_short = new VodShort()
  718. vod_short.vod_id = "msearch:" + item["id"]
  719. if (item["title"] === undefined) {
  720. vod_short.vod_name = item["target"]["title"]
  721. } else {
  722. vod_short.vod_name = item["title"]
  723. }
  724. if (item["pic"] === undefined) {
  725. vod_short.vod_pic = item["target"]["cover_url"]
  726. } else {
  727. vod_short.vod_pic = item["pic"]["normal"]
  728. }
  729. if (item["rating"] === undefined) {
  730. vod_short.vod_remarks = "评分:" + item["target"]["rating"]["value"].toString()
  731. } else {
  732. vod_short.vod_remarks = "评分:" + item["rating"]["value"].toString()
  733. }
  734. vod_list.push(vod_short);
  735. }
  736. return vod_list
  737. }
  738. sign(url, ts, method = 'GET') {
  739. let _api_secret_key = "bf7dddc7c9cfe6f7"
  740. let url_path = "%2F" + url.split("/").slice(3).join("%2F")
  741. let raw_sign = [method.toLocaleUpperCase(), url_path, ts.toString()].join("&")
  742. return CryptoJS.HmacSHA1(raw_sign, _api_secret_key).toString(CryptoJS.enc.Base64)
  743. }
  744. async doubanSearch(wd) {
  745. try {
  746. let _api_url = "https://frodo.douban.com/api/v2"
  747. let _api_key = "0dad551ec0f84ed02907ff5c42e8ec70"
  748. let url = _api_url + "/search/movie"
  749. let date = new Date()
  750. let ts = date.getFullYear().toString() + (date.getMonth() + 1).toString() + date.getDate().toString()
  751. let params = {
  752. '_sig': this.sign(url, ts),
  753. '_ts': ts,
  754. 'apiKey': _api_key,
  755. 'count': 20,
  756. 'os_rom': 'android',
  757. 'q': encodeURIComponent(wd),
  758. 'start': 0
  759. }
  760. let content = await this.fetch(url, params, this.getSearchHeader())
  761. if (!_.isEmpty(content)) {
  762. let content_json = JSON.parse(content)
  763. await this.jadeLog.debug(`豆瓣搜索结果:${content}`)
  764. return await this.parseDoubanVodShortListFromJson(content_json["items"])
  765. }
  766. return null
  767. } catch (e) {
  768. await this.jadeLog.error("反向代理出错,失败原因为:" + e)
  769. }
  770. }
  771. }
  772. export {Spider, Result}