build-speedtest-domainset.ts 5.6 KB

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