diff --git a/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts b/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts
new file mode 100644
index 00000000..90a1ca29
--- /dev/null
+++ b/src/util/util/networking/abuseipdb/AbuseIpDbClient.ts
@@ -0,0 +1,65 @@
+import { Config, DateBuilder } from "@spacebar/util";
+import { AbuseIpDbBlacklistResponse, AbuseIpDbCheckResponse } from "./AbuseIpDbSampleResponses";
+
+export default class AbuseIpDbClient {
+ private static ipCheckCache: Map<
+ string,
+ {
+ data: AbuseIpDbCheckResponse;
+ expires: number;
+ }
+ > = new Map();
+
+ private static blacklistCache: {
+ data: AbuseIpDbBlacklistResponse;
+ expires: number;
+ } | null = null;
+
+ static async checkIpAddress(ip: string): Promise<AbuseIpDbCheckResponse | null> {
+ const { ipdataApiKey } = Config.get().security;
+ if (!ipdataApiKey) return null;
+ if (this.ipCheckCache.get(ip)?.expires ?? 0 > Date.now()) return this.ipCheckCache.get(ip)!.data;
+
+ console.log(`[AbuseIPDB] Checking IP address ${ip}...`);
+ const resp = (await (await fetch(`https://api.abuseipdb.com/api/v2/check?ipAddress=${ip}`)).json()) as Promise<AbuseIpDbCheckResponse>;
+ this.ipCheckCache.set(ip, {
+ data: await resp,
+ expires: new DateBuilder().addHours(12).buildTimestamp(),
+ });
+ return await resp;
+ }
+
+ static async getBlacklist(): Promise<AbuseIpDbBlacklistResponse | null> {
+ const { abuseIpDbApiKey, abuseipdbBlacklistRatelimit } = Config.get().security;
+ if (!abuseIpDbApiKey) return null;
+ if (this.blacklistCache?.expires ?? 0 > Date.now()) return this.blacklistCache!.data;
+
+ console.log("[AbuseIPDB] Fetching blacklist...");
+ const resp = (await (
+ await fetch(`https://api.abuseipdb.com/api/v2/blacklist`, {
+ headers: {
+ Key: abuseIpDbApiKey,
+ Accept: "application/json",
+ },
+ })
+ ).json()) as Promise<AbuseIpDbBlacklistResponse>;
+
+ this.blacklistCache = {
+ data: await resp,
+ expires: new DateBuilder().addHours(Math.ceil(24 / abuseipdbBlacklistRatelimit)).buildTimestamp(),
+ };
+
+ return await resp;
+ }
+
+ static async isIpBlacklisted(ip: string): Promise<boolean> {
+ const { abuseipdbConfidenceScoreTreshold } = Config.get().security;
+ const blacklist = await this.getBlacklist();
+ if (!blacklist) return false;
+
+ const entry = blacklist.data.find((x) => x.ipAddress === ip);
+ if (!entry) return false;
+
+ return entry.abuseConfidenceScore >= abuseipdbConfidenceScoreTreshold;
+ }
+}
diff --git a/src/util/util/networking/abuseipdb/AbuseIpDbSampleResponses.ts b/src/util/util/networking/abuseipdb/AbuseIpDbSampleResponses.ts
new file mode 100644
index 00000000..210af2ce
--- /dev/null
+++ b/src/util/util/networking/abuseipdb/AbuseIpDbSampleResponses.ts
@@ -0,0 +1,73 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2025 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+export type AbuseIpDbCheckResponse = typeof abuseIpDbCheckResponseSample;
+export type AbuseIpDbBlacklistResponse = typeof abuseIpDbBlacklistResponseSample;
+
+const abuseIpDbCheckResponseSample = {
+ data: {
+ ipAddress: "118.25.6.39",
+ isPublic: true,
+ ipVersion: 4,
+ isWhitelisted: false,
+ abuseConfidenceScore: 100,
+ countryCode: "CN",
+ countryName: "China",
+ usageType: "Data Center/Web Hosting/Transit",
+ isp: "Tencent Cloud Computing (Beijing) Co. Ltd",
+ domain: "tencent.com",
+ hostnames: [],
+ isTor: false,
+ totalReports: 1,
+ numDistinctUsers: 1,
+ lastReportedAt: "2018-12-20T20:55:14+00:00",
+ reports: [
+ {
+ reportedAt: "2018-12-20T20:55:14+00:00",
+ comment: "Dec 20 20:55:14 srv206 sshd[13937]: Invalid user oracle from 118.25.6.39",
+ categories: [18, 22],
+ reporterId: 1,
+ reporterCountryCode: "US",
+ reporterCountryName: "United States",
+ },
+ ],
+ },
+};
+
+const abuseIpDbBlacklistResponseSample = {
+ meta: {
+ generatedAt: "2020-09-24T19:54:11+00:00",
+ },
+ data: [
+ {
+ ipAddress: "5.188.10.179",
+ abuseConfidenceScore: 100,
+ lastReportedAt: "2020-09-24T19:17:02+00:00",
+ },
+ {
+ ipAddress: "185.222.209.14",
+ abuseConfidenceScore: 100,
+ lastReportedAt: "2020-09-24T19:17:02+00:00",
+ },
+ {
+ ipAddress: "191.96.249.183",
+ abuseConfidenceScore: 100,
+ lastReportedAt: "2020-09-24T19:17:01+00:00",
+ },
+ ],
+};
diff --git a/src/util/util/networking/abuseipdb/index.ts b/src/util/util/networking/abuseipdb/index.ts
new file mode 100644
index 00000000..a8178078
--- /dev/null
+++ b/src/util/util/networking/abuseipdb/index.ts
@@ -0,0 +1,18 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2025 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+export * from "./AbuseIpDbClient";
diff --git a/src/util/util/networking/index.ts b/src/util/util/networking/index.ts
new file mode 100644
index 00000000..717ba2a6
--- /dev/null
+++ b/src/util/util/networking/index.ts
@@ -0,0 +1,19 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2025 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+export * from "./abuseipdb";
+export * from "./ipdata";
diff --git a/src/util/util/networking/ipdata/IpDataClient.ts b/src/util/util/networking/ipdata/IpDataClient.ts
new file mode 100644
index 00000000..7571770b
--- /dev/null
+++ b/src/util/util/networking/ipdata/IpDataClient.ts
@@ -0,0 +1,35 @@
+import { Config, DateBuilder } from "@spacebar/util";
+import { IpDataIpLookupResponse } from "./IpDataSampleResponses";
+
+export default class IpDataClient {
+ private static ipInfoCache: Map<
+ string,
+ {
+ data: IpDataIpLookupResponse;
+ expires: number;
+ }
+ > = new Map();
+
+ static async getIpInfo(ip: string): Promise<IpDataIpLookupResponse | null> {
+ const { ipdataApiKey } = Config.get().security;
+ if (!ipdataApiKey) return null;
+ if (this.ipInfoCache.get(ip)?.expires ?? 0 > Date.now()) return this.ipInfoCache.get(ip)!.data;
+
+ console.log(`[IpData] Fetching info for IP address ${ip}...`);
+ const resp = (await (await fetch(`https://eu-api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json()) as Promise<IpDataIpLookupResponse>;
+ this.ipInfoCache.set(ip, {
+ data: await resp,
+ expires: new DateBuilder().addHours(12).buildTimestamp(),
+ });
+ return await resp;
+ }
+
+ //TODO add function that support both ip and domain names
+ static isProxy(data: IpDataIpLookupResponse) {
+ if (!data || !data.asn || !data.threat) return false;
+ if (data.asn.type !== "isp") return true;
+ if (Object.values(data.threat).some((x) => x)) return true;
+
+ return false;
+ }
+}
diff --git a/src/util/util/networking/ipdata/IpDataSampleResponses.ts b/src/util/util/networking/ipdata/IpDataSampleResponses.ts
new file mode 100644
index 00000000..fa6d0fa8
--- /dev/null
+++ b/src/util/util/networking/ipdata/IpDataSampleResponses.ts
@@ -0,0 +1,79 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2025 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+export type IpDataIpLookupResponse = typeof ipDataSampleIpLookupResponse;
+
+const ipDataSampleIpLookupResponse = {
+ ip: "",
+ is_eu: true,
+ city: "",
+ region: "",
+ region_code: "",
+ country_name: "",
+ country_code: "",
+ continent_name: "",
+ continent_code: "",
+ latitude: 0,
+ longitude: 0,
+ postal: "",
+ calling_code: "",
+ flag: "",
+ emoji_flag: "",
+ emoji_unicode: "",
+ asn: {
+ asn: "",
+ name: "",
+ domain: "",
+ route: "",
+ type: "isp",
+ },
+ languages: [
+ {
+ name: "",
+ native: "",
+ },
+ ],
+ currency: {
+ name: "",
+ code: "",
+ symbol: "",
+ native: "",
+ plural: "",
+ },
+ time_zone: {
+ name: "",
+ abbr: "",
+ offset: "",
+ is_dst: true,
+ current_time: "",
+ },
+ threat: {
+ is_tor: false,
+ is_icloud_relay: false,
+ is_proxy: false,
+ is_datacenter: false,
+ is_anonymous: false,
+ is_known_attacker: false,
+ is_known_abuser: false,
+ is_threat: false,
+ is_bogon: false,
+ blocklists: [],
+ },
+ count: 0,
+ status: 200,
+};
diff --git a/src/util/util/networking/ipdata/index.ts b/src/util/util/networking/ipdata/index.ts
new file mode 100644
index 00000000..a0391cb4
--- /dev/null
+++ b/src/util/util/networking/ipdata/index.ts
@@ -0,0 +1,18 @@
+/*
+ Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
+ Copyright (C) 2025 Spacebar and Spacebar Contributors
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+export * from "./IpDataClient";
|