generated from nhcarrigan/template
feat: migrate from github
This commit is contained in:
99
src/commands/ban.ts
Normal file
99
src/commands/ban.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import {
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
import { processModAction } from "../utils/processModAction";
|
||||
|
||||
export const ban: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("ban")
|
||||
.setDescription("Ban a user from the server.")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to ban.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("The reason for banning.")
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(400)
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName("prune")
|
||||
.setDescription("Number of days to prune messages.")
|
||||
.setMinValue(0)
|
||||
.setMaxValue(7)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("evidence")
|
||||
.setDescription(
|
||||
"A link to the evidence for the ban. For multiple links, separate with a space."
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(PermissionFlagsBits.BanMembers)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
const evidence =
|
||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
||||
const prune = interaction.options.getInteger("prune") || 0;
|
||||
const user = interaction.options.getUser("user", true);
|
||||
const target =
|
||||
guild.members.cache.get(user.id) ||
|
||||
(await guild.members.fetch(user.id).catch(() => null));
|
||||
|
||||
if (target && isModerator(target)) {
|
||||
await interaction.editReply({
|
||||
content: "You cannot ban a moderator."
|
||||
});
|
||||
return;
|
||||
}
|
||||
await processModAction(
|
||||
bot,
|
||||
interaction,
|
||||
guild,
|
||||
user,
|
||||
"ban",
|
||||
reason,
|
||||
evidence,
|
||||
undefined,
|
||||
undefined,
|
||||
prune
|
||||
);
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "ban command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
90
src/commands/cases.ts
Normal file
90
src/commands/cases.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { GuildMember, EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { customSubstring } from "../utils/customSubstring";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
|
||||
export const cases: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("case")
|
||||
.setDescription("View a specific moderation case.")
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName("number")
|
||||
.setDescription("The case number to view.")
|
||||
.setRequired(true)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isModerator(member as GuildMember)) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const target = interaction.options.getUser("user", true);
|
||||
const number = interaction.options.getInteger("number", true);
|
||||
|
||||
const requestedCase = await bot.db.cases.findFirst({
|
||||
where: {
|
||||
userId: target.id,
|
||||
serverId: guild.id,
|
||||
number
|
||||
}
|
||||
});
|
||||
|
||||
if (!requestedCase) {
|
||||
await interaction.editReply({
|
||||
content: "That user doesn't seem to have a moderation history yet."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const viewEmbed = new EmbedBuilder();
|
||||
viewEmbed.setTitle(
|
||||
`Case ${requestedCase.number} - ${requestedCase.action}`
|
||||
);
|
||||
viewEmbed.setAuthor({
|
||||
name: target.tag,
|
||||
iconURL: target.displayAvatarURL()
|
||||
});
|
||||
viewEmbed.setDescription(customSubstring(requestedCase.reason, 4000));
|
||||
viewEmbed.addFields(
|
||||
{
|
||||
name: "Evidence",
|
||||
value:
|
||||
customSubstring(requestedCase.evidence.join("\n"), 2000) ||
|
||||
"No evidence provided"
|
||||
},
|
||||
{
|
||||
name: "Date",
|
||||
value: requestedCase.timestamp
|
||||
},
|
||||
{
|
||||
name: "Moderator",
|
||||
value: requestedCase.moderator
|
||||
}
|
||||
);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [viewEmbed]
|
||||
});
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "cases command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
136
src/commands/config.ts
Normal file
136
src/commands/config.ts
Normal file
@ -0,0 +1,136 @@
|
||||
import {
|
||||
SlashCommandBuilder,
|
||||
SlashCommandSubcommandBuilder,
|
||||
Guild,
|
||||
GuildMember,
|
||||
PermissionFlagsBits
|
||||
} from "discord.js";
|
||||
|
||||
import { logChannelChoices } from "../config/LogChannelChoices";
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { getConfig } from "../modules/data/getConfig";
|
||||
import { handleAppealLink } from "../modules/subcommands/config/handleAppealLink";
|
||||
import { handleInviteLink } from "../modules/subcommands/config/handleInviteLink";
|
||||
import { handleList } from "../modules/subcommands/config/handleList";
|
||||
import { handleLogging } from "../modules/subcommands/config/handleLogging";
|
||||
import { handleRole } from "../modules/subcommands/config/handleRole";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const config: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("config")
|
||||
.setDescription("Modify the config settings.")
|
||||
.addSubcommand(
|
||||
new SlashCommandSubcommandBuilder()
|
||||
.setName("list")
|
||||
.setDescription("List your server's current config settings")
|
||||
)
|
||||
.addSubcommand(
|
||||
new SlashCommandSubcommandBuilder()
|
||||
.setName("invite-link")
|
||||
.setDescription(
|
||||
"Set the link to be sent to someone to rejoin the server after they are kicked."
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setRequired(true)
|
||||
.setName("link")
|
||||
.setDescription("The invite link to send.")
|
||||
)
|
||||
)
|
||||
.addSubcommand(
|
||||
new SlashCommandSubcommandBuilder()
|
||||
.setName("appeal-link")
|
||||
.setDescription(
|
||||
"Set the link to be sent to someone when they are banned to appeal the decision."
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setRequired(true)
|
||||
.setName("link")
|
||||
.setDescription("The appeal link to send.")
|
||||
)
|
||||
)
|
||||
.addSubcommand(
|
||||
new SlashCommandSubcommandBuilder()
|
||||
.setName("logging")
|
||||
.setDescription("Configure a logging channel.")
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("log-type")
|
||||
.setDescription("The type of log to configure.")
|
||||
.addChoices(...logChannelChoices)
|
||||
.setRequired(true)
|
||||
)
|
||||
.addChannelOption((option) =>
|
||||
option
|
||||
.setName("channel")
|
||||
.setDescription("The channel to log to.")
|
||||
.setRequired(true)
|
||||
)
|
||||
)
|
||||
.addSubcommand(
|
||||
new SlashCommandSubcommandBuilder()
|
||||
.setName("roles")
|
||||
.setDescription("Toggle roles to be self-assignable by users.")
|
||||
.addRoleOption((o) =>
|
||||
o
|
||||
.setName("role")
|
||||
.setDescription("The role to toggle.")
|
||||
.setRequired(true)
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const member = interaction.member as GuildMember;
|
||||
const guild = interaction.guild as Guild;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "You must be in a server to use this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const config = await getConfig(bot, guild.id);
|
||||
|
||||
if (!member.permissions.has(PermissionFlagsBits.ManageGuild)) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to use this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
switch (subcommand) {
|
||||
case "list":
|
||||
await handleList(bot, interaction, config);
|
||||
break;
|
||||
case "logging":
|
||||
await handleLogging(bot, interaction, config);
|
||||
break;
|
||||
case "invite-link":
|
||||
await handleInviteLink(bot, interaction, config);
|
||||
break;
|
||||
case "appeal-link":
|
||||
await handleAppealLink(bot, interaction, config);
|
||||
break;
|
||||
case "roles":
|
||||
await handleRole(bot, interaction, config);
|
||||
break;
|
||||
default:
|
||||
await interaction.editReply({
|
||||
content: "This is an invalid subcommand. Please contact Naomi."
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "config command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
88
src/commands/help.ts
Normal file
88
src/commands/help.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { execSync } from "child_process";
|
||||
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
EmbedBuilder,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { checkEntitledGuild } from "../utils/checkEntitledGuild";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const help: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("help")
|
||||
.setDMPermission(false)
|
||||
.setDescription("Get help with the bot."),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply();
|
||||
|
||||
const version = process.env.npm_package_version;
|
||||
const commit = execSync("git rev-parse HEAD").toString().trim();
|
||||
const subscribed = await checkEntitledGuild(bot, interaction.guild);
|
||||
|
||||
const servers = bot.guilds.cache.size;
|
||||
const members = bot.guilds.cache.reduce(
|
||||
(sum, guild) => sum + guild.memberCount,
|
||||
0
|
||||
);
|
||||
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setTitle("Naomi's Moderation Bot");
|
||||
embed.setDescription(
|
||||
"This is a highly focused moderation bot designed to deliver the best experience when it comes to keeping your community safe and welcoming. To ensure we are able to deliver the features our users require, this bot is only available through a $5/month subscription."
|
||||
);
|
||||
embed.addFields(
|
||||
{
|
||||
name: "Version",
|
||||
value: version ? `v${version}` : "unable to parse version",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Current Commit",
|
||||
value: `[${commit.slice(
|
||||
0,
|
||||
7
|
||||
)}](https://github.com/nhcarrigan/mod-bot/commit/${commit})`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Is this server subscribed?",
|
||||
value: subscribed ? "Yes!" : "No :c",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Details",
|
||||
value: `Currently protecting ${servers} servers and watching over ${members} users.`
|
||||
}
|
||||
);
|
||||
|
||||
const supportButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL("https://chat.naomi.lgbt")
|
||||
.setLabel("Join our Support Server");
|
||||
const subscribeButton = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setURL("https://docs.nhcarrigan.com/#/donate")
|
||||
.setLabel("Subscribe for Access");
|
||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
supportButton,
|
||||
subscribeButton
|
||||
);
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [embed],
|
||||
components: [row]
|
||||
});
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "help command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
198
src/commands/history.ts
Normal file
198
src/commands/history.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import {
|
||||
GuildMember,
|
||||
EmbedBuilder,
|
||||
SlashCommandBuilder,
|
||||
ButtonBuilder,
|
||||
ActionRowBuilder,
|
||||
ComponentType,
|
||||
ButtonStyle
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { getNextIndex, getPreviousIndex } from "../utils/getArrayIndex";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
|
||||
export const history: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("history")
|
||||
.setDescription("View a user's history.")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to view the history for.")
|
||||
.setRequired(true)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isModerator(member as GuildMember)) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const target = interaction.options.getUser("user", true);
|
||||
|
||||
const cases = await bot.db.cases.findMany({
|
||||
where: {
|
||||
userId: target.id,
|
||||
serverId: guild.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!cases.length) {
|
||||
await interaction.editReply({
|
||||
content: "That user is squeaky clean!"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const caseNumbers = cases
|
||||
.filter((c) => c.action !== "note")
|
||||
.map((c) => `**#${c.number} - ${c.action}**`);
|
||||
const noteNumbers = cases
|
||||
.filter((c) => c.action === "note")
|
||||
.map((c) => `**#${c.number} - ${c.action}**`);
|
||||
``;
|
||||
|
||||
const historyEmbed = new EmbedBuilder();
|
||||
historyEmbed.setTitle(`${target.tag}'s history`);
|
||||
historyEmbed.addFields(
|
||||
{
|
||||
name: "Bans",
|
||||
value: String(cases.filter((c) => c.action === "ban").length) || "0",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Unbans",
|
||||
value:
|
||||
String(cases.filter((c) => c.action === "unban").length) || "0",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Softbans",
|
||||
value:
|
||||
String(cases.filter((c) => c.action === "softban").length) || "0",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Kicks",
|
||||
value: String(cases.filter((c) => c.action === "kick").length) || "0",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Mutes",
|
||||
value: String(cases.filter((c) => c.action === "mute").length) || "0",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Unmutes",
|
||||
value:
|
||||
String(cases.filter((c) => c.action === "unmute").length) || "0",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Warns",
|
||||
value: String(cases.filter((c) => c.action === "warn").length) || "0",
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Notes",
|
||||
value: String(cases.filter((c) => c.action === "note").length) || "0",
|
||||
inline: true
|
||||
}
|
||||
);
|
||||
|
||||
const embeds = [historyEmbed];
|
||||
|
||||
if (caseNumbers.length) {
|
||||
const manualEmbed = new EmbedBuilder()
|
||||
.setTitle("Manual Cases")
|
||||
.setDescription(caseNumbers.join(", "));
|
||||
embeds.push(manualEmbed);
|
||||
}
|
||||
|
||||
if (noteNumbers.length) {
|
||||
const noteEmbed = new EmbedBuilder()
|
||||
.setTitle("Notes")
|
||||
.setDescription(noteNumbers.join(", "));
|
||||
embeds.push(noteEmbed);
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
const nextButton = new ButtonBuilder()
|
||||
.setCustomId("next")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel(
|
||||
embeds[getNextIndex(embeds, index)]?.data.title || "Unknown embed."
|
||||
)
|
||||
.setEmoji("▶️");
|
||||
const prevButton = new ButtonBuilder()
|
||||
.setCustomId("prev")
|
||||
.setStyle(ButtonStyle.Primary)
|
||||
.setLabel(
|
||||
embeds[getPreviousIndex(embeds, index)]?.data.title ||
|
||||
"Unknown embed."
|
||||
)
|
||||
.setEmoji("◀️");
|
||||
const initialRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
prevButton,
|
||||
nextButton
|
||||
);
|
||||
|
||||
const response = await interaction.editReply({
|
||||
embeds: [embeds[index] as EmbedBuilder],
|
||||
components: [initialRow]
|
||||
});
|
||||
|
||||
const collector =
|
||||
response.createMessageComponentCollector<ComponentType.Button>({
|
||||
time: 1000 * 60 * 5
|
||||
});
|
||||
|
||||
collector.on("collect", async (i) => {
|
||||
await i.deferUpdate();
|
||||
index =
|
||||
i.customId === "next"
|
||||
? getNextIndex(embeds, index)
|
||||
: getPreviousIndex(embeds, index);
|
||||
prevButton.setLabel(
|
||||
embeds[getPreviousIndex(embeds, index)]?.data.title ||
|
||||
"Unknown embed."
|
||||
);
|
||||
nextButton.setLabel(
|
||||
embeds[getNextIndex(embeds, index)]?.data.title || "Unknown embed."
|
||||
);
|
||||
const newRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
prevButton,
|
||||
nextButton
|
||||
);
|
||||
await i.editReply({
|
||||
embeds: [embeds[index] as EmbedBuilder],
|
||||
components: [newRow]
|
||||
});
|
||||
});
|
||||
|
||||
collector.on("end", async () => {
|
||||
await interaction.editReply({
|
||||
components: []
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "history command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
145
src/commands/kick.ts
Normal file
145
src/commands/kick.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import {
|
||||
GuildMember,
|
||||
Message,
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
PermissionFlagsBits,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
import { processModAction } from "../utils/processModAction";
|
||||
|
||||
export const kick: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("kick")
|
||||
.setDescription("Kick a user from the server.")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to kick.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("The reason for kicking.")
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(400)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("evidence")
|
||||
.setDescription(
|
||||
"A link to the evidence for the kick. For multiple links, separate with a space."
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(
|
||||
PermissionFlagsBits.KickMembers
|
||||
)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
const evidence =
|
||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
||||
const user = interaction.options.getUser("user", true);
|
||||
const target =
|
||||
guild.members.cache.get(user.id) ||
|
||||
(await guild.members.fetch(user.id).catch(() => null));
|
||||
|
||||
if (!target) {
|
||||
await interaction.editReply({
|
||||
content: `${user.tag} is not in this server and thus cannot be kicked.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isModerator(target)) {
|
||||
await interaction.editReply({
|
||||
content: "You cannot kick a moderator."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const yes = new ButtonBuilder()
|
||||
.setCustomId("confirm")
|
||||
.setLabel("Confirm")
|
||||
.setStyle(ButtonStyle.Success);
|
||||
const no = new ButtonBuilder()
|
||||
.setCustomId("cancel")
|
||||
.setLabel("Cancel")
|
||||
.setStyle(ButtonStyle.Danger);
|
||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(yes, no);
|
||||
const response = (await interaction.editReply({
|
||||
content: `Are you sure you want to kick <@!${user.id}>?`,
|
||||
components: [row]
|
||||
})) as Message;
|
||||
|
||||
const collector =
|
||||
response.createMessageComponentCollector<ComponentType.Button>({
|
||||
filter: (click) => click.user.id === interaction.user.id,
|
||||
time: 10000,
|
||||
max: 1
|
||||
});
|
||||
|
||||
collector.on("end", async (clicks) => {
|
||||
const choice = clicks.first()?.customId;
|
||||
if (!clicks || clicks.size <= 0 || !choice) {
|
||||
await interaction.editReply({
|
||||
content: "This command has timed out.",
|
||||
components: []
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (choice === "confirm") {
|
||||
await processModAction(
|
||||
bot,
|
||||
interaction,
|
||||
guild,
|
||||
user,
|
||||
"kick",
|
||||
reason,
|
||||
evidence
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (choice === "cancel") {
|
||||
interaction.editReply({
|
||||
content: "Kick cancelled.",
|
||||
components: []
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "kick command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
156
src/commands/leaderboard.ts
Normal file
156
src/commands/leaderboard.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { generateLeaderboardImage } from "../modules/commands/generateProfileImage";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const leaderboard: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("leaderboard")
|
||||
.setDescription("See the levels for this community.")
|
||||
.setDMPermission(false),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply();
|
||||
|
||||
const levels = await bot.db.levels.findMany({
|
||||
where: {
|
||||
serverId: interaction.guild.id
|
||||
},
|
||||
orderBy: {
|
||||
points: "desc"
|
||||
}
|
||||
});
|
||||
|
||||
const mapped = levels.map((user, index) => ({
|
||||
...user,
|
||||
index: index + 1
|
||||
}));
|
||||
|
||||
let page = 1;
|
||||
const lastPage = Math.ceil(mapped.length / 10);
|
||||
|
||||
const pageBack = new ButtonBuilder()
|
||||
.setCustomId("prev")
|
||||
.setDisabled(true)
|
||||
.setLabel("◀")
|
||||
.setStyle(ButtonStyle.Primary);
|
||||
const pageForward = new ButtonBuilder()
|
||||
.setCustomId("next")
|
||||
.setLabel("▶")
|
||||
.setStyle(ButtonStyle.Primary);
|
||||
|
||||
if (page <= 1) {
|
||||
pageBack.setDisabled(true);
|
||||
} else {
|
||||
pageBack.setDisabled(false);
|
||||
}
|
||||
|
||||
if (page >= lastPage) {
|
||||
pageForward.setDisabled(true);
|
||||
} else {
|
||||
pageForward.setDisabled(false);
|
||||
}
|
||||
|
||||
const attachment = await generateLeaderboardImage(
|
||||
bot,
|
||||
mapped.slice(page * 10 - 10, page * 10)
|
||||
);
|
||||
|
||||
if (!attachment) {
|
||||
await interaction.editReply({
|
||||
content: "Failed to load leaderboard image.",
|
||||
files: [],
|
||||
components: []
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const sent = await interaction.editReply({
|
||||
files: [attachment],
|
||||
components: [
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
pageBack,
|
||||
pageForward
|
||||
)
|
||||
]
|
||||
});
|
||||
|
||||
const clickyClick =
|
||||
sent.createMessageComponentCollector<ComponentType.Button>({
|
||||
time: 300000,
|
||||
filter: (click) => click.user.id === interaction.user.id
|
||||
});
|
||||
|
||||
clickyClick.on("collect", async (click) => {
|
||||
await click.deferUpdate();
|
||||
if (click.customId === "prev") {
|
||||
page--;
|
||||
}
|
||||
if (click.customId === "next") {
|
||||
page++;
|
||||
}
|
||||
|
||||
if (page <= 1) {
|
||||
pageBack.setDisabled(true);
|
||||
} else {
|
||||
pageBack.setDisabled(false);
|
||||
}
|
||||
|
||||
if (page >= lastPage) {
|
||||
pageForward.setDisabled(true);
|
||||
} else {
|
||||
pageForward.setDisabled(false);
|
||||
}
|
||||
|
||||
const attachment = await generateLeaderboardImage(
|
||||
bot,
|
||||
mapped.slice(page * 10 - 10, page * 10)
|
||||
);
|
||||
|
||||
if (!attachment) {
|
||||
await interaction.editReply({
|
||||
content: "Failed to load leaderboard image.",
|
||||
files: [],
|
||||
components: []
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
files: [attachment],
|
||||
components: [
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
pageBack,
|
||||
pageForward
|
||||
)
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
clickyClick.on("end", async () => {
|
||||
pageBack.setDisabled(true);
|
||||
pageForward.setDisabled(true);
|
||||
await interaction.editReply({
|
||||
components: [
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
pageBack,
|
||||
pageForward
|
||||
)
|
||||
]
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "leaderboard subcommand", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
106
src/commands/levelRoles.ts
Normal file
106
src/commands/levelRoles.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import {
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder,
|
||||
SlashCommandSubcommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const levelRoles: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("level-role")
|
||||
.setDescription("Manage level roles.")
|
||||
.setDMPermission(false)
|
||||
.addSubcommand(
|
||||
new SlashCommandSubcommandBuilder()
|
||||
.setName("create")
|
||||
.setDescription("Create a new level role.")
|
||||
.addRoleOption((o) =>
|
||||
o
|
||||
.setName("role")
|
||||
.setDescription("The role to assign")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption((o) =>
|
||||
o
|
||||
.setName("level")
|
||||
.setDescription("The level at which to assign the role.")
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(1000)
|
||||
)
|
||||
)
|
||||
.addSubcommand(
|
||||
new SlashCommandSubcommandBuilder()
|
||||
.setName("delete")
|
||||
.setDescription("Delete a level role.")
|
||||
.addRoleOption((o) =>
|
||||
o
|
||||
.setName("role")
|
||||
.setDescription("The role to remove")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption((o) =>
|
||||
o
|
||||
.setName("level")
|
||||
.setDescription("The level at which the role was being assigned")
|
||||
.setRequired(true)
|
||||
.setMinValue(1)
|
||||
.setMaxValue(1000)
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member } = interaction;
|
||||
|
||||
if (!member.permissions.has(PermissionFlagsBits.ManageRoles)) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
const role = interaction.options.getRole("role", true);
|
||||
const level = interaction.options.getInteger("level", true);
|
||||
const action = interaction.options.getSubcommand(true);
|
||||
|
||||
let success = false;
|
||||
if (action === "create") {
|
||||
success = !!(await bot.db.levelRoles
|
||||
.create({
|
||||
data: {
|
||||
serverId: interaction.guild.id,
|
||||
roleId: role.id,
|
||||
level
|
||||
}
|
||||
})
|
||||
.catch(() => null));
|
||||
}
|
||||
if (action === "delete") {
|
||||
success = !!(await bot.db.levelRoles
|
||||
.delete({
|
||||
where: {
|
||||
serverId_level_roleId: {
|
||||
serverId: interaction.guild.id,
|
||||
roleId: role.id,
|
||||
level
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => null));
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
content: success
|
||||
? `Successfully ${action}ed your level ${level} ${role} assignment.`
|
||||
: `Failed to ${action} your level ${level} ${role} assignment.`
|
||||
});
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "level roles command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
98
src/commands/lockdown.ts
Normal file
98
src/commands/lockdown.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import {
|
||||
ChannelType,
|
||||
EmbedBuilder,
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder,
|
||||
TextChannel
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { getConfig } from "../modules/data/getConfig";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const lockdown: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("lockdown")
|
||||
.setDescription("Lock down a channel.")
|
||||
.addChannelOption((option) =>
|
||||
option
|
||||
.setName("channel")
|
||||
.setDescription("The channel to lock down.")
|
||||
.setRequired(true)
|
||||
.addChannelTypes(ChannelType.GuildText)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "You must be in a guild to use this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(
|
||||
PermissionFlagsBits.ManageChannels
|
||||
)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = interaction.options.getChannel(
|
||||
"channel",
|
||||
true
|
||||
) as TextChannel;
|
||||
|
||||
if (!("send" in channel)) {
|
||||
await interaction.editReply({
|
||||
content: "You must use this command to target a text based channel."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await channel.permissionOverwrites.edit(
|
||||
guild.id,
|
||||
{
|
||||
SendMessages: false
|
||||
},
|
||||
{ reason: `Lockdown Performed by ${interaction.user.tag}` }
|
||||
);
|
||||
|
||||
const config = await getConfig(bot, guild.id);
|
||||
if (!config.modLogChannel) {
|
||||
return;
|
||||
}
|
||||
const logChannel =
|
||||
guild.channels.cache.get(config.modLogChannel) ||
|
||||
(await guild.channels.fetch(config.modLogChannel));
|
||||
|
||||
if (!logChannel || !("send" in logChannel)) {
|
||||
return;
|
||||
}
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setTitle("Channel Locked Down");
|
||||
embed.setDescription(
|
||||
`The <#${channel.id}> channel has been locked down.`
|
||||
);
|
||||
embed.setAuthor({
|
||||
name: interaction.user.tag,
|
||||
iconURL: interaction.user.displayAvatarURL()
|
||||
});
|
||||
await logChannel.send({ embeds: [embed] });
|
||||
|
||||
await interaction.editReply({ content: "Channel has been locked down!" });
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "lockdown", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
49
src/commands/massBan.ts
Normal file
49
src/commands/massBan.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ModalBuilder,
|
||||
SlashCommandBuilder,
|
||||
TextInputBuilder,
|
||||
TextInputStyle
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const massBan: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("massban")
|
||||
.setDescription("Ban a list of user IDs at once."),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
const textInput = new TextInputBuilder()
|
||||
.setCustomId("mass-ban-ids")
|
||||
.setLabel("Input your list of IDs separated by new lines")
|
||||
.setMaxLength(4000)
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.setRequired(true);
|
||||
const reasonInput = new TextInputBuilder()
|
||||
.setCustomId("reason")
|
||||
.setLabel("Reason for the mass ban?")
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setRequired(true);
|
||||
const inputRow = new ActionRowBuilder<TextInputBuilder>().addComponents(
|
||||
textInput
|
||||
);
|
||||
const reasonRow = new ActionRowBuilder<TextInputBuilder>().addComponents(
|
||||
reasonInput
|
||||
);
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId("mass-ban-modal")
|
||||
.setTitle("Mass Ban")
|
||||
.addComponents(inputRow, reasonRow);
|
||||
|
||||
await interaction.showModal(modal);
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "mass ban", err);
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
122
src/commands/mute.ts
Normal file
122
src/commands/mute.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import {
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
import { processModAction } from "../utils/processModAction";
|
||||
|
||||
export const mute: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("mute")
|
||||
.setDescription("Mute a member")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to mute.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName("duration")
|
||||
.setDescription("The duration of the mute.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("duration-unit")
|
||||
.setDescription("The unit for the duration value")
|
||||
.setRequired(true)
|
||||
.addChoices(
|
||||
{ name: "Minutes", value: "minutes" },
|
||||
{ name: "Hours", value: "hours" },
|
||||
{ name: "Days", value: "days" },
|
||||
{ name: "Weeks", value: "weeks" }
|
||||
)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("The reason for the mute.")
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(400)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("evidence")
|
||||
.setDescription(
|
||||
"A link to the evidence for the mute. For multiple links, separate with a space."
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(
|
||||
PermissionFlagsBits.ModerateMembers
|
||||
)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = interaction.options.getUser("user", true);
|
||||
const target =
|
||||
guild.members.cache.get(user.id) ||
|
||||
(await guild.members.fetch(user.id).catch(() => null));
|
||||
|
||||
if (!target) {
|
||||
await interaction.editReply({
|
||||
content: "That member appears to have left the server."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isModerator(target)) {
|
||||
await interaction.editReply({
|
||||
content: "You cannot mute a moderator."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const duration = interaction.options.getInteger("duration", true);
|
||||
const durationUnit = interaction.options.getString("duration-unit", true);
|
||||
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
const evidence =
|
||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
||||
|
||||
await processModAction(
|
||||
bot,
|
||||
interaction,
|
||||
guild,
|
||||
user,
|
||||
"mute",
|
||||
reason,
|
||||
evidence,
|
||||
duration,
|
||||
durationUnit
|
||||
);
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "mute command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
67
src/commands/note.ts
Normal file
67
src/commands/note.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { GuildMember, SlashCommandBuilder } from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
import { processModAction } from "../utils/processModAction";
|
||||
|
||||
export const note: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("note")
|
||||
.setDescription("Add a note to a member's record.")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to add a note to.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("The reason for adding this note.")
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(400)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isModerator(member as GuildMember)) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = interaction.options.getUser("user", true);
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
|
||||
const target =
|
||||
guild.members.cache.get(user.id) ||
|
||||
(await guild.members.fetch(user.id).catch(() => null));
|
||||
|
||||
if (target && isModerator(target)) {
|
||||
await interaction.editReply({
|
||||
content: "You cannot add a note to a moderator."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await processModAction(bot, interaction, guild, user, "note", reason, []);
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "note command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
50
src/commands/ping.ts
Normal file
50
src/commands/ping.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { EmbedBuilder, SlashCommandBuilder } from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const ping: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("ping")
|
||||
.setDescription("Check the response time of the bot."),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply();
|
||||
const receivedInteraction = Date.now();
|
||||
const { createdTimestamp } = interaction;
|
||||
|
||||
const discordLatency = receivedInteraction - createdTimestamp;
|
||||
const websocketLatency = bot.ws.ping;
|
||||
|
||||
await bot.db.$runCommandRaw({ ping: 1 });
|
||||
const databaseLatency = Date.now() - receivedInteraction;
|
||||
|
||||
const pingEmbed = new EmbedBuilder();
|
||||
pingEmbed.setTitle("Pong!");
|
||||
pingEmbed.setDescription("Here is my latency information!");
|
||||
pingEmbed.addFields(
|
||||
{
|
||||
name: "Interaction Latency",
|
||||
value: `${discordLatency}ms`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Websocket Latency",
|
||||
value: `${websocketLatency}ms`,
|
||||
inline: true
|
||||
},
|
||||
{
|
||||
name: "Database Latency",
|
||||
value: `${databaseLatency}ms`,
|
||||
inline: true
|
||||
}
|
||||
);
|
||||
await interaction.editReply({ embeds: [pingEmbed] });
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "ping command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
129
src/commands/profile.ts
Normal file
129
src/commands/profile.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import {
|
||||
validateColour,
|
||||
validateImage
|
||||
} from "../modules/commands/profileValidation";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const profile: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("profile")
|
||||
.setDescription("Edit your profile that appears in the leaderboard")
|
||||
.setDMPermission(false)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("avatar")
|
||||
.setDescription(
|
||||
"The avatar to appear on your profile card must be a URL that points to an image."
|
||||
)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("background-colour")
|
||||
.setDescription(
|
||||
"The semi-transparent background color for your profile card must be a 6-digit hex value."
|
||||
)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("background-image")
|
||||
.setDescription(
|
||||
"The background image for your profile card must be a URL that points to an image."
|
||||
)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("colour")
|
||||
.setDescription(
|
||||
"The color for the text on your profile card must be a 6-digit hex value."
|
||||
)
|
||||
),
|
||||
run: async (CamperChan, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const responses = ["Your profile settings have been updated!"];
|
||||
const opts = {
|
||||
avatar: interaction.options.getString("avatar"),
|
||||
backgroundColour: interaction.options.getString("background-colour"),
|
||||
backgroundImage: interaction.options.getString("background-image"),
|
||||
colour: interaction.options.getString("colour")
|
||||
};
|
||||
if (opts.avatar) {
|
||||
const isValid = await validateImage(opts.avatar);
|
||||
if (!isValid) {
|
||||
responses.push(`${opts.avatar} is not a valid image URL.`);
|
||||
opts.avatar = "";
|
||||
}
|
||||
}
|
||||
if (opts.backgroundImage) {
|
||||
const isValid = await validateImage(opts.backgroundImage);
|
||||
if (!isValid) {
|
||||
responses.push(`${opts.backgroundImage} is not a valid image URL.`);
|
||||
opts.backgroundImage = "";
|
||||
}
|
||||
}
|
||||
if (opts.colour) {
|
||||
if (opts.colour.startsWith("#")) {
|
||||
opts.colour = opts.colour.slice(1);
|
||||
}
|
||||
if (!validateColour(opts.colour)) {
|
||||
opts.colour = null;
|
||||
responses.push(
|
||||
`${interaction.options.getString("colour")} is not a valid hex code. Please try again with a 6 character hex code (# is optional).`
|
||||
);
|
||||
}
|
||||
}
|
||||
if (opts.backgroundColour) {
|
||||
if (opts.backgroundColour.startsWith("#")) {
|
||||
opts.backgroundColour = opts.backgroundColour.slice(1);
|
||||
}
|
||||
if (!validateColour(opts.backgroundColour)) {
|
||||
opts.backgroundColour = null;
|
||||
responses.push(
|
||||
`${interaction.options.getString("background-colour")} is not a valid hex code. Please try again with a 6 character hex code (# is optional).`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const query = (
|
||||
Object.entries(opts) as [keyof typeof opts, string][]
|
||||
).reduce(
|
||||
(acc, [key, val]) => {
|
||||
if (val) {
|
||||
acc[key] = val;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{} as Record<keyof typeof opts, string>
|
||||
);
|
||||
|
||||
await CamperChan.db.levels.upsert({
|
||||
where: {
|
||||
serverId_userId: {
|
||||
serverId: interaction.guild.id,
|
||||
userId: interaction.user.id
|
||||
}
|
||||
},
|
||||
update: {
|
||||
...query
|
||||
},
|
||||
create: {
|
||||
userId: interaction.user.id,
|
||||
serverId: interaction.guild.id,
|
||||
username: interaction.user.username,
|
||||
...query
|
||||
}
|
||||
});
|
||||
|
||||
await interaction.editReply({ content: responses.join("\n") });
|
||||
} catch (err) {
|
||||
const id = await errorHandler(CamperChan, "user settings command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
71
src/commands/prune.ts
Normal file
71
src/commands/prune.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { SlashCommandBuilder, PermissionFlagsBits } from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { getConfig } from "../modules/data/getConfig";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const prune: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("prune")
|
||||
.setDescription("Prune messages from THIS channel.")
|
||||
.addNumberOption((option) =>
|
||||
option
|
||||
.setName("amount")
|
||||
.setDescription("The amount of messages to remove")
|
||||
.setMinValue(1)
|
||||
.setMaxValue(100)
|
||||
.setRequired(true)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "Could not find the member or guild."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!member.permissions.has(PermissionFlagsBits.ManageMessages)) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to prune messages."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = interaction.channel;
|
||||
const amount = interaction.options.getNumber("amount", true);
|
||||
|
||||
if (!channel) {
|
||||
await interaction.editReply({
|
||||
content: "Please provide a text channel or thread."
|
||||
});
|
||||
return;
|
||||
}
|
||||
const messages = await channel.messages.fetch({ limit: amount });
|
||||
for (const message of messages.values()) {
|
||||
await message.delete().catch(() => null);
|
||||
}
|
||||
|
||||
await interaction.editReply({ content: "Complete." });
|
||||
const config = await getConfig(bot, interaction.guild.id);
|
||||
if (config.modLogChannel) {
|
||||
const logChannel =
|
||||
interaction.guild.channels.cache.get(config.modLogChannel) ||
|
||||
(await interaction.guild.channels.fetch(config.modLogChannel));
|
||||
|
||||
if (logChannel && "send" in logChannel) {
|
||||
await logChannel.send({
|
||||
content: `Prune run by <@!${interaction.user.id}>. Deleted Messages: ${amount}`
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "prune interaction", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
52
src/commands/rank.ts
Normal file
52
src/commands/rank.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { generateProfileImage } from "../modules/commands/generateProfileImage";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const rank: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setDMPermission(false)
|
||||
.setName("rank")
|
||||
.setDescription("See your level rank in the community."),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply();
|
||||
const { user } = interaction;
|
||||
|
||||
const target = user.id;
|
||||
|
||||
const record = await bot.db.levels.findUnique({
|
||||
where: {
|
||||
serverId_userId: {
|
||||
userId: target,
|
||||
serverId: interaction.guild.id
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!record) {
|
||||
await interaction.editReply({
|
||||
content: "Error loading your database record."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const file = await generateProfileImage(bot, record);
|
||||
|
||||
if (!file) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error generating your profile. :c"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.editReply({ files: [file] });
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "rank command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
52
src/commands/role.ts
Normal file
52
src/commands/role.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { SlashCommandBuilder } from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const role: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setDMPermission(false)
|
||||
.setName("role")
|
||||
.setDescription("Give yourself a permitted role, or remove it.")
|
||||
.addRoleOption((o) =>
|
||||
o.setName("role").setDescription("The role to toggle.").setRequired(true)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const role = interaction.options.getRole("role", true);
|
||||
const isPermitted = await bot.db.roles
|
||||
.findUnique({
|
||||
where: {
|
||||
serverId_roleId: {
|
||||
serverId: interaction.guild.id,
|
||||
roleId: role.id
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => null);
|
||||
if (!isPermitted) {
|
||||
await interaction.editReply({
|
||||
content: `The <@&${role.id}> role is not self-assignable.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (interaction.member.roles.cache.has(role.id)) {
|
||||
await interaction.member.roles.remove(role.id);
|
||||
await interaction.editReply({
|
||||
content: `The <@&${role.id}> role has been removed.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
await interaction.member.roles.add(role.id);
|
||||
await interaction.editReply({
|
||||
content: `The <@&${role.id}> role has been added.`
|
||||
});
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "role command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
102
src/commands/softBan.ts
Normal file
102
src/commands/softBan.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import {
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
import { processModAction } from "../utils/processModAction";
|
||||
|
||||
export const softBan: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("softban")
|
||||
.setDescription(
|
||||
"Bans a user from the server, cleans up their messages, and removes the ban."
|
||||
)
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to softban.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("The reason for softbanning.")
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(400)
|
||||
)
|
||||
.addIntegerOption((option) =>
|
||||
option
|
||||
.setName("prune")
|
||||
.setDescription("Number of days to prune messages.")
|
||||
.setMinValue(1)
|
||||
.setMaxValue(7)
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("evidence")
|
||||
.setDescription(
|
||||
"A link to the evidence for the ban. For multiple links, separate with a space."
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(PermissionFlagsBits.BanMembers)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
const evidence =
|
||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
||||
const prune = interaction.options.getInteger("prune", true);
|
||||
const user = interaction.options.getUser("user", true);
|
||||
const target =
|
||||
guild.members.cache.get(user.id) ||
|
||||
(await guild.members.fetch(user.id).catch(() => null));
|
||||
|
||||
if (target && isModerator(target)) {
|
||||
await interaction.editReply({
|
||||
content: "You cannot ban a moderator."
|
||||
});
|
||||
return;
|
||||
}
|
||||
await processModAction(
|
||||
bot,
|
||||
interaction,
|
||||
guild,
|
||||
user,
|
||||
"softban",
|
||||
reason,
|
||||
evidence,
|
||||
undefined,
|
||||
undefined,
|
||||
prune
|
||||
);
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "softban command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
87
src/commands/unban.ts
Normal file
87
src/commands/unban.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { processModAction } from "../utils/processModAction";
|
||||
|
||||
export const unban: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("unban")
|
||||
.setDescription("Unban a user from the server.")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to unban.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("The reason for unbanning.")
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(400)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("evidence")
|
||||
.setDescription(
|
||||
"A link to the evidence for the unban. For multiple links, separate with a space."
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(PermissionFlagsBits.BanMembers)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
const evidence =
|
||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
||||
const user = interaction.options.getUser("user", true);
|
||||
|
||||
const isBanned = await guild.bans.fetch(user.id).catch(() => false);
|
||||
|
||||
if (!isBanned) {
|
||||
await interaction.editReply({
|
||||
content: `ID ${user.id} is not banned.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await processModAction(
|
||||
bot,
|
||||
interaction,
|
||||
guild,
|
||||
user,
|
||||
"unban",
|
||||
reason,
|
||||
evidence
|
||||
);
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "unban command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
96
src/commands/unlock.ts
Normal file
96
src/commands/unlock.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import {
|
||||
ChannelType,
|
||||
EmbedBuilder,
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder,
|
||||
TextChannel
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { getConfig } from "../modules/data/getConfig";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
|
||||
export const unlock: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("unlock")
|
||||
.setDescription("Remove lock down from a channel.")
|
||||
.addChannelOption((option) =>
|
||||
option
|
||||
.setName("channel")
|
||||
.setDescription("The channel to unlock.")
|
||||
.setRequired(true)
|
||||
.addChannelTypes(ChannelType.GuildText)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "You must be in a guild to use this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(
|
||||
PermissionFlagsBits.ManageChannels
|
||||
)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = interaction.options.getChannel(
|
||||
"channel",
|
||||
true
|
||||
) as TextChannel;
|
||||
|
||||
if (!("send" in channel)) {
|
||||
await interaction.editReply({
|
||||
content: "You must use this command to target a text based channel."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await channel.permissionOverwrites.edit(
|
||||
guild.id,
|
||||
{
|
||||
SendMessages: null
|
||||
},
|
||||
{ reason: `Lockdown Removed by ${interaction.user.tag}` }
|
||||
);
|
||||
|
||||
const config = await getConfig(bot, guild.id);
|
||||
if (!config.modLogChannel) {
|
||||
return;
|
||||
}
|
||||
const logChannel =
|
||||
guild.channels.cache.get(config.modLogChannel) ||
|
||||
(await guild.channels.fetch(config.modLogChannel));
|
||||
|
||||
if (!logChannel || !("send" in logChannel)) {
|
||||
return;
|
||||
}
|
||||
const embed = new EmbedBuilder();
|
||||
embed.setTitle("Channel Unlocked");
|
||||
embed.setDescription(`The <#${channel.id}> channel has been unlocked.`);
|
||||
embed.setAuthor({
|
||||
name: interaction.user.tag,
|
||||
iconURL: interaction.user.displayAvatarURL()
|
||||
});
|
||||
await logChannel.send({ embeds: [embed] });
|
||||
|
||||
await interaction.editReply({ content: "Channel has been unlocked!" });
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "unlock command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
106
src/commands/unmute.ts
Normal file
106
src/commands/unmute.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import {
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
import { processModAction } from "../utils/processModAction";
|
||||
|
||||
export const unmute: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("unmute")
|
||||
.setDescription("Unmute a member")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to unmute.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("The reason for the unmute.")
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(400)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("evidence")
|
||||
.setDescription(
|
||||
"A link to the evidence for the unmute. For multiple links, separate with a space."
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(
|
||||
PermissionFlagsBits.ModerateMembers
|
||||
)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = interaction.options.getUser("user", true);
|
||||
const target =
|
||||
guild.members.cache.get(user.id) ||
|
||||
(await guild.members.fetch(user.id).catch(() => null));
|
||||
|
||||
if (!target) {
|
||||
await interaction.editReply({
|
||||
content: "That member appears to have left the server."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target.isCommunicationDisabled()) {
|
||||
await interaction.editReply({
|
||||
content: "That member is not muted."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isModerator(target)) {
|
||||
await interaction.editReply({
|
||||
content:
|
||||
"A moderator should never be muted. How on earth did you achieve this???"
|
||||
});
|
||||
}
|
||||
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
const evidence =
|
||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
||||
|
||||
await processModAction(
|
||||
bot,
|
||||
interaction,
|
||||
guild,
|
||||
user,
|
||||
"unmute",
|
||||
reason,
|
||||
evidence
|
||||
);
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "unmute command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
99
src/commands/warn.ts
Normal file
99
src/commands/warn.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import {
|
||||
GuildMember,
|
||||
PermissionFlagsBits,
|
||||
SlashCommandBuilder
|
||||
} from "discord.js";
|
||||
|
||||
import { Command } from "../interfaces/Command";
|
||||
import { errorHandler } from "../utils/errorHandler";
|
||||
import { isModerator } from "../utils/isModerator";
|
||||
import { processModAction } from "../utils/processModAction";
|
||||
|
||||
export const warn: Command = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("warn")
|
||||
.setDescription("Warn a member")
|
||||
.addUserOption((option) =>
|
||||
option
|
||||
.setName("user")
|
||||
.setDescription("The user to warn.")
|
||||
.setRequired(true)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("reason")
|
||||
.setDescription("The reason for the warning.")
|
||||
.setRequired(true)
|
||||
.setMinLength(1)
|
||||
.setMaxLength(400)
|
||||
)
|
||||
.addStringOption((option) =>
|
||||
option
|
||||
.setName("evidence")
|
||||
.setDescription(
|
||||
"A link to the evidence for the warning. For multiple links, separate with a space."
|
||||
)
|
||||
),
|
||||
run: async (bot, interaction) => {
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
const { member, guild } = interaction;
|
||||
|
||||
if (!member || !guild) {
|
||||
await interaction.editReply({
|
||||
content: "There was an error loading the guild and member data."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!(member as GuildMember).permissions.has(
|
||||
PermissionFlagsBits.KickMembers
|
||||
)
|
||||
) {
|
||||
await interaction.editReply({
|
||||
content: "You do not have permission to run this command."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const user = interaction.options.getUser("user", true);
|
||||
const target =
|
||||
guild.members.cache.get(user.id) ||
|
||||
(await guild.members.fetch(user.id).catch(() => null));
|
||||
|
||||
if (!target) {
|
||||
await interaction.editReply({
|
||||
content: "That member appears to have left the server."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isModerator(target)) {
|
||||
await interaction.editReply({
|
||||
content: "You cannot warn a moderator."
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const reason = interaction.options.getString("reason", true);
|
||||
const evidence =
|
||||
interaction.options.getString("evidence")?.split(/\s+/) || [];
|
||||
|
||||
await processModAction(
|
||||
bot,
|
||||
interaction,
|
||||
guild,
|
||||
user,
|
||||
"warn",
|
||||
reason,
|
||||
evidence
|
||||
);
|
||||
} catch (err) {
|
||||
const id = await errorHandler(bot, "warn command", err);
|
||||
await interaction.editReply({
|
||||
content: `Something went wrong. Please [join our support server](https://chat.naomi.lgbt) and provide this ID: \`${id}\``
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user