all repos — stealth-developers @ b7e7791a84dbee1d8abbebf84930f7913d9a99f9

src/automod/index.ts (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
import { hasManagerPermissions } from "@/utils/discord/permissions";
import type { Client, Message, Snowflake } from "discord.js";

type Pending = {
	channels: Set<Snowflake>;
	timer: NodeJS.Timeout;
};

const pendingReports = new Map<Snowflake, Pending>();
const cooldowns = new Map<Snowflake, NodeJS.Timeout>();

async function getLogChannel(client: Client) {
	return client.channels.fetch("1478940790112915486");
}

type AutomodResult = { delete: true } | null;
type AutomodFunction = (
	client: Client,
	message: Message,
) => Promise<AutomodResult>;

export async function handleMessage(client: Client, message: Message) {
	if (message.author.bot || !message.guildId) return;
	const automodFunctions: AutomodFunction[] = [handleSpamLinks];

	for (const func of automodFunctions) {
		const result = await func(client, message);
		if (result?.delete) {
			await message.delete().catch(() => null);
			break;
		}
	}
}

async function handleSpamLinks(
	client: Client,
	message: Message,
): Promise<AutomodResult> {
	if (message.author.bot || !message.guildId || !message.member) return null;
	if (await hasManagerPermissions(message.member)) return null;
	if (message.channelId === "1478940790112915486") return null;

	const { content } = message;
	const blacklisted = [
		"discord.gg/",
		"discordapp.com/invite/",
		"discord.com/invite/",
	];

	if (!blacklisted.some((link) => content.includes(link))) return null;

	if (cooldowns.has(message.author.id)) {
		return { delete: true };
	}

	const existing = pendingReports.get(message.author.id);
	if (!existing) {
		const content = [
			`<@${message.author.id}>, your message contained an invite link and was deleted to prevent phishing attempts.`,
			"Keep in mind, we don't allow advertising other servers here.",
		];
		await message.reply(content.join(" "));

		const logChannel = await getLogChannel(client);
		if (logChannel?.isSendable()) {
			await message.forward(logChannel);
			logChannel.send(
				`Deleted a message from <@${message.author.id}> in <#${message.channelId}> containing a potential phishing link.`,
			);
		}

		const timer = setTimeout(
			() => flushReport(client, message.author.id),
			30_000,
		);
		pendingReports.set(message.author.id, {
			channels: new Set([message.channelId]),
			timer,
		});

		return { delete: true };
	}

	existing.channels.add(message.channelId);

	clearTimeout(existing.timer);
	existing.timer = setTimeout(
		() => flushReport(client, message.author.id),
		30_000,
	);

	return { delete: true };
}

async function flushReport(client: Client, authorId: Snowflake) {
	const pending = pendingReports.get(authorId);
	if (!pending) return;

	pendingReports.delete(authorId);
	clearTimeout(pending.timer);

	const logChannel = await getLogChannel(client);
	if (!logChannel?.isSendable()) return;

	const channels = Array.from(pending.channels);
	const channelMentions = channels.map((c) => `<#${c}>`).join(", ");

	logChannel.send(
		`<@${authorId}> posted links in channels: ${channelMentions}`,
	);

	const cooldownTimer = setTimeout(
		() => cooldowns.delete(authorId),
		5 * 60 * 1000,
	);
	cooldowns.set(authorId, cooldownTimer);
}