@swimmingliu/autovpn 1.6.4 → 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/pipeline/availability.js +106 -1
- package/dist/web/renderer/i18n.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ Current Node backend notes:
|
|
|
72
72
|
- Add `--skip-deploy --skip-verify` when you want an offline Node pipeline check.
|
|
73
73
|
- Plain Node foreground deploy/verify runs use Node for Wrangler deploy, primary blocked-project fallback, share-project sync/fallback, custom-domain binding, custom-domain DNS upsert, and verify.
|
|
74
74
|
- Deploy and verify can be rolled back with `AUTOVPN_STAGE_BACKEND_DEPLOY=python` and `AUTOVPN_STAGE_BACKEND_VERIFY=python`.
|
|
75
|
-
- `AUTOVPN_NO_PYTHON=1` disables implicit Python backend resolution and default Python runtime stage fallback. Use it as a v3 readiness gate. Empty offline runs now complete in Node
|
|
75
|
+
- `AUTOVPN_NO_PYTHON=1` disables implicit Python backend resolution and default Python runtime stage fallback. Use it as a v3 readiness gate. Empty offline runs now complete in Node. Speedtest and availability use the per-node Mihomo runtime by default so candidate measurements and provider checks traverse each candidate node. Set `AUTOVPN_SPEEDTEST_RUNTIME=direct` or `AUTOVPN_AVAILABILITY_RUNTIME=direct` only for direct-host diagnostic checks.
|
|
76
76
|
- Project `.env` is loaded before resolving profile and artifact paths. Explicit process environment values still win over `.env`.
|
|
77
77
|
- `autovpn serve` is Node-native and exposes the browser UI plus `/api/health`,
|
|
78
78
|
`/api/state`, `/api/runs`, `/api/runs/current/stop`, and `/api/events`.
|
|
@@ -17,6 +17,9 @@ const CHALLENGE_PHRASES = [
|
|
|
17
17
|
'verify you are human',
|
|
18
18
|
'enable javascript and cookies'
|
|
19
19
|
];
|
|
20
|
+
const GEMINI_REGION_MARKERS = [',2,1,200,"', ',2,1,200,\\"'];
|
|
21
|
+
const CLAUDE_BLOCKED_CODES = new Set(['AF', 'BY', 'CN', 'CU', 'HK', 'IR', 'KP', 'MO', 'RU', 'SY']);
|
|
22
|
+
const GEMINI_BLOCKED_CODES = new Set(['CHN', 'RUS', 'BLR', 'CUB', 'IRN', 'PRK', 'SYR', 'HKG', 'MAC']);
|
|
20
23
|
const PYTHON_AVAILABILITY_HELPER = `
|
|
21
24
|
import json
|
|
22
25
|
import sys
|
|
@@ -75,6 +78,30 @@ function hostnameFor(url) {
|
|
|
75
78
|
return '';
|
|
76
79
|
}
|
|
77
80
|
}
|
|
81
|
+
function targetKey(target) {
|
|
82
|
+
return target.name.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_');
|
|
83
|
+
}
|
|
84
|
+
function extractTraceLocation(body) {
|
|
85
|
+
for (const line of body.split(/\r?\n/)) {
|
|
86
|
+
if (line.startsWith('loc=')) {
|
|
87
|
+
return line.slice(4).trim().toUpperCase();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return '';
|
|
91
|
+
}
|
|
92
|
+
function extractGeminiCountryCode(body) {
|
|
93
|
+
for (const marker of GEMINI_REGION_MARKERS) {
|
|
94
|
+
const index = body.indexOf(marker);
|
|
95
|
+
if (index < 0) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const code = body.slice(index + marker.length, index + marker.length + 3);
|
|
99
|
+
if (/^[A-Z]{3}$/.test(code)) {
|
|
100
|
+
return code;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return '';
|
|
104
|
+
}
|
|
78
105
|
export function normalizeProviderTargets(targets) {
|
|
79
106
|
if (targets == null) {
|
|
80
107
|
return PROVIDER_TARGETS.map((target) => ({ ...target, allowed_hosts: [...target.allowed_hosts], negative_phrases: [...target.negative_phrases] }));
|
|
@@ -126,6 +153,83 @@ export function evaluateProviderResponse(target, response) {
|
|
|
126
153
|
matched_phrase: ''
|
|
127
154
|
};
|
|
128
155
|
}
|
|
156
|
+
const key = targetKey(target);
|
|
157
|
+
if (key === 'chatgpt_ios') {
|
|
158
|
+
const body = response.body.toLowerCase();
|
|
159
|
+
if (body.includes('you may be connected to a disallowed isp')) {
|
|
160
|
+
return {
|
|
161
|
+
provider: target.name,
|
|
162
|
+
passed: false,
|
|
163
|
+
reason: 'disallowed_isp',
|
|
164
|
+
status_code: statusCode,
|
|
165
|
+
final_url: finalUrl,
|
|
166
|
+
matched_phrase: ''
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (body.includes('request is not allowed. please try again later.')) {
|
|
170
|
+
return {
|
|
171
|
+
provider: target.name,
|
|
172
|
+
passed: true,
|
|
173
|
+
reason: 'ok',
|
|
174
|
+
status_code: statusCode,
|
|
175
|
+
final_url: finalUrl,
|
|
176
|
+
matched_phrase: ''
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (body.includes('sorry, you have been blocked')) {
|
|
180
|
+
return {
|
|
181
|
+
provider: target.name,
|
|
182
|
+
passed: false,
|
|
183
|
+
reason: 'blocked',
|
|
184
|
+
status_code: statusCode,
|
|
185
|
+
final_url: finalUrl,
|
|
186
|
+
matched_phrase: ''
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
provider: target.name,
|
|
191
|
+
passed: false,
|
|
192
|
+
reason: 'unlock_failed',
|
|
193
|
+
status_code: statusCode,
|
|
194
|
+
final_url: finalUrl,
|
|
195
|
+
matched_phrase: ''
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (key === 'chatgpt' || key === 'chatgpt_web') {
|
|
199
|
+
const unsupported = response.body.toLowerCase().includes('unsupported_country');
|
|
200
|
+
return {
|
|
201
|
+
provider: target.name,
|
|
202
|
+
passed: !unsupported,
|
|
203
|
+
reason: unsupported ? 'unsupported_region' : 'ok',
|
|
204
|
+
status_code: statusCode,
|
|
205
|
+
final_url: finalUrl,
|
|
206
|
+
matched_phrase: unsupported ? 'unsupported_country' : ''
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (key === 'claude') {
|
|
210
|
+
const countryCode = extractTraceLocation(response.body);
|
|
211
|
+
const blocked = CLAUDE_BLOCKED_CODES.has(countryCode);
|
|
212
|
+
return {
|
|
213
|
+
provider: target.name,
|
|
214
|
+
passed: Boolean(countryCode) && !blocked,
|
|
215
|
+
reason: !countryCode ? 'unlock_failed' : blocked ? 'unsupported_region' : 'ok',
|
|
216
|
+
status_code: statusCode,
|
|
217
|
+
final_url: finalUrl,
|
|
218
|
+
matched_phrase: countryCode
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (key === 'gemini') {
|
|
222
|
+
const countryCode = extractGeminiCountryCode(response.body);
|
|
223
|
+
const blocked = GEMINI_BLOCKED_CODES.has(countryCode);
|
|
224
|
+
return {
|
|
225
|
+
provider: target.name,
|
|
226
|
+
passed: Boolean(countryCode) && !blocked,
|
|
227
|
+
reason: !countryCode ? 'unlock_failed' : blocked ? 'unsupported_region' : 'ok',
|
|
228
|
+
status_code: statusCode,
|
|
229
|
+
final_url: finalUrl,
|
|
230
|
+
matched_phrase: countryCode
|
|
231
|
+
};
|
|
232
|
+
}
|
|
129
233
|
if (statusCode >= 400) {
|
|
130
234
|
return {
|
|
131
235
|
provider: target.name,
|
|
@@ -594,7 +698,8 @@ async function checkBatchInNode(input, options) {
|
|
|
594
698
|
return [];
|
|
595
699
|
}
|
|
596
700
|
const targets = normalizeProviderTargets(input.targets);
|
|
597
|
-
const
|
|
701
|
+
const requestedRuntime = String((options.env ?? process.env).AUTOVPN_AVAILABILITY_RUNTIME ?? '').trim().toLowerCase();
|
|
702
|
+
const useMihomoRuntime = requestedRuntime !== 'direct';
|
|
598
703
|
const checkLinkAvailability = options.checkLinkAvailability ?? ((speedResult, config) => (useMihomoRuntime
|
|
599
704
|
? checkLinkAvailabilityMihomo(speedResult, config, {
|
|
600
705
|
...options,
|