build-speedtest-domainset.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import { domainDeduper } from './lib/domain-deduper';
  2. import path from 'path';
  3. import { createRuleset } from './lib/create-file';
  4. import { sortDomains } from './lib/stable-sort-domain';
  5. import { Sema } from 'async-sema';
  6. import * as tldts from 'tldts';
  7. import { task } from './lib/trace-runner';
  8. import { fetchWithRetry } from './lib/fetch-retry';
  9. import { SHARED_DESCRIPTION } from './lib/constants';
  10. import { getGorhillPublicSuffixPromise } from './lib/get-gorhill-publicsuffix';
  11. import picocolors from 'picocolors';
  12. import { fetchRemoteTextByLine } from './lib/fetch-text-by-line';
  13. import { processLine } from './lib/process-line';
  14. const s = new Sema(2);
  15. const latestTopUserAgentsPromise = fetchWithRetry('https://unpkg.com/top-user-agents@latest/index.json')
  16. .then(res => res.json<string[]>());
  17. const querySpeedtestApi = async (keyword: string): Promise<Array<string | null>> => {
  18. const topUserAgents = (await Promise.all([
  19. latestTopUserAgentsPromise,
  20. s.acquire()
  21. ]))[0];
  22. try {
  23. const randomUserAgent = topUserAgents[Math.floor(Math.random() * topUserAgents.length)];
  24. const key = `fetch speedtest endpoints: ${keyword}`;
  25. console.log(key);
  26. console.time(key);
  27. const res = await fetchWithRetry(`https://www.speedtest.net/api/js/servers?engine=js&search=${keyword}&limit=100`, {
  28. headers: {
  29. dnt: '1',
  30. Referer: 'https://www.speedtest.net/',
  31. accept: 'application/json, text/plain, */*',
  32. 'User-Agent': randomUserAgent,
  33. 'Accept-Language': 'en-US,en;q=0.9',
  34. ...(randomUserAgent.includes('Chrome')
  35. ? {
  36. 'Sec-Ch-Ua-Mobile': '?0',
  37. 'Sec-Fetch-Dest': 'empty',
  38. 'Sec-Fetch-Mode': 'cors',
  39. 'Sec-Fetch-Site': 'same-origin',
  40. 'Sec-Gpc': '1'
  41. }
  42. : {})
  43. },
  44. signal: AbortSignal.timeout(1000 * 4),
  45. retry: {
  46. retries: 2
  47. }
  48. });
  49. const json = await res.json<Array<{ url: string }>>();
  50. console.timeEnd(key);
  51. return json.map(({ url }) => tldts.getHostname(url, { detectIp: false }));
  52. } catch (e) {
  53. console.log(e);
  54. return [];
  55. } finally {
  56. s.release();
  57. }
  58. };
  59. export const buildSpeedtestDomainSet = task(import.meta.path, async () => {
  60. // Predefined domainset
  61. /** @type {Set<string>} */
  62. const domains = new Set<string>([
  63. '.speedtest.net',
  64. '.speedtestcustom.com',
  65. '.ooklaserver.net',
  66. '.speed.misaka.one',
  67. '.speed.cloudflare.com',
  68. '.speedtest.rt.ru',
  69. '.speedtest.aptg.com.tw',
  70. '.speedtest.gslnetworks.com',
  71. '.speedtest.jsinfo.net',
  72. '.speedtest.i3d.net',
  73. '.speedtestkorea.com',
  74. '.speedtest.telus.com',
  75. '.speedtest.telstra.net',
  76. '.speedtest.clouvider.net',
  77. '.speedtest.idv.tw',
  78. '.speedtest.frontier.com',
  79. '.speedtest.orange.fr',
  80. '.speedtest.centurylink.net',
  81. '.srvr.bell.ca',
  82. '.speedtest.contabo.net',
  83. 'speedtest.hk.chinamobile.com',
  84. 'speedtestbb.hk.chinamobile.com',
  85. '.hizinitestet.com',
  86. '.linknetspeedtest.net.br',
  87. 'speedtest.rit.edu',
  88. 'speedtest.ropa.de',
  89. 'speedtest.sits.su',
  90. 'speedtest.tigo.cr',
  91. 'speedtest.upp.com',
  92. '.speedtest.pni.tw',
  93. '.speed.pfm.gg',
  94. '.speedtest.faelix.net',
  95. '.speedtest.labixe.net',
  96. '.speedtest.warian.net',
  97. '.speedtest.starhub.com',
  98. '.speedtest.gibir.net.tr',
  99. '.speedtest.ozarksgo.net',
  100. '.speedtest.exetel.com.au',
  101. '.speedtest.sbcglobal.net',
  102. '.speedtest.leaptel.com.au',
  103. '.speedtest.windstream.net',
  104. '.speedtest.vodafone.com.au',
  105. '.speedtest.rascom.ru',
  106. '.speedtest.dchost.com',
  107. '.speedtest.highnet.com',
  108. '.speedtest.seattle.wa.limewave.net',
  109. '.speedtest.optitel.com.au',
  110. '.speednet.net.tr',
  111. '.speedtest.angolacables.co.ao',
  112. // Fast.com
  113. '.fast.com',
  114. // MacPaw
  115. 'speedtest.macpaw.com',
  116. // speedtestmaster
  117. '.netspeedtestmaster.com',
  118. // Google Search Result of "speedtest", powered by this
  119. '.measurement-lab.org',
  120. // Google Fiber legacy speedtest site (new fiber speedtest use speedtestcustom.com)
  121. '.speed.googlefiber.net',
  122. // librespeed
  123. '.backend.librespeed.org'
  124. ]);
  125. // Download previous speedtest domainset
  126. for await (const l of await fetchRemoteTextByLine('https://ruleset.skk.moe/List/domainset/speedtest.conf')) {
  127. const line = processLine(l);
  128. if (line) {
  129. domains.add(line);
  130. }
  131. }
  132. let timer;
  133. const pMap = ([
  134. 'Hong Kong',
  135. 'Taiwan',
  136. 'China Telecom',
  137. 'China Mobile',
  138. 'China Unicom',
  139. 'Japan',
  140. 'Tokyo',
  141. 'Singapore',
  142. 'Korea',
  143. 'Canada',
  144. 'Toronto',
  145. 'Montreal',
  146. 'Los Ang',
  147. 'San Jos',
  148. 'Seattle',
  149. 'New York',
  150. 'Dallas',
  151. 'Miami',
  152. 'Berlin',
  153. 'Frankfurt',
  154. 'London',
  155. 'Paris',
  156. 'Amsterdam',
  157. 'Moscow',
  158. 'Australia',
  159. 'Sydney',
  160. 'Brazil',
  161. 'Turkey'
  162. ]).reduce<Record<string, Promise<void>>>((pMap, keyword) => {
  163. pMap[keyword] = querySpeedtestApi(keyword).then(hostnameGroup => {
  164. hostnameGroup.forEach(hostname => {
  165. if (hostname) {
  166. domains.add(hostname);
  167. }
  168. });
  169. });
  170. return pMap;
  171. }, {});
  172. try {
  173. timer = setTimeout(() => {
  174. console.error(picocolors.red('Task timeout!'));
  175. Object.entries(pMap).forEach(([name, p]) => {
  176. console.log(`[${name}]`, Bun.peek.status(p));
  177. });
  178. throw new Error('timeout');
  179. }, 1000 * 60 * 2);
  180. await Promise.all(Object.values(pMap));
  181. } finally {
  182. if (timer) {
  183. clearTimeout(timer);
  184. }
  185. }
  186. const gorhill = await getGorhillPublicSuffixPromise();
  187. const deduped = sortDomains(domainDeduper(Array.from(domains)), gorhill);
  188. const description = [
  189. ...SHARED_DESCRIPTION,
  190. '',
  191. 'This file contains common speedtest endpoints.'
  192. ];
  193. return Promise.all(createRuleset(
  194. 'Sukka\'s Ruleset - Speedtest Domains',
  195. description,
  196. new Date(),
  197. deduped,
  198. 'domainset',
  199. path.resolve(import.meta.dir, '../List/domainset/speedtest.conf'),
  200. path.resolve(import.meta.dir, '../Clash/domainset/speedtest.txt')
  201. ));
  202. });
  203. if (import.meta.main) {
  204. buildSpeedtestDomainSet();
  205. }