Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 61 additions & 5 deletions src/commands/RuleCommand.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ColorResolvable, CommandInteraction, EmbedBuilder, ApplicationCommandOptionType} from "discord.js";
import {ColorResolvable, CommandInteraction, EmbedBuilder, ApplicationCommandOptionType, User} from "discord.js";
import {Discord, Slash, SlashChoice, SlashOption} from "discordx";
import getConfigValue from "../utils/getConfigValue";
import GenericObject from "../interfaces/GenericObject";
Expand All @@ -13,11 +13,19 @@ const rules = getConfigValue<Rule[]>("rules").map(it => ({name: it.name, value:

@Discord()
class RuleCommand {
private lastPinged = new Map<string, number>();
private lastPingPerPinger = new Map<string, number>();

@Slash({ name: "rule", description: "Get info about a rule" })
async onInteract(
@SlashChoice(...rules) @SlashOption({ name: "rule", description: "Name of a rule", type: ApplicationCommandOptionType.String, required: true }) ruleName: string,
interaction: CommandInteraction
): Promise<void> {
@SlashOption({ name: "user", description: "User to ping", type: ApplicationCommandOptionType.User, required: false}) mentionedUser: User | undefined,
interaction: CommandInteraction): Promise<void> {
if (interaction === undefined) {
console.error("Interaction is undefined in RuleCommand");
return;
}

const embed = new EmbedBuilder();

const rule = getConfigValue<Rule[]>("rules").find(rule => rule.triggers.includes(ruleName));
Expand All @@ -30,8 +38,56 @@ class RuleCommand {
embed.addFields([{ name: "To familiarise yourself with all of the server's rules please see", value: "<#240884566519185408>" }]);
embed.setColor(getConfigValue<GenericObject<ColorResolvable>>("EMBED_COLOURS").SUCCESS);
}
await interaction.reply({embeds: [embed]});
await interaction.reply({ embeds: [embed] });

if (mentionedUser) {
const userId = mentionedUser.id;
const now = Date.now();
const cooldownValue = getConfigValue<number>("RULE_PING_COOLDOWN_PINGED");

const lastTime = this.lastPinged.get(userId);

if (lastTime) {
const secondsPassed = (now - lastTime) / 1000;

if (secondsPassed < cooldownValue) {
const remaining = (cooldownValue - secondsPassed).toFixed(0);

await interaction.followUp({
content: `Slow down! That user can be pinged again in ${remaining}s.`,
ephemeral: true
});
return;
}
}

// If they passed the check, update the map with the new time
this.lastPinged.set(userId, now);

const cooldownValuePinger = getConfigValue<number>("RULE_PING_COOLDOWN_PINGER");

const lastTimePinger = this.lastPingPerPinger.get(interaction.user.id);

if (lastTimePinger) {
const secondsPassed = (now - lastTimePinger) / 1000;

if (secondsPassed < cooldownValuePinger) {
const remaining = (cooldownValuePinger - secondsPassed).toFixed(0);

await interaction.followUp({
content: `Slow down! You can ping someone again in ${remaining}s.`,
ephemeral: true
});
return;
}
}

// If they passed the check, update the map with the new time
this.lastPingPerPinger.set(interaction.user.id, now);

await interaction.followUp({ content: `<@${mentionedUser?.id}> Please read the rule mentioned above, and take a moment to familiarise yourself with the rules.` });
}
}
}

export default RuleCommand;
export default RuleCommand;
4 changes: 3 additions & 1 deletion src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,7 @@
"triggers": ["9", "sfw", "clean", "appropriate"],
"description": "Keep it appropriate, some people use this at school or at work."
}
]
],
"RULE_PING_COOLDOWN_PINGED": 30,
"RULE_PING_COOLDOWN_PINGER": 180
}
43 changes: 43 additions & 0 deletions src/event/handlers/AntiSpamHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,49 @@ class AntiSpamHandler extends EventHandler {

const member = message.member;

let antiSpamChannel = await message.guild.channels.fetch(antiSpamChannelId) as TextChannel;

if (!antiSpamChannel) { // Retry loop
let tries = 0;
let success = 0;

while (tries <= 3) {
antiSpamChannel = await message.guild.channels.fetch(antiSpamChannelId) as TextChannel;
if (antiSpamChannel) {
success = 1;
break;
}
tries += 1;
}
if (!success) {
logger.error("Failed to increment counter");
try {
await member.ban({deleteMessageSeconds: 60 * 60, reason: "Member posted in anti-spam channel"});

const logsChannel = await message.guild.channels.fetch(logsChannelId) as TextChannel;

await logsChannel.send(`User ${member.user.username} (\`${member.id}\`) was banned for posting in <#${antiSpamChannelId}>. Their recent messages have been deleted.`);
} catch (error) {
logger.error("AntiSpamHandler error", {error});
}
return;
}
}
const antiSpamMessages = await antiSpamChannel.messages.fetch({ limit: 100 });
// Check if any of those messages were sent by THIS bot
const hasSentMessageInAntiSpamChannel = antiSpamMessages.some((msg: Message) => msg.author.id === message.client.user?.id && msg.embeds.length === 0);

if (hasSentMessageInAntiSpamChannel && antiSpamMessages.find((msg: Message) => msg.author.id === message.client.user?.id && msg.embeds.length === 0)) {
const botMessage = antiSpamMessages.find((msg: Message) => msg.author.id === message.client.user?.id && msg.embeds.length === 0);
const botMessageContent = botMessage!.content;
const counterValueStr = botMessageContent.split(" ")[0].replace(/\D/g, "");
const counterValueInt = parseInt(counterValueStr, 10);

botMessage!.edit(`**${counterValueInt + 1}** spam accounts banned.`);
} else {
antiSpamChannel.send("**1** spam account has been banned.");
}

try {
await member.ban({deleteMessageSeconds: 60 * 60, reason: "Member posted in anti-spam channel"});

Expand Down
4 changes: 3 additions & 1 deletion test/appTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ describe("App", () => {
sandbox = createSandbox();

// @ts-ignore
(axios as unknown as AxiosCacheInstance).defaults.cache = undefined;
const axiosCache = axios as unknown as AxiosCacheInstance;

axiosCache.defaults.cache = undefined;

loginStub = sandbox.stub(Client.prototype, "login");
getStub = sandbox.stub(axios, "get").resolves();
Expand Down
Loading