Skip to content

Commit

Permalink
Merge pull request #279 from jason5ng32/dev
Browse files Browse the repository at this point in the history
Improvements
  • Loading branch information
jason5ng32 authored Feb 3, 2025
2 parents 8d1a401 + c5357ca commit 3c66a7c
Show file tree
Hide file tree
Showing 86 changed files with 3,250 additions and 1,177 deletions.
9 changes: 5 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ ALLOWED_DOMAINS=""
BING_MAP_API_KEY=""
GOOGLE_MAP_API_KEY=""
IPINFO_API_TOKEN=""
KEYCDN_USER_AGENT=""
CLOUDFLARE_API=""
IPAPIIS_API_KEY=""
IP2LOCATION_API_KEY=""
IPCHECKING_API_KEY=""
MAC_LOOKUP_API_KEY=""
IPCHECKING_API_ENDPOINT=""
# Security related
SECURITY_BLACKLIST_LOG_FILE_PATH=""
SECURITY_RATE_LIMIT=""
SECURITY_DELAY_AFTER=""
# Google Analytics
VITE_GOOGLE_ANALYTICS_ID=""
# CURL API
VITE_CURL_IPV4_DOMAIN=""
VITE_CURL_IPV6_DOMAIN=""
VITE_CURL_IPV64_DOMAIN=""
VITE_CURL_IPV64_DOMAIN=""
# Google Analytics
VITE_GOOGLE_ANALYTICS_ID=""
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ dist-ssr

package-lock.json
localtest

.VSCodeCounter
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ You can use the program without adding any environment variables, but if you wan
| `IPCHECKING_API_KEY` | No | `""` | API Key for IPCheck.ing, used to obtain accurate IP geolocation information |
| `IPINFO_API_TOKEN` | No | `""` | API Token for IPInfo.io, used to obtain IP geolocation information through IPInfo.io |
| `IPAPIIS_API_KEY` | No | `""` | API Key for IPAPI.is, used to obtain IP geolocation information through IPAPI.is |
| `KEYCDN_USER_AGENT` | No | `""` | The domain name when using KeyCDN, must contain https prefix. Used to obtain IP address information through KeyCDN |
| `IP2LOCATION_API_KEY` | No | `""` | API Key for IP2Location.io, used to obtain IP geolocation information through IP2Location.io |
| `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 |
| `IPCHECKING_API_ENDPOINT` | **Yes** | `""` | IPCheck.ing API endpoint |
| `VITE_GOOGLE_ANALYTICS_ID` | **Yes** | `""` | Google Analytics ID, used to track user behavior |
| `VITE_CURL_IPV4_DOMAIN` | No | `""` | Provides the IPv4 domain for the CURL API to users |
| `VITE_CURL_IPV6_DOMAIN` | No | `""` | Provides the IPv6 domain for the CURL API to users |
Expand Down Expand Up @@ -156,7 +157,7 @@ If you're using a proxy for internet access, consider adding this rule to your p

```ini
# IP Testing
IP-CIDR,1.0.0.1/32,Proxy,no-resolve
IP-CIDR,1.0.0.2/32,Proxy,no-resolve
IP-CIDR6,2606:4700:4700::1111/128,Proxy,no-resolve
DOMAIN,4.ipcheck.ing,DIRECT
DOMAIN,6.ipcheck.ing,DIRECT
Expand Down
5 changes: 3 additions & 2 deletions README_FR.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ Vous pouvez utiliser le programme sans ajouter de variables d'environnement, mai
| `IPCHECKING_API_KEY` | Non | `""` | Clé API pour IPCheck.ing, utilisée pour obtenir des informations de géolocalisation précises sur l'adresse IP |
| `IPINFO_API_TOKEN` | Non | `""` | Jeton API pour IPInfo.io, utilisé pour obtenir des informations de géolocalisation sur l'adresse IP via IPInfo.io |
| `IPAPIIS_API_KEY` | Non | `""` | Clé API pour IPAPI.is, utilisée pour obtenir des informations de géolocalisation sur l'adresse IP via IPAPI.is |
| `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 |
| `IP2LOCATION_API_KEY` | Non | `""` | Clé API pour IP2Location.io, utilisée pour obtenir des informations de géolocalisation sur l'adresse IP via IP2Location.io |
| `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 |
| `IPCHECKING_API_ENDPOINT` | **Oui** | `""` | URL de l'API IPCheck.ing |
| `VITE_GOOGLE_ANALYTICS_ID` | **Oui** | `""` | Identifiant Google Analytics, utilisé pour l'analyse des utilisateurs |
| `VITE_CURL_IPV4_DOMAIN` | Non | `""` | Fournit aux utilisateurs le domaine IPv4 pour l'API CURL |
| `VITE_CURL_IPV6_DOMAIN` | Non | `""` | Fournit aux utilisateurs le domaine IPv6 pour l'API CURL |
Expand Down Expand Up @@ -156,7 +157,7 @@ Si vous utilisez un proxy pour accéder à Internet, envisagez d'ajouter cette r

