diff --git a/src/util/util/networking/index.ts b/src/util/util/networking/index.ts
index 717ba2a68..62e20d86b 100644
--- a/src/util/util/networking/index.ts
+++ b/src/util/util/networking/index.ts
@@ -17,3 +17,4 @@
*/
export * from "./abuseipdb";
export * from "./ipdata";
+export * from "./stopforumspam/StopForumSpamClient";
diff --git a/src/util/util/networking/stopforumspam/StopForumSpamClient.ts b/src/util/util/networking/stopforumspam/StopForumSpamClient.ts
new file mode 100644
index 000000000..7767915ac
--- /dev/null
+++ b/src/util/util/networking/stopforumspam/StopForumSpamClient.ts
@@ -0,0 +1,131 @@
+/*
+ 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 .
+*/
+
+import { DateBuilder } from "@spacebar/util";
+
+// https://www.stopforumspam.com/usage
+export class StopForumSpamClient {
+ private static stopForumSpamIpCache: Map<
+ string,
+ {
+ data: StopForumSpamResponse["ip"];
+ expires: number;
+ }
+ > = new Map();
+ private static stopForumSpamEmailCache: Map<
+ string,
+ {
+ data: StopForumSpamResponse["email"];
+ expires: number;
+ }
+ > = new Map();
+ private static stopForumSpamUsernameCache: Map<
+ string,
+ {
+ data: StopForumSpamResponse["username"];
+ expires: number;
+ }
+ > = new Map();
+
+ public static async checkAsync(email?: string, ipAddress?: string, username?: string): Promise {
+ const params = new URLSearchParams();
+ const cachedResults: StopForumSpamResponse = { success: 1 };
+ if (email) {
+ const cachedEmail = StopForumSpamClient.stopForumSpamEmailCache.get(email);
+ if (cachedEmail && cachedEmail.expires > Date.now()) cachedResults.email = cachedEmail.data;
+ else params.append("email", email);
+ }
+ if (ipAddress) {
+ const cachedIp = StopForumSpamClient.stopForumSpamIpCache.get(ipAddress);
+ if (cachedIp && cachedIp.expires > Date.now()) cachedResults.ip = cachedIp.data;
+ else params.append("ip", ipAddress);
+ }
+ if (username) {
+ const cachedUsername = StopForumSpamClient.stopForumSpamUsernameCache.get(username);
+ if (cachedUsername && cachedUsername.expires > Date.now()) cachedResults.username = cachedUsername.data;
+ else params.append("username", username);
+ }
+
+ if (params.toString() === "") {
+ // We don't need to fetch anything...
+ console.log("[StopForumSpamClient] Using cached results for all parameters:", { email, ipAddress, username });
+ return cachedResults;
+ }
+
+ const response = await fetch(`https://api.stopforumspam.org/api?${params.toString()}&json&confidence`, {
+ method: "GET",
+ });
+
+ if (!response.ok) {
+ console.error(`StopForumSpam API request failed with status ${response.status}`);
+ console.error(await response.text());
+ throw new Error(`StopForumSpam API request failed with status ${response.status}`);
+ }
+
+ const data = (await response.json()) as StopForumSpamResponse;
+ if (data.success !== 1) {
+ console.error("StopForumSpam API request was not successful", data);
+ throw new Error("StopForumSpam API request was not successful");
+ }
+
+ if (data.ip)
+ StopForumSpamClient.stopForumSpamIpCache.set(data.ip.value, {
+ data: data.ip,
+ expires: new DateBuilder().addHours(12).buildTimestamp(),
+ });
+
+ if (data.email)
+ StopForumSpamClient.stopForumSpamEmailCache.set(data.email.value, {
+ data: data.email,
+ expires: new DateBuilder().addHours(12).buildTimestamp(),
+ });
+
+ if (data.username)
+ StopForumSpamClient.stopForumSpamUsernameCache.set(data.username.value, {
+ data: data.username,
+ expires: new DateBuilder().addHours(12).buildTimestamp(),
+ });
+
+ return data;
+ }
+}
+export interface StopForumSpamResponse {
+ success: 0 | 1;
+ ip?: {
+ value: string;
+ appears: 0 | 1;
+ lastseen: string;
+ frequency: number;
+ confidence?: number;
+ delegated: string;
+ };
+ email?: {
+ value: string;
+ appears: 0 | 1;
+ lastseen: string;
+ frequency: number;
+ confidence?: number;
+ };
+ username?: {
+ value: string;
+ appears: 0 | 1;
+ lastseen: string;
+ frequency: number;
+ confidence?: number;
+ };
+}