forked from github-mirror/Verome-API
Add IP-based region detection and All filter option in UI
This commit is contained in:
85
mod.ts
85
mod.ts
@@ -53,6 +53,76 @@ function matchRoute(pathname: string, pattern: string): Record<string, string> |
|
||||
return params;
|
||||
}
|
||||
|
||||
// Country code to language mapping
|
||||
const countryLanguageMap: Record<string, string> = {
|
||||
TN: "ar", DZ: "ar", MA: "ar", EG: "ar", SA: "ar", AE: "ar", KW: "ar", QA: "ar", BH: "ar", OM: "ar", JO: "ar", LB: "ar", IQ: "ar", LY: "ar", SD: "ar", YE: "ar", SY: "ar", PS: "ar",
|
||||
FR: "fr", BE: "fr", CH: "fr", CA: "fr", SN: "fr", CI: "fr", ML: "fr", BF: "fr", NE: "fr", TG: "fr", BJ: "fr", CM: "fr", MG: "fr",
|
||||
DE: "de", AT: "de",
|
||||
ES: "es", MX: "es", AR: "es", CO: "es", PE: "es", VE: "es", CL: "es", EC: "es", GT: "es", CU: "es", BO: "es", DO: "es", HN: "es", PY: "es", SV: "es", NI: "es", CR: "es", PA: "es", UY: "es",
|
||||
PT: "pt", BR: "pt", AO: "pt", MZ: "pt",
|
||||
IT: "it",
|
||||
NL: "nl",
|
||||
RU: "ru", BY: "ru", KZ: "ru",
|
||||
TR: "tr",
|
||||
JP: "ja",
|
||||
KR: "ko",
|
||||
CN: "zh", TW: "zh", HK: "zh",
|
||||
IN: "hi",
|
||||
TH: "th",
|
||||
VN: "vi",
|
||||
ID: "id",
|
||||
PL: "pl",
|
||||
UA: "uk",
|
||||
RO: "ro",
|
||||
GR: "el",
|
||||
CZ: "cs",
|
||||
SE: "sv",
|
||||
NO: "no",
|
||||
DK: "da",
|
||||
FI: "fi",
|
||||
HU: "hu",
|
||||
IL: "he",
|
||||
IR: "fa",
|
||||
PK: "ur",
|
||||
BD: "bn",
|
||||
PH: "tl",
|
||||
MY: "ms",
|
||||
};
|
||||
|
||||
// Detect region from IP using Cloudflare/Deno Deploy headers or fallback to IP lookup
|
||||
async function detectRegionFromIP(req: Request): Promise<{ country: string; language: string } | null> {
|
||||
try {
|
||||
// Try Cloudflare/Deno Deploy headers first (fastest)
|
||||
const cfCountry = req.headers.get("cf-ipcountry") || req.headers.get("x-country");
|
||||
if (cfCountry && cfCountry !== "XX") {
|
||||
const language = countryLanguageMap[cfCountry] || "en";
|
||||
return { country: cfCountry, language };
|
||||
}
|
||||
|
||||
// Get client IP
|
||||
const forwardedFor = req.headers.get("x-forwarded-for");
|
||||
const clientIP = forwardedFor ? forwardedFor.split(",")[0].trim() : null;
|
||||
|
||||
if (!clientIP || clientIP === "127.0.0.1" || clientIP.startsWith("192.168.") || clientIP.startsWith("10.")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fallback to IP geolocation API (ip-api.com is free, no key needed)
|
||||
const geoResponse = await fetch(`http://ip-api.com/json/${clientIP}?fields=countryCode`);
|
||||
if (geoResponse.ok) {
|
||||
const geoData = await geoResponse.json();
|
||||
if (geoData.countryCode) {
|
||||
const language = countryLanguageMap[geoData.countryCode] || "en";
|
||||
return { country: geoData.countryCode, language };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Main request handler
|
||||
async function handler(req: Request): Promise<Response> {
|
||||
const url = new URL(req.url);
|
||||
@@ -100,8 +170,19 @@ async function handler(req: Request): Promise<Response> {
|
||||
const filter = searchParams.get("filter") || undefined;
|
||||
const continuationToken = searchParams.get("continuationToken") || undefined;
|
||||
const ignoreSpelling = searchParams.get("ignore_spelling") === "true";
|
||||
const region = searchParams.get("region") || searchParams.get("gl") || undefined;
|
||||
const language = searchParams.get("language") || searchParams.get("hl") || undefined;
|
||||
|
||||
// Get region from param or detect from IP
|
||||
let region = searchParams.get("region") || searchParams.get("gl") || undefined;
|
||||
let language = searchParams.get("language") || searchParams.get("hl") || undefined;
|
||||
|
||||
// Auto-detect region from IP if not provided
|
||||
if (!region) {
|
||||
const detectedRegion = await detectRegionFromIP(req);
|
||||
if (detectedRegion) {
|
||||
region = detectedRegion.country;
|
||||
if (!language) language = detectedRegion.language;
|
||||
}
|
||||
}
|
||||
|
||||
if (!query && !continuationToken) {
|
||||
return error("Missing required query parameter 'q' or 'continuationToken'");
|
||||
|
||||
7
ui.ts
7
ui.ts
@@ -166,9 +166,12 @@ export const html = `<!DOCTYPE html>
|
||||
</style>
|
||||
<div class="search-box">
|
||||
<select class="api-select" id="searchType" style="min-width:120px">
|
||||
<option value="">All</option>
|
||||
<option value="songs">Songs</option>
|
||||
<option value="videos">Videos</option>
|
||||
<option value="artists">Artists</option>
|
||||
<option value="albums">Albums</option>
|
||||
<option value="playlists">Playlists</option>
|
||||
</select>
|
||||
<input type="text" class="search-input" id="searchInput" placeholder="Search for songs, artists, or albums...">
|
||||
<button class="btn" id="searchBtn" onclick="doSearch()">Search</button>
|
||||
@@ -242,8 +245,8 @@ export const html = `<!DOCTYPE html>
|
||||
function updateProg(){if(!player||!playerReady)return;var c=player.getCurrentTime()||0,t=player.getDuration()||0;document.getElementById('currentTime').textContent=fmt(c);document.getElementById('duration').textContent=fmt(t);document.getElementById('progressFill').style.width=t>0?(c/t*100)+'%':'0%'}
|
||||
function fmt(s){var m=Math.floor(s/60),sec=Math.floor(s%60);return m+':'+(sec<10?'0':'')+sec}
|
||||
function seek(e){if(!player||!playerReady)return;var bar=document.getElementById('progressBar'),rect=bar.getBoundingClientRect(),pct=(e.clientX-rect.left)/rect.width;player.seekTo(pct*(player.getDuration()||0),true)}
|
||||
async function doSearch(){var q=document.getElementById('searchInput').value.trim();if(!q)return;var filter=document.getElementById('searchType').value;document.getElementById('searchBtn').disabled=true;document.getElementById('loading').style.display='block';document.getElementById('resultsList').innerHTML='';try{var res=await fetch('/api/search?q='+encodeURIComponent(q)+'&filter='+filter);var data=await res.json();songs=data.results||[];renderResults(filter)}catch(e){songs=[];renderResults(filter)}document.getElementById('searchBtn').disabled=false;document.getElementById('loading').style.display='none'}
|
||||
function renderResults(filter){var list=document.getElementById('resultsList');if(!songs.length){list.innerHTML='<div class="no-results">No results found</div>';return}if(filter==='artists'){list.innerHTML=songs.map((s,i)=>'<div class="result-item" onclick="openArtist(\\''+s.browseId+'\\')"><img class="result-thumb" style="border-radius:50%" src="'+(s.thumbnails?.[0]?.url||'')+'"><div class="result-info"><div class="result-title">'+esc(s.title||'Unknown')+'</div><div class="result-artist">Artist</div></div></div>').join('')}else if(filter==='albums'){list.innerHTML=songs.map((s,i)=>'<div class="result-item" onclick="openAlbum(\\''+s.browseId+'\\')"><img class="result-thumb" src="'+(s.thumbnails?.[0]?.url||'')+'"><div class="result-info"><div class="result-title">'+esc(s.title||'Unknown')+'</div><div class="result-artist">'+esc(s.artists?.map(a=>a.name).join(', ')||'Album')+'</div></div></div>').join('')}else{list.innerHTML=songs.map((s,i)=>'<div class="result-item'+(i===currentIndex?' active':'')+'" onclick="playSong('+i+')"><img class="result-thumb" src="'+(s.thumbnails?.[0]?.url||'https://img.youtube.com/vi/'+s.videoId+'/mqdefault.jpg')+'"><div class="result-info"><div class="result-title">'+esc(s.title||'Unknown')+'</div><div class="result-artist">'+esc(s.artists?.map(a=>a.name).join(', ')||'Unknown')+'</div></div><div class="result-duration">'+(s.duration||'')+'</div></div>').join('')}}
|
||||
async function doSearch(){var q=document.getElementById('searchInput').value.trim();if(!q)return;var filter=document.getElementById('searchType').value;document.getElementById('searchBtn').disabled=true;document.getElementById('loading').style.display='block';document.getElementById('resultsList').innerHTML='';try{var url='/api/search?q='+encodeURIComponent(q);if(filter)url+='&filter='+filter;var res=await fetch(url);var data=await res.json();songs=data.results||[];renderResults(filter)}catch(e){songs=[];renderResults(filter)}document.getElementById('searchBtn').disabled=false;document.getElementById('loading').style.display='none'}
|
||||
function renderResults(filter){var list=document.getElementById('resultsList');if(!songs.length){list.innerHTML='<div class="no-results">No results found</div>';return}if(filter==='artists'){list.innerHTML=songs.map((s,i)=>'<div class="result-item" onclick="openArtist(\\''+s.browseId+'\\')"><img class="result-thumb" style="border-radius:50%" src="'+(s.thumbnails?.[0]?.url||'')+'"><div class="result-info"><div class="result-title">'+esc(s.title||'Unknown')+'</div><div class="result-artist">Artist</div></div></div>').join('')}else if(filter==='albums'){list.innerHTML=songs.map((s,i)=>'<div class="result-item" onclick="openAlbum(\\''+s.browseId+'\\')"><img class="result-thumb" src="'+(s.thumbnails?.[0]?.url||'')+'"><div class="result-info"><div class="result-title">'+esc(s.title||'Unknown')+'</div><div class="result-artist">'+esc(s.artists?.map(a=>a.name).join(', ')||'Album')+'</div></div></div>').join('')}else{list.innerHTML=songs.map((s,i)=>{var isPlayable=s.videoId&&(s.resultType==='song'||s.resultType==='video'||!s.resultType);var onclick=isPlayable?'playSong('+i+')':s.resultType==='artist'?'openArtist(\\''+s.browseId+'\\')':s.resultType==='album'?'openAlbum(\\''+s.browseId+'\\')':'';var typeLabel=s.isTopResult?'<span style="color:#00d4aa;font-size:.65rem;margin-left:6px">TOP</span>':s.resultType&&s.resultType!=='song'?'<span style="color:#525252;font-size:.65rem;margin-left:6px">'+s.resultType.toUpperCase()+'</span>':'';return '<div class="result-item'+(i===currentIndex?' active':'')+'" onclick="'+onclick+'"><img class="result-thumb" src="'+(s.thumbnails?.[0]?.url||(s.videoId?'https://img.youtube.com/vi/'+s.videoId+'/mqdefault.jpg':''))+'"><div class="result-info"><div class="result-title">'+esc(s.title||'Unknown')+typeLabel+'</div><div class="result-artist">'+esc(s.artists?.map(a=>a.name).join(', ')||(s.subtitle||'Unknown'))+'</div></div><div class="result-duration">'+(s.duration||'')+'</div></div>'}).join('')}}
|
||||
function openArtist(id){switchTab('api');document.getElementById('apiEndpoint').value='artist';updateApiInputs();document.getElementById('api_browseId').value=id;updateApiUrl();testApi()}
|
||||
function openAlbum(id){switchTab('api');document.getElementById('apiEndpoint').value='album';updateApiInputs();document.getElementById('api_browseId').value=id;updateApiUrl();testApi()}
|
||||
function playSong(i){if(!songs[i])return;if(!playerReady){setTimeout(()=>playSong(i),300);return}currentIndex=i;var s=songs[i];document.getElementById('playerTitle').textContent=s.title||'Unknown';document.getElementById('playerArtist').textContent=s.artists?.map(a=>a.name).join(', ')||'Unknown';document.getElementById('playerThumb').src=s.thumbnails?.[0]?.url||'https://img.youtube.com/vi/'+s.videoId+'/mqdefault.jpg';document.getElementById('player').className='player visible';document.querySelectorAll('.result-item').forEach((el,idx)=>el.className=idx===i?'result-item active':'result-item');player.loadVideoById(s.videoId);isPlaying=true;document.getElementById('playBtn').textContent='⏸'}
|
||||
|
||||
Reference in New Issue
Block a user