From 62825bd40b4e818db950901836b94e8a1c5ef780 Mon Sep 17 00:00:00 2001 From: Kaan <90235641+zrodevkaan@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:13:15 -0600 Subject: [PATCH 1/8] Antiscam Checks --- src/events/antiscam.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/events/antiscam.ts diff --git a/src/events/antiscam.ts b/src/events/antiscam.ts new file mode 100644 index 0000000..ed21352 --- /dev/null +++ b/src/events/antiscam.ts @@ -0,0 +1,23 @@ +import { Events, GuildMember, Message, MessageFlags } from "discord.js"; +import Messages from "../util/messages"; + +const URL_REGEX = + /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/g; + +export default { + name: Events.MessageCreate, + + async execute(message: Message) { + const content = message.content; + + const linkAmount = Array.from(content.matchAll(URL_REGEX)); + if (linkAmount.length == 4) { + await message.reply( + Messages.error("This message contains potential scam material.", { + ephemeral: true, + }) + ); + await message.delete(); + } + }, +}; From 541bdb01361aa9aff99663830245c69f14108d09 Mon Sep 17 00:00:00 2001 From: Kaan <90235641+zrodevkaan@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:17:04 -0600 Subject: [PATCH 2/8] Error handling --- src/events/antiscam.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/events/antiscam.ts b/src/events/antiscam.ts index ed21352..cfad2e3 100644 --- a/src/events/antiscam.ts +++ b/src/events/antiscam.ts @@ -12,12 +12,16 @@ export default { const linkAmount = Array.from(content.matchAll(URL_REGEX)); if (linkAmount.length == 4) { - await message.reply( - Messages.error("This message contains potential scam material.", { - ephemeral: true, - }) - ); - await message.delete(); + try { + await message.reply( + Messages.error("This message contains potential scam material.", { + ephemeral: true, + }) + ); + await message.delete(); + } catch (error) { + // this should never happen but this is so the bot doesnt disconect. + } } }, }; From 62b594a289ded99a374812037049f66ad819f2e5 Mon Sep 17 00:00:00 2001 From: Kaan <90235641+zrodevkaan@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:31:55 -0600 Subject: [PATCH 3/8] Guh ephemeral doesnt work on regular messages --- src/events/antiscam.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/events/antiscam.ts b/src/events/antiscam.ts index cfad2e3..8003c16 100644 --- a/src/events/antiscam.ts +++ b/src/events/antiscam.ts @@ -4,6 +4,9 @@ import Messages from "../util/messages"; const URL_REGEX = /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/g; +const isStaff = (author: GuildMember) => + author.permissions.has("ManageMessages"); + export default { name: Events.MessageCreate, @@ -11,14 +14,16 @@ export default { const content = message.content; const linkAmount = Array.from(content.matchAll(URL_REGEX)); - if (linkAmount.length == 4) { + const guildMember = message.guild?.members.cache.get(message.author.id); + console.log(guildMember?.permissions); + if (linkAmount.length == 4 && guildMember && !isStaff(guildMember)) { try { - await message.reply( - Messages.error("This message contains potential scam material.", { - ephemeral: true, - }) + const reply = await message.reply( + Messages.error("This message contains potential scam material.") ); await message.delete(); + + setTimeout(() => reply.delete().catch(() => {}), 3000); } catch (error) { // this should never happen but this is so the bot doesnt disconect. } From 75201e6429eaaeba7ec0351d85e807a6a63dada4 Mon Sep 17 00:00:00 2001 From: Kaan <90235641+zrodevkaan@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:32:23 -0600 Subject: [PATCH 4/8] Remove log --- src/events/antiscam.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/events/antiscam.ts b/src/events/antiscam.ts index 8003c16..b59ef2c 100644 --- a/src/events/antiscam.ts +++ b/src/events/antiscam.ts @@ -15,7 +15,6 @@ export default { const linkAmount = Array.from(content.matchAll(URL_REGEX)); const guildMember = message.guild?.members.cache.get(message.author.id); - console.log(guildMember?.permissions); if (linkAmount.length == 4 && guildMember && !isStaff(guildMember)) { try { const reply = await message.reply( From 0864ae0d5a1e7a2c3e5e9d08c80d8b22bda8395f Mon Sep 17 00:00:00 2001 From: Kaan <90235641+zrodevkaan@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:43:33 -0600 Subject: [PATCH 5/8] Fix lint --- src/events/antiscam.ts | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/events/antiscam.ts b/src/events/antiscam.ts index b59ef2c..fdf2a93 100644 --- a/src/events/antiscam.ts +++ b/src/events/antiscam.ts @@ -2,30 +2,30 @@ import { Events, GuildMember, Message, MessageFlags } from "discord.js"; import Messages from "../util/messages"; const URL_REGEX = - /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/g; + /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/g; const isStaff = (author: GuildMember) => - author.permissions.has("ManageMessages"); + author.permissions.has("ManageMessages"); export default { - name: Events.MessageCreate, + name: Events.MessageCreate, - async execute(message: Message) { - const content = message.content; + async execute(message: Message) { + const content = message.content; - const linkAmount = Array.from(content.matchAll(URL_REGEX)); - const guildMember = message.guild?.members.cache.get(message.author.id); - if (linkAmount.length == 4 && guildMember && !isStaff(guildMember)) { - try { - const reply = await message.reply( - Messages.error("This message contains potential scam material.") - ); - await message.delete(); + const linkAmount = Array.from(content.matchAll(URL_REGEX)); + const guildMember = message.guild?.members.cache.get(message.author.id); + if (linkAmount.length == 4 && guildMember && !isStaff(guildMember)) { + try { + const reply = await message.reply( + Messages.error("This message contains potential scam material.") + ); + await message.delete(); - setTimeout(() => reply.delete().catch(() => {}), 3000); - } catch (error) { - // this should never happen but this is so the bot doesnt disconect. - } - } - }, + setTimeout(() => reply.delete().catch(() => { }), 3000); + } catch (error) { + // this should never happen but this is so the bot doesnt disconect. + } + } + }, }; From 2c69b94760ba9060e27c91746400c141e43b3df6 Mon Sep 17 00:00:00 2001 From: Kaan <90235641+zrodevkaan@users.noreply.github.com> Date: Wed, 10 Dec 2025 18:16:17 -0600 Subject: [PATCH 6/8] Revamp Rework & Merged Into detectspam --- src/events/antiscam.ts | 31 ---------- src/events/detectspam.ts | 128 +++++++++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 63 deletions(-) delete mode 100644 src/events/antiscam.ts diff --git a/src/events/antiscam.ts b/src/events/antiscam.ts deleted file mode 100644 index fdf2a93..0000000 --- a/src/events/antiscam.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Events, GuildMember, Message, MessageFlags } from "discord.js"; -import Messages from "../util/messages"; - -const URL_REGEX = - /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/g; - -const isStaff = (author: GuildMember) => - author.permissions.has("ManageMessages"); - -export default { - name: Events.MessageCreate, - - async execute(message: Message) { - const content = message.content; - - const linkAmount = Array.from(content.matchAll(URL_REGEX)); - const guildMember = message.guild?.members.cache.get(message.author.id); - if (linkAmount.length == 4 && guildMember && !isStaff(guildMember)) { - try { - const reply = await message.reply( - Messages.error("This message contains potential scam material.") - ); - await message.delete(); - - setTimeout(() => reply.delete().catch(() => { }), 3000); - } catch (error) { - // this should never happen but this is so the bot doesnt disconect. - } - } - }, -}; diff --git a/src/events/detectspam.ts b/src/events/detectspam.ts index 8fce715..200fc22 100644 --- a/src/events/detectspam.ts +++ b/src/events/detectspam.ts @@ -1,12 +1,61 @@ -import {EmbedBuilder, Events, Message, PermissionFlagsBits} from "discord.js"; -import {guildDB} from "../db"; +import { EmbedBuilder, Events, Message, PermissionFlagsBits } from "discord.js"; +import { guildDB } from "../db"; import Colors from "../util/colors"; +import Messages from "../util/messages"; -const fakeDiscordRegex = new RegExp(`([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\\.(com|net|app|gift|ru|uk)`, "ig"); -const okayDiscordRegex = new RegExp(`([a-zA-Z-\\.]+\\.)?discord((?:app)|(?:status))?\\.(com|net|app)`, "i"); -const fakeSteamRegex = new RegExp(`str?e[ea]?mcomm?m?un[un]?[un]?[tl]?[il][tl]?ty\\.(com|net|ru|us)`, "ig"); -const sketchyRuRegex = new RegExp(`([a-zA-Z-\\.]+).ru.com`, "ig"); +type Patterns = { + regex: RegExp, + whitelist: string[], + mute: boolean, + predicate: (links: [], self: Patterns) => boolean, + reason: string, +} + +const phishingPatterns = [ + { + regex: /([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\.(com|net|app|gift|ru|uk)/ig, + whitelist: ['discord.com', 'discordapp.com'], + mute: true, + predicate: (links, self) => { + const hosts = links.map(match => { + const url = match[0]; + return URL.parse(url)?.host; + }).filter(Boolean); + return hosts.some(host => !self.whitelist.includes(host)); + }, + reason: 'Fake Discord Domain' + }, + { + regex: /str?e[ea]?mcomm?m?un[un]?[un]?[tl]?[il][tl]?ty\.(com|net|ru|us)/ig, + whitelist: ['steamcommunity.com'], + mute: true, + predicate: (links, self) => { + const hosts = links.map(match => { + const url = match[0]; + return URL.parse(url)?.host; + }); + + return hosts.some(host => !self.whitelist.includes(host)); + }, + reason: 'Fake Steam Link' + }, + { + regex: /([a-zA-Z-\\.]+)\.ru\.com/ig, + whitelist: [], + mute: true, + predicate: (links, self) => links.length > 0, + reason: 'Suspicious .ru.com Domain' + }, + { + regex: /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/ig, + whitelist: [], + mute: true, + predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on. + reason: 'Potential Scam Message', + maxCount: 4 + } +] as Patterns[] // TODO: consider de-duping with invitefilter event export default { @@ -22,29 +71,44 @@ export default { const current = await guildDB.get(message.guild.id) ?? {}; if (!current?.detectspam) return; + /* + const fakeDiscordMatches = message.content.match(fakeDiscordRegex) || []; + const fakeSteamMatches = message.content.match(fakeSteamRegex) || []; + const isFakeDiscord = fakeDiscordMatches.some(s => { + if (okayDiscordRegex.test(s)) return false; + else if (s.toLowerCase() === "betterdiscord.app") return false; + return true; + }); + const isFakeSteam = fakeSteamMatches.some(s => s.toLowerCase() !== "steamcommunity.com"); + const isSketchy = sketchyRuRegex.test(message.content); + if (!isFakeDiscord && !isFakeSteam && !isSketchy) return; // Not spam, let's get out of here + + let reason = "Sketchy Link"; + if (isFakeDiscord) reason = "Fake Discord Link"; + if (isFakeSteam) reason = "Fake Steam Link"; + + try { + await message.delete(); + } + catch { + // TODO: logging? + console.error("Could not delete detect spam message. Likely permissions."); + }*/ - const fakeDiscordMatches = message.content.match(fakeDiscordRegex) || []; - const fakeSteamMatches = message.content.match(fakeSteamRegex) || []; - const isFakeDiscord = fakeDiscordMatches.some(s => { - if (okayDiscordRegex.test(s)) return false; - else if (s.toLowerCase() === "betterdiscord.app") return false; - return true; - }); - const isFakeSteam = fakeSteamMatches.some(s => s.toLowerCase() !== "steamcommunity.com"); - const isSketchy = sketchyRuRegex.test(message.content); - if (!isFakeDiscord && !isFakeSteam && !isSketchy) return; // Not spam, let's get out of here + let reasons: string[] = []; - let reason = "Sketchy Link"; - if (isFakeDiscord) reason = "Fake Discord Link"; - if (isFakeSteam) reason = "Fake Steam Link"; + for (var pattern of phishingPatterns) { + const links = Array.from(message.content.matchAll(pattern.regex)) + const shouldReason = pattern.predicate(links, pattern) + if (shouldReason) { + reasons.push(pattern.reason) + } + } - try { + if (reasons.length > 0) { await message.delete(); } - catch { - // TODO: logging? - console.error("Could not delete detect spam message. Likely permissions."); - } + let didMute = false; const muteRoleId = message.guild.roles.cache.findKey(r => r.name.toLowerCase().includes("mute")); @@ -67,21 +131,21 @@ export default { if (!modlogId || !modlogChannel || !modlogChannel.isTextBased()) return; // Can't log const dEmbed = new EmbedBuilder().setColor(Colors.Info) - .setAuthor({name: message.author.username, iconURL: message.author.displayAvatarURL()}) + .setAuthor({ name: message.author.username, iconURL: message.author.displayAvatarURL() }) .setDescription(`Message sent by ${message.author.username} in ${message.channel.name}\n\n` + message.content) - .addFields({name: "Reason", value: reason}) - .setFooter({text: `ID: ${message.author.id}`}).setTimestamp(message.createdTimestamp); - await modlogChannel.send({embeds: [dEmbed]}); + .addFields({ name: "Reason(s)", value: reasons.join(', ') }) + .setFooter({ text: `ID: ${message.author.id}` }).setTimestamp(message.createdTimestamp); + await modlogChannel.send({ embeds: [dEmbed] }); if (didMute) { const mEmbed = new EmbedBuilder().setColor(Colors.Info) - .setAuthor({name: "Member Muted", iconURL: message.author.displayAvatarURL()}) + .setAuthor({ name: "Member Muted", iconURL: message.author.displayAvatarURL() }) .setDescription(`${message.author.displayName} ${message.author.tag}`) - .addFields({name: "Reason", value: reason}) - .setFooter({text: `ID: ${message.author.id}`}).setTimestamp(message.createdTimestamp); + .addFields({ name: "Reason(s)", value: reasons.join(', ') }) + .setFooter({ text: `ID: ${message.author.id}` }).setTimestamp(message.createdTimestamp); - await modlogChannel.send({embeds: [mEmbed]}); + await modlogChannel.send({ embeds: [mEmbed] }); } }, }; \ No newline at end of file From 2a17bcbb1d44ce74bb7987e0ab99723b29e73c4d Mon Sep 17 00:00:00 2001 From: Kaan <90235641+zrodevkaan@users.noreply.github.com> Date: Sun, 18 Jan 2026 22:27:30 -0600 Subject: [PATCH 7/8] Fixed --- src/events/detectspam.ts | 61 ++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/events/detectspam.ts b/src/events/detectspam.ts index 200fc22..6b520c1 100644 --- a/src/events/detectspam.ts +++ b/src/events/detectspam.ts @@ -1,26 +1,24 @@ -import { EmbedBuilder, Events, Message, PermissionFlagsBits } from "discord.js"; -import { guildDB } from "../db"; +import {EmbedBuilder, Events, Message, PermissionFlagsBits} from "discord.js"; +import {guildDB} from "../db"; import Colors from "../util/colors"; -import Messages from "../util/messages"; - type Patterns = { regex: RegExp, whitelist: string[], - mute: boolean, - predicate: (links: [], self: Patterns) => boolean, + predicate: (links: RegExpMatchArray[], self: Patterns) => boolean, reason: string, + maxCount?: number, } const phishingPatterns = [ { regex: /([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\.(com|net|app|gift|ru|uk)/ig, whitelist: ['discord.com', 'discordapp.com'], - mute: true, predicate: (links, self) => { const hosts = links.map(match => { const url = match[0]; - return URL.parse(url)?.host; + const fullUrl = url.startsWith('http') ? url : `https://${url}`; + return URL.parse(fullUrl)?.host; }).filter(Boolean); return hosts.some(host => !self.whitelist.includes(host)); }, @@ -29,12 +27,12 @@ const phishingPatterns = [ { regex: /str?e[ea]?mcomm?m?un[un]?[un]?[tl]?[il][tl]?ty\.(com|net|ru|us)/ig, whitelist: ['steamcommunity.com'], - mute: true, predicate: (links, self) => { const hosts = links.map(match => { const url = match[0]; - return URL.parse(url)?.host; - }); + const fullUrl = url.startsWith('http') ? url : `https://${url}`; + return URL.parse(fullUrl)?.host; + }).filter(Boolean); return hosts.some(host => !self.whitelist.includes(host)); }, @@ -43,14 +41,18 @@ const phishingPatterns = [ { regex: /([a-zA-Z-\\.]+)\.ru\.com/ig, whitelist: [], - mute: true, - predicate: (links, self) => links.length > 0, + predicate: (links) => links.length > 0, reason: 'Suspicious .ru.com Domain' }, { - regex: /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)/ig, + regex: /nsfwcord/ig, // new recent scam + whitelist: [], + predicate: (links) => links.length > 0, + reason: 'Sex bot scam' + }, + { + regex: /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._+~#=]{2,256}\.[a-z]{2,6}\b[-a-zA-Z0-9@:%_+.~#?&\/=]*/ig, whitelist: [], - mute: true, predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on. reason: 'Potential Scam Message', maxCount: 4 @@ -97,16 +99,20 @@ export default { let reasons: string[] = []; - for (var pattern of phishingPatterns) { + for (const pattern of phishingPatterns) { const links = Array.from(message.content.matchAll(pattern.regex)) - const shouldReason = pattern.predicate(links, pattern) + const shouldReason = pattern.predicate(links) if (shouldReason) { reasons.push(pattern.reason) } } - if (reasons.length > 0) { + if (reasons.length === 0) return; + + try { await message.delete(); + } catch (e) { + console.error("Could not delete message. Likely deleted or no permissions.", e); } @@ -118,8 +124,7 @@ export default { try { await member.roles.add(muteRoleId); didMute = true; - } - catch { + } catch { // TODO: logging? console.error("Could not add mute role. Likely permissions."); } @@ -131,21 +136,21 @@ export default { if (!modlogId || !modlogChannel || !modlogChannel.isTextBased()) return; // Can't log const dEmbed = new EmbedBuilder().setColor(Colors.Info) - .setAuthor({ name: message.author.username, iconURL: message.author.displayAvatarURL() }) + .setAuthor({name: message.author.username, iconURL: message.author.displayAvatarURL()}) .setDescription(`Message sent by ${message.author.username} in ${message.channel.name}\n\n` + message.content) - .addFields({ name: "Reason(s)", value: reasons.join(', ') }) - .setFooter({ text: `ID: ${message.author.id}` }).setTimestamp(message.createdTimestamp); - await modlogChannel.send({ embeds: [dEmbed] }); + .addFields({name: "Reason(s)", value: reasons.join(', ')}) + .setFooter({text: `ID: ${message.author.id}`}).setTimestamp(message.createdTimestamp); + await modlogChannel.send({embeds: [dEmbed]}); if (didMute) { const mEmbed = new EmbedBuilder().setColor(Colors.Info) - .setAuthor({ name: "Member Muted", iconURL: message.author.displayAvatarURL() }) + .setAuthor({name: "Member Muted", iconURL: message.author.displayAvatarURL()}) .setDescription(`${message.author.displayName} ${message.author.tag}`) - .addFields({ name: "Reason(s)", value: reasons.join(', ') }) - .setFooter({ text: `ID: ${message.author.id}` }).setTimestamp(message.createdTimestamp); + .addFields({name: "Reason(s)", value: reasons.join(', ')}) + .setFooter({text: `ID: ${message.author.id}`}).setTimestamp(message.createdTimestamp); - await modlogChannel.send({ embeds: [mEmbed] }); + await modlogChannel.send({embeds: [mEmbed]}); } }, }; \ No newline at end of file From dfad82809ef4addc79487687f9314704bc8bdf68 Mon Sep 17 00:00:00 2001 From: Kaan <90235641+zrodevkaan@users.noreply.github.com> Date: Sun, 18 Jan 2026 22:40:14 -0600 Subject: [PATCH 8/8] Whoops --- src/events/detectspam.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events/detectspam.ts b/src/events/detectspam.ts index 6b520c1..db853fe 100644 --- a/src/events/detectspam.ts +++ b/src/events/detectspam.ts @@ -101,7 +101,7 @@ export default { for (const pattern of phishingPatterns) { const links = Array.from(message.content.matchAll(pattern.regex)) - const shouldReason = pattern.predicate(links) + const shouldReason = pattern.predicate(links, pattern) if (shouldReason) { reasons.push(pattern.reason) }