diff --git a/.env.example b/.env.example index ad508865..1c22053f 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,18 @@ +# Server BACKEND_PORT="11966" FRONTEND_PORT="18966" -BING_MAP_API_KEY="" ALLOWED_DOMAINS="" +# APIs +BING_MAP_API_KEY="" IPINFO_API_TOKEN="" KEYCDN_USER_AGENT="" -IPCHECKING_API_KEY="" CLOUDFLARE_API="" +IPAPIIS_API_KEY="" +IPCHECKING_API_KEY="" +MAC_LOOKUP_API_KEY="" +# Security related SECURITY_BLACKLIST_LOG_FILE_PATH="" SECURITY_RATE_LIMIT="" SECURITY_DELAY_AFTER="" -IPAPIIS_API_KEY="" -MAC_LOOKUP_API_KEY="" \ No newline at end of file +# Google Analytics +VITE_GOOGLE_ANALYTICS_ID="" \ No newline at end of file diff --git a/README.md b/README.md index 98f38090..978aa8fc 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ You can use the program without adding any environment variables, but if you wan | `KEYCDN_USER_AGENT` | No | `""` | The domain name when using KeyCDN, must contain https prefix. Used to obtain IP address information through KeyCDN | | `CLOUDFLARE_API` | No | `""` | API Key for Cloudflare, used to obtain AS system information through Cloudflare | | `MAC_LOOKUP_API_KEY` | No | `""` | API Key for MAC Lookup, used to obtain MAC address information | +| `VITE_GOOGLE_ANALYTICS_ID` | **Yes** | `""` | Google Analytics ID, used to track user behavior | ### Using Environment Variables in a Node Environment diff --git a/README_FR.md b/README_FR.md index 410d7fd5..ce8d09ab 100644 --- a/README_FR.md +++ b/README_FR.md @@ -103,6 +103,7 @@ Vous pouvez utiliser le programme sans ajouter de variables d'environnement, mai | `KEYCDN_USER_AGENT` | Non | `""` | Le nom de domaine lorsque vous utilisez KeyCDN, doit contenir le préfixe https. Utilisé pour obtenir des informations sur l'adresse IP via KeyCDN | | `CLOUDFLARE_API` | Non | `""` | Clé API pour Cloudflare, utilisée pour obtenir des informations sur le système AS via Cloudflare | | `MAC_LOOKUP_API_KEY` | Non | `""` | Clé API pour MAC Lookup, utilisée pour obtenir des informations sur l'adresse MAC via MAC Lookup | +| `VITE_GOOGLE_ANALYTICS_ID` | **Oui** | `""` | Identifiant Google Analytics, utilisé pour l'analyse des utilisateurs | ### Utilisation des variables d'environnement dans un environnement Node diff --git a/README_ZH.md b/README_ZH.md index 85d1d01c..0809b781 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -105,6 +105,7 @@ docker run -d -p 18966:18966 --name myip --restart always jason5ng32/myip:latest | `KEYCDN_USER_AGENT` | 否 | `""` | 使用 KeyCDN 时的域名,需包含 https 前缀。用于通过 KeyCDN 获取 IP 归属地信息 | | `CLOUDFLARE_API` | 否 | `""` | Cloudflare 的 API Key,用于通过 Cloudflare 获取 AS 系统的信息 | | `MAC_LOOKUP_API_KEY` | 否 | `""` | MAC 查询的 API Key,用于通过 MAC Lookup 获取 MAC 地址的归属信息 | +| `VITE_GOOGLE_ANALYTICS_ID` | **是** | `""` | Google Analytics 的 ID,用于统计访问量 | ### 在 Node 环境里使用环境变量 diff --git a/api/configs.js b/api/configs.js index 89bbaea6..dfa88cd6 100644 --- a/api/configs.js +++ b/api/configs.js @@ -14,7 +14,7 @@ export default (req, res) => { } const hostname = referer ? new URL(referer).hostname : ''; - const originalSite = hostname === 'ipcheck.ing' || hostname === 'www.ipcheck.ing'; + const originalSite = hostname === 'ipcheck.ing' || hostname === 'www.ipcheck.ing' || hostname === 'localtest.ipcheck.ing'; const envConfigs = { bingMap: process.env.BING_MAP_API_KEY, diff --git a/api/macchecker.js b/api/macchecker.js index 978fc284..0c0e23cb 100644 --- a/api/macchecker.js +++ b/api/macchecker.js @@ -64,15 +64,15 @@ const modifyData = (data) => { data.isLocal = isLocal ? true : false; data.isGlobal = !isLocal ? true : false; data.isUnicast = !isMulticast ? true : false; - data.macPrefix = data.macPrefix? data.macPrefix : 'N/A'; - data.company = data.company? data.company : 'N/A'; - data.country = data.country? data.country : 'N/A'; - data.address = data.address? data.address : 'N/A'; - data.updated = data.updated? data.updated : 'N/A'; - data.blockStart = data.blockStart? data.blockStart : 'N/A'; - data.blockEnd = data.blockEnd? data.blockEnd : 'N/A'; - data.blockSize = data.blockSize? data.blockSize : 'N/A'; - data.blockType = data.blockType? data.blockType : 'N/A'; + data.macPrefix = data.macPrefix ? data.macPrefix.match(/.{1,2}/g).join(':') : 'N/A'; + data.company = data.company ? data.company : 'N/A'; + data.country = data.country ? data.country : 'N/A'; + data.address = data.address ? data.address : 'N/A'; + data.updated = data.updated ? data.updated : 'N/A'; + data.blockStart = data.blockStart ? data.blockStart.match(/.{1,2}/g).join(':') : 'N/A'; + data.blockEnd = data.blockEnd ? data.blockEnd.match(/.{1,2}/g).join(':') : 'N/A'; + data.blockSize = data.blockSize ? data.blockSize : 'N/A'; + data.blockType = data.blockType ? data.blockType : 'N/A'; return data; } \ No newline at end of file diff --git a/api/maxmind.js b/api/maxmind.js new file mode 100644 index 00000000..62078645 --- /dev/null +++ b/api/maxmind.js @@ -0,0 +1,61 @@ +import maxmind from 'maxmind'; +import { isValidIP } from '../common/valid-ip.js'; +import { refererCheck } from '../common/referer-check.js'; + +let cityLookup, asnLookup; + +// 异步初始化数据库 +async function initDatabases() { + cityLookup = await maxmind.open('./common/maxmind-db/GeoLite2-City.mmdb'); + asnLookup = await maxmind.open('./common/maxmind-db/GeoLite2-ASN.mmdb'); +} + +initDatabases(); + +export default (req, res) => { + + // 限制只能从指定域名访问 + const referer = req.headers.referer; + if (!refererCheck(referer)) { + return res.status(403).json({ error: referer ? 'Access denied' : 'What are you doing?' }); + } + + const ip = req.query.ip; + if (!ip) { + return res.status(400).json({ error: 'No IP address provided' }); + } + + // 检查 IP 地址是否合法 + if (!isValidIP(ip)) { + return res.status(400).json({ error: 'Invalid IP address' }); + } + + // 获取请求语言 + const lang = req.query.lang === 'zh-CN' || req.query.lang === 'en' || req.query.lang === 'fr' ? req.query.lang : 'en'; + + try { + const city = cityLookup.get(ip); + const asn = asnLookup.get(ip); + let result = modifyJson(ip, lang, city, asn); + res.json(result); + } catch (e) { + res.status(500).json({ error: e.message }); + } +} + +function modifyJson(ip, lang, city, asn) { + city = city || {}; + asn = asn || {}; + return { + ip, + city: city.city ? city.city.names[lang] || city.city.names.en : "N/A", + region: city.subdivisions ? city.subdivisions[0].names[lang] || city.subdivisions[0].names.en : "N/A", + country: city.country ? city.country.iso_code : "N/A", + country_name: city.country ? city.country.names[lang] : "N/A", + country_code: city.country ? city.country.iso_code : "N/A", + latitude: city.location ? city.location.latitude : "N/A", + longitude: city.location ? city.location.longitude : "N/A", + asn: asn.autonomous_system_number ? "AS" + asn.autonomous_system_number : "N/A", + org: asn.autonomous_system_organization ? asn.autonomous_system_organization : "N/A" + }; +}; \ No newline at end of file diff --git a/backend-server.js b/backend-server.js index b358d5b1..19ff78d0 100644 --- a/backend-server.js +++ b/backend-server.js @@ -18,6 +18,7 @@ import whois from './api/whois.js'; import ipapiisHandler from './api/ipapiis.js'; import invisibilitytestHandler from './api/invisibilitytest.js'; import macChecker from './api/macchecker.js'; +import maxmindHandler from './api/maxmind.js'; dotenv.config(); @@ -137,6 +138,7 @@ app.get('/api/whois', whois); app.get('/api/ipapiis', ipapiisHandler); app.get('/api/invisibility', invisibilitytestHandler); app.get('/api/macchecker', macChecker); +app.get('/api/maxmind', maxmindHandler); // 使用查询参数处理所有配置请求 app.get('/api/configs', validateConfigs); diff --git a/common/maxmind-db/GeoLite2-ASN.mmdb b/common/maxmind-db/GeoLite2-ASN.mmdb new file mode 100644 index 00000000..e4e3fa06 Binary files /dev/null and b/common/maxmind-db/GeoLite2-ASN.mmdb differ diff --git a/common/maxmind-db/GeoLite2-City.mmdb b/common/maxmind-db/GeoLite2-City.mmdb new file mode 100644 index 00000000..b46ce8e9 Binary files /dev/null and b/common/maxmind-db/GeoLite2-City.mmdb differ diff --git a/frontend/components/SpeedTest.vue b/frontend/components/SpeedTest.vue index 05313814..a228cd8b 100644 --- a/frontend/components/SpeedTest.vue +++ b/frontend/components/SpeedTest.vue @@ -86,15 +86,15 @@ v-if="speedTestStatus === 'finished' && hasScores">
{{ t('speedtest.score') }}
{{ t('speedtest.videoStreaming') }}
-
+
{{ speedTest.streamingScore }}
{{ t('speedtest.gaming') }}
-
+
{{ speedTest.gamingScore }}
{{ t('speedtest.rtc') }}
-
+
{{ speedTest.rtcScore }}
{{ t('speedtest.resultNote') }}
@@ -365,4 +365,8 @@ defineExpose({
background-color: var(--bs-btn-hover-bg);
border-color: var(--bs-btn-hover-border-color);
}
+.jn-text-warning {
+ --bs-text-opacity: 1;
+ color: #c67c14;
+}
diff --git a/frontend/components/advanced-tools/MacChecker.vue b/frontend/components/advanced-tools/MacChecker.vue
index 2bd879ee..3b09be09 100644
--- a/frontend/components/advanced-tools/MacChecker.vue
+++ b/frontend/components/advanced-tools/MacChecker.vue
@@ -37,7 +37,7 @@
{{ t('macchecker.manufacturer') }}
{{ t('macchecker.property') }}