build-speedtest-domainset.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import { domainDeduper } from './lib/domain-deduper';
  2. import path from 'node:path';
  3. import { createRuleset } from './lib/create-file';
  4. import { sortDomains } from './lib/stable-sort-domain';
  5. import { Sema } from 'async-sema';
  6. import { getHostname } from 'tldts';
  7. import { task } from './trace';
  8. import { fetchWithRetry } from './lib/fetch-retry';
  9. import { SHARED_DESCRIPTION } from './lib/constants';
  10. import { readFileIntoProcessedArray } from './lib/fetch-text-by-line';
  11. import { TTL, deserializeArray, fsFetchCache, serializeArray, createCacheKey } from './lib/cache-filesystem';
  12. import { createTrie } from './lib/trie';
  13. import { output } from './lib/misc';
  14. const s = new Sema(2);
  15. const cacheKey = createCacheKey(__filename);
  16. const latestTopUserAgentsPromise = fsFetchCache.apply(
  17. cacheKey('https://cdn.jsdelivr.net/npm/top-user-agents@latest/src/desktop.json'),
  18. () => fetchWithRetry(
  19. 'https://cdn.jsdelivr.net/npm/top-user-agents@latest/src/desktop.json',
  20. { signal: AbortSignal.timeout(1000 * 60) }
  21. )
  22. .then(res => res.json() as Promise<string[]>)
  23. .then((userAgents) => userAgents.filter(ua => ua.startsWith('Mozilla/5.0 '))),
  24. {
  25. serializer: serializeArray,
  26. deserializer: deserializeArray,
  27. ttl: TTL.THREE_DAYS()
  28. }
  29. );
  30. const querySpeedtestApi = async (keyword: string): Promise<Array<string | null>> => {
  31. const topUserAgents = await latestTopUserAgentsPromise;
  32. const url = `https://www.speedtest.net/api/js/servers?engine=js&search=${keyword}&limit=100`;
  33. try {
  34. const randomUserAgent = topUserAgents[Math.floor(Math.random() * topUserAgents.length)];
  35. return await fsFetchCache.apply(
  36. cacheKey(url),
  37. () => s.acquire().then(() => fetchWithRetry(url, {
  38. headers: {
  39. dnt: '1',
  40. Referer: 'https://www.speedtest.net/',
  41. accept: 'application/json, text/plain, */*',
  42. 'User-Agent': randomUserAgent,
  43. 'Accept-Language': 'en-US,en;q=0.9',
  44. ...(randomUserAgent.includes('Chrome')
  45. ? {
  46. 'Sec-Ch-Ua-Mobile': '?0',
  47. 'Sec-Fetch-Dest': 'empty',
  48. 'Sec-Fetch-Mode': 'cors',
  49. 'Sec-Fetch-Site': 'same-origin',
  50. 'Sec-Gpc': '1'
  51. }
  52. : {})
  53. },
  54. signal: AbortSignal.timeout(1000 * 60),
  55. retry: {
  56. retries: 2
  57. }
  58. })).then(r => r.json() as any).then((data: Array<{ url: string, host: string }>) => data.reduce<string[]>(
  59. (prev, cur) => {
  60. const line = cur.host || cur.url;
  61. const hn = getHostname(line, { detectIp: false, validateHostname: true });
  62. if (hn) {
  63. prev.push(hn);
  64. }
  65. return prev;
  66. }, []
  67. )).finally(() => s.release()),
  68. {
  69. ttl: TTL.ONE_WEEK(),
  70. serializer: serializeArray,
  71. deserializer: deserializeArray
  72. }
  73. );
  74. } catch (e) {
  75. console.error(e);
  76. return [];
  77. }
  78. };
  79. export const buildSpeedtestDomainSet = task(require.main === module, __filename)(async (span) => {
  80. const domainTrie = createTrie(
  81. [
  82. // speedtest.net
  83. '.speedtest.net',
  84. '.speedtestcustom.com',
  85. '.ooklaserver.net',
  86. '.speed.misaka.one',
  87. '.speedtest.rt.ru',
  88. '.speedtest.aptg.com.tw',
  89. '.speedtest.gslnetworks.com',
  90. '.speedtest.jsinfo.net',
  91. '.speedtest.i3d.net',
  92. '.speedtestkorea.com',
  93. '.speedtest.telus.com',
  94. '.speedtest.telstra.net',
  95. '.speedtest.clouvider.net',
  96. '.speedtest.idv.tw',
  97. '.speedtest.frontier.com',
  98. '.speedtest.orange.fr',
  99. '.speedtest.centurylink.net',
  100. '.srvr.bell.ca',
  101. '.speedtest.contabo.net',
  102. 'speedtest.hk.chinamobile.com',
  103. 'speedtestbb.hk.chinamobile.com',
  104. '.hizinitestet.com',
  105. '.linknetspeedtest.net.br',
  106. 'speedtest.rit.edu',
  107. 'speedtest.ropa.de',
  108. 'speedtest.sits.su',
  109. 'speedtest.tigo.cr',
  110. 'speedtest.upp.com',
  111. '.speedtest.pni.tw',
  112. '.speed.pfm.gg',
  113. '.speedtest.faelix.net',
  114. '.speedtest.labixe.net',
  115. '.speedtest.warian.net',
  116. '.speedtest.starhub.com',
  117. '.speedtest.gibir.net.tr',
  118. '.speedtest.ozarksgo.net',
  119. '.speedtest.exetel.com.au',
  120. '.speedtest.sbcglobal.net',
  121. '.speedtest.leaptel.com.au',
  122. '.speedtest.windstream.net',
  123. '.speedtest.vodafone.com.au',
  124. '.speedtest.rascom.ru',
  125. '.speedtest.dchost.com',
  126. '.speedtest.highnet.com',
  127. '.speedtest.seattle.wa.limewave.net',
  128. '.speedtest.optitel.com.au',
  129. '.speednet.net.tr',
  130. '.speedtest.angolacables.co.ao',
  131. '.ookla-speedtest.fsr.com',
  132. '.speedtest.comnet.com.tr',
  133. '.speedtest.gslnetworks.com.au',
  134. '.test.gslnetworks.com.au',
  135. '.speedtest.gslnetworks.com',
  136. '.speedtestunonet.com.br',
  137. '.speedtest.alagas.net',
  138. 'speedtest.surfshark.com',
  139. '.speedtest.aarnet.net.au',
  140. '.ookla.rcp.net',
  141. '.ookla-speedtests.e2ro.com',
  142. '.speedtest.com.sg',
  143. '.ookla.ddnsgeek.com',
  144. '.speedtest.pni.tw',
  145. '.speedtest.cmcnetworks.net',
  146. '.speedtestwnet.com.br',
  147. // Cloudflare
  148. '.speed.cloudflare.com',
  149. // Wi-Fi Man
  150. '.wifiman.com',
  151. '.wifiman.me',
  152. '.wifiman.ubncloud.com',
  153. // Fast.com
  154. '.fast.com',
  155. // MacPaw
  156. 'speedtest.macpaw.com',
  157. // speedtestmaster
  158. '.netspeedtestmaster.com',
  159. // Google Search Result of "speedtest", powered by this
  160. '.measurement-lab.org',
  161. '.measurementlab.net',
  162. // Google Fiber legacy speedtest site (new fiber speedtest use speedtestcustom.com)
  163. '.speed.googlefiber.net',
  164. // librespeed
  165. '.backend.librespeed.org',
  166. // Apple,
  167. 'mensura.cdn-apple.com', // From netQuality command
  168. // OpenSpeedtest
  169. 'open.cachefly.net'
  170. ],
  171. true,
  172. true
  173. );
  174. await span.traceChildAsync(
  175. 'fetch previous speedtest domainset',
  176. async () => {
  177. try {
  178. (
  179. await readFileIntoProcessedArray(path.resolve(__dirname, '../List/domainset/speedtest.conf'))
  180. ) .forEach(line => {
  181. const hn = getHostname(line, { detectIp: false, validateHostname: true });
  182. if (hn) {
  183. domainTrie.add(hn);
  184. }
  185. });
  186. } catch { }
  187. }
  188. );
  189. await Promise.all([
  190. 'Hong Kong',
  191. 'Taiwan',
  192. 'China Telecom',
  193. 'China Mobile',
  194. 'China Unicom',
  195. 'Japan',
  196. 'Tokyo',
  197. 'Singapore',
  198. 'Korea',
  199. 'Seoul',
  200. 'Canada',
  201. 'Toronto',
  202. 'Montreal',
  203. 'Los Ang',
  204. 'San Jos',
  205. 'Seattle',
  206. 'New York',
  207. 'Dallas',
  208. 'Miami',
  209. 'Berlin',
  210. 'Frankfurt',
  211. 'London',
  212. 'Paris',
  213. 'Amsterdam',
  214. 'Moscow',
  215. 'Australia',
  216. 'Sydney',
  217. 'Brazil',
  218. 'Turkey'
  219. ].map((keyword) => span.traceChildAsync(
  220. `fetch speedtest endpoints: ${keyword}`,
  221. () => querySpeedtestApi(keyword)
  222. ).then(hostnameGroup => hostnameGroup.forEach(hostname => {
  223. if (hostname) {
  224. domainTrie.add(hostname);
  225. }
  226. }))));
  227. const deduped = span.traceChildSync('sort result', () => sortDomains(domainDeduper(domainTrie)));
  228. const description = [
  229. ...SHARED_DESCRIPTION,
  230. '',
  231. 'This file contains common speedtest endpoints.'
  232. ];
  233. return createRuleset(
  234. span,
  235. 'Sukka\'s Ruleset - Speedtest Domains',
  236. description,
  237. new Date(),
  238. deduped,
  239. 'domainset',
  240. ...output('speedtest', 'domainset')
  241. );
  242. });