#!/usr/bin/env node 'use strict' /** * 测试通过 HTTP 代理访问 u.xxoo365.top(HTTPS)。 * * 用法(项目根目录执行): * node scripts/proxy-u-xxoo365-test.js direct * node scripts/proxy-u-xxoo365-test.js proxy 103.217.191.15:30104 * node scripts/proxy-u-xxoo365-test.js qg-fetch * * 可选环境变量: * TEST_URL=https://u.xxoo365.top/ * TEST_TIMEOUT_MS=20000 * TEST_METHOD=GET # GET / HEAD * TEST_PATH=/health # 会拼到 TEST_URL 的 origin 后 * QG_AREA=... QG_AREA_EX=... QG_ISP=1|2|3 QG_DISTINCT=0|1 */ const fs = require('fs') const path = require('path') const axios = require('axios') const HttpsProxyAgent = require('https-proxy-agent') const QG_GET_URL = 'https://share.proxy.qg.net/get' const DEFAULT_BROWSER_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36' function readConfig() { const cfgPath = path.join(__dirname, '..', 'config.json') return JSON.parse(fs.readFileSync(cfgPath, 'utf8')) } function buildTargetUrl() { const base = (process.env.TEST_URL || 'https://u.xxoo365.top/').trim() const pathOverride = (process.env.TEST_PATH || '').trim() if (!pathOverride) return base const u = new URL(base) u.pathname = pathOverride.startsWith('/') ? pathOverride : `/${pathOverride}` return u.toString() } function normalizeMethod() { const m = (process.env.TEST_METHOD || 'GET').toUpperCase() return m === 'HEAD' ? 'HEAD' : 'GET' } function buildBrowserHeaders(url, method) { const u = new URL(url) const ua = String(process.env.TEST_UA || DEFAULT_BROWSER_UA).trim() const acceptLang = String(process.env.TEST_ACCEPT_LANGUAGE || 'zh-CN,zh;q=0.9,en;q=0.8').trim() const accept = method === 'HEAD' ? String(process.env.TEST_ACCEPT || '*/*').trim() : String( process.env.TEST_ACCEPT || 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' ).trim() return { Accept: accept, 'Accept-Language': acceptLang, 'Cache-Control': 'no-cache', Pragma: 'no-cache', DNT: '1', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1', 'User-Agent': ua, Referer: String(process.env.TEST_REFERER || `${u.origin}/`).trim() } } function toProxyUrl(server, user, pass) { const [host, portRaw] = String(server || '').trim().split(':') const port = Number(portRaw) if (!host || !Number.isFinite(port)) { throw new Error(`代理地址非法: ${server}`) } let auth = '' if (user) { auth = `${encodeURIComponent(user)}:${encodeURIComponent(pass || '')}@` } return `http://${auth}${host}:${port}` } async function fetchProxyFromQg(cfg) { const q = cfg.qgChannelProxy || {} const key = String(q.extractKey || '').trim() if (!key) { throw new Error('config.json 缺少 qgChannelProxy.extractKey') } const params = { key, num: 1 } const area = String(process.env.QG_AREA || '').trim() const areaEx = String(process.env.QG_AREA_EX || '').trim() if (area) params.area = area if (areaEx) params.area_ex = areaEx if (['1', '2', '3'].includes(String(process.env.QG_ISP || ''))) { params.isp = Number(process.env.QG_ISP) } params.distinct = String(process.env.QG_DISTINCT || '1') !== '0' const res = await axios.get(QG_GET_URL, { params, timeout: 20000, proxy: false, validateStatus: () => true }) const body = res.data || {} if (body.code !== 'SUCCESS' || !Array.isArray(body.data) || !body.data[0]?.server) { throw new Error(`青果 /get 失败: ${body.code || JSON.stringify(body).slice(0, 180)}`) } return { server: body.data[0].server, proxy_ip: body.data[0].proxy_ip || null, deadline: body.data[0].deadline || null, request_id: body.request_id || null } } async function probe(label, requestConfig) { const timeout = Number(process.env.TEST_TIMEOUT_MS || 20000) const url = buildTargetUrl() const method = normalizeMethod() const headers = buildBrowserHeaders(url, method) const t0 = Date.now() console.log(`\n=== ${label} ===`) console.log('REQUEST:', method, url) try { const res = await axios({ url, method, timeout, validateStatus: () => true, headers, ...requestConfig }) const ms = Date.now() - t0 console.log('RESULT: OK', `${ms}ms`, 'HTTP', res.status) if (method !== 'HEAD') { const body = typeof res.data === 'string' ? res.data : JSON.stringify(res.data) console.log('BODY_PREVIEW:', body.slice(0, 200).replace(/\s+/g, ' ')) } } catch (e) { const ms = Date.now() - t0 console.log('RESULT: FAIL', `${ms}ms`) console.log('message:', e.message) console.log('code:', e.code) if (e.response) console.log('http_status:', e.response.status) } } async function main() { const mode = (process.argv[2] || 'direct').toLowerCase() const cfg = readConfig() const q = cfg.qgChannelProxy || {} const user = String(process.env.QG_PROXY_USER || q.authUser || '').trim() const pass = String(process.env.QG_PROXY_PASSWORD || q.authPassword || '').trim() const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0' if (mode === 'direct') { await probe('直连', { proxy: false }) return } let server if (mode === 'proxy') { server = process.argv[3] if (!server) throw new Error('proxy 模式需传 host:port') } else if (mode === 'qg-fetch') { const got = await fetchProxyFromQg(cfg) server = got.server console.log( `[Qg] server=${got.server} proxy_ip=${got.proxy_ip || '—'} deadline=${got.deadline || '—'} request_id=${got.request_id || '—'}` ) } else { throw new Error('模式仅支持: direct | proxy | qg-fetch') } const proxyUrl = toProxyUrl(server, user, pass) const httpsAgent = new HttpsProxyAgent(proxyUrl, { rejectUnauthorized }) await probe(`代理 ${server}`, { proxy: false, httpsAgent }) } main().catch(err => { console.error(err.stack || err) process.exit(1) })