Skip to content

AntiScam Checks#2

Open
zrodevkaan wants to merge 8 commits into
BetterDiscord:mainfrom
zrodevkaan:main
Open

AntiScam Checks#2
zrodevkaan wants to merge 8 commits into
BetterDiscord:mainfrom
zrodevkaan:main

Conversation

@zrodevkaan

Copy link
Copy Markdown

this is a low level check.
Please double check on your local machine for testing.

@zerebos zerebos left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't have some necessary sanity checking, take a look at detectspam event, should check if in guild, if bot, channel permissions, etc. This is also not toggleable, it feels like maybe it should be collapsed into detect spam.

Also code-wise there are other issues but I can fix after merging

Rework & Merged Into detectspam

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the spam detection system by consolidating multiple regex patterns and their validation logic into a unified data-driven approach using a pattern configuration array.

Changes:

  • Refactored spam detection from separate regex variables to a centralized phishingPatterns array with configurable predicates
  • Updated embed logging to support multiple reasons instead of a single reason
  • Removed error handling for message deletion (commented out old code but removed try-catch from new implementation)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/events/detectspam.ts Outdated
Comment thread src/events/detectspam.ts Outdated
Comment thread src/events/detectspam.ts Outdated
Comment thread src/events/detectspam.ts
Comment thread src/events/detectspam.ts Outdated
Comment thread src/events/detectspam.ts
Comment thread src/events/detectspam.ts
Comment thread src/events/detectspam.ts
Comment thread src/events/detectspam.ts
Comment thread src/events/detectspam.ts Outdated
@zrodevkaan zrodevkaan requested a review from zerebos January 19, 2026 04:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/events/detectspam.ts
Comment on lines +31 to +35
const hosts = links.map(match => {
const url = match[0];
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.host;
}).filter(Boolean);

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for URL parsing. If the URL is malformed, URL.parse or new URL() will throw an error. Wrap the URL parsing in a try-catch block to prevent the entire spam detection from failing on malformed URLs.

Copilot uses AI. Check for mistakes.
Comment thread src/events/detectspam.ts
{
regex: /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._+~#=]{2,256}\.[a-z]{2,6}\b[-a-zA-Z0-9@:%_+.~#?&\/=]*/ig,
whitelist: [],
predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on.

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The equality check should use strict equality. Replace '==' with '===' to avoid type coercion issues and follow JavaScript best practices.

Suggested change
predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on.
predicate: (links, self) => links.length === self.maxCount, // this should probably be more than 4 later on.

Copilot uses AI. Check for mistakes.
Comment thread src/events/detectspam.ts
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.host;
}).filter(Boolean);
return hosts.some(host => !self.whitelist.includes(host));

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whitelist check should use the full hostname, not just the host. URL.parse().host may include the port number, which could cause legitimate URLs with ports to be flagged. Consider comparing against hostname instead or normalize the comparison.

Copilot uses AI. Check for mistakes.
Comment thread src/events/detectspam.ts
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'],

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern will incorrectly match 'betterdiscord.app' which was explicitly whitelisted in the old code. Add 'betterdiscord.app' to the whitelist array to maintain the same behavior as the previous implementation.

Suggested change
whitelist: ['discord.com', 'discordapp.com'],
whitelist: ['discord.com', 'discordapp.com', 'betterdiscord.app'],

Copilot uses AI. Check for mistakes.
Comment thread src/events/detectspam.ts
Comment on lines +55 to +56
whitelist: [],
predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on.

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generic URL regex pattern is too broad and will match many legitimate URLs. It will trigger on any message containing 4+ URLs regardless of their legitimacy. Consider adding common legitimate domains to the whitelist or refining this pattern to avoid false positives.