```ini
# Test d'adresse IP
IP-CIDR,1.0.0.1/32,Proxy,no-resolve
IP-CIDR,1.0.0.2/32,Proxy,no-resolve
IP-CIDR6,2606:4700:4700::1111/128,Proxy,no-resolve
DOMAIN,4.ipcheck.ing,DIRECT
DOMAIN,6.ipcheck.ing,DIRECT
Expand Down
5 changes: 3 additions & 2 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ docker run -d -p 18966:18966 --name myip --restart always jason5ng32/myip:latest
| `IPCHECKING_API_KEY` || `""` | IPCheck.ing 的 API Key,用于获取精准的 IP 归属地信息 |
| `IPINFO_API_TOKEN` || `""` | IPInfo.io 的 API Token,用于通过 IPInfo.io 获取 IP 归属地信息 |
| `IPAPIIS_API_KEY` || `""` | IPAPI.is 的 API Key,用于通过 IPAPI.is 获取 IP 归属地信息 |
| `KEYCDN_USER_AGENT` || `""` | 使用 KeyCDN 时的域名,需包含 https 前缀。用于通过 KeyCDN 获取 IP 归属地信息 |
| `IP2LOCATION_API_KEY` || `""` | IP2Location.io 的 API Key,用于通过 IP2Location.io 获取 IP 归属地信息 |
| `CLOUDFLARE_API` || `""` | Cloudflare 的 API Key,用于通过 Cloudflare 获取 AS 系统的信息 |
| `MAC_LOOKUP_API_KEY` || `""` | MAC 查询的 API Key,用于通过 MAC Lookup 获取 MAC 地址的归属信息 |
| `IPCHECKING_API_ENDPOINT` | **** | `""` | IPCheck.ing 的 API 端点 URL |
| `VITE_GOOGLE_ANALYTICS_ID` | **** | `""` | Google Analytics 的 ID,用于统计访问量 |
| `VITE_CURL_IPV4_DOMAIN` || `""` | 为用户提供 CURL API 的 IPv4 域名 |
| `VITE_CURL_IPV6_DOMAIN` || `""` | 为用户提供 CURL API 的 IPv6 域名 |
Expand Down Expand Up @@ -156,7 +157,7 @@ docker run -d -p 18966:18966 \

```ini
# IP Testing
IP-CIDR,1.0.0.1/32,Proxy,no-resolve
IP-CIDR,1.0.0.2/32,Proxy,no-resolve
IP-CIDR6,2606:4700:4700::1111/128,Proxy,no-resolve
DOMAIN,4.ipcheck.ing,DIRECT
DOMAIN,6.ipcheck.ing,DIRECT
Expand Down
100 changes: 44 additions & 56 deletions api/cfradar.js → api/cf-radar.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import { parse } from 'dotenv';
import { refererCheck } from '../common/referer-check.js';

Expand All @@ -12,15 +11,19 @@ function createFetchOptions() {
};
}

// 通用的 fetch 请求函数
async function fetchFromCloudflare(endpoint) {
const url = `https://api.cloudflare.com/client/v4${endpoint}`;
const headers = createFetchOptions().headers;
const options = { headers };
const response = await fetch(url, options);
return response.json();
}

