Skip to content

Commit

Permalink
Add alternate/spam account detection
Browse files Browse the repository at this point in the history
  • Loading branch information
Rian8337 committed Sep 22, 2024
1 parent 36cb86f commit a3c61a9
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 59 deletions.
2 changes: 0 additions & 2 deletions src/core/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ export abstract class Config {
"635549568854917150",
] as readonly Snowflake[];

static readonly reportChannel = "reports";

static readonly activityList = [
["Underworld Console", ActivityType.Playing],
["Rulid Village", ActivityType.Watching],
Expand Down
5 changes: 5 additions & 0 deletions src/core/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,9 @@ export class Constants {
*/
static readonly dppProfileDisplayerRole =
"1082254268691644446" satisfies Snowflake;

/**
* The ID of the report channel in the main server.
*/
static readonly reportChannel = "652902812354609162" satisfies Snowflake;
}
75 changes: 75 additions & 0 deletions src/events/guildMemberUpdate/utils/onboardingAccountCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Constants } from "@alice-core/Constants";
import { EventUtil } from "@alice-structures/core/EventUtil";
import { EmbedCreator } from "@alice-utils/creators/EmbedCreator";
import { DateTimeFormatHelper } from "@alice-utils/helpers/DateTimeFormatHelper";
import { bold, GuildMember, GuildMemberFlags, userMention } from "discord.js";

export const run: EventUtil["run"] = async (
_,
oldMember: GuildMember,
newMember: GuildMember,
) => {
if (newMember.guild.id !== Constants.mainServer) {
return;
}

// Do not trigger detection for rejoining members and members with verification bypass.
if (
newMember.flags.any(
GuildMemberFlags.DidRejoin | GuildMemberFlags.BypassesVerification,
)
) {
return;
}

if (
!oldMember.flags.has(GuildMemberFlags.StartedOnboarding) ||
!newMember.flags.has(GuildMemberFlags.CompletedOnboarding)
) {
return;
}

const { joinedTimestamp } = newMember;

if (joinedTimestamp === null) {
return;
}

const onboardingCompleteDuration = Date.now() - joinedTimestamp;

// Mark the member as a potential alternate account if they complete onboarding within
// 1 minute of their join.
if (onboardingCompleteDuration > 60000) {
return;
}

const reportChannel = await newMember.guild.channels
.fetch(Constants.reportChannel)
.catch(() => null);

if (!reportChannel?.isSendable()) {
return;
}

await reportChannel.send({
embeds: [
EmbedCreator.createNormalEmbed({
color: "Red",
timestamp: true,
footerText: `User ID: ${newMember.id}`,
})
.setTitle("Potential Alternate/Spam Account Detected")
.setDescription(
`${bold("User")}: ${newMember.user.tag} (${userMention(newMember.user.id)})\n\n` +
`Completed onboarding in ${bold(DateTimeFormatHelper.secondsToDHMS(onboardingCompleteDuration / 1000))} seconds after joining the server.`,
),
],
});
};

export const config: EventUtil["config"] = {
description:
"Responsible for checking if a newly joined member is a potential alternate or spam account.",
togglePermissions: ["BotOwner"],
toggleScope: ["GUILD"],
};
20 changes: 8 additions & 12 deletions src/interactions/commands/General/report/report.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {
GuildMember,
EmbedBuilder,
TextChannel,
PermissionsBitField,
roleMention,
Expand All @@ -19,7 +17,7 @@ import { ReportLocalization } from "@alice-localization/interactions/commands/Ge
import { InteractionHelper } from "@alice-utils/helpers/InteractionHelper";

export const run: SlashCommand["run"] = async (_, interaction) => {
const localization: ReportLocalization = new ReportLocalization(
const localization = new ReportLocalization(
CommandHelper.getLocale(interaction),
);

Expand All @@ -38,8 +36,8 @@ export const run: SlashCommand["run"] = async (_, interaction) => {
});
}

const toReport: GuildMember | null = await interaction
.guild!.members.fetch(interaction.options.getUser("user", true))
const toReport = await interaction.guild.members
.fetch(interaction.options.getUser("user", true))
.catch(() => null);

if (!toReport) {
Expand Down Expand Up @@ -69,9 +67,9 @@ export const run: SlashCommand["run"] = async (_, interaction) => {
});
}

