forked from github-mirror/Verome-API
Добавлен статус в интерфейс
This commit is contained in:
64
mod.ts
64
mod.ts
@@ -98,6 +98,70 @@ async function handler(req: Request): Promise<Response> {
|
||||
if (pathname === "/favicon.ico") return new Response(null, { status: 204 });
|
||||
if (pathname === "/health") return json({ status: "ok" });
|
||||
|
||||
// ============ PROXY STATUS ============
|
||||
|
||||
if (pathname === "/api/proxy/status") {
|
||||
const httpsProxy = Deno.env.get("HTTPS_PROXY") || Deno.env.get("https_proxy");
|
||||
const httpProxy = Deno.env.get("HTTP_PROXY") || Deno.env.get("http_proxy");
|
||||
const proxyUrl = Deno.env.get("PROXY_URL");
|
||||
const proxyActive = !!(httpsProxy || httpProxy || proxyUrl);
|
||||
|
||||
const mask = (u: string | undefined) =>
|
||||
u ? u.replace(/:\/\/[^@]*@/, "://<hidden>@") : null;
|
||||
|
||||
// 1. Определяем текущий внешний IP (через прокси, если настроен)
|
||||
let ip = "unknown";
|
||||
let ipInfo: Record<string, string> = {};
|
||||
let latencyMs = -1;
|
||||
|
||||
try {
|
||||
const t0 = Date.now();
|
||||
const ipRes = await fetch("https://ipinfo.io/json", {
|
||||
headers: { "User-Agent": "curl/7.88.0", "Accept": "application/json" },
|
||||
signal: AbortSignal.timeout(8000),
|
||||
});
|
||||
latencyMs = Date.now() - t0;
|
||||
if (ipRes.ok) {
|
||||
ipInfo = await ipRes.json();
|
||||
ip = ipInfo.ip || "unknown";
|
||||
}
|
||||
} catch { /* timeout or connection error */ }
|
||||
|
||||
// 2. Определяем IP без прокси (прямое соединение), чтобы сравнить
|
||||
let directIp = "unknown";
|
||||
try {
|
||||
// Временно вызываем нативный fetch напрямую, минуя патч
|
||||
const nativeFetch: typeof fetch = (globalThis as any).__nativeFetch || fetch;
|
||||
const res = await nativeFetch("https://api.ipify.org?format=json", {
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
directIp = data.ip || "unknown";
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
|
||||
const proxyWorking = proxyActive && ip !== "unknown" && ip !== directIp;
|
||||
|
||||
return json({
|
||||
proxy_enabled: proxyActive,
|
||||
proxy_working: proxyWorking,
|
||||
proxy_url: mask((httpsProxy || httpProxy || proxyUrl) || undefined),
|
||||
current_ip: ip,
|
||||
direct_ip: directIp,
|
||||
ip_masked: directIp !== "unknown" && ip !== directIp,
|
||||
latency_ms: latencyMs,
|
||||
location: ipInfo.city
|
||||
? `${ipInfo.city}, ${ipInfo.region}, ${ipInfo.country}`
|
||||
: (ipInfo.country || null),
|
||||
org: ipInfo.org || null,
|
||||
timezone: ipInfo.timezone || null,
|
||||
status: proxyActive
|
||||
? (proxyWorking ? "proxy_ok" : (ip === "unknown" ? "proxy_error" : "proxy_transparent"))
|
||||
: "no_proxy",
|
||||
});
|
||||
}
|
||||
|
||||
// ============ PROXY CONFIG ============
|
||||
|
||||
if (pathname === "/api/proxy/config") {
|
||||
|
||||
113
ui.ts
113
ui.ts
@@ -77,7 +77,27 @@ export const html = `<!DOCTYPE html>
|
||||
.time{font-size:.7rem;color:var(--muted);min-width:40px;font-family:monospace}
|
||||
.bar{flex:1;height:4px;background:var(--surface2);border-radius:2px;cursor:pointer}
|
||||
.fill{height:100%;background:var(--accent);border-radius:2px;width:0%}
|
||||
@media(max-width:600px){.container{padding:40px 16px 180px}.logo{width:80px;height:80px}.title{font-size:2rem}.desc{display:none}.search-row{flex-direction:column}}
|
||||
/* ── Proxy Status Widget ─────────────────────────────── */
|
||||
.proxy-widget{display:flex;align-items:center;gap:12px;padding:10px 18px;margin:20px auto 0;max-width:520px;background:var(--surface);border:1px solid var(--border);border-radius:12px;font-size:.8rem;transition:border-color .3s}
|
||||
.proxy-widget.ok{border-color:rgba(16,185,129,.4)}
|
||||
.proxy-widget.error{border-color:rgba(239,68,68,.4)}
|
||||
.proxy-widget.loading{border-color:var(--border)}
|
||||
.proxy-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dim)}
|
||||
.proxy-widget.ok .proxy-dot{background:#10b981;box-shadow:0 0 6px rgba(16,185,129,.6)}
|
||||
.proxy-widget.error .proxy-dot{background:#ef4444;box-shadow:0 0 6px rgba(239,68,68,.6)}
|
||||
.proxy-widget.loading .proxy-dot{background:var(--dim);animation:blink 1s infinite}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:.3}}
|
||||
.proxy-main{flex:1;min-width:0}
|
||||
.proxy-label{font-weight:600;color:var(--text)}
|
||||
.proxy-sub{color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.proxy-meta{display:flex;gap:10px;margin-top:4px;flex-wrap:wrap}
|
||||
.proxy-tag{font-size:.7rem;padding:2px 8px;border-radius:4px;background:var(--surface2);color:var(--dim)}
|
||||
.proxy-tag.green{color:#10b981;background:rgba(16,185,129,.1)}
|
||||
.proxy-tag.red{color:#ef4444;background:rgba(239,68,68,.1)}
|
||||
.proxy-tag.blue{color:#60a5fa;background:rgba(96,165,250,.1)}
|
||||
.proxy-refresh{background:none;border:none;color:var(--dim);cursor:pointer;padding:4px;font-size:.9rem;transition:color .2s;flex-shrink:0}
|
||||
.proxy-refresh:hover{color:var(--text)}
|
||||
@media(max-width:600px){.container{padding:40px 16px 180px}.logo{width:80px;height:80px}.title{font-size:2rem}.desc{display:none}.search-row{flex-direction:column}.proxy-widget{flex-wrap:wrap}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -87,6 +107,17 @@ export const html = `<!DOCTYPE html>
|
||||
<img src="/assets/logo.png" alt="Virome" class="logo">
|
||||
<h1 class="title">Virome API</h1>
|
||||
<p class="subtitle">Music API for YouTube Music, Lyrics & Streaming</p>
|
||||
|
||||
<!-- Proxy Status Widget -->
|
||||
<div class="proxy-widget loading" id="proxyWidget">
|
||||
<div class="proxy-dot"></div>
|
||||
<div class="proxy-main">
|
||||
<div class="proxy-label" id="proxyLabel">Checking proxy...</div>
|
||||
<div class="proxy-sub" id="proxySub"></div>
|
||||
<div class="proxy-meta" id="proxyMeta"></div>
|
||||
</div>
|
||||
<button class="proxy-refresh" id="proxyRefreshBtn" onclick="loadProxyStatus()" title="Refresh">↻</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nav">
|
||||
@@ -462,5 +493,85 @@ async function testApi(){
|
||||
try{var res=await fetch(url);var data=await res.json();document.getElementById('response').innerHTML='<pre>'+JSON.stringify(data,null,2)+'</pre>'}catch(e){document.getElementById('response').innerHTML='<pre>Error: '+e.message+'</pre>'}
|
||||
}
|
||||
updateInputs();
|
||||
|
||||
// ── Proxy Status Widget ────────────────────────────────────────────────────
|
||||
async function loadProxyStatus(){
|
||||
var w=document.getElementById('proxyWidget');
|
||||
var label=document.getElementById('proxyLabel');
|
||||
var sub=document.getElementById('proxySub');
|
||||
var meta=document.getElementById('proxyMeta');
|
||||
var btn=document.getElementById('proxyRefreshBtn');
|
||||
|
||||
// Reset to loading state
|
||||
w.className='proxy-widget loading';
|
||||
label.textContent='Checking proxy...';
|
||||
sub.textContent='';
|
||||
meta.innerHTML='';
|
||||
btn.style.animation='spin 1s linear infinite';
|
||||
|
||||
try{
|
||||
var res=await fetch('/api/proxy/status');
|
||||
var d=await res.json();
|
||||
|
||||
btn.style.animation='';
|
||||
|
||||
if(d.status==='proxy_ok'){
|
||||
// ✅ Прокси работает и IP подменён
|
||||
w.className='proxy-widget ok';
|
||||
label.textContent='✓ Proxy active — IP masked';
|
||||
sub.textContent=d.current_ip+(d.location?' · '+d.location:'');
|
||||
var tags='';
|
||||
if(d.latency_ms>0)tags+='<span class="proxy-tag blue">'+d.latency_ms+'ms</span>';
|
||||
if(d.org)tags+='<span class="proxy-tag">'+esc(d.org)+'</span>';
|
||||
if(d.timezone)tags+='<span class="proxy-tag">'+esc(d.timezone)+'</span>';
|
||||
tags+='<span class="proxy-tag green">IP hidden</span>';
|
||||
meta.innerHTML=tags;
|
||||
|
||||
}else if(d.status==='proxy_transparent'){
|
||||
// ⚠️ Прокси настроен, но IP тот же — возможно прозрачный
|
||||
w.className='proxy-widget error';
|
||||
label.textContent='⚠ Proxy configured but IP not changed';
|
||||
sub.textContent='IP: '+d.current_ip+(d.location?' · '+d.location:'');
|
||||
var tags='';
|
||||
if(d.latency_ms>0)tags+='<span class="proxy-tag blue">'+d.latency_ms+'ms</span>';
|
||||
tags+='<span class="proxy-tag red">transparent?</span>';
|
||||
if(d.proxy_url)tags+='<span class="proxy-tag">'+esc(d.proxy_url)+'</span>';
|
||||
meta.innerHTML=tags;
|
||||
|
||||
}else if(d.status==='proxy_error'){
|
||||
// ❌ Прокси настроен, но не работает
|
||||
w.className='proxy-widget error';
|
||||
label.textContent='✕ Proxy error — no connection';
|
||||
sub.textContent=d.proxy_url?'Proxy: '+d.proxy_url:'Check proxy settings';
|
||||
meta.innerHTML='<span class="proxy-tag red">unreachable</span>';
|
||||
|
||||
}else{
|
||||
// ℹ️ Прокси не настроен — прямое соединение
|
||||
w.className='proxy-widget';
|
||||
label.textContent='No proxy — direct connection';
|
||||
sub.textContent=d.current_ip+(d.location?' · '+d.location:'');
|
||||
var tags='';
|
||||
if(d.latency_ms>0)tags+='<span class="proxy-tag blue">'+d.latency_ms+'ms</span>';
|
||||
if(d.org)tags+='<span class="proxy-tag">'+esc(d.org)+'</span>';
|
||||
meta.innerHTML=tags;
|
||||
}
|
||||
}catch(e){
|
||||
btn.style.animation='';
|
||||
w.className='proxy-widget error';
|
||||
label.textContent='Failed to get proxy status';
|
||||
sub.textContent=String(e);
|
||||
meta.innerHTML='';
|
||||
}
|
||||
}
|
||||
|
||||
// Spin animation for refresh button
|
||||
(function(){
|
||||
var s=document.createElement('style');
|
||||
s.textContent='@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}';
|
||||
document.head.appendChild(s);
|
||||
})();
|
||||
|
||||
// Load on page open
|
||||
loadProxyStatus();
|
||||
</script>
|
||||
</html>`;
|
||||
|
||||
Reference in New Issue
Block a user