proxy-u-xxoo365-test.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. #!/usr/bin/env node
  2. 'use strict'
  3. /**
  4. * 测试通过 HTTP 代理访问 u.xxoo365.top(HTTPS)。
  5. *
  6. * 用法(项目根目录执行):
  7. * node scripts/proxy-u-xxoo365-test.js direct
  8. * node scripts/proxy-u-xxoo365-test.js proxy 103.217.191.15:30104
  9. * node scripts/proxy-u-xxoo365-test.js qg-fetch
  10. *
  11. * 可选环境变量:
  12. * TEST_URL=https://u.xxoo365.top/
  13. * TEST_TIMEOUT_MS=20000
  14. * TEST_METHOD=GET # GET / HEAD
  15. * TEST_PATH=/health # 会拼到 TEST_URL 的 origin 后
  16. * QG_AREA=... QG_AREA_EX=... QG_ISP=1|2|3 QG_DISTINCT=0|1
  17. */
  18. const fs = require('fs')
  19. const path = require('path')
  20. const axios = require('axios')
  21. const HttpsProxyAgent = require('https-proxy-agent')
  22. const QG_POOL_URL = 'https://share.proxy.qg.net/pool'
  23. const DEFAULT_BROWSER_UA =
  24. 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36'
  25. function readConfig() {
  26. const cfgPath = path.join(__dirname, '..', 'config.json')
  27. return JSON.parse(fs.readFileSync(cfgPath, 'utf8'))
  28. }
  29. function buildTargetUrl() {
  30. const base = (process.env.TEST_URL || 'https://u.xxoo365.top/').trim()
  31. const pathOverride = (process.env.TEST_PATH || '').trim()
  32. if (!pathOverride) return base
  33. const u = new URL(base)
  34. u.pathname = pathOverride.startsWith('/') ? pathOverride : `/${pathOverride}`
  35. return u.toString()
  36. }
  37. function normalizeMethod() {
  38. const m = (process.env.TEST_METHOD || 'GET').toUpperCase()
  39. return m === 'HEAD' ? 'HEAD' : 'GET'
  40. }
  41. function buildBrowserHeaders(url, method) {
  42. const u = new URL(url)
  43. const ua = String(process.env.TEST_UA || DEFAULT_BROWSER_UA).trim()
  44. const acceptLang = String(process.env.TEST_ACCEPT_LANGUAGE || 'zh-CN,zh;q=0.9,en;q=0.8').trim()
  45. const accept =
  46. method === 'HEAD'
  47. ? String(process.env.TEST_ACCEPT || '*/*').trim()
  48. : String(
  49. process.env.TEST_ACCEPT ||
  50. 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'
  51. ).trim()
  52. return {
  53. Accept: accept,
  54. 'Accept-Language': acceptLang,
  55. 'Cache-Control': 'no-cache',
  56. Pragma: 'no-cache',
  57. DNT: '1',
  58. 'Sec-Fetch-Dest': 'document',
  59. 'Sec-Fetch-Mode': 'navigate',
  60. 'Sec-Fetch-Site': 'none',
  61. 'Sec-Fetch-User': '?1',
  62. 'Upgrade-Insecure-Requests': '1',
  63. 'User-Agent': ua,
  64. Referer: String(process.env.TEST_REFERER || `${u.origin}/`).trim()
  65. }
  66. }
  67. function toProxyUrl(server, user, pass) {
  68. const [host, portRaw] = String(server || '').trim().split(':')
  69. const port = Number(portRaw)
  70. if (!host || !Number.isFinite(port)) {
  71. throw new Error(`代理地址非法: ${server}`)
  72. }
  73. let auth = ''
  74. if (user) {
  75. auth = `${encodeURIComponent(user)}:${encodeURIComponent(pass || '')}@`
  76. }
  77. return `http://${auth}${host}:${port}`
  78. }
  79. async function fetchProxyFromQg(cfg) {
  80. const q = cfg.qgChannelProxy || {}
  81. const key = String(q.extractKey || '').trim()
  82. if (!key) {
  83. throw new Error('config.json 缺少 qgChannelProxy.extractKey')
  84. }
  85. const params = { key, num: 1 }
  86. const area = String(process.env.QG_AREA || '').trim()
  87. const areaEx = String(process.env.QG_AREA_EX || '').trim()
  88. if (area) params.area = area
  89. if (areaEx) params.area_ex = areaEx
  90. if (['1', '2', '3'].includes(String(process.env.QG_ISP || ''))) {
  91. params.isp = Number(process.env.QG_ISP)
  92. }
  93. params.distinct = String(process.env.QG_DISTINCT || '1') !== '0'
  94. const res = await axios.get(QG_POOL_URL, {
  95. params,
  96. timeout: 20000,
  97. proxy: false,
  98. validateStatus: () => true
  99. })
  100. const body = res.data || {}
  101. if (body.code !== 'SUCCESS' || !Array.isArray(body.data) || !body.data[0]?.server) {
  102. throw new Error(`青果 /pool 失败: ${body.code || JSON.stringify(body).slice(0, 180)}`)
  103. }
  104. return {
  105. server: body.data[0].server,
  106. proxy_ip: body.data[0].proxy_ip || null,
  107. deadline: body.data[0].deadline || null,
  108. request_id: body.request_id || null
  109. }
  110. }
  111. async function probe(label, requestConfig) {
  112. const timeout = Number(process.env.TEST_TIMEOUT_MS || 20000)
  113. const url = buildTargetUrl()
  114. const method = normalizeMethod()
  115. const headers = buildBrowserHeaders(url, method)
  116. const t0 = Date.now()
  117. console.log(`\n=== ${label} ===`)
  118. console.log('REQUEST:', method, url)
  119. try {
  120. const res = await axios({
  121. url,
  122. method,
  123. timeout,
  124. validateStatus: () => true,
  125. headers,
  126. ...requestConfig
  127. })
  128. const ms = Date.now() - t0
  129. console.log('RESULT: OK', `${ms}ms`, 'HTTP', res.status)
  130. if (method !== 'HEAD') {
  131. const body = typeof res.data === 'string' ? res.data : JSON.stringify(res.data)
  132. console.log('BODY_PREVIEW:', body.slice(0, 200).replace(/\s+/g, ' '))
  133. }
  134. } catch (e) {
  135. const ms = Date.now() - t0
  136. console.log('RESULT: FAIL', `${ms}ms`)
  137. console.log('message:', e.message)
  138. console.log('code:', e.code)
  139. if (e.response) console.log('http_status:', e.response.status)
  140. }
  141. }
  142. async function main() {
  143. const mode = (process.argv[2] || 'direct').toLowerCase()
  144. const cfg = readConfig()
  145. const q = cfg.qgChannelProxy || {}
  146. const user = String(process.env.QG_PROXY_USER || q.authUser || '').trim()
  147. const pass = String(process.env.QG_PROXY_PASSWORD || q.authPassword || '').trim()
  148. const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
  149. if (mode === 'direct') {
  150. await probe('直连', { proxy: false })
  151. return
  152. }
  153. let server
  154. if (mode === 'proxy') {
  155. server = process.argv[3]
  156. if (!server) throw new Error('proxy 模式需传 host:port')
  157. } else if (mode === 'qg-fetch') {
  158. const got = await fetchProxyFromQg(cfg)
  159. server = got.server
  160. console.log(
  161. `[Qg] server=${got.server} proxy_ip=${got.proxy_ip || '—'} deadline=${got.deadline || '—'} request_id=${got.request_id || '—'}`
  162. )
  163. } else {
  164. throw new Error('模式仅支持: direct | proxy <host:port> | qg-fetch')
  165. }
  166. const proxyUrl = toProxyUrl(server, user, pass)
  167. const httpsAgent = new HttpsProxyAgent(proxyUrl, { rejectUnauthorized })
  168. await probe(`代理 ${server}`, { proxy: false, httpsAgent })
  169. }
  170. main().catch(err => {
  171. console.error(err.stack || err)
  172. process.exit(1)
  173. })