proxy-u-xxoo365-test.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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_GET_URL = 'https://share.proxy.qg.net/get'
  23. function readConfig() {
  24. const cfgPath = path.join(__dirname, '..', 'config.json')
  25. return JSON.parse(fs.readFileSync(cfgPath, 'utf8'))
  26. }
  27. function buildTargetUrl() {
  28. const base = (process.env.TEST_URL || 'https://u.xxoo365.top/').trim()
  29. const pathOverride = (process.env.TEST_PATH || '').trim()
  30. if (!pathOverride) return base
  31. const u = new URL(base)
  32. u.pathname = pathOverride.startsWith('/') ? pathOverride : `/${pathOverride}`
  33. return u.toString()
  34. }
  35. function normalizeMethod() {
  36. const m = (process.env.TEST_METHOD || 'GET').toUpperCase()
  37. return m === 'HEAD' ? 'HEAD' : 'GET'
  38. }
  39. function toProxyUrl(server, user, pass) {
  40. const [host, portRaw] = String(server || '').trim().split(':')
  41. const port = Number(portRaw)
  42. if (!host || !Number.isFinite(port)) {
  43. throw new Error(`代理地址非法: ${server}`)
  44. }
  45. let auth = ''
  46. if (user) {
  47. auth = `${encodeURIComponent(user)}:${encodeURIComponent(pass || '')}@`
  48. }
  49. return `http://${auth}${host}:${port}`
  50. }
  51. async function fetchProxyFromQg(cfg) {
  52. const q = cfg.qgChannelProxy || {}
  53. const key = String(q.extractKey || '').trim()
  54. if (!key) {
  55. throw new Error('config.json 缺少 qgChannelProxy.extractKey')
  56. }
  57. const params = { key, num: 1 }
  58. const area = String(process.env.QG_AREA || '').trim()
  59. const areaEx = String(process.env.QG_AREA_EX || '').trim()
  60. if (area) params.area = area
  61. if (areaEx) params.area_ex = areaEx
  62. if (['1', '2', '3'].includes(String(process.env.QG_ISP || ''))) {
  63. params.isp = Number(process.env.QG_ISP)
  64. }
  65. params.distinct = String(process.env.QG_DISTINCT || '1') !== '0'
  66. const res = await axios.get(QG_GET_URL, {
  67. params,
  68. timeout: 20000,
  69. proxy: false,
  70. validateStatus: () => true
  71. })
  72. const body = res.data || {}
  73. if (body.code !== 'SUCCESS' || !Array.isArray(body.data) || !body.data[0]?.server) {
  74. throw new Error(`青果 /get 失败: ${body.code || JSON.stringify(body).slice(0, 180)}`)
  75. }
  76. return {
  77. server: body.data[0].server,
  78. proxy_ip: body.data[0].proxy_ip || null,
  79. deadline: body.data[0].deadline || null,
  80. request_id: body.request_id || null
  81. }
  82. }
  83. async function probe(label, requestConfig) {
  84. const timeout = Number(process.env.TEST_TIMEOUT_MS || 20000)
  85. const url = buildTargetUrl()
  86. const method = normalizeMethod()
  87. const t0 = Date.now()
  88. console.log(`\n=== ${label} ===`)
  89. console.log('REQUEST:', method, url)
  90. try {
  91. const res = await axios({
  92. url,
  93. method,
  94. timeout,
  95. validateStatus: () => true,
  96. ...requestConfig
  97. })
  98. const ms = Date.now() - t0
  99. console.log('RESULT: OK', `${ms}ms`, 'HTTP', res.status)
  100. if (method !== 'HEAD') {
  101. const body = typeof res.data === 'string' ? res.data : JSON.stringify(res.data)
  102. console.log('BODY_PREVIEW:', body.slice(0, 200).replace(/\s+/g, ' '))
  103. }
  104. } catch (e) {
  105. const ms = Date.now() - t0
  106. console.log('RESULT: FAIL', `${ms}ms`)
  107. console.log('message:', e.message)
  108. console.log('code:', e.code)
  109. if (e.response) console.log('http_status:', e.response.status)
  110. }
  111. }
  112. async function main() {
  113. const mode = (process.argv[2] || 'direct').toLowerCase()
  114. const cfg = readConfig()
  115. const q = cfg.qgChannelProxy || {}
  116. const user = String(process.env.QG_PROXY_USER || q.authUser || '').trim()
  117. const pass = String(process.env.QG_PROXY_PASSWORD || q.authPassword || '').trim()
  118. const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0'
  119. if (mode === 'direct') {
  120. await probe('直连', { proxy: false })
  121. return
  122. }
  123. let server
  124. if (mode === 'proxy') {
  125. server = process.argv[3]
  126. if (!server) throw new Error('proxy 模式需传 host:port')
  127. } else if (mode === 'qg-fetch') {
  128. const got = await fetchProxyFromQg(cfg)
  129. server = got.server
  130. console.log(
  131. `[Qg] server=${got.server} proxy_ip=${got.proxy_ip || '—'} deadline=${got.deadline || '—'} request_id=${got.request_id || '—'}`
  132. )
  133. } else {
  134. throw new Error('模式仅支持: direct | proxy <host:port> | qg-fetch')
  135. }
  136. const proxyUrl = toProxyUrl(server, user, pass)
  137. const httpsAgent = new HttpsProxyAgent(proxyUrl, { rejectUnauthorized })
  138. await probe(`代理 ${server}`, { proxy: false, httpsAgent })
  139. }
  140. main().catch(err => {
  141. console.error(err.stack || err)
  142. process.exit(1)
  143. })