Suggested change
whitelist: [],
predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on.
whitelist: [
'discord.com',
'discordapp.com',
'steamcommunity.com',
'github.com',
'gitlab.com',
'bitbucket.org',
'google.com',
'youtube.com',
'youtu.be',
'twitch.tv',
'twitter.com',
'x.com',
'reddit.com'
],
predicate: (links, self) => {
const suspiciousLinks = links.filter(match => {
const url = match[0].toLowerCase();
return !self.whitelist.some(domain => url.includes(domain));
});
return suspiciousLinks.length === self.maxCount; // this should probably be more than 4 later on.
},

Copilot uses AI. Check for mistakes.
Comment thread src/events/detectspam.ts
Comment on lines +13 to +38
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'],
predicate: (links, self) => {
const hosts = links.map(match => {
const url = match[0];
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.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'],
predicate: (links, self) => {
const hosts = links.map(match => {
const url = match[0];
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.host;
}).filter(Boolean);

return hosts.some(host => !self.whitelist.includes(host));
},

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This predicate function is duplicated in the second pattern (lines 30-37). Consider extracting this into a shared helper function to reduce code duplication and improve maintainability.

Suggested change
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'],
predicate: (links, self) => {
const hosts = links.map(match => {
const url = match[0];
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.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'],
predicate: (links, self) => {
const hosts = links.map(match => {
const url = match[0];
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.host;
}).filter(Boolean);
return hosts.some(host => !self.whitelist.includes(host));
},
function hasNonWhitelistedHost(links: RegExpMatchArray[], whitelist: string[]): boolean {
const hosts = links.map(match => {
const url = match[0];
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.host;
}).filter(Boolean);
return hosts.some(host => !whitelist.includes(host as 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'],
predicate: (links, self) => hasNonWhitelistedHost(links, self.whitelist),
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'],
predicate: (links, self) => hasNonWhitelistedHost(links, self.whitelist),

Copilot uses AI. Check for mistakes.
Comment thread src/events/detectspam.ts
Comment thread src/events/detectspam.ts
Comment thread src/events/detectspam.ts
Comment thread src/events/detectspam.ts
@zerebos

zerebos commented Feb 16, 2026

Copy link
Copy Markdown
Member

Please mark resolve or fix above

@zerebos

zerebos commented Jun 19, 2026

Copy link
Copy Markdown
Member

/gemini review these incoming changes

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the spam and phishing detection logic in src/events/detectspam.ts to use a structured array of patterns with custom predicates and whitelists. Feedback focuses on resolving potential false positives in subdomain matching, fixing unintended character ranges in regexes, ensuring backward compatibility by avoiding URL.parse on older Node.js versions, correcting the link count threshold check, and removing commented-out legacy code.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/events/detectspam.ts
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.host;
}).filter(Boolean);
return hosts.some(host => !self.whitelist.includes(host));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Using self.whitelist.includes(host) will cause legitimate subdomains of whitelisted domains (such as canary.discord.com, ptb.discord.com, or media.discordapp.com) to be flagged as fake domains. This will result in false positives where legitimate messages are deleted and users are muted.\n\nChecking if the host matches exactly or ends with . + the allowed domain resolves this issue.

Suggested change
return hosts.some(host => !self.whitelist.includes(host));
return hosts.some(host => !self.whitelist.some(allowed => host === allowed || host.endsWith('.' + allowed)));

Comment thread src/events/detectspam.ts
const sketchyRuRegex = new RegExp(`([a-zA-Z-\\.]+).ru.com`, "ig");
const phishingPatterns = [
{
regex: /([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\.(com|net|app|gift|ru|uk)/ig,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In a regular expression literal, \\ represents a literal backslash. Therefore, -\\ inside the character class [a-zA-Z-\\.] creates a character range from - (ASCII 45) to \\ (ASCII 92). This range matches many unintended characters such as numbers, colons, slashes, and uppercase letters, which can lead to false positives.\n\nUsing [a-zA-Z.-]+ (placing the hyphen at the end of the character class) correctly matches only letters, dots, and hyphens without creating an unintended range.

Suggested change
regex: /([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\.(com|net|app|gift|ru|uk)/ig,
regex: /([a-zA-Z.-]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z.-]+)?\\.(com|net|app|gift|ru|uk)/ig,

Comment thread src/events/detectspam.ts
Comment on lines +18 to +22
const hosts = links.map(match => {
const url = match[0];
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.host;
}).filter(Boolean);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

URL.parse is a relatively new method introduced in Node.js v22.0.0 and v21.2.0. If this bot is deployed on an older LTS version of Node.js (such as v18 or v20), calling URL.parse will throw a TypeError and crash the application.\n\nUsing new URL() wrapped in a try/catch block is a safer, backward-compatible approach.

            const hosts = links.map(match => {\n                const url = match[0];\n                const fullUrl = url.startsWith('http') ? url : 'https://' + url;\n                try {\n                    return new URL(fullUrl).host;\n                } catch {\n                    return null;\n                }\n            }).filter(Boolean) as string[];

Comment thread src/events/detectspam.ts
Comment on lines +31 to +35
const hosts = links.map(match => {
const url = match[0];
const fullUrl = url.startsWith('http') ? url : `https://${url}`;
return URL.parse(fullUrl)?.host;
}).filter(Boolean);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

URL.parse is a relatively new method introduced in Node.js v22.0.0 and v21.2.0. If this bot is deployed on an older LTS version of Node.js (such as v18 or v20), calling URL.parse will throw a TypeError and crash the application.\n\nUsing new URL() wrapped in a try/catch block is a safer, backward-compatible approach.

            const hosts = links.map(match => {\n                const url = match[0];\n                const fullUrl = url.startsWith('http') ? url : 'https://' + url;\n                try {\n                    return new URL(fullUrl).host;\n                } catch {\n                    return null;\n                }\n            }).filter(Boolean) as string[];

Comment thread src/events/detectspam.ts
}).filter(Boolean);

return hosts.some(host => !self.whitelist.includes(host));
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using self.whitelist.includes(host) will cause legitimate subdomains of whitelisted domains (such as help.steamcommunity.com) to be flagged as fake domains.\n\nChecking if the host matches exactly or ends with . + the allowed domain resolves this issue.

            return hosts.some(host => !self.whitelist.some(allowed => host === allowed || host.endsWith('.' + allowed)));

Comment thread src/events/detectspam.ts
},
{
regex: /([a-zA-Z-\\.]+)\.ru\.com/ig,
whitelist: [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In a regular expression literal, \\ represents a literal backslash. Therefore, -\\ inside the character class [a-zA-Z-\\.] creates a character range from - (ASCII 45) to \\ (ASCII 92). This range matches many unintended characters such as numbers, colons, slashes, and uppercase letters, which can lead to false positives.\n\nUsing [a-zA-Z.-]+ (placing the hyphen at the end of the character class) correctly matches only letters, dots, and hyphens without creating an unintended range.

Suggested change
whitelist: [],
regex: /([a-zA-Z.-]+)\\.ru\\.com/ig,

Comment thread src/events/detectspam.ts
regex: /(?:http[s]?:\/\/.)?(?:www\.)?[-a-zA-Z0-9@%._+~#=]{2,256}\.[a-z]{2,6}\b[-a-zA-Z0-9@:%_+.~#?&\/=]*/ig,
whitelist: [],
predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on.
reason: 'Potential Scam Message',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using links.length == self.maxCount means that if a message contains more than 4 links (e.g., 5 or 10 links), the predicate will evaluate to false, allowing the spam message to bypass the filter.\n\nChanging this to >= ensures that any message with 4 or more links is caught.

Suggested change
reason: 'Potential Scam Message',
predicate: (links, self) => !!self.maxCount && links.length >= self.maxCount, // this should probably be more than 4 later on.

Comment thread src/events/detectspam.ts
Comment on lines +76 to +98
/*
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.");
}*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Leaving large blocks of commented-out dead code reduces readability and maintainability. Since Git history preserves the previous implementation, this commented-out block should be removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants