diff --git a/src/api/admin.py b/src/api/admin.py
index 7ffb354..7c4e0ae 100644
--- a/src/api/admin.py
+++ b/src/api/admin.py
@@ -122,6 +122,9 @@ class UpdateProxyConfigRequest(BaseModel):
proxy_enabled: bool
proxy_url: Optional[str] = None
+class TestProxyRequest(BaseModel):
+ test_url: Optional[str] = "https://sora.chatgpt.com"
+
class UpdateAdminPasswordRequest(BaseModel):
old_password: str
new_password: str
@@ -794,6 +797,50 @@ async def update_proxy_config(
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
+@router.post("/api/proxy/test")
+async def test_proxy_config(
+ request: TestProxyRequest,
+ token: str = Depends(verify_admin_token)
+) -> dict:
+ """Test proxy connectivity with custom URL"""
+ from curl_cffi.requests import AsyncSession
+
+ config_obj = await proxy_manager.get_proxy_config()
+ if not config_obj.proxy_enabled or not config_obj.proxy_url:
+ return {"success": False, "message": "代理未启用或地址为空"}
+
+ # Use provided test URL or default
+ test_url = request.test_url or "https://sora.chatgpt.com"
+
+ try:
+ async with AsyncSession() as session:
+ response = await session.get(
+ test_url,
+ timeout=15,
+ impersonate="chrome",
+ proxy=config_obj.proxy_url
+ )
+ status_code = response.status_code
+ if 200 <= status_code < 400:
+ return {
+ "success": True,
+ "message": f"代理可用 (HTTP {status_code})",
+ "status_code": status_code,
+ "test_url": test_url
+ }
+ return {
+ "success": False,
+ "message": f"代理响应异常: HTTP {status_code}",
+ "status_code": status_code,
+ "test_url": test_url
+ }
+ except Exception as e:
+ return {
+ "success": False,
+ "message": f"代理连接失败: {str(e)}",
+ "test_url": test_url
+ }
+
# Watermark-free config endpoints
@router.get("/api/watermark-free/config")
async def get_watermark_free_config(token: str = Depends(verify_admin_token)) -> dict:
diff --git a/static/manage.html b/static/manage.html
index 5381026..8ce0631 100644
--- a/static/manage.html
+++ b/static/manage.html
@@ -345,7 +345,19 @@
支持 HTTP 和 SOCKS5 代理
-
+
+
+
+
用于测试代理连接的目标域名
+
+
+
+
+
+
+
+ ⚠️ 提示:代理测试成功仅表示代理服务器可以正常连接到目标域名,并不能保证代理 IP 所在地区可以使用 Sora 服务。请确保您的代理 IP 位于支持 Sora 的地区。
+
@@ -961,6 +973,8 @@
toggleDebugMode=async()=>{const enabled=$('cfgDebugEnabled').checked;try{const r=await apiRequest('/api/admin/debug',{method:'POST',body:JSON.stringify({enabled:enabled})});if(!r)return;const d=await r.json();if(d.success){showToast(enabled?'调试模式已开启':'调试模式已关闭','success')}else{showToast('操作失败: '+(d.detail||'未知错误'),'error');$('cfgDebugEnabled').checked=!enabled}}catch(e){showToast('操作失败: '+e.message,'error');$('cfgDebugEnabled').checked=!enabled}},
downloadDebugLogs=async()=>{try{const token=localStorage.getItem('adminToken');if(!token){showToast('未登录','error');return}const r=await fetch('/api/admin/logs/download',{headers:{Authorization:`Bearer ${token}`}});if(!r.ok){if(r.status===404){showToast('日志文件不存在','error')}else{showToast('下载失败','error')}return}const blob=await r.blob();const url=URL.createObjectURL(blob);const link=document.createElement('a');link.href=url;link.download=`logs_${new Date().toISOString().split('T')[0]}.txt`;document.body.appendChild(link);link.click();document.body.removeChild(link);URL.revokeObjectURL(url);showToast('日志文件下载成功','success')}catch(e){showToast('下载失败: '+e.message,'error')}},
loadProxyConfig=async()=>{try{const r=await apiRequest('/api/proxy/config');if(!r)return;const d=await r.json();$('cfgProxyEnabled').checked=d.proxy_enabled||false;$('cfgProxyUrl').value=d.proxy_url||''}catch(e){console.error('加载代理配置失败:',e)}},
+ setProxyStatus=(msg,type='muted')=>{const el=$('proxyStatusMessage');if(!el)return;if(!msg){el.textContent='';el.classList.add('hidden');return}el.textContent=msg;el.classList.remove('hidden','text-muted-foreground','text-green-600','text-red-600');if(type==='success')el.classList.add('text-green-600');else if(type==='error')el.classList.add('text-red-600');else el.classList.add('text-muted-foreground')},
+ testProxyConfig=async()=>{const enabled=$('cfgProxyEnabled').checked;const url=$('cfgProxyUrl').value.trim();const testUrl=$('cfgProxyTestUrl').value.trim()||'https://sora.chatgpt.com';if(!enabled||!url){setProxyStatus('代理未启用或地址为空','error');return}try{setProxyStatus('正在测试代理连接...','muted');const r=await apiRequest('/api/proxy/test',{method:'POST',body:JSON.stringify({test_url:testUrl})});if(!r)return;const d=await r.json();if(d.success){setProxyStatus(`✓ ${d.message||'代理可用'} - 测试域名: ${d.test_url||testUrl}`,'success')}else{setProxyStatus(`✗ ${d.message||'代理不可用'} - 测试域名: ${d.test_url||testUrl}`,'error')}}catch(e){setProxyStatus('代理测试失败: '+e.message,'error')}},
saveProxyConfig=async()=>{try{const r=await apiRequest('/api/proxy/config',{method:'POST',body:JSON.stringify({proxy_enabled:$('cfgProxyEnabled').checked,proxy_url:$('cfgProxyUrl').value.trim()})});if(!r)return;const d=await r.json();d.success?showToast('代理配置保存成功','success'):showToast('保存失败','error')}catch(e){showToast('保存失败: '+e.message,'error')}},
loadWatermarkFreeConfig=async()=>{try{const r=await apiRequest('/api/watermark-free/config');if(!r)return;const d=await r.json();$('cfgWatermarkFreeEnabled').checked=d.watermark_free_enabled||false;$('cfgParseMethod').value=d.parse_method||'third_party';$('cfgCustomParseUrl').value=d.custom_parse_url||'';$('cfgCustomParseToken').value=d.custom_parse_token||'';toggleWatermarkFreeOptions();toggleCustomParseOptions()}catch(e){console.error('加载无水印模式配置失败:',e)}},
saveWatermarkFreeConfig=async()=>{try{const enabled=$('cfgWatermarkFreeEnabled').checked,parseMethod=$('cfgParseMethod').value,customUrl=$('cfgCustomParseUrl').value.trim(),customToken=$('cfgCustomParseToken').value.trim();if(enabled&&parseMethod==='custom'){if(!customUrl)return showToast('请输入解析服务器地址','error');if(!customToken)return showToast('请输入访问密钥','error')}const r=await apiRequest('/api/watermark-free/config',{method:'POST',body:JSON.stringify({watermark_free_enabled:enabled,parse_method:parseMethod,custom_parse_url:customUrl||null,custom_parse_token:customToken||null})});if(!r)return;const d=await r.json();d.success?showToast('无水印模式配置保存成功','success'):showToast('保存失败','error')}catch(e){showToast('保存失败: '+e.message,'error')}},