tools.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. import axios from 'axios';
  2. import axiosRetry from 'axios-retry';
  3. import { XMLParser } from 'fast-xml-parser';
  4. import * as cheerio from 'cheerio';
  5. import { Parser as M3u8Parser } from 'm3u8-parser';
  6. import _ from 'lodash';
  7. import { sites } from '@/lib/dexie';
  8. const iconv = require('iconv-lite');
  9. const dns = require('dns');
  10. const net = require('net');
  11. axiosRetry(axios, {
  12. retries: 3,
  13. retryDelay: (retryCount) => {
  14. return retryCount * 1000;
  15. }
  16. });
  17. // 初始化对象xml转json https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v4/1.GettingStarted.md
  18. const options = { // XML 转 JSON 配置
  19. trimValues: true,
  20. textNodeName: '_t',
  21. ignoreAttributes: false,
  22. attributeNamePrefix: '_',
  23. parseAttributeValue: true
  24. }
  25. const parser = new XMLParser(options);
  26. Object.fromEntries = function fromEntries (iterable) {
  27. return [...iterable].reduce((obj, [key, val]) => {
  28. obj[key] = val;
  29. return obj;
  30. }, {});
  31. };
  32. const buildUrl = function (url,params_str){
  33. const u = new URL(url);
  34. const p = new URLSearchParams(params_str);
  35. const api = u.origin + u.pathname;
  36. let params = Object.fromEntries(u.searchParams.entries());
  37. let params_obj = Object.fromEntries(p.entries());
  38. Object.assign(params,params_obj);
  39. let plist = [];
  40. for(let key in params){
  41. plist.push(key+'='+params[key]);
  42. }
  43. return api + '?' + plist.join('&')
  44. };
  45. // 资源爬虫
  46. const zy = {
  47. /**
  48. * 获取资源分类 和 所有资源的总数, 分页等信息
  49. * @param {*} key 资源网 key
  50. * @returns
  51. */
  52. async class (key) {
  53. try {
  54. const site = await sites.find({key:key});
  55. const url = site.api;
  56. const res = await axios.get(url);
  57. const json = res.data;
  58. const jsondata = json?.rss === undefined ? json : json.rss;
  59. if (!jsondata?.class || !jsondata?.list) return null;
  60. return {
  61. class: jsondata.class,
  62. page: jsondata.page,
  63. pagecount: jsondata.pagecount,
  64. pagesize: parseInt(jsondata.limit),
  65. recordcount: jsondata.total
  66. };
  67. } catch (err) {
  68. throw err;
  69. }
  70. },
  71. /**
  72. * 获取资源列表
  73. * @param {*} key 资源网 key
  74. * @param {number} [pg=1] 翻页 page
  75. * @param {*} t 分类 type
  76. * @returns
  77. */
  78. async list(key, pg = 1, t) {
  79. try {
  80. const site = await sites.find({key:key});
  81. const url = t ? buildUrl(site.api,`?ac=videolist&t=${t}&pg=${pg}`) : buildUrl(site.api,`?ac=videolist&pg=${pg}`);
  82. const res = await axios.get(url);
  83. const json = res.data;
  84. const jsondata = json.rss || json;
  85. const videoList = jsondata.list || [];
  86. return videoList;
  87. } catch (err) {
  88. throw err;
  89. }
  90. },
  91. /**
  92. * 获取资源热榜列表
  93. * @param {*} key 资源网 key
  94. * @param {number} [pg=1] 翻页 page
  95. * @param {*} t 分类 type
  96. * @param {*} h 时间 time
  97. * @returns
  98. */
  99. // https://y.ioszxc123.me/api/v1/Vod/hot?limit=10&order=1&os=2&page=1&type=2
  100. async hot(key, h) {
  101. try {
  102. const site = await sites.find({key:key});
  103. const url = buildUrl(site.api,`?ac=hot&h=${h}`);
  104. const res = await axios.get(url);
  105. const json = res.data;
  106. const jsondata = json.rss || json;
  107. const videoList = jsondata.list || [];
  108. const data = [];
  109. for (let i = 0; i < 10; i++) {
  110. const item = videoList[i]
  111. if ( i in [0, 1, 2, 3 ]) {
  112. const pic = await this.detail(key, item.vod_id);
  113. item['vod_pic'] = pic.vod_pic
  114. }
  115. data.push(item);
  116. }
  117. return data;
  118. } catch (err) {
  119. throw err;
  120. }
  121. },
  122. /**
  123. * 获取总资源数, 以及页数
  124. * @param {*} key 资源网
  125. * @param {*} t 分类 type
  126. * @returns page object
  127. */
  128. async page (key, t) {
  129. try {
  130. const site = await sites.find({key:key});
  131. let url = buildUrl(site.api,`?ac=videolist`);
  132. if (t) url += `&t=${t}`;
  133. const res = await axios.get(url);
  134. // 某些源站不含页码时获取到的数据parser无法解析
  135. const data = res.data.match(/<list [^>]*>/)[0] + '</list>';
  136. const json = parser.parse(data);
  137. const { _page, _pagecount, _pagesize, _recordcount } = json.rss?.list || {};
  138. const pg = {
  139. page: _page,
  140. pagecount: _pagecount,
  141. pagesize: _pagesize,
  142. recordcount: _recordcount
  143. };
  144. // const jsondata = json.rss === undefined ? json : json.rss
  145. // const pg = {
  146. // page: jsondata.list._page,
  147. // pagecount: jsondata.list._pagecount,
  148. // pagesize: jsondata.list._pagesize,
  149. // recordcount: jsondata.list._recordcount
  150. // }
  151. return pg;
  152. } catch (err) {
  153. throw err;
  154. }
  155. },
  156. /**
  157. * 搜索资源
  158. * @param {*} key 资源网 key
  159. * @param {*} wd 搜索关键字
  160. * @returns
  161. */
  162. async search(key, wd) {
  163. try {
  164. const site = await sites.find({key:key});
  165. const url = buildUrl(site.api,`?wd=${encodeURIComponent(wd)}`);
  166. const res = await axios.get(url, { timeout: 3000 });
  167. const json = res.data;
  168. const jsondata = json?.rss ?? json;
  169. if (json && jsondata.total > 0) {
  170. let videoList = jsondata.list;
  171. if (Array.isArray(videoList)) {
  172. return videoList;
  173. }
  174. }
  175. } catch (err) {
  176. throw err;
  177. }
  178. },
  179. /**
  180. * 搜索资源详情
  181. * @param {*} key 资源网 key
  182. * @param {*} wd 搜索关键字
  183. * @returns
  184. */
  185. async searchFirstDetail(key, wd) {
  186. try {
  187. const site = await sites.find({key:key});
  188. const url = buildUrl(site.api,`?wd=${encodeURI(wd)}`)
  189. const res = await axios.get(url, { timeout: 3000 })
  190. const json = res.data
  191. const jsondata = json?.rss === undefined ? json : json.rss
  192. if (jsondata || jsondata?.list) {
  193. let videoList = jsondata.list
  194. if (Object.prototype.toString.call(videoList) === '[object Object]') videoList = [].concat(videoList)
  195. if (videoList?.length) {
  196. const detailRes = await this.detail(key, videoList[0].vod_id)
  197. return detailRes
  198. } else return null
  199. } else return null
  200. } catch (err) {
  201. throw err;
  202. }
  203. },
  204. /**
  205. * 获取资源详情
  206. * @param {*} key 资源网 key
  207. * @param {*} id 资源唯一标识符 id
  208. * @returns
  209. */
  210. async detail(key, id) {
  211. try {
  212. const site = await sites.find({key:key});
  213. const url = buildUrl(site.api,`?ac=videolist&ids=${id}`);
  214. const res = await axios.get(url);
  215. const json = res.data;
  216. const jsondata = json?.rss ?? json;
  217. const videoList = jsondata?.list?.[0];
  218. if (!videoList) return;
  219. // Parse video
  220. // 播放源
  221. const playFrom = videoList.vod_play_from;
  222. const playSource = playFrom.split('$').filter(Boolean);
  223. // 剧集
  224. const playUrl = videoList.vod_play_url;
  225. const playUrlDiffPlaySource = playUrl.split('$$$'); // 分离不同播放源
  226. const playEpisodes = playUrlDiffPlaySource.map((item) => {
  227. return item.replace(/\$+/g, '$').split('#').filter(e => {
  228. const isHttp = e.startsWith('http');
  229. const hasHttp = e.split('$')[1]?.startsWith('http');
  230. return Boolean(e && (isHttp || hasHttp));
  231. });
  232. });
  233. const fullList = Object.fromEntries(playSource.map((key, index) => [key, playEpisodes[index]]));
  234. videoList.fullList = fullList;
  235. return videoList;
  236. } catch (err) {
  237. throw err;
  238. }
  239. },
  240. /**
  241. * 检查资源
  242. * @param {*} key 资源网 key
  243. * @returns boolean
  244. */
  245. async check (key) {
  246. try {
  247. const cls = await this.class(key)
  248. if (cls) return true
  249. else return false
  250. } catch (err) {
  251. console.log(err)
  252. return false
  253. }
  254. },
  255. /**
  256. * 检查直播源
  257. * @param {*} channel 直播频道 url
  258. * @returns boolean
  259. */
  260. async checkChannel(url) {
  261. try {
  262. const res = await axios.get(url);
  263. const manifest = res.data;
  264. const parser = new M3u8Parser();
  265. parser.push(manifest);
  266. parser.end();
  267. const parsedManifest = parser.manifest;
  268. if (parsedManifest.segments.length > 0) {
  269. return true;
  270. }
  271. // 兼容性处理 抓包多次请求规则 #EXT-X-STREAM-INF 带文件路径的相对路径
  272. const responseURL = res.request.responseURL
  273. const { uri } = parsedManifest.playlists[0]
  274. let newUrl
  275. if (res.data.indexOf("encoder") > 0) {
  276. // request1: http://1.204.169.243/live.aishang.ctlcdn.com/00000110240389_1/playlist.m3u8?CONTENTID=00000110240389_1&AUTHINFO=FABqh274XDn8fkurD5614t%2B1RvYajgx%2Ba3PxUJe1SMO4OjrtFitM6ZQbSJEFffaD35hOAhZdTXOrK0W8QvBRom%2BXaXZYzB%2FQfYjeYzGgKhP%2Fdo%2BXpr4quVxlkA%2BubKvbU1XwJFRgrbX%2BnTs60JauQUrav8kLj%2FPH8LxkDFpzvkq75UfeY%2FVNDZygRZLw4j%2BXtwhj%2FIuXf1hJAU0X%2BheT7g%3D%3D&USERTOKEN=eHKuwve%2F35NVIR5qsO5XsuB0O2BhR0KR
  277. // #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=8000000,CODECS="avc,mp21" encoder/0/playlist.m3u8?CONTENTID=00000110240127_1&AUTHINFO=FABqh274XDn8fkurD5614t%2B1RvYajgx%2Ba3PxUJe1SMO4OjrtFitM6ZQbSJEFffaD35hOAhZdTXOrK0W8QvBRom%2BXaXZYzB%2FQfYjeYzGgKhP%2Fdo%2BXpr4quVxlkA%2BubKvbU1XwJFRgrbX%2BnTs60JauQUrav8kLj%2FPH8LxkDFpzvkq75UfeY%2FVNDZygRZLw4j%2BXtwhj%2FIuXf1hJAU0X%2BheT7g%3D%3D&USERTOKEN=eHKuwve%2F35NVIR5qsO5XsuB0O2BhR0KR
  278. // request2: http://1.204.169.243/live.aishang.ctlcdn.com/00000110240303_1/encoder/0/playlist.m3u8?CONTENTID=00000110240303_1&AUTHINFO=FABqh274XDn8fkurD5614t%2B1RvYajgx%2Ba3PxUJe1SMO4OjrtFitM6ZQbSJEFffaD35hOAhZdTXOrK0W8QvBRom%2BXaXZYzB%2FQfYjeYzGgKhP%2Fdo%2BXpr4quVxlkA%2BubKvbU1XwJFRgrbX%2BnTs60JauQUrav8kLj%2FPH8LxkDFpzvkq75UfeY%2FVNDZygRZLw4j%2BXtwhj%2FIuXf1hJAU0X%2BheT7g%3D%3D&USERTOKEN=eHKuwve%2F35NVIR5qsO5XsuB0O2BhR0KR
  279. const index = responseURL.lastIndexOf("\/");
  280. const urlLastParam= responseURL.substring(0, index+1);
  281. newUrl = urlLastParam + uri;
  282. return this.checkChannel(newUrl);
  283. } else if (uri.indexOf("http") === 0|| uri.indexOf("//") === 0) {
  284. // request1: http://[2409:8087:3869:8021:1001::e5]:6610/PLTV/88888888/224/3221225491/2/index.m3u8?IASHttpSessionId=OTT8798520230127055253191816
  285. // #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=8468480 http://[2409:8087:3869:8021:1001::e5]:6610/PLTV/88888888/224/3221225491/2/1000.m3u8?IASHttpSessionId=OTT8798520230127055253191816&zte_bandwidth=1000&bandwidth=8468480&ispcode=888&timeformat=local&channel=3221225491&m3u8_level=2&ztecid=3221225491
  286. // request2: http://[2409:8087:3869:8021:1001::e5]:6610/PLTV/88888888/224/3221225491/2/1000.m3u8?IASHttpSessionId=OTT8867820230127053805215983&zte_bandwidth=1000&bandwidth=8467456&ispcode=888&timeformat=local&channel=3221225491&m3u8_level=2&ztecid=3221225491
  287. newUrl = uri
  288. return this.checkChannel(newUrl);
  289. } else if (/^\/[^\/]/.test(uri) || (/^[^\/]/.test(uri) && uri.indexOf("http") === 0)) {
  290. // request1: http://baidu.live.cqccn.com/__cl/cg:live/__c/hxjc_4K/__op/default/__f//index.m3u8
  291. // #EXT-X-STREAM-INF:BANDWIDTH=15435519,AVERAGE-BANDWIDTH=15435519,RESOLUTION=3840x2160,CODECS="hvc1.1.6.L150.b0,mp4a.40.2",AUDIO="audio_mp4a.40.2_48000",CLOSED-CAPTIONS=NONE,FRAME-RATE=25 1/v15M/index.m3u8
  292. // request2: http://baidu.live.cqccn.com/__cl/cg:live/__c/hxjc_4K/__op/default/__f//1/v15M/index.m3u8
  293. const index = responseURL.lastIndexOf("\/");
  294. const urlLastParam= responseURL.substring(0, index+1);
  295. newUrl = urlLastParam + uri;
  296. return this.checkChannel(newUrl);
  297. }
  298. return false;
  299. } catch (err) {
  300. throw err;
  301. }
  302. },
  303. /**
  304. * 提取ck/dp播放器m3u8
  305. * @param {*} parserFilmUrl film url
  306. * @returns boolean
  307. */
  308. async parserFilmUrl(url) {
  309. const urlDomain = url.match(/(\w+):\/\/([^\:|\/]+)(\:\d*)?(\/)/)[0];
  310. try {
  311. const response = await axios.get(url);
  312. let urlPlay;
  313. // 全局提取完整地址
  314. const urlGlobal = response.data.match(/(https?:\/\/[^\s]+\.m3u8)/);
  315. if (urlGlobal) {
  316. urlPlay = urlGlobal[0];
  317. return urlPlay;
  318. }
  319. // 局部提取地址 提取参数拼接域名
  320. const urlParm = response.data.match(/\/(.*?)(\.m3u8)/);
  321. if (urlParm) urlPlay = urlDomain + urlParm[0];
  322. return urlPlay;
  323. } catch (err) {
  324. throw err;
  325. }
  326. },
  327. /**
  328. * 获取电子节目单
  329. * @param {*} url epg阶段单api
  330. * @param {*} tvg_name 节目名称
  331. * @param {*} date 日期 2023-01-31
  332. * @returns 电子节目单列表
  333. */
  334. async iptvEpg(url, tvg_name, date) {
  335. try {
  336. const res = await axios.get(url, {
  337. params: {
  338. ch: tvg_name,
  339. date: date
  340. }
  341. });
  342. const epgData = res.data.epg_data;
  343. return epgData;
  344. } catch (err) {
  345. throw err;
  346. }
  347. },
  348. /**
  349. * 判断 m3u8 文件是否为直播流
  350. * @param {*} url m3u8地址
  351. * @returns 是否是直播流
  352. */
  353. async isLiveM3U8(url) {
  354. try {
  355. const res = await axios.get(url);
  356. const m3u8Content = res.data;
  357. // 从m3u8文件中解析媒体段(MEDIA-SEQUENCE)的值
  358. const mediaSequenceMatch = m3u8Content.match(/#EXT-X-MEDIA-SEQUENCE:(\d+)/);
  359. const mediaSequence = mediaSequenceMatch ? parseInt(mediaSequenceMatch[1]) : null;
  360. // 判断是直播还是点播
  361. const isLiveStream = mediaSequence === null || mediaSequence === 0;
  362. return !isLiveStream;
  363. } catch (err) {
  364. throw err;
  365. }
  366. },
  367. /**
  368. * 获取豆瓣页面链接
  369. * @param {*} id 视频唯一标识
  370. * @param {*} name 视频名称
  371. * @param {*} year 视频年份
  372. * @returns 豆瓣页面链接,如果没有搜到该视频,返回搜索页面链接
  373. */
  374. async doubanLink(id, name, year) {
  375. const nameToSearch = encodeURI(name.trim())
  376. const doubanSearchLink = id && parseInt(id) !== 0 ? `https://movie.douban.com/subject/${id}` : `https://www.douban.com/search?cat=1002&q=${nameToSearch}`
  377. try {
  378. const res = await axios.get(doubanSearchLink)
  379. const $ = cheerio.load(res.data)
  380. let link = ''
  381. $('div.result').each(function () {
  382. const linkInDouban = $(this).find('div>div>h3>a').first()
  383. const nameInDouban = linkInDouban.text().replace(/\s/g, '')
  384. const subjectCast = $(this).find('span.subject-cast').text()
  385. if (nameToSearch === encodeURI(nameInDouban) && subjectCast && subjectCast.includes(year)) {
  386. link = linkInDouban.attr('href')
  387. return
  388. }
  389. })
  390. return link || doubanSearchLink
  391. } catch (err) {
  392. throw err
  393. }
  394. },
  395. /**
  396. * 获取豆瓣评分
  397. * @param {*} id 视频唯一标识
  398. * @param {*} name 视频名称
  399. * @param {*} year 视频年份
  400. * @returns 豆瓣评分
  401. */
  402. async doubanRate(id, name, year) {
  403. try {
  404. const link = await this.doubanLink(id, name, year);
  405. if (link.includes('https://www.douban.com/search')) {
  406. return '暂无评分';
  407. } else {
  408. const response = await axios.get(link);
  409. const parsedHtml = cheerio.load(response.data);
  410. const rating = parsedHtml('body').find('#interest_sectl').first().find('strong').first().text().replace(/\s/g, '');
  411. return rating || '暂无评分';
  412. // const rating = parsedHtml('body').find('#interest_sectl').first().find('strong').first();
  413. // if (rating.text()) {
  414. // return rating.text().replace(/\s/g, '');
  415. // } else {
  416. // return '暂无评分';
  417. // }
  418. }
  419. } catch (err) {
  420. throw err;
  421. }
  422. },
  423. /**
  424. * 获取豆瓣相关视频推荐列表
  425. * @param {*} id 视频唯一标识
  426. * @param {*} name 视频名称
  427. * @param {*} year 视频年份
  428. * @returns 豆瓣相关视频推荐列表
  429. */
  430. async doubanRecommendations(id, name, year) {
  431. try {
  432. const link = await this.doubanLink(id, name, year);
  433. if (link.includes('https://www.douban.com/search')) {
  434. return [];
  435. } else {
  436. const response = await axios.get(link);
  437. const $ = cheerio.load(response.data);
  438. const recommendations = $('div.recommendations-bd').find('div>dl>dd>a').map((index, element) => $(element).text()).get();
  439. return recommendations;
  440. }
  441. } catch (err) {
  442. throw err;
  443. }
  444. },
  445. /**
  446. * 获取豆瓣热点视频列表
  447. * @param {*} type 类型
  448. * @param {*} tag 标签
  449. * @param {*} limit 显示条数
  450. * @param {*} start 跳过
  451. * @returns 豆瓣热点视频推荐列表
  452. */
  453. async doubanHot(type, tag, limit = 20, start = 0) {
  454. const doubanHotLink = `https://movie.douban.com/j/search_subjects?type=${type}&tag=${encodeURI(tag)}&page_limit=${limit}&page_start=${start}`;
  455. try {
  456. const { data: { subjects } } = await axios.get(doubanHotLink);
  457. return subjects.map(item => ({
  458. vod_id: item.id,
  459. vod_name: item.title,
  460. vod_remarks: item.episodes_info,
  461. vod_pic: item.cover,
  462. }));
  463. } catch (err) {
  464. throw err;
  465. }
  466. },
  467. /**
  468. * 获取酷云热点视频列表
  469. * @param {*} date 日期2023-5-3
  470. * @param {*} type 类型 1.电影 2.剧集 3.综艺
  471. * @param {*} plat 平台 1.腾讯视频 2.爱奇艺 3.优酷 4.芒果
  472. * @returns 酷云热点视频推荐列表
  473. */
  474. async kuyunHot( date, type, plat) {
  475. const kuyunHotLink = `https://eye.kuyun.com/api/netplat/ranking?date=${date}&type=${type}&plat=${plat}`;
  476. try {
  477. const { data: { data: { list } } } = await axios.get(kuyunHotLink);
  478. return list.map(item => ({
  479. vod_id: item.ca_id,
  480. vod_name: item.name,
  481. vod_hot: item.num,
  482. }));
  483. } catch (err) {
  484. throw err;
  485. }
  486. },
  487. /**
  488. * 获取解析url链接的标题
  489. * @param {*} url 需要解析的地址
  490. * @returns 解析标题
  491. */
  492. async getAnalysizeTitle (url) {
  493. try {
  494. const res = await axios.get(url, { responseType: 'arraybuffer' });
  495. let html = '';
  496. if (url.includes('sohu')) {
  497. html = iconv.decode(Buffer.from(res.data), 'gb2312');
  498. } else {
  499. html = iconv.decode(Buffer.from(res.data), 'utf-8');
  500. }
  501. const $ = cheerio.load(html);
  502. return $("title").text();
  503. } catch (err) {
  504. throw err;
  505. }
  506. },
  507. /**
  508. * 获取配置文件
  509. * @param {*} url 需要获取的地址
  510. * @returns 配置文件
  511. */
  512. async getConfig(url) {
  513. try {
  514. const res = await axios.get(url);
  515. return res.data || false;
  516. } catch (err) {
  517. throw err;
  518. }
  519. },
  520. /**
  521. * 判断是否支持ipv6
  522. * @returns ture/false
  523. */
  524. async checkSupportIpv6() {
  525. try {
  526. const res = await axios.get('https://6.ipw.cn');
  527. const ip = res.data;
  528. const isIpv6 = /([0-9a-z]*:{1,4}){1,7}[0-9a-z]{1,4}/i.test(ip);
  529. return isIpv6;
  530. } catch (err) {
  531. throw err;
  532. }
  533. },
  534. /**
  535. * 判断url是否为ipv6
  536. * @returns ture/false
  537. */
  538. async checkUrlIpv6(url) {
  539. let hostname = new URL(url).hostname;
  540. const ipv6Regex = /^\[([\da-fA-F:]+)\]$/; // 匹配 IPv6 地址
  541. const match = ipv6Regex.exec(hostname);
  542. if(match){
  543. // console.log(match[1])
  544. hostname = match[1];
  545. }
  546. const ipType = net.isIP(hostname);
  547. if (ipType === 4) {
  548. // console.log(`1.ipv4:${hostname}`)
  549. return 'IPv4';
  550. } else if (ipType === 6) {
  551. // console.log(`1.ipv6:${hostname}`)
  552. return 'IPv6';
  553. } else {
  554. try {
  555. const addresses = await dns.promises.resolve(hostname);
  556. const ipType = net.isIP(addresses[0]);
  557. if (ipType === 4) {
  558. // console.log(`2.ipv4:${addresses[0]}`)
  559. return 'IPv4';
  560. } else if (ipType === 6) {
  561. // console.log(`2.ipv6:${addresses[0]}`)
  562. return 'IPv6';
  563. } else {
  564. return 'Unknown';
  565. }
  566. } catch (err) {
  567. console.log(url,hostname)
  568. throw err;
  569. }
  570. }
  571. }
  572. }
  573. export default zy