// ASN 信息
async function getASNInfo(asn) {
try {
const url = `https://api.cloudflare.com/client/v4/radar/entities/asns/${asn}`;
const headers = createFetchOptions().headers;
const options = { headers };
const response = await fetch(url, options);
const json = await response.json();
return json;
return await fetchFromCloudflare(`/radar/entities/asns/${asn}`);
} catch (error) {
console.error(error);
throw new Error('Failed to fetch ASN info');
Expand All @@ -30,12 +33,7 @@ async function getASNInfo(asn) {
// IP 版本分布
async function getASNIPVersion(asn) {
try {
const url = `https://api.cloudflare.com/client/v4/radar/http/summary/ip_version?asn=${asn}&dateRange=7d`;
const headers = createFetchOptions().headers;
const options = { headers };
const response = await fetch(url, options);
const json = await response.json();
return json;
return await fetchFromCloudflare(`/radar/http/summary/ip_version?asn=${asn}&dateRange=7d`);
} catch (error) {
console.error(error);
throw new Error('Failed to fetch ASN IP version');
Expand All @@ -45,12 +43,7 @@ async function getASNIPVersion(asn) {
// HTTP 协议分布
async function getASNHTTPProtocol(asn) {
try {
const url = `https://api.cloudflare.com/client/v4/radar/http/summary/http_protocol?asn=${asn}&dateRange=7d`;
const headers = createFetchOptions().headers;
const options = { headers };
const response = await fetch(url, options);
const json = await response.json();
return json;
return await fetchFromCloudflare(`/radar/http/summary/http_protocol?asn=${asn}&dateRange=7d`);
} catch (error) {
console.error(error);
throw new Error('Failed to fetch ASN HTTP protocol');
Expand All @@ -60,12 +53,7 @@ async function getASNHTTPProtocol(asn) {
// 设备分布
async function getASNDeviceType(asn) {
try {
const url = `https://api.cloudflare.com/client/v4/radar/http/summary/device_type?asn=${asn}&dateRange=7d`;
const headers = createFetchOptions().headers;
const options = { headers };
const response = await fetch(url, options);
const json = await response.json();
return json;
return await fetchFromCloudflare(`/radar/http/summary/device_type?asn=${asn}&dateRange=7d`);
} catch (error) {
console.error(error);
throw new Error('Failed to fetch ASN device type');
Expand All @@ -75,24 +63,35 @@ async function getASNDeviceType(asn) {
// 机器人分布
async function getASNBotType(asn) {
try {
const url = `https://api.cloudflare.com/client/v4/radar/http/summary/bot_class?asn=${asn}&dateRange=7d`;
const headers = createFetchOptions().headers;
const options = { headers };
const response = await fetch(url, options);
const json = await response.json();
return json;
return await fetchFromCloudflare(`/radar/http/summary/bot_class?asn=${asn}&dateRange=7d`);
} catch (error) {
console.error(error);
throw new Error('Failed to fetch ASN bot type');
}
};

// 使用 Promise.all 进行并行请求
async function getAllASNData(asn) {
try {
const [asnInfo, ipVersion, httpProtocol, deviceType, botType] = await Promise.all([
getASNInfo(asn),
getASNIPVersion(asn),
getASNHTTPProtocol(asn),
getASNDeviceType(asn),
getASNBotType(asn)
]);
return { asnInfo, ipVersion, httpProtocol, deviceType, botType };
} catch (error) {
console.error(error);
throw new Error('Failed to fetch all ASN data');
}
}

// 验证 asn 是否合法
function isValidASN(asn) {
return /^[0-9]+$/.test(asn);
};


// 格式化输出

function formatData(data) {
Expand Down Expand Up @@ -143,37 +142,26 @@ export default async (req, res) => {
}

try {
const results = await Promise.allSettled([
getASNInfo(asn),
getASNIPVersion(asn),
getASNHTTPProtocol(asn),
getASNDeviceType(asn),
getASNBotType(asn)
]);

// 直接转换每个结果,如果状态是 'fulfilled' 则返回值,否则返回一个包含错误信息的对象
const response = results.map(result => {
return result.status === 'fulfilled' ? result.value : { error: 'Failed to fetch data' };
});
const { asnInfo, ipVersion, httpProtocol, deviceType, botType } = await getAllASNData(asn);

// 清洗数据
function cleanUpResponseData(data) {
return {
asnName: data[0]?.result?.asn?.name,
asnOrgName: data[0]?.result?.asn?.orgName,
estimatedUsers: data[0]?.result?.asn?.estimatedUsers?.estimatedUsers,
IPv4_Pct: data[1]?.result?.summary_0?.IPv4,
IPv6_Pct: data[1]?.result?.summary_0?.IPv6,
HTTP_Pct: data[2]?.result?.summary_0?.http,
HTTPS_Pct: data[2]?.result?.summary_0?.https,
Desktop_Pct: data[3]?.result?.summary_0?.desktop,
Mobile_Pct: data[3]?.result?.summary_0?.mobile,
Bot_Pct: data[4]?.result?.summary_0?.bot,
Human_Pct: data[4]?.result?.summary_0?.human
asnName: data.asnInfo.result.asn.name,
asnOrgName: data.asnInfo.result.asn.orgName,
estimatedUsers: data.asnInfo.result.asn.estimatedUsers.estimatedUsers,
IPv4_Pct: data.ipVersion.result.summary_0.IPv4,
IPv6_Pct: data.ipVersion.result.summary_0.IPv6,
HTTP_Pct: data.httpProtocol.result.summary_0.http,
HTTPS_Pct: data.httpProtocol.result.summary_0.https,
Desktop_Pct: data.deviceType.result.summary_0.desktop,
Mobile_Pct: data.deviceType.result.summary_0.mobile,
Bot_Pct: data.botType.result.summary_0.bot,
Human_Pct: data.botType.result.summary_0.human
};
}

const cleanedResponse = cleanUpResponseData(response);
const cleanedResponse = cleanUpResponseData({ asnInfo, ipVersion, httpProtocol, deviceType, botType });
const finalResponse = formatData(cleanedResponse);
filterData(finalResponse);

Expand Down
5 changes: 3 additions & 2 deletions api/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ export default (req, res) => {
}

const hostname = referer ? new URL(referer).hostname : '';
const originalSite = hostname === 'ipcheck.ing' || hostname === 'www.ipcheck.ing' || hostname === 'localtest.ipcheck.ing';
const allowedHostnames = ['ipcheck.ing', 'www.ipcheck.ing', 'localtest.ipcheck.ing'];
const originalSite = allowedHostnames.includes(hostname);

const envConfigs = {
map: process.env.GOOGLE_MAP_API_KEY,
ipInfo: process.env.IPINFO_API_TOKEN,
ipChecking: process.env.IPCHECKING_API_KEY,
keyCDN: process.env.KEYCDN_USER_AGENT,
ip2location: process.env.IP2LOCATION_API_KEY,
originalSite,
cloudFlare: process.env.CLOUDFLARE_API,
ipapiis: process.env.IPAPIIS_API_KEY,
Expand Down
File renamed without changes.
38 changes: 38 additions & 0 deletions api/get-user-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { refererCheck } from '../common/referer-check.js';

export default async (req, res) => {

// 限制只能从指定域名访问
const referer = req.headers.referer;
if (!refererCheck(referer)) {
return res.status(403).json({ error: referer ? 'Access denied' : 'What are you doing?' });
}

const key = process.env.IPCHECKING_API_KEY;

if (!key) {
return res.status(500).json({ error: 'API key is missing' });
}

// 构建请求
const apiEndpoint = process.env.IPCHECKING_API_ENDPOINT;
const url = new URL(`${apiEndpoint}/userinfo?key=${key}`);

try {
const apiResponse = await fetch(url, {
headers: {
...req.headers,
}
});

if (!apiResponse.ok) {
throw new Error(`API responded with status: ${apiResponse.status}`);
}

const data = await apiResponse.json();
res.json(data);
} catch (error) {
console.error("Error during API request:", error);
res.status(500).json({ error: error.message });
}
}
File renamed without changes.
File renamed without changes.
44 changes: 28 additions & 16 deletions api/invisibilitytest.js → api/invisibility-test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { get } from 'https';
import { refererCheck } from '../common/referer-check.js';

// 如果长度不等于 28 且不是字母与数字的组合,则返回 false
Expand All @@ -14,7 +13,7 @@ function isValidUserID(userID) {
return true;
}

export default (req, res) => {
export default async (req, res) => {

// 限制只能从指定域名访问
const referer = req.headers.referer;
Expand All @@ -33,25 +32,38 @@ export default (req, res) => {
}

const apikey = process.env.IPCHECKING_API_KEY;

if (!apikey) {
return res.status(500).json({ error: 'API key is missing' });
}

const url = new URL(`https://api.ipcheck.ing/getpdresult/${id}?apikey=${apikey}`);
const apiEndpoint = process.env.IPCHECKING_API_ENDPOINT;
const url = new URL(`${apiEndpoint}/getpdresult/${id}?apikey=${apikey}`);

get(url, apiRes => {
let data = '';
apiRes.on('data', chunk => data += chunk);
apiRes.on('end', () => {
try {
const result = JSON.parse(data);
res.json(result);
} catch (e) {
res.status(500).json({ error: 'Error parsing JSON' });
try {
const apiResponse = await fetch(url, {
headers: {
...req.headers,
}
});
}).on('error', (e) => {
res.status(500).json({ error: e.message });
});

// 捕捉上游错误
if (!apiResponse.ok) {
let errorDetail = '';
try {
const errorData = await apiResponse.json();
errorDetail = errorData.message || JSON.stringify(errorData);
} catch {
errorDetail = apiResponse.statusText;
}
throw new Error(`API responded with status: ${apiResponse.status} - ${errorDetail}`);
}

const data = await apiResponse.json();
res.json(data);
} catch (error) {
console.error("Error during API request:", error);
res.status(500).json({ error: error.message });
}

};
File renamed without changes.
Loading

0 comments on commit 3c66a7c

Please sign in to comment.