const reason: string = interaction.options.getString("reason")!;
const reason = interaction.options.getString("reason")!;

const embed: EmbedBuilder = EmbedCreator.createNormalEmbed({
const embed = EmbedCreator.createNormalEmbed({
author: interaction.user,
color: interaction.member.displayColor,
timestamp: true,
Expand All @@ -89,10 +87,8 @@ export const run: SlashCommand["run"] = async (_, interaction) => {
`${bold(localization.getTranslation("reason"))}: ${reason}`,
);

const reportChannel: TextChannel = <TextChannel>(
interaction.guild!.channels.cache.find(
(c) => c.name === Config.reportChannel,
)
const reportChannel = <TextChannel>(
interaction.guild.channels.cache.get(Constants.reportChannel)
);

reportChannel.send({
Expand All @@ -104,7 +100,7 @@ export const run: SlashCommand["run"] = async (_, interaction) => {
embeds: [embed],
});

const replyEmbed: EmbedBuilder = EmbedCreator.createNormalEmbed({
const replyEmbed = EmbedCreator.createNormalEmbed({
color: "#527ea3",
timestamp: true,
});
Expand Down
38 changes: 18 additions & 20 deletions src/interactions/contextmenus/message/reportMessage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Config } from "@alice-core/Config";
import { Constants } from "@alice-core/Constants";
import { ReportMessageLocalization } from "@alice-localization/interactions/contextmenus/message/reportMessage/ReportMessageLocalization";
import { MessageContextMenuCommand } from "@alice-structures/core/MessageContextMenuCommand";
import { EmbedCreator } from "@alice-utils/creators/EmbedCreator";
Expand All @@ -7,26 +8,25 @@ import { MessageCreator } from "@alice-utils/creators/MessageCreator";
import { CommandHelper } from "@alice-utils/helpers/CommandHelper";
import { InteractionHelper } from "@alice-utils/helpers/InteractionHelper";
import {
EmbedBuilder,
GuildMember,
PermissionsBitField,
TextChannel,
bold,
hyperlink,
roleMention,
} from "discord.js";

export const run: MessageContextMenuCommand["run"] = async (
client,
interaction,
) => {
const localization: ReportMessageLocalization =
new ReportMessageLocalization(CommandHelper.getLocale(interaction));
export const run: MessageContextMenuCommand["run"] = async (_, interaction) => {
if (!interaction.inCachedGuild()) {
return;
}

const localization = new ReportMessageLocalization(
CommandHelper.getLocale(interaction),
);

const toReport: GuildMember | null =
(await interaction.guild?.members
.fetch(interaction.targetMessage.author)
.catch(() => null)) ?? null;
const toReport = await interaction.guild.members
.fetch(interaction.targetMessage.author)
.catch(() => null);

if (
!toReport ||
Expand All @@ -48,7 +48,7 @@ export const run: MessageContextMenuCommand["run"] = async (
});
}

const confirmation: boolean = await MessageButtonCreator.createConfirmation(
const confirmation = await MessageButtonCreator.createConfirmation(
interaction,
{
content: MessageCreator.createWarn(
Expand All @@ -64,9 +64,9 @@ export const run: MessageContextMenuCommand["run"] = async (
return;
}

const embed: EmbedBuilder = EmbedCreator.createNormalEmbed({
const embed = EmbedCreator.createNormalEmbed({
author: interaction.user,
color: (<GuildMember | null>interaction.member)?.displayColor,
color: interaction.member.displayColor,
timestamp: true,
});

Expand All @@ -84,10 +84,8 @@ export const run: MessageContextMenuCommand["run"] = async (
)})`,
);

const reportChannel: TextChannel = <TextChannel>(
interaction.guild!.channels.cache.find(
(c) => c.name === Config.reportChannel,
)
const reportChannel = <TextChannel>(
interaction.guild.channels.cache.get(Constants.reportChannel)
);

reportChannel.send({
Expand All @@ -99,7 +97,7 @@ export const run: MessageContextMenuCommand["run"] = async (
embeds: [embed],
});

const replyEmbed: EmbedBuilder = EmbedCreator.createNormalEmbed({
const replyEmbed = EmbedCreator.createNormalEmbed({
color: "#527ea3",
timestamp: true,
});
Expand Down
44 changes: 19 additions & 25 deletions src/utils/helpers/DateTimeFormatHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,29 @@ export abstract class DateTimeFormatHelper {
* @returns The formatted date.
*/
static secondsToDHMS(seconds: number, language: Language = "en"): string {
const localization: DateTimeFormatHelperLocalization =
this.getLocalization(language);
const localization = this.getLocalization(language);

seconds = Math.trunc(seconds);

const days: number = Math.floor(seconds / 86400);
const days = Math.floor(seconds / 86400);
seconds -= days * 86400;

const hours: number = Math.floor(seconds / 3600);
const hours = Math.floor(seconds / 3600);
seconds -= hours * 3600;

const minutes: number = Math.floor(seconds / 60);
const minutes = Math.floor(seconds / 60);
seconds -= minutes * 60;

const final: string[] = [
const final = [
`${days} ${localization.getTranslation(days > 1 ? "days" : "day")}`,
`${hours} ${localization.getTranslation(
hours > 1 ? "hours" : "hour"
hours > 1 ? "hours" : "hour",
)}`,
`${minutes} ${localization.getTranslation(
minutes > 1 ? "minutes" : "minute"
minutes > 1 ? "minutes" : "minute",
)}`,
`${seconds} ${localization.getTranslation(
seconds > 1 ? "seconds" : "second"
seconds > 1 ? "seconds" : "second",
)}`,
];

Expand All @@ -55,19 +54,16 @@ export abstract class DateTimeFormatHelper {
static secondsToDDHHMMSS(seconds: number): string {
seconds = Math.trunc(seconds);

const days: number = Math.floor(seconds / 86400);
const days = Math.floor(seconds / 86400);
seconds -= days * 86400;

const hours: number = Math.floor(seconds / 3600);
const hours = Math.floor(seconds / 3600);
seconds -= hours * 3600;

const minutes: number = Math.floor(seconds / 60);
const minutes = Math.floor(seconds / 60);
seconds -= minutes * 60;

const final: string[] = [
minutes.toString(),
seconds.toString().padStart(2, "0"),
];
const final = [minutes.toString(), seconds.toString().padStart(2, "0")];

if (hours > 0) {
final.unshift(hours.toString());
Expand All @@ -91,8 +87,8 @@ export abstract class DateTimeFormatHelper {
* @returns The converted time format in seconds.
*/
static DHMStoSeconds(dhms: string): number {
let time: number = 0;
const timeEntry: string[] = dhms.toLowerCase().split(/[dhms:]/g);
let time = 0;
const timeEntry = dhms.toLowerCase().split(/[dhms:]/g);

if (/[dhms]/g.test(dhms)) {
// Contains either "d", "h", "m", or "s",
Expand All @@ -102,14 +98,14 @@ export abstract class DateTimeFormatHelper {
if (isNaN(time)) {
break;
}
const str: string = dhms.charAt(i);
const str = dhms.charAt(i);
if (/[dhms]/.test(str) && !usedFormats.includes(str)) {
if (mark === i) {
++mark;
continue;
}
usedFormats.push(str);
const currentTime: number = parseFloat(dhms.slice(mark, i));
const currentTime = parseFloat(dhms.slice(mark, i));
mark = i + 1;

let multiplier = 1;
Expand Down Expand Up @@ -159,9 +155,7 @@ export abstract class DateTimeFormatHelper {
* @returns The converted string.
*/
static dateToHumanReadable(date: Date): string {
const str: string = date.toUTCString();

return str.split(" ").slice(1, 4).join(" ");
return date.toUTCString().split(" ").slice(1, 4).join(" ");
}

/**
Expand All @@ -188,7 +182,7 @@ export abstract class DateTimeFormatHelper {
* @returns The formatted date.
*/
static dateToLocaleString(date: Date, language: Language): string {
const localeToConvert: string = LocaleHelper.convertToBCP47(language);
const localeToConvert = LocaleHelper.convertToBCP47(language);

return localeToConvert === "en-US"
? date.toUTCString()
Expand All @@ -205,7 +199,7 @@ export abstract class DateTimeFormatHelper {
* @param language The language to localize.
*/
private static getLocalization(
language: Language
language: Language,
): DateTimeFormatHelperLocalization {
return new DateTimeFormatHelperLocalization(language);
}
Expand Down

0 comments on commit a3c61a9

Please sign in to comment.