const _IMG = '/luci-static/ipip/'; const translate = {}; let cachedIP = null; let translationEnabled = localStorage.getItem('translationEnabled') !== 'false'; let currentLanguage = 'zh-CN'; const translationsIP = { zh: { unknown: '未知', clickRefresh: '点击刷新 IP 地址', failedUpdate: '更新 IP 信息失败', checking: '获取中...', failed: '获取 IP 信息失败', translate: '翻译' }, en: { unknown: 'Unknown', clickRefresh: 'Click to refresh IP address', failedUpdate: 'Failed to update IP information', checking: 'Checking...', failed: 'Failed to get IP information', translate: 'Translate' }, hk: { unknown: '未知', clickRefresh: '點擊刷新 IP 地址', failedUpdate: '更新 IP 資訊失敗', checking: '獲取中...', failed: '獲取 IP 資訊失敗', translate: '翻譯' }, ko: { unknown: '알 수 없음', clickRefresh: 'IP 주소 새로고침', failedUpdate: 'IP 정보 업데이트 실패', checking: '가져오는 중...', failed: 'IP 정보를 가져오지 못했습니다', translate: '번역' }, ja: { unknown: '不明', clickRefresh: 'IP アドレスを更新', failedUpdate: 'IP 情報の更新に失敗しました', checking: '取得中...', failed: 'IP 情報の取得に失敗しました', translate: '翻訳' }, vi: { unknown: 'Không xác định', clickRefresh: 'Nhấn để làm mới địa chỉ IP', failedUpdate: 'Cập nhật thông tin IP thất bại', checking: 'Đang lấy...', failed: 'Lấy thông tin IP thất bại', translate: 'Dịch' }, th: { unknown: 'ไม่ทราบ', clickRefresh: 'คลิกเพื่อรีเฟรชที่อยู่ IP', failedUpdate: 'อัปเดตข้อมูล IP ล้มเหลว', checking: 'กำลังดึงข้อมูล...', failed: 'ไม่สามารถดึงข้อมูล IP', translate: 'แปล' }, ru: { unknown: 'Неизвестно', clickRefresh: 'Нажмите, чтобы обновить IP-адрес', failedUpdate: 'Не удалось обновить информацию об IP', checking: 'Получение...', failed: 'Не удалось получить информацию об IP', translate: 'Перевести' }, ar: { unknown: 'غير معروف', clickRefresh: 'اضغط لتحديث عنوان الـ IP', failedUpdate: 'فشل تحديث معلومات الـ IP', checking: 'جارٍ الجلب...', failed: 'فشل في جلب معلومات الـ IP', translate: 'ترجمة' }, es: { unknown: 'Desconocido', clickRefresh: 'Haga clic para actualizar la dirección IP', failedUpdate: 'Error al actualizar la información de IP', checking: 'Obteniendo...', failed: 'Error al obtener la información de IP', translate: 'Traducir' }, de: { unknown: 'Unbekannt', clickRefresh: 'Klicken, um die IP-Adresse zu aktualisieren', failedUpdate: 'Aktualisieren der IP-Informationen fehlgeschlagen', checking: 'Wird abgerufen...', failed: 'Abrufen der IP-Informationen fehlgeschlagen', translate: 'Übersetzen' }, fr: { unknown: 'Inconnu', clickRefresh: 'Cliquez pour actualiser l’adresse IP', failedUpdate: 'Échec de la mise à jour des informations IP', checking: 'Récupération...', failed: 'Échec de la récupération des informations IP', translate: 'Traduire' }, bn: { unknown: 'অজানা', clickRefresh: 'IP ঠিকানা রিফ্রেশ করতে ক্লিক করুন', failedUpdate: 'IP তথ্য আপডেট ব্যর্থ হয়েছে', checking: 'আনছে...', failed: 'IP তথ্য আনতে ব্যর্থ হয়েছে', translate: 'অনুবাদ' } }; async function initIPDisplay() { const lang = await getRealTimeLanguage(); const text = translationsIP[lang] || translationsIP['zh']; const modalText = modalTranslations[lang] || modalTranslations['zh']; const flag = document.getElementById('flag'); if (flag) flag.title = text.clickRefresh || 'Click to Refresh'; } document.addEventListener('DOMContentLoaded', () => { initIPDisplay(); }); async function getCurrentLanguage() { try { const response = await fetch('/spectra/lib/language.txt', {cache: 'no-store'}); if (response.ok) { const lang = await response.text(); return lang.trim() || 'zh-CN'; } } catch (error) { console.error('Failed to read language file:', error); } return 'zh-CN'; } async function getRealTimeLanguage() { return await getCurrentLanguage(); } async function onLanguageChange() { //console.log('Language changed, refreshing display'); const keys = Object.keys(localStorage); keys.forEach(key => { if (key.startsWith('trans_')) { localStorage.removeItem(key); } }); if (cachedIP) { await myAppIP.Ipip(cachedIP, 'ipip'); } } function setupLanguageChangeListener() { let lastLanguage = 'zh-CN'; setInterval(async () => { const currentLang = await getRealTimeLanguage(); if (currentLang !== lastLanguage) { //console.log('Language changed detected:', currentLang); lastLanguage = currentLang; onLanguageChange(); } }, 1000); } document.addEventListener('DOMContentLoaded', function() { const savedState = localStorage.getItem('translationEnabled'); if (savedState !== null) { translationEnabled = savedState === 'true'; } updateTranslationToggleColor(); }); function updateTranslationToggleColor() { const existingToggles = document.querySelectorAll('.translation-toggle'); existingToggles.forEach(toggle => { const icon = toggle.querySelector('i'); if (translationEnabled) { icon.style.color = '#28a745'; } else { icon.style.color = '#6c757d'; } }); } function toggleTranslation() { translationEnabled = !translationEnabled; localStorage.setItem('translationEnabled', translationEnabled); updateTranslationToggleColor(); const translations = languageTranslations[currentLang] || languageTranslations['zh']; const translationMsg = translationEnabled ? translations['translation_enabled'] : translations['translation_disabled']; if (typeof showLogMessage === 'function') { showLogMessage(encodeURIComponent(translationMsg), 'info'); } if (typeof speakMessage === 'function') { speakMessage(translationMsg); } if (cachedIP) { myAppIP.Ipip(cachedIP, 'ipip'); } } function setupHoverEffect() { const ipContainer = document.getElementById('d-ip'); const toggle = document.querySelector('.translation-toggle'); if (ipContainer && toggle) { ipContainer.addEventListener('mouseenter', () => toggle.style.opacity = '1'); ipContainer.addEventListener('mouseleave', () => toggle.style.opacity = '0'); } } async function myAppTranslate(text) { if (!translationEnabled) return text; if (!text || typeof text !== 'string' || text.trim() === '') return text; const targetLanguage = await getRealTimeLanguage(); const langMap = { 'zh':'zh-CN','zh-tw':'zh-TW','zh-CN':'zh-CN','hk':'zh-TW', 'zh-mo': 'zh-TW', 'en':'en','ja':'ja','ko':'ko','fr':'fr','de':'de','es':'es', 'it':'it','pt':'pt','ru':'ru','ar':'ar','hi':'hi','bn':'bn', 'ms':'ms','id':'id','vi':'vi','th':'th','nl':'nl','pl':'pl', 'tr':'tr','sv':'sv','no':'no','fi':'fi','da':'da','cs':'cs', 'he':'he','el':'el','hu':'hu','ro':'ro','sk':'sk','bg':'bg','uk':'uk' }; const targetLang = langMap[targetLanguage] || targetLanguage.split('-')[0]; if (targetLang.startsWith('en')) return text; const cacheKey = `trans_${text}_${targetLang}`; const cachedTranslation = localStorage.getItem(cacheKey); if (cachedTranslation) return cachedTranslation; const apis = [ { url: `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=en|${targetLang}`, method: 'GET', parseResponse: d => d && d.responseData && d.responseData.translatedText ? d.responseData.translatedText : text }, { url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}`, method: 'GET', parseResponse: d => Array.isArray(d) && d[0]?.[0]?.[0] ? d[0][0][0] : text } ]; for(const api of apis){ try{ const response = await fetch(api.url, { method: api.method, headers: api.headers || {}, body: api.body || null, signal: AbortSignal.timeout(3000) }); if(response.ok){ const data = await response.json(); const translatedText = api.parseResponse(data); if (translatedText && translatedText !== text) { localStorage.setItem(cacheKey, translatedText); return translatedText; } } } catch(e){ console.warn(`Translation API ${api.url} failed:`, e); continue; } } return text; } async function saveIPCache(data) { if (!data.ip) return; data.language = await getRealTimeLanguage(); const saveData = { ip: data.ip, country: data.country || '', region: data.region || '', city: data.city || '', isp: data.isp || '', asn: data.asn || '', asn_organization: data.asn_organization || '', country_code: data.country_code || '', timezone: data.timezone || data.time_zone || '', latitude: data.latitude || data.lat || '', longitude: data.longitude || data.lon || data.lng || '', language: data.language, translatedCountry: data.translatedCountry || '', translatedRegion: data.translatedRegion || '', translatedCity: data.translatedCity || '', translatedISP: data.translatedISP || '', translatedASNOrg: data.translatedASNOrg || '' }; console.log('Saving cache with language:', saveData.language); try { const response = await fetch('/spectra/save_ip_cache.php', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(saveData) }); const result = await response.json(); console.log('Save result:', result); } catch (err) { console.error("Failed to save IP cache:", err); } } async function getCachedIPInfo(ip) { try { const resp = await fetch('/spectra/lib/ip_cache.json', {cache:'no-store'}); const cache = await resp.json(); const item = cache.find(item=>item.ip===ip); if (!item) return null; const currentLang = await getRealTimeLanguage(); //console.log('Cache check - Current language:', currentLang); //console.log('Available translations:', item.translations ? Object.keys(item.translations) : 'none'); if (item.translations && item.translations[currentLang]) { //console.log('Using translation for language:', currentLang); return { ...item, ...item.translations[currentLang], language: currentLang }; } console.log('No translation for current language, returning base data'); return { ...item, language: currentLang }; } catch (err) { console.log('Cache read error:', err); return null; } } let myAppIP = { isRefreshing:false, lastGeoData:null, ipApis:[ {url:'https://api.ipify.org?format=json',type:'json',key:'ip'}, {url:'https://api-ipv4.ip.sb/geoip',type:'json',key:'ip'}, {url:'https://myip.ipip.net',type:'text'}, {url:'http://pv.sohu.com/cityjson',type:'text'}, {url:'https://ipinfo.io/json',type:'json',key:'ip'}, {url:'https://ipapi.co/json/',type:'json'}, {url:'https://freegeoip.app/json/',type:'json'}, {url: ip => `https://ipapi.com/${ip}/json`, type: 'json', concurrent: true}, {url: ip => `https://api.ip.sb/geoip/${ip}`, type: 'json', concurrent: true}, {url: ip => `https://ipwho.is/${ip}`, type: 'json', concurrent: true}, {url: ip => `https://api.ipapi.is/?q=${ip}`, type: 'json', concurrent: true} ], get:(url,type)=>fetch(url,{method:'GET',cache:'no-store'}) .then(resp=>type==='text'?Promise.all([resp.ok,resp.status,resp.text(),resp.headers]):Promise.all([resp.ok,resp.status,resp.json(),resp.headers])) .then(([ok,status,data,headers])=>ok?{ok,status,data,headers}:Promise.reject(data.error)) .catch(e=>{console.error("Error fetching data:",e);throw e;}), concurrentGeoQuery: async function(ip) { const concurrentApis = this.ipApis.filter(api => api.concurrent); if (!concurrentApis.length) throw new Error("No concurrent APIs configured"); return new Promise((resolve, reject) => { let resolved = false; let completed = 0; concurrentApis.forEach(api => { const apiUrl = typeof api.url === 'function' ? api.url(ip) : api.url; this.get(apiUrl, api.type) .then(resp => { if (!resolved && resp.data) { resolved = true; resolve(resp.data); } }) .catch(err => console.warn("Concurrent API failed:", apiUrl, err)) .finally(() => { completed++; if (completed === concurrentApis.length && !resolved) { reject(new Error("All concurrent APIs failed")); } }); }); }); }, fetchIP: async ()=>{ let error; for(let api of myAppIP.ipApis){ if (api.concurrent) continue; try{ const response = await myAppIP.get(api.url,api.type); let ipData = api.type==='json' ? (api.key?response.data[api.key]:response.data) : response.data.match(/\d+\.\d+\.\d+\.\d+/)?.[0]; if(ipData){ cachedIP=ipData; document.getElementById('d-ip').innerHTML=ipData; return ipData; } }catch(e){ error=e; continue; } } throw error||new Error("All IP APIs failed"); }, Ipip: async(ip,elID)=>{ //console.log('Ipip called with IP:', ip); let cachedData = await getCachedIPInfo(ip); const currentLang = await getRealTimeLanguage(); //console.log('Current language:', currentLang); if(cachedData && cachedData.translatedCountry) { //console.log('Using cached data with translation'); myAppIP.lastGeoData = cachedData; await myAppIP.updateUI(cachedData,elID); return; } if(cachedData) { console.log('Cached data found but no translation for current language, re-translating'); const baseData = { country: cachedData.country || '', region: cachedData.region || '', city: cachedData.city || '', isp: cachedData.isp || '', asn_organization: cachedData.asn_organization || '', asn: cachedData.asn || '', country_code: cachedData.country_code || '', timezone: cachedData.timezone || '', latitude: cachedData.latitude || '', longitude: cachedData.longitude || '' }; myAppIP.lastGeoData = {...baseData}; if(translationEnabled){ console.log('Starting translation for language:', currentLang); myAppIP.lastGeoData.translatedCountry = await myAppTranslate(baseData.country); myAppIP.lastGeoData.translatedRegion = await myAppTranslate(baseData.region); myAppIP.lastGeoData.translatedCity = await myAppTranslate(baseData.city); myAppIP.lastGeoData.translatedISP = await myAppTranslate(baseData.isp); myAppIP.lastGeoData.translatedASNOrg = await myAppTranslate(baseData.asn_organization); myAppIP.lastGeoData.language = currentLang; console.log('Translation completed'); } myAppIP.lastGeoData.ip = ip; myAppIP.lastGeoData.asn = baseData.asn; myAppIP.lastGeoData.country_code = baseData.country_code; await myAppIP.updateUI(myAppIP.lastGeoData,elID); saveIPCache(myAppIP.lastGeoData); return; } try { const geoData = await this.concurrentGeoQuery(ip); cachedIP=ip; myAppIP.lastGeoData=geoData; const standardizedData = { ip: geoData.ip || ip, country: geoData.country || geoData.country_name || '', region: geoData.region || geoData.region_name || geoData.state || '', city: geoData.city || '', isp: geoData.isp || geoData.connection?.isp || geoData.org || '', asn: geoData.asn || geoData.connection?.asn || '', asn_organization: geoData.org || geoData.connection?.org || geoData.connection?.isp || '', country_code: geoData.country_code || geoData.countryCode || geoData.country_code || '', timezone: geoData.timezone || geoData.time_zone || '', latitude: geoData.latitude || geoData.lat || '', longitude: geoData.longitude || geoData.lon || geoData.lng || '' }; if(translationEnabled){ standardizedData.translatedCountry = await myAppTranslate(standardizedData.country||''); standardizedData.translatedRegion = await myAppTranslate(standardizedData.region||''); standardizedData.translatedCity = await myAppTranslate(standardizedData.city||''); standardizedData.translatedISP = await myAppTranslate(standardizedData.isp||''); standardizedData.translatedASNOrg = await myAppTranslate(standardizedData.asn_organization||''); standardizedData.language = currentLang; } await myAppIP.updateUI(standardizedData,elID); saveIPCache(standardizedData); } catch(concurrentError) { console.log("Concurrent query failed, falling back to sequential:", concurrentError); const geoApis = [ {url:`https://api.ip.sb/geoip/${ip}`,type:'json'}, {url:'https://myip.ipip.net',type:'text'}, {url:`http://ip-api.com/json/${ip}`,type:'json'}, {url:`https://ipinfo.io/${ip}/json`,type:'json'}, {url:`https://ipapi.co/${ip}/json`,type:'json'}, {url:`https://freegeoip.app/json/${ip}`,type:'json'} ]; let geoData=null,error; for(let api of geoApis){ try{ const response = await myAppIP.get(api.url,api.type); geoData=response.data; break; } catch(e){ error=e; continue; } } if(!geoData) throw error||new Error("All Geo APIs failed"); cachedIP=ip; myAppIP.lastGeoData=geoData; geoData.timezone = geoData.timezone || geoData.time_zone || ''; geoData.latitude = geoData.latitude || geoData.lat || ''; geoData.longitude = geoData.longitude || geoData.lon || geoData.lng || ''; if(translationEnabled){ geoData.translatedCountry = await myAppTranslate(geoData.country||''); geoData.translatedRegion = await myAppTranslate(geoData.region||''); geoData.translatedCity = await myAppTranslate(geoData.city||''); geoData.translatedISP = await myAppTranslate(geoData.isp||''); geoData.translatedASNOrg = await myAppTranslate(geoData.asn_organization||''); geoData.language = currentLang; } await myAppIP.updateUI(geoData,elID); saveIPCache(geoData); } }, updateUI: async(data,elID)=>{ try{ const lang = await getRealTimeLanguage(); const text = translationsIP[lang] || translationsIP['zh']; const country = translationEnabled && data.translatedCountry ? data.translatedCountry : data.country || text.unknown; const region = translationEnabled && data.translatedRegion ? data.translatedRegion : data.region || ""; const city = translationEnabled && data.translatedCity ? data.translatedCity : data.city || ""; const isp = translationEnabled && data.translatedISP ? data.translatedISP : data.isp || ""; const asnOrg = translationEnabled && data.translatedASNOrg ? data.translatedASNOrg : data.asn_organization || ""; let location = region && city && region!==city ? `${region} ${city}` : region||city||''; let displayISP = isp; let displayASN = asnOrg; if(isp && asnOrg && asnOrg.includes(isp)) displayISP=''; else if(isp && asnOrg && isp.includes(asnOrg)) displayASN=''; document.getElementById('d-ip').innerHTML = `
${cachedIP} ${country}
`; document.getElementById('ipip').innerHTML = `${location} ${displayISP} ${data.asn||''} ${displayASN}`; const countryCode = data.country_code||'unknown'; const flagSrc = (countryCode!=='unknown')?_IMG+"flags/"+countryCode.toLowerCase()+".svg":'/luci-static/ipip/flags/cn.svg'; $("#flag").attr("src",flagSrc); } catch(e){ console.error("updateUI error:",e); document.getElementById('d-ip').innerHTML = text.failedUpdate; $("#flag").attr("src","/luci-static/ipip/flags/cn.svg"); } }, getIpipnetIP: async ()=>{ if(myAppIP.isRefreshing) return; myAppIP.isRefreshing = true; const lang = await getRealTimeLanguage(); const text = translationsIP[lang] || translationsIP['zh']; document.getElementById('d-ip').textContent = text.checking; document.getElementById('ipip').innerHTML=""; const flag = document.getElementById("flag"); const loading = document.getElementById("loading"); loading.style.display = 'flex'; flag.style.visibility = 'hidden'; try { const ip = await myAppIP.fetchIP(); await myAppIP.Ipip(ip, 'ipip'); } catch (e) { console.error("getIpipnetIP error:", e); document.getElementById('ipip').innerHTML = text.failed; } finally { loading.style.display = 'none'; flag.style.visibility = 'visible'; myAppIP.isRefreshing = false; } }, showDetailModal: async function() { if (!myAppIP.lastGeoData) return; const lang = await getRealTimeLanguage(); const text = translationsIP[lang] || translationsIP['zh']; const data = myAppIP.lastGeoData; const country = translationEnabled && data.translatedCountry ? data.translatedCountry : data.country || text.unknown; const region = translationEnabled && data.translatedRegion ? data.translatedRegion : data.region || ""; const city = translationEnabled && data.translatedCity ? data.translatedCity : data.city || ""; const isp = translationEnabled && data.translatedISP ? data.translatedISP : data.isp || ""; const asnOrg = translationEnabled && data.translatedASNOrg ? data.translatedASNOrg : data.asn_organization || ""; const timezone = data.timezone || data.time_zone || 'N/A'; const latitude = data.latitude || data.lat || 'N/A'; const longitude = data.longitude || data.lon || data.lng || 'N/A'; let locationParts = [country]; if (region && region !== country) { locationParts.push(region); } if (city && city !== region && city !== country) { locationParts.push(city); } const locationDisplay = locationParts.join(' '); let countryCode = data.country_code || 'unknown'; if (countryCode.toUpperCase() === 'TW') { countryCode = 'CN'; } else if (countryCode.toUpperCase() === 'HK' || countryCode.toUpperCase() === 'MO') { countryCode = 'CN'; } const translations = { 'zh': { title: 'IP 地址详情', ip: 'IP 地址', location: '地理位置', isp: '网络服务商', asn: '自治系统号', countryCode: '国家代码', timezone: '时区', coordinates: '坐标', translation: '翻译', enabled: '已启用', disabled: '已禁用', close: '关闭', refresh: '刷新', mapView: '地图视图', viewOnGoogleMaps: '在Google地图中查看', latitude: '纬度', longitude: '经度' }, 'en': { title: 'IP Address Details', ip: 'IP Address', location: 'Location', isp: 'ISP', asn: 'ASN', countryCode: 'Country Code', timezone: 'Timezone', coordinates: 'Coordinates', translation: 'Translation', enabled: 'Enabled', disabled: 'Disabled', close: 'Close', refresh: 'Refresh', mapView: 'Map View', viewOnGoogleMaps: 'View on Google Maps', latitude: 'Latitude', longitude: 'Longitude' }, 'ja': { title: 'IPアドレス詳細', ip: 'IPアドレス', location: '所在地', isp: 'ISP', asn: 'AS番号', countryCode: '国コード', timezone: 'タイムゾーン', coordinates: '座標', translation: '翻訳', enabled: '有効', disabled: '無効', close: '閉じる', refresh: '更新', mapView: '地図表示', viewOnGoogleMaps: 'Googleマップで表示', latitude: '緯度', longitude: '経度' }, 'hk': { title: 'IP 地址詳情', ip: 'IP 地址', location: '地理位置', isp: '網絡服務商', asn: '自治系統號', countryCode: '國家代碼', timezone: '時區', coordinates: '坐標', translation: '翻譯', enabled: '已啟用', disabled: '已禁用', close: '關閉', refresh: '刷新', mapView: '地圖視圖', viewOnGoogleMaps: '在Google地圖中查看', latitude: '緯度', longitude: '經度' }, 'ko': { title: 'IP 주소 세부정보', ip: 'IP 주소', location: '위치', isp: '인터넷 서비스 제공업체', asn: 'ASN', countryCode: '국가 코드', timezone: '시간대', coordinates: '좌표', translation: '번역', enabled: '사용', disabled: '사용 안 함', close: '닫기', refresh: '새로고침', mapView: '지도 보기', viewOnGoogleMaps: 'Google 지도에서 보기', latitude: '위도', longitude: '경도' }, 'ru': { title: 'Детали IP-адреса', ip: 'IP-адрес', location: 'Местоположение', isp: 'Интернет-провайдер', asn: 'ASN', countryCode: 'Код страны', timezone: 'Часовой пояс', coordinates: 'Координаты', translation: 'Перевод', enabled: 'Включено', disabled: 'Отключено', close: 'Закрыть', refresh: 'Обновить', mapView: 'Просмотр карты', viewOnGoogleMaps: 'Посмотреть в Google Картах', latitude: 'Широта', longitude: 'Долгота' }, 'ar': { title: 'تفاصيل عنوان IP', ip: 'عنوان IP', location: 'الموقع', isp: 'مزود خدمة الإنترنت', asn: 'ASN', countryCode: 'رمز الدولة', timezone: 'المنطقة الزمنية', coordinates: 'الإحداثيات', translation: 'ترجمة', enabled: 'مُمكّن', disabled: 'معطّل', close: 'إغلاق', refresh: 'تحديث', mapView: 'عرض الخريطة', viewOnGoogleMaps: 'عرض على خرائط Google', latitude: 'خط العرض', longitude: 'خط الطول' }, 'es': { title: 'Detalles de la dirección IP', ip: 'Dirección IP', location: 'Ubicación', isp: 'Proveedor de Internet', asn: 'ASN', countryCode: 'Código de país', timezone: 'Zona horaria', coordinates: 'Coordenadas', translation: 'Traducción', enabled: 'Habilitado', disabled: 'Deshabilitado', close: 'Cerrar', refresh: 'Actualizar', mapView: 'Vista de mapa', viewOnGoogleMaps: 'Ver en Google Maps', latitude: 'Latitud', longitude: 'Longitud' }, 'de': { title: 'IP-Adressdetails', ip: 'IP-Adresse', location: 'Standort', isp: 'ISP', asn: 'ASN', countryCode: 'Ländercode', timezone: 'Zeitzone', coordinates: 'Koordinaten', translation: 'Übersetzung', enabled: 'Aktiviert', disabled: 'Deaktiviert', close: 'Schließen', refresh: 'Aktualisieren', mapView: 'Kartenansicht', viewOnGoogleMaps: 'In Google Maps ansehen', latitude: 'Breitengrad', longitude: 'Längengrad' }, 'fr': { title: 'Détails de l’adresse IP', ip: 'Adresse IP', location: 'Emplacement', isp: 'FAI', asn: 'ASN', countryCode: 'Code du pays', timezone: 'Fuseau horaire', coordinates: 'Coordonnées', translation: 'Traduction', enabled: 'Activé', disabled: 'Désactivé', close: 'Fermer', refresh: 'Rafraîchir', mapView: 'Vue carte', viewOnGoogleMaps: 'Voir sur Google Maps', latitude: 'Latitude', longitude: 'Longitude' }, 'vi': { title: 'Chi tiết địa chỉ IP', ip: 'Địa chỉ IP', location: 'Vị trí', isp: 'Nhà cung cấp dịch vụ Internet', asn: 'ASN', countryCode: 'Mã quốc gia', timezone: 'Múi giờ', coordinates: 'Tọa độ', translation: 'Dịch', enabled: 'Bật', disabled: 'Tắt', close: 'Đóng', refresh: 'Làm mới', mapView: 'Xem bản đồ', viewOnGoogleMaps: 'Xem trên Google Maps', latitude: 'Vĩ độ', longitude: 'Kinh độ' }, 'bn': { title: 'IP ঠিকানার বিবরণ', ip: 'IP ঠিকানা', location: 'অবস্থান', isp: 'আইএসপি', asn: 'ASN', countryCode: 'দেশ কোড', timezone: 'সময় অঞ্চল', coordinates: 'অবস্থানাঙ্ক', translation: 'অনুবাদ', enabled: 'সক্রিয়', disabled: 'নিষ্ক্রিয়', close: 'বন্ধ করুন', refresh: 'রিফ্রেশ', mapView: 'মানচিত্র ভিউ', viewOnGoogleMaps: 'Google মানচিত্রে দেখুন', latitude: 'অক্ষাংশ', longitude: 'দ্রাঘিমাংশ' }, 'th': { title: 'รายละเอียดที่อยู่ IP', ip: 'ที่อยู่ IP', location: 'ตำแหน่งที่ตั้ง', isp: 'ผู้ให้บริการอินเทอร์เน็ต', asn: 'ASN', countryCode: 'รหัสประเทศ', timezone: 'เขตเวลา', coordinates: 'พิกัด', translation: 'การแปล', enabled: 'เปิดใช้งาน', disabled: 'ปิดใช้งาน', close: 'ปิด', refresh: 'รีเฟรช', mapView: 'มุมมองแผนที่', viewOnGoogleMaps: 'ดูบน Google Maps', latitude: 'ละติจูด', longitude: 'ลองจิจูด' } }; const getLanguageText = async () => { try { const currentLang = await getRealTimeLanguage(); return translations[currentLang] || translations['zh-CN']; } catch (error) { console.error('Failed to get language:', error); return translations['zh-CN']; } }; const modalOverlay = document.createElement('div'); modalOverlay.className = 'ip-modal-overlay'; modalOverlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); display: flex; justify-content: center; align-items: center; z-index: 10000; opacity: 0; transition: opacity 0.3s ease; `; const modalContent = document.createElement('div'); modalContent.className = 'ip-modal-content'; modalContent.style.cssText = ` background: var(--bg-container); border-radius: 12px; width: 90%; max-width: 800px; max-height: 80vh; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.3); transform: scale(0.9); transition: transform 0.3s ease; `; modalContent.innerHTML = `

📋 Loading...

Loading...
`; modalOverlay.appendChild(modalContent); document.body.appendChild(modalOverlay); setTimeout(() => { modalOverlay.style.opacity = '1'; modalContent.style.transform = 'scale(1)'; }, 10); getLanguageText().then(langText => { modalContent.innerHTML = `

${langText.title}

${country}
${langText.ip}: ${cachedIP}
${langText.location}: ${locationDisplay}
${langText.isp}: ${isp}
${langText.asn}: ${data.asn || ''} ${asnOrg}
${langText.countryCode}: ${countryCode}
${langText.timezone}: ${timezone}
${langText.coordinates}: ${latitude !== 'N/A' && longitude !== 'N/A' ? `${latitude}, ${longitude}` : 'N/A'}
${langText.translation}: ${translationEnabled ? langText.enabled : langText.disabled}
${latitude !== 'N/A' && longitude !== 'N/A' ? `
${langText.mapView}: ${langText.viewOnGoogleMaps}
${langText.latitude}: ${latitude}, ${langText.longitude}: ${longitude}
` : ''}
`; setupModalEvents(modalOverlay, modalContent); }).catch(error => { console.error('Failed to load language:', error); }); const setupModalEvents = (overlay, content) => { const controlPanel = document.getElementById('control-panel-modal'); if (controlPanel) { try { UIkit.modal(controlPanel).hide(); } catch (e) {} } const closeBtn = content.querySelector('.ip-modal-close'); const closeModalBtn = content.querySelector('.cbi-button-remove'); const closeModal = () => { overlay.style.opacity = '0'; content.style.transform = 'scale(0.9)'; setTimeout(() => { if (overlay.parentNode) { overlay.parentNode.removeChild(overlay); } }, 300); }; closeBtn.addEventListener('click', closeModal); closeModalBtn?.addEventListener('click', closeModal); overlay.addEventListener('click', (e) => { if (e.target === overlay) { closeModal(); } }); const refreshBtn = content.querySelector('.cbi-button-apply'); refreshBtn?.addEventListener('click', () => { closeModal(); setTimeout(() => { this.getIpipnetIP(); }, 300); }); }; setupModalEvents(modalOverlay, modalContent); }, }; document.addEventListener('DOMContentLoaded', function() { setupLanguageChangeListener(); const flagElement = document.getElementById('flag'); if (flagElement) { flagElement.addEventListener('click', () => myAppIP.getIpipnetIP()); } myAppIP.getIpipnetIP(); setInterval(() => myAppIP.getIpipnetIP(), 180000); }); function pingHost(id, name, url) { const translations = languageTranslations[currentLang] || languageTranslations['zh']; const normal = document.getElementById(`${id}-normal`); const gray = document.getElementById(`${id}-gray`); const overlay = document.getElementById('result-overlay'); const start = performance.now(); fetch(url, { mode: 'no-cors' }) .then(() => { const end = performance.now(); const ping = Math.round(end - start); normal.style.display = 'inline'; gray.style.display = 'none'; let color = '#00c800'; if (ping > 300) { color = 'red'; } else if (ping > 150) { color = 'orange'; } overlay.innerHTML = `${name} ${translations.ping}: ${ping} ms`; overlay.style.color = color; overlay.style.display = 'block'; clearTimeout(overlay.timer); overlay.timer = setTimeout(() => { overlay.style.display = 'none'; }, 5000); }) .catch(() => { normal.style.display = 'none'; gray.style.display = 'inline'; overlay.innerHTML = `${name} ${translations.timeout}`; overlay.style.color = 'red'; overlay.style.display = 'block'; clearTimeout(overlay.timer); overlay.timer = setTimeout(() => { overlay.style.display = 'none'; }, 5000); }); } function checkAllSites() { const sites = [ {id: 'baidu', name: 'Baidu', url: 'https://www.baidu.com'}, {id: 'taobao', name: 'Taobao', url: 'https://www.taobao.com'}, {id: 'google', name: 'Google', url: 'https://www.google.com'}, {id: 'youtube', name: 'YouTube', url: 'https://www.youtube.com'} ]; sites.forEach(site => { const normal = document.getElementById(`${site.id}-normal`); const gray = document.getElementById(`${site.id}-gray`); fetch(site.url, { mode: 'no-cors' }) .then(() => { normal.style.display = 'inline'; gray.style.display = 'none'; }) .catch(() => { normal.style.display = 'none'; gray.style.display = 'inline'; }); }); } window.addEventListener('load', () => { checkAllSites(); setInterval(checkAllSites, 180000); setTimeout(() => { const elements = document.querySelectorAll('[id*="_status"]'); elements.forEach((element, index) => { setTimeout(() => { element.click(); }, index * 1000); }); }, 3000); });