Merge branch 'master' into db-rewrite

This commit is contained in:
Nix Krystik
2026-05-29 12:52:13 +00:00
committed by GitHub
95 changed files with 5597 additions and 5593 deletions
+140 -140
View File
@@ -1,141 +1,141 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, Guild, GuildMember, InteractionContextType, MessageMentions, PermissionFlagsBits, SlashCommandBuilder, time, TimestampStyles, User } from 'discord.js';
import { Database } from '../shared/Database';
import { AltData, comprehensiveAltLookupFromDiscord, deferInteraction } from '../utils';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'ban',
data: new SlashCommandBuilder()
.setName('ban')
.setDescription('Bans a user.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to ban.')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('reason')
.setDescription('The reason for the ban')
.setRequired(false)
.setMaxLength(400)
)
.addNumberOption(option =>
option
.setName('hours')
.setDescription('The duration of the ban, added with other options (0 for permanent).')
.setRequired(false)
)
.addNumberOption(option =>
option
.setName('minutes')
.setDescription('The duration of the ban, added with other options (0 for permanent).')
.setRequired(false)
)
.addNumberOption(option =>
option
.setName('seconds')
.setDescription('The duration of the ban, added with other options (0 for permanent).')
.setRequired(false)
)
.addNumberOption(option =>
option
.setName('delete-message-days')
.setDescription('How far back to delete messages (in days, default: 0 days).')
.setRequired(false)
.setMinValue(0)
.setMaxValue(7)
)
.addBooleanOption(option =>
option
.setName('full-ban')
.setDescription('Whether or not to prevent the user from joining on known alts (and ban all existing alts).')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await deferInteraction(interaction);
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
if (!interaction.guild.members.me) return interaction.editReply('An error has occurred. Please try again later.');
const input = interaction.options.getString('user', true);
const matches = mentionRegex.exec(input);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : input;
const reason = interaction.options.getString('reason') ?? '';
const hours = (interaction.options.getNumber('hours') ?? 0) * 3.6e+6;
const minutes = (interaction.options.getNumber('minutes') ?? 0) * 60000;
const seconds = (interaction.options.getNumber('seconds') ?? 0) * 1000;
const duration = hours + minutes + seconds;
const deleteMessageDays = (interaction.options.getNumber('delete-message-days') ?? 0) * 86400;
const fullBan = interaction.options.getBoolean('full-ban') ?? false;
let banMember: GuildMember | null = null;
try {
banMember = await interaction.guild.members.fetch(idToUse);
} catch (e) {
// Member not in server.
}
const member = await interaction.guild.members.fetch(interaction.user.id);
if (banMember && member.roles.highest.comparePositionTo(banMember.roles.highest) <= 0) {
return await interaction.editReply('You do not have permission to ban this user.');
}
if (banMember && !banMember.bannable) {
return await interaction.editReply('I do not have permission to ban this user.');
}
const expiresAt = new Date(Date.now() + duration);
await Database.putBan(idToUse, duration > 0 ? expiresAt : null, fullBan);
try {
await interaction.guild.bans.create(idToUse, {
reason: (reason + ` ${fullBan ? 'Full banned' : 'Banned'} by ${interaction.user.username} (${interaction.user.id})${duration > 0 ? `. Expires at: ${time(expiresAt, TimestampStyles.ShortDateTime)}` : ''}`).trim(),
deleteMessageSeconds: deleteMessageDays
});
} catch (e) {
console.error(e);
return await interaction.editReply("Error banning user (couldn't ban).");
}
if (fullBan) {
const alts = await comprehensiveAltLookupFromDiscord(idToUse, interaction.guild);
await removeAllAlts([alts], interaction.guild, interaction.user, fullBan, reason, deleteMessageDays, duration, expiresAt);
}
await interaction.editReply(`<@${idToUse}> (${idToUse}) has been ${fullBan ? 'full banned' : 'banned'}.`);
}
};
async function removeAllAlts(altData: AltData[], guild: Guild, moderator: User, fullBan: boolean, reason: string, deleteMessageDays: number, duration: number, expiresAt: Date) {
for (const data of altData) {
if (data.type == 'discord') {
try {
if (!data.banned) {
await guild.members.kick(data.thisId as string, (reason + ` ${fullBan ? 'Full banned' : 'Banned'} by ${moderator.username} (${moderator.id})${duration > 0 ? `. Expires at: ${time(expiresAt, TimestampStyles.ShortDateTime)}` : ''}`).trim());
}
} catch (e) {
console.error(e);
}
}
await removeAllAlts(data.alts, guild, moderator, fullBan, reason, deleteMessageDays, duration, expiresAt);
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, Guild, GuildMember, InteractionContextType, MessageMentions, PermissionFlagsBits, SlashCommandBuilder, time, TimestampStyles, User } from 'discord.js';
import { Database } from '../shared/Database';
import { AltData, comprehensiveAltLookupFromDiscord, deferInteraction } from '../utils';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'ban',
data: new SlashCommandBuilder()
.setName('ban')
.setDescription('Bans a user.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to ban.')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('reason')
.setDescription('The reason for the ban')
.setRequired(false)
.setMaxLength(400)
)
.addNumberOption(option =>
option
.setName('hours')
.setDescription('The duration of the ban, added with other options (0 for permanent).')
.setRequired(false)
)
.addNumberOption(option =>
option
.setName('minutes')
.setDescription('The duration of the ban, added with other options (0 for permanent).')
.setRequired(false)
)
.addNumberOption(option =>
option
.setName('seconds')
.setDescription('The duration of the ban, added with other options (0 for permanent).')
.setRequired(false)
)
.addNumberOption(option =>
option
.setName('delete-message-days')
.setDescription('How far back to delete messages (in days, default: 0 days).')
.setRequired(false)
.setMinValue(0)
.setMaxValue(7)
)
.addBooleanOption(option =>
option
.setName('full-ban')
.setDescription('Whether or not to prevent the user from joining on known alts (and ban all existing alts).')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await deferInteraction(interaction);
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
if (!interaction.guild.members.me) return interaction.editReply('An error has occurred. Please try again later.');
const input = interaction.options.getString('user', true);
const matches = mentionRegex.exec(input);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : input;
const reason = interaction.options.getString('reason') ?? '';
const hours = (interaction.options.getNumber('hours') ?? 0) * 3.6e+6;
const minutes = (interaction.options.getNumber('minutes') ?? 0) * 60000;
const seconds = (interaction.options.getNumber('seconds') ?? 0) * 1000;
const duration = hours + minutes + seconds;
const deleteMessageDays = (interaction.options.getNumber('delete-message-days') ?? 0) * 86400;
const fullBan = interaction.options.getBoolean('full-ban') ?? false;
let banMember: GuildMember | null = null;
try {
banMember = await interaction.guild.members.fetch(idToUse);
} catch (e) {
// Member not in server.
}
const member = await interaction.guild.members.fetch(interaction.user.id);
if (banMember && member.roles.highest.comparePositionTo(banMember.roles.highest) <= 0) {
return await interaction.editReply('You do not have permission to ban this user.');
}
if (banMember && !banMember.bannable) {
return await interaction.editReply('I do not have permission to ban this user.');
}
const expiresAt = new Date(Date.now() + duration);
await Database.putBan(idToUse, duration > 0 ? expiresAt : null, fullBan);
try {
await interaction.guild.bans.create(idToUse, {
reason: (reason + ` ${fullBan ? 'Full banned' : 'Banned'} by ${interaction.user.username} (${interaction.user.id})${duration > 0 ? `. Expires at: ${time(expiresAt, TimestampStyles.ShortDateTime)}` : ''}`).trim(),
deleteMessageSeconds: deleteMessageDays
});
} catch (e) {
console.error(e);
return await interaction.editReply("Error banning user (couldn't ban).");
}
if (fullBan) {
const alts = await comprehensiveAltLookupFromDiscord(idToUse, interaction.guild);
await removeAllAlts([alts], interaction.guild, interaction.user, fullBan, reason, deleteMessageDays, duration, expiresAt);
}
await interaction.editReply(`<@${idToUse}> (${idToUse}) has been ${fullBan ? 'full banned' : 'banned'}.`);
}
};
async function removeAllAlts(altData: AltData[], guild: Guild, moderator: User, fullBan: boolean, reason: string, deleteMessageDays: number, duration: number, expiresAt: Date) {
for (const data of altData) {
if (data.type == 'discord') {
try {
if (!data.banned) {
await guild.members.kick(data.thisId as string, (reason + ` ${fullBan ? 'Full banned' : 'Banned'} by ${moderator.username} (${moderator.id})${duration > 0 ? `. Expires at: ${time(expiresAt, TimestampStyles.ShortDateTime)}` : ''}`).trim());
}
} catch (e) {
console.error(e);
}
}
await removeAllAlts(data.alts, guild, moderator, fullBan, reason, deleteMessageDays, duration, expiresAt);
}
}
+47 -47
View File
@@ -1,48 +1,48 @@
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
import { KnowledgebaseItem } from '../types';
export default {
name: 'cite',
data: new SlashCommandBuilder()
.setName('cite')
.setDescription('Cite content from the knowledgebase.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.addIntegerOption(option =>
option
.setName('name')
.setDescription('The name of the entry.')
.setRequired(true)
.setAutocomplete(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
if (!interaction.guild) return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Must be ran in guild.' });
await interaction.deferReply();
const id = interaction.options.getInteger('name', true);
const item = await Database.getFromKnowledgebase(id);
if (!item) return interaction.editReply('Knowledgebase item not found.');
return interaction.editReply(item.content);
},
autoComplete: async function (client: Client, interaction: AutocompleteInteraction) {
if (!interaction.guild) return interaction.respond([]);
const items: KnowledgebaseItem[] = await Database.getAllKnowledgebaseItems(interaction.guild.id);
const value = interaction.options.getFocused();
const toRespond = items.filter(i => !value ? true : i.content.includes(value));
if (toRespond.length > 25) toRespond.length = 25;
interaction.respond(toRespond.map(p => ({
name: p.name,
value: p.id
})));
}
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
import { KnowledgebaseItem } from '../types';
export default {
name: 'cite',
data: new SlashCommandBuilder()
.setName('cite')
.setDescription('Cite content from the knowledgebase.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.addIntegerOption(option =>
option
.setName('name')
.setDescription('The name of the entry.')
.setRequired(true)
.setAutocomplete(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
if (!interaction.guild) return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Must be ran in guild.' });
await interaction.deferReply();
const id = interaction.options.getInteger('name', true);
const item = await Database.getFromKnowledgebase(id);
if (!item) return interaction.editReply('Knowledgebase item not found.');
return interaction.editReply(item.content);
},
autoComplete: async function (client: Client, interaction: AutocompleteInteraction) {
if (!interaction.guild) return interaction.respond([]);
const items: KnowledgebaseItem[] = await Database.getAllKnowledgebaseItems(interaction.guild.id);
const value = interaction.options.getFocused();
const toRespond = items.filter(i => !value ? true : i.content.includes(value));
if (toRespond.length > 25) toRespond.length = 25;
interaction.respond(toRespond.map(p => ({
name: p.name,
value: p.id
})));
}
};
+41 -41
View File
@@ -1,42 +1,42 @@
import { ActionRowBuilder, ApplicationIntegrationType, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
export default {
name: 'devwatch',
data: new SlashCommandBuilder()
.setName('devwatch')
.setDescription('Sends a dev watch role toggle button.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
.addStringOption(option =>
option
.setName('content')
.setDescription('The content of the message.')
.setRequired(false)
)
.addStringOption(option =>
option
.setName('button-label')
.setDescription('The button label.')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
if (!interaction.channel || !interaction.channel.isSendable())
return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Missing permissions to send to channel.' });
const content = interaction.options.getString('content') ?? '';
const label = interaction.options.getString('button-label') ?? 'Toggle DevWatch Role';
const button = new ButtonBuilder()
.setCustomId('dev-watch')
.setStyle(ButtonStyle.Primary)
.setLabel(label);
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(button);
await interaction.channel.send({ components: [row], content });
interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Sent.' });
}
import { ActionRowBuilder, ApplicationIntegrationType, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
export default {
name: 'devwatch',
data: new SlashCommandBuilder()
.setName('devwatch')
.setDescription('Sends a dev watch role toggle button.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
.addStringOption(option =>
option
.setName('content')
.setDescription('The content of the message.')
.setRequired(false)
)
.addStringOption(option =>
option
.setName('button-label')
.setDescription('The button label.')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
if (!interaction.channel || !interaction.channel.isSendable())
return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Missing permissions to send to channel.' });
const content = interaction.options.getString('content') ?? '';
const label = interaction.options.getString('button-label') ?? 'Toggle DevWatch Role';
const button = new ButtonBuilder()
.setCustomId('dev-watch')
.setStyle(ButtonStyle.Primary)
.setLabel(label);
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(button);
await interaction.channel.send({ components: [row], content });
interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Sent.' });
}
};
+41 -41
View File
@@ -1,42 +1,42 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { config } from '../config';
import { deferInteraction, getDiscordAlts, getE621User } from '../utils';
export default {
name: 'finduser',
data: new SlashCommandBuilder()
.setName('finduser')
.setDescription("Find a user's discord account based on their e621 usernamename or id.")
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addStringOption(option =>
option
.setName('user')
.setDescription('The e621 username or e621 id to find the discord user of.')
.setRequired(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await deferInteraction(interaction);
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
const user = interaction.options.getString('user', true);
try {
const e621User = await getE621User(user);
if (!e621User) {
return interaction.editReply('I got lost along the way. Who again?');
}
const content = await getDiscordAlts(e621User.id, interaction.guild, 1, [e621User.id]);
interaction.editReply(`[${e621User.name}](${config.E621_BASE_URL}/users/${e621User.id})<${e621User.id}>'s e621 and discord account(s):\n${content}`);
} catch (e) {
console.error(e);
interaction.editReply('I got lost in the net.');
}
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { config } from '../config';
import { deferInteraction, getDiscordAlts, getE621User } from '../utils';
export default {
name: 'finduser',
data: new SlashCommandBuilder()
.setName('finduser')
.setDescription("Find a user's discord account based on their e621 usernamename or id.")
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addStringOption(option =>
option
.setName('user')
.setDescription('The e621 username or e621 id to find the discord user of.')
.setRequired(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await deferInteraction(interaction);
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
const user = interaction.options.getString('user', true);
try {
const e621User = await getE621User(user);
if (!e621User) {
return interaction.editReply('I got lost along the way. Who again?');
}
const content = await getDiscordAlts(e621User.id, interaction.guild, 1, [e621User.id]);
interaction.editReply(`[${e621User.name}](${config.E621_BASE_URL}/users/${e621User.id})<${e621User.id}>'s e621 and discord account(s):\n${content}`);
} catch (e) {
console.error(e);
interaction.editReply('I got lost in the net.');
}
}
};
+86 -86
View File
@@ -1,87 +1,87 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, MessageMentions, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'github-mapping',
data: new SlashCommandBuilder()
.setName('github-mapping')
.setDescription('Maps github users to discord ids for releases.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add a user mapping.')
.addStringOption(option =>
option
.setName('discord-user')
.setDescription('The discord user id, or mention, of the user.')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('github-name')
.setDescription('The github username of the user (case sensitive).')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove a user mapping.')
.addStringOption(option =>
option
.setName('discord-user')
.setDescription('The discord user id, or mention, of the user.')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('List all github-discord mappings.')
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
const subcommand = await interaction.options.getSubcommand(true);
if (subcommand == 'add') {
const discordUserInput = interaction.options.getString('discord-user', true);
const matches = mentionRegex.exec(discordUserInput);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : discordUserInput;
const githubName = interaction.options.getString('github-name', true);
const existingMappingId = await Database.getGithubFromDiscordId(idToUse);
const existingMappingName = await Database.getDiscordIdFromGithub(githubName);
if (existingMappingId) return interaction.editReply(`Discord user id is already mapped to ${existingMappingId}`);
if (existingMappingName) return interaction.editReply(`Github username is already mapped to <@${existingMappingName}> (${existingMappingName})`);
Database.putGithubUserMapping(idToUse, githubName);
return interaction.editReply('Mapping added.');
} else if (subcommand == 'remove') {
const discordUserInput = interaction.options.getString('discord-user', true);
const matches = mentionRegex.exec(discordUserInput);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : discordUserInput;
Database.removeGithubUserMapping(idToUse);
return interaction.editReply('Mapping removed.');
} else if (subcommand == 'list') {
const allMappings = await Database.getAllGithubUserMappings();
return interaction.editReply(allMappings.map(m => `- <@${m.discord_id}> (${m.discord_id}) - ${m.github_username}`).join('\n'));
}
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, MessageMentions, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'github-mapping',
data: new SlashCommandBuilder()
.setName('github-mapping')
.setDescription('Maps github users to discord ids for releases.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add a user mapping.')
.addStringOption(option =>
option
.setName('discord-user')
.setDescription('The discord user id, or mention, of the user.')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('github-name')
.setDescription('The github username of the user (case sensitive).')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove a user mapping.')
.addStringOption(option =>
option
.setName('discord-user')
.setDescription('The discord user id, or mention, of the user.')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('List all github-discord mappings.')
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
const subcommand = await interaction.options.getSubcommand(true);
if (subcommand == 'add') {
const discordUserInput = interaction.options.getString('discord-user', true);
const matches = mentionRegex.exec(discordUserInput);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : discordUserInput;
const githubName = interaction.options.getString('github-name', true);
const existingMappingId = await Database.getGithubFromDiscordId(idToUse);
const existingMappingName = await Database.getDiscordIdFromGithub(githubName);
if (existingMappingId) return interaction.editReply(`Discord user id is already mapped to ${existingMappingId}`);
if (existingMappingName) return interaction.editReply(`Github username is already mapped to <@${existingMappingName}> (${existingMappingName})`);
Database.putGithubUserMapping(idToUse, githubName);
return interaction.editReply('Mapping added.');
} else if (subcommand == 'remove') {
const discordUserInput = interaction.options.getString('discord-user', true);
const matches = mentionRegex.exec(discordUserInput);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : discordUserInput;
Database.removeGithubUserMapping(idToUse);
return interaction.editReply('Mapping removed.');
} else if (subcommand == 'list') {
const allMappings = await Database.getAllGithubUserMappings();
return interaction.editReply(allMappings.map(m => `- <@${m.discord_id}> (${m.discord_id}) - ${m.github_username}`).join('\n'));
}
}
};
+126 -126
View File
@@ -1,127 +1,127 @@
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, ModalBuilder, PermissionFlagsBits, SlashCommandBuilder, TextInputStyle } from 'discord.js';
import { Database } from '../shared/Database';
import { KnowledgebaseItem } from '../types';
import { createTextInput, deferInteraction, logCustomEvent } from '../utils';
export default {
name: 'knowledgebase',
data: new SlashCommandBuilder()
.setName('knowledgebase')
.setDescription('Access the compendium of knowledge.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add to the knowledgebase.')
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Purge knowledge from the universe.')
.addIntegerOption(option =>
option
.setName('name')
.setDescription('The name of the entry to remove.')
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('edit')
.setDescription('Edit a knowledgebase entry.')
.addIntegerOption(option =>
option
.setName('name')
.setDescription('The name of the entry to edit.')
.setRequired(true)
.setAutocomplete(true)
)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
if (!interaction.guild) return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Must be ran in guild.' });
const subcommand = interaction.options.getSubcommand(true);
if (subcommand == 'add') {
const modal = new ModalBuilder()
.setCustomId('add-knowledgebase-item-modal')
.setTitle('Add to knowledgebase');
const nameLabel = createTextInput('name', 'Knowledgebase Item Name', null, true, TextInputStyle.Short, 300, 1);
const contentLabel = createTextInput('content', 'Item Content', null, true, TextInputStyle.Paragraph, 2000, 1);
modal.addLabelComponents(nameLabel, contentLabel);
return await interaction.showModal(modal);
} else if (subcommand == 'remove') {
await deferInteraction(interaction);
const id = interaction.options.getInteger('name', true);
const item = await Database.getFromKnowledgebase(id);
if (!item) return interaction.editReply('Knowledgebase item not found.');
logCustomEvent(interaction.guild!, {
title: 'Knowledgebase Item Removed',
description: null,
color: 0xFF0000,
timestamp: new Date(),
fields: [
{
name: 'User',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Name',
value: item.name,
inline: true
},
{
name: 'Content',
value: item.content,
inline: true
}
]
});
await Database.removeFromKnowledgebase(id);
return interaction.editReply(`Removed knowledgebase entry \`${item.name}\`.`);
} else if (subcommand == 'edit') {
const id = interaction.options.getInteger('name', true);
const existingItem = await Database.getFromKnowledgebase(id);
if (!existingItem) return interaction.editReply('Knowledgebase item not found.');
const modal = new ModalBuilder()
.setCustomId(`edit-knowledgebase-item-modal_${id}`)
.setTitle(`Editing knowledgebase item ${existingItem.name.slice(0, 18)}`);
const contentLabel = createTextInput('content', 'New Content', null, true, TextInputStyle.Paragraph, 2000, 1);
modal.addLabelComponents(contentLabel);
return await interaction.showModal(modal);
}
},
autoComplete: async function (client: Client, interaction: AutocompleteInteraction) {
if (!interaction.guild) return interaction.respond([]);
const items: KnowledgebaseItem[] = await Database.getAllKnowledgebaseItems(interaction.guild.id);
const value = interaction.options.getFocused();
const toRespond = items.filter(i => !value ? true : i.name.includes(value));
if (toRespond.length > 25) toRespond.length = 25;
interaction.respond(toRespond.map(p => ({
name: p.name,
value: p.id
})));
}
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, ModalBuilder, PermissionFlagsBits, SlashCommandBuilder, TextInputStyle } from 'discord.js';
import { Database } from '../shared/Database';
import { KnowledgebaseItem } from '../types';
import { createTextInput, deferInteraction, logCustomEvent } from '../utils';
export default {
name: 'knowledgebase',
data: new SlashCommandBuilder()
.setName('knowledgebase')
.setDescription('Access the compendium of knowledge.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageMessages)
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add to the knowledgebase.')
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Purge knowledge from the universe.')
.addIntegerOption(option =>
option
.setName('name')
.setDescription('The name of the entry to remove.')
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('edit')
.setDescription('Edit a knowledgebase entry.')
.addIntegerOption(option =>
option
.setName('name')
.setDescription('The name of the entry to edit.')
.setRequired(true)
.setAutocomplete(true)
)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
if (!interaction.guild) return interaction.reply({ flags: [MessageFlags.Ephemeral], content: 'Must be ran in guild.' });
const subcommand = interaction.options.getSubcommand(true);
if (subcommand == 'add') {
const modal = new ModalBuilder()
.setCustomId('add-knowledgebase-item-modal')
.setTitle('Add to knowledgebase');
const nameLabel = createTextInput('name', 'Knowledgebase Item Name', null, true, TextInputStyle.Short, 300, 1);
const contentLabel = createTextInput('content', 'Item Content', null, true, TextInputStyle.Paragraph, 2000, 1);
modal.addLabelComponents(nameLabel, contentLabel);
return await interaction.showModal(modal);
} else if (subcommand == 'remove') {
await deferInteraction(interaction);
const id = interaction.options.getInteger('name', true);
const item = await Database.getFromKnowledgebase(id);
if (!item) return interaction.editReply('Knowledgebase item not found.');
logCustomEvent(interaction.guild!, {
title: 'Knowledgebase Item Removed',
description: null,
color: 0xFF0000,
timestamp: new Date(),
fields: [
{
name: 'User',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Name',
value: item.name,
inline: true
},
{
name: 'Content',
value: item.content,
inline: true
}
]
});
await Database.removeFromKnowledgebase(id);
return interaction.editReply(`Removed knowledgebase entry \`${item.name}\`.`);
} else if (subcommand == 'edit') {
const id = interaction.options.getInteger('name', true);
const existingItem = await Database.getFromKnowledgebase(id);
if (!existingItem) return interaction.editReply('Knowledgebase item not found.');
const modal = new ModalBuilder()
.setCustomId(`edit-knowledgebase-item-modal_${id}`)
.setTitle(`Editing knowledgebase item ${existingItem.name.slice(0, 18)}`);
const contentLabel = createTextInput('content', 'New Content', null, true, TextInputStyle.Paragraph, 2000, 1);
modal.addLabelComponents(contentLabel);
return await interaction.showModal(modal);
}
},
autoComplete: async function (client: Client, interaction: AutocompleteInteraction) {
if (!interaction.guild) return interaction.respond([]);
const items: KnowledgebaseItem[] = await Database.getAllKnowledgebaseItems(interaction.guild.id);
const value = interaction.options.getFocused();
const toRespond = items.filter(i => !value ? true : i.name.includes(value));
if (toRespond.length > 25) toRespond.length = 25;
interaction.respond(toRespond.map(p => ({
name: p.name,
value: p.id
})));
}
};
+133 -133
View File
@@ -1,134 +1,134 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, MessageMentions, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { config } from '../config';
import { Database } from '../shared/Database';
import { logCustomEvent, resolveUser } from '../utils';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'link',
data: new SlashCommandBuilder()
.setName('link')
.setDescription('Manually link discord users and e621 users.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
.addSubcommand(subcommand =>
subcommand
.setName('create')
.setDescription('Create a link.')
.addStringOption(option =>
option
.setName('discord-user')
.setDescription('The discord user id, or mention, of the user.')
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName('e621-id')
.setDescription('The id of the e621 user.')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove a link.')
.addStringOption(option =>
option
.setName('discord-user')
.setDescription('The discord user id, or mention, of the user.')
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName('e621-id')
.setDescription('The id of the e621 user.')
.setRequired(true)
)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
const subcommand = await interaction.options.getSubcommand(true);
const discordUserInput = interaction.options.getString('discord-user', true);
const matches = mentionRegex.exec(discordUserInput);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : discordUserInput;
const user = await resolveUser(client, idToUse, interaction.guild);
if (!user) return interaction.editReply('User not found.');
const e621Id = interaction.options.getInteger('e621-id', true);
if (subcommand == 'create') {
const existingLinks = await Database.getDiscordIds(e621Id);
if (existingLinks.includes(user.id)) return interaction.editReply('Accounts already linked.');
await Database.putUser(e621Id, user);
await logCustomEvent(interaction.guild!, {
title: 'Account Link Created',
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'Admin',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Discord User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'E621 User',
value: `${config.E621_BASE_URL}/users/${e621Id}`,
inline: true
}
]
});
interaction.editReply('Accounts linked');
} else if (subcommand == 'remove') {
const existingLinks = await Database.getDiscordIds(e621Id);
if (!existingLinks.includes(user.id)) return interaction.editReply('Accounts not linked.');
await Database.removeUser(e621Id, user.id);
await logCustomEvent(interaction.guild!, {
title: 'Account Link Removed',
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'Admin',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Discord User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'E621 User',
value: `${config.E621_BASE_URL}/users/${e621Id}`,
inline: true
}
]
});
interaction.editReply('Accounts unlinked');
}
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, MessageMentions, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { config } from '../config';
import { Database } from '../shared/Database';
import { logCustomEvent, resolveUser } from '../utils';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'link',
data: new SlashCommandBuilder()
.setName('link')
.setDescription('Manually link discord users and e621 users.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
.addSubcommand(subcommand =>
subcommand
.setName('create')
.setDescription('Create a link.')
.addStringOption(option =>
option
.setName('discord-user')
.setDescription('The discord user id, or mention, of the user.')
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName('e621-id')
.setDescription('The id of the e621 user.')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove a link.')
.addStringOption(option =>
option
.setName('discord-user')
.setDescription('The discord user id, or mention, of the user.')
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName('e621-id')
.setDescription('The id of the e621 user.')
.setRequired(true)
)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
const subcommand = await interaction.options.getSubcommand(true);
const discordUserInput = interaction.options.getString('discord-user', true);
const matches = mentionRegex.exec(discordUserInput);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : discordUserInput;
const user = await resolveUser(client, idToUse, interaction.guild);
if (!user) return interaction.editReply('User not found.');
const e621Id = interaction.options.getInteger('e621-id', true);
if (subcommand == 'create') {
const existingLinks = await Database.getDiscordIds(e621Id);
if (existingLinks.includes(user.id)) return interaction.editReply('Accounts already linked.');
await Database.putUser(e621Id, user);
await logCustomEvent(interaction.guild!, {
title: 'Account Link Created',
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'Admin',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Discord User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'E621 User',
value: `${config.E621_BASE_URL}/users/${e621Id}`,
inline: true
}
]
});
interaction.editReply('Accounts linked');
} else if (subcommand == 'remove') {
const existingLinks = await Database.getDiscordIds(e621Id);
if (!existingLinks.includes(user.id)) return interaction.editReply('Accounts not linked.');
await Database.removeUser(e621Id, user.id);
await logCustomEvent(interaction.guild!, {
title: 'Account Link Removed',
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'Admin',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Discord User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'E621 User',
value: `${config.E621_BASE_URL}/users/${e621Id}`,
inline: true
}
]
});
interaction.editReply('Accounts unlinked');
}
}
};
+27 -27
View File
@@ -1,28 +1,28 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { openModTicketModal } from '../utils';
export default {
name: 'mod-ticket',
data: new SlashCommandBuilder()
.setName('mod-ticket')
.setDescription('Opens a mod private ticket and pulls the user into it.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
.addUserOption(option =>
option
.setName('user')
.setDescription('The user to pull in to the ticket.')
.setRequired(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
if (!interaction.guild) return interaction.editReply('This command must be used in a server.');
const user = interaction.options.getUser('user', true);
const member = await interaction.guild.members.fetch(user.id);
if (!member) return interaction.editReply('Could not find member.');
openModTicketModal(interaction, member);
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { openModTicketModal } from '../utils';
export default {
name: 'mod-ticket',
data: new SlashCommandBuilder()
.setName('mod-ticket')
.setDescription('Opens a mod private ticket and pulls the user into it.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
.addUserOption(option =>
option
.setName('user')
.setDescription('The user to pull in to the ticket.')
.setRequired(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
if (!interaction.guild) return interaction.editReply('This command must be used in a server.');
const user = interaction.options.getUser('user', true);
const member = await interaction.guild.members.fetch(user.id);
if (!member) return interaction.editReply('Could not find member.');
openModTicketModal(interaction, member);
}
};
+35 -35
View File
@@ -1,36 +1,36 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, SlashCommandBuilder } from 'discord.js';
import { config } from '../config';
import { syncName } from '../utils';
export default {
name: 'name-sync',
data: new SlashCommandBuilder()
.setName('name-sync')
.setDescription('Sync your discord nickname to your e621 name.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall)
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM)
.addIntegerOption(option =>
option
.setName('id')
.setDescription('The id of the e621 user to sync your nickname to.')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
const id = interaction.options.getInteger('id');
const guild = await interaction.client.guilds.fetch(config.DISCORD_GUILD_ID!);
if (!guild) {
return interaction.editReply('An error has occurred. Please try again later.');
}
const member = await guild.members.fetch(interaction.user.id);
if (!member || !guild.members.me) {
return interaction.editReply('An error has occurred. Please try again later.');
}
await syncName(interaction, member, id);
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, SlashCommandBuilder } from 'discord.js';
import { config } from '../config';
import { syncName } from '../utils';
export default {
name: 'name-sync',
data: new SlashCommandBuilder()
.setName('name-sync')
.setDescription('Sync your discord nickname to your e621 name.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall, ApplicationIntegrationType.UserInstall)
.setContexts(InteractionContextType.Guild, InteractionContextType.BotDM)
.addIntegerOption(option =>
option
.setName('id')
.setDescription('The id of the e621 user to sync your nickname to.')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
const id = interaction.options.getInteger('id');
const guild = await interaction.client.guilds.fetch(config.DISCORD_GUILD_ID!);
if (!guild) {
return interaction.editReply('An error has occurred. Please try again later.');
}
const member = await guild.members.fetch(interaction.user.id);
if (!member || !guild.members.me) {
return interaction.editReply('An error has occurred. Please try again later.');
}
await syncName(interaction, member, id);
}
};
+238 -238
View File
@@ -1,239 +1,239 @@
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, MessageMentions, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
import { deferInteraction, logCustomEvent, resolveUser } from '../utils';
import { getNoteMessage } from '../utils/note-utils';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'notes',
data: new SlashCommandBuilder()
.setName('notes')
.setDescription('Add, view, or remove user notes.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add notes to a user.')
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to add a note to.')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('reason')
.setDescription('The reason for the note.')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('edit')
.setDescription('Edit notes on a user.')
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to edit the notes of.')
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName('note')
.setDescription('The note to edit.')
.setRequired(true)
.setAutocomplete(true)
)
.addStringOption(option =>
option
.setName('new-reason')
.setDescription('The new reason for the note.')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove notes from a user.')
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to remove a note from.')
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName('note')
.setDescription('The note to remove.')
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription("List a user's notes")
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to list the notes of.')
.setRequired(true)
)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const subcommand = interaction.options.getSubcommand(true);
const input = interaction.options.getString('user', true);
const matches = mentionRegex.exec(input);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : input;
await deferInteraction(interaction);
const user = await resolveUser(client, idToUse, interaction.guild);
if (!user) return interaction.editReply('User not found.');
if (subcommand == 'add') {
const reason = interaction.options.getString('reason', true);
logCustomEvent(interaction.guild!, {
title: 'Note Added',
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'Moderator',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'Note',
value: reason,
inline: true
}
]
});
await Database.putNote(user.id, reason, interaction.user.id);
interaction.editReply(`Note added to <@${user.id}> (\`${user.username}\` | \`${user.id}\`).\n\nReason:\n${reason}`);
} else if (subcommand == 'remove') {
const noteId = interaction.options.getInteger('note', true);
const notes = await Database.getNotes(user.id);
const note = notes.find(n => n.id == noteId);
if (!note) return interaction.editReply('Note not found.');
logCustomEvent(interaction.guild!, {
title: 'Note Removed',
description: null,
color: 0xFF0000,
timestamp: new Date(),
fields: [
{
name: 'Moderator',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'Note',
value: `${note.reason}\nBy: <@${note.mod_id}>`,
inline: true
}
]
});
await Database.removeNote(noteId);
interaction.editReply('Removed note.');
} else if (subcommand == 'edit') {
const noteId = interaction.options.getInteger('note', true);
const notes = await Database.getNotes(user.id);
const note = notes.find(n => n.id == noteId);
if (!note) return interaction.editReply('Note not found.');
const reason = interaction.options.getString('new-reason', true);
logCustomEvent(interaction.guild!, {
title: 'Note Edited',
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'Moderator',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'Old reason',
value: note.reason
},
{
name: 'New reason',
value: reason,
inline: true
}
]
});
await Database.editNote(noteId, note.reason, reason, interaction.user.id);
interaction.editReply(`Note on <@${user.id}> (\`${user.username}\` | \`${user.id}\`) edited.\n\nNew reason:\n${reason}`);
} else if (subcommand == 'list') {
const noteMessage = await getNoteMessage(user.id, 1);
if (!noteMessage) return interaction.editReply(`No notes found for <@${user.id}> (\`${user.username}\` | \`${user.id}\`)`);
interaction.editReply(noteMessage);
}
},
autoComplete: async function (client: Client, interaction: AutocompleteInteraction) {
const input = interaction.options.getString('user', true);
const matches = mentionRegex.exec(input);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : input;
if (!idToUse) return interaction.respond([]);
const value = interaction.options.getFocused().toLowerCase();
const notes = await Database.getNotes(idToUse);
const toRespond = notes.filter(w => !value ? true : w.reason.toLowerCase().includes(value));
if (toRespond.length > 25) toRespond.length = 25;
interaction.respond(toRespond.map((w) => {
return {
name: w.reason.substring(0, 50),
value: w.id
};
}));
}
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, MessageMentions, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
import { deferInteraction, logCustomEvent, resolveUser } from '../utils';
import { getNoteMessage } from '../utils/note-utils';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'notes',
data: new SlashCommandBuilder()
.setName('notes')
.setDescription('Add, view, or remove user notes.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add notes to a user.')
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to add a note to.')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('reason')
.setDescription('The reason for the note.')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('edit')
.setDescription('Edit notes on a user.')
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to edit the notes of.')
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName('note')
.setDescription('The note to edit.')
.setRequired(true)
.setAutocomplete(true)
)
.addStringOption(option =>
option
.setName('new-reason')
.setDescription('The new reason for the note.')
.setRequired(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove notes from a user.')
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to remove a note from.')
.setRequired(true)
)
.addIntegerOption(option =>
option
.setName('note')
.setDescription('The note to remove.')
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription("List a user's notes")
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to list the notes of.')
.setRequired(true)
)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const subcommand = interaction.options.getSubcommand(true);
const input = interaction.options.getString('user', true);
const matches = mentionRegex.exec(input);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : input;
await deferInteraction(interaction);
const user = await resolveUser(client, idToUse, interaction.guild);
if (!user) return interaction.editReply('User not found.');
if (subcommand == 'add') {
const reason = interaction.options.getString('reason', true);
logCustomEvent(interaction.guild!, {
title: 'Note Added',
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'Moderator',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'Note',
value: reason,
inline: true
}
]
});
await Database.putNote(user.id, reason, interaction.user.id);
interaction.editReply(`Note added to <@${user.id}> (\`${user.username}\` | \`${user.id}\`).\n\nReason:\n${reason}`);
} else if (subcommand == 'remove') {
const noteId = interaction.options.getInteger('note', true);
const notes = await Database.getNotes(user.id);
const note = notes.find(n => n.id == noteId);
if (!note) return interaction.editReply('Note not found.');
logCustomEvent(interaction.guild!, {
title: 'Note Removed',
description: null,
color: 0xFF0000,
timestamp: new Date(),
fields: [
{
name: 'Moderator',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'Note',
value: `${note.reason}\nBy: <@${note.mod_id}>`,
inline: true
}
]
});
await Database.removeNote(noteId);
interaction.editReply('Removed note.');
} else if (subcommand == 'edit') {
const noteId = interaction.options.getInteger('note', true);
const notes = await Database.getNotes(user.id);
const note = notes.find(n => n.id == noteId);
if (!note) return interaction.editReply('Note not found.');
const reason = interaction.options.getString('new-reason', true);
logCustomEvent(interaction.guild!, {
title: 'Note Edited',
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'Moderator',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'Old reason',
value: note.reason
},
{
name: 'New reason',
value: reason,
inline: true
}
]
});
await Database.editNote(noteId, note.reason, reason, interaction.user.id);
interaction.editReply(`Note on <@${user.id}> (\`${user.username}\` | \`${user.id}\`) edited.\n\nNew reason:\n${reason}`);
} else if (subcommand == 'list') {
const noteMessage = await getNoteMessage(user.id, 1);
if (!noteMessage) return interaction.editReply(`No notes found for <@${user.id}> (\`${user.username}\` | \`${user.id}\`)`);
interaction.editReply(noteMessage);
}
},
autoComplete: async function (client: Client, interaction: AutocompleteInteraction) {
const input = interaction.options.getString('user', true);
const matches = mentionRegex.exec(input);
mentionRegex.lastIndex = 0;
const idToUse = matches ? matches.groups!.id : input;
if (!idToUse) return interaction.respond([]);
const value = interaction.options.getFocused().toLowerCase();
const notes = await Database.getNotes(idToUse);
const toRespond = notes.filter(w => !value ? true : w.reason.toLowerCase().includes(value));
if (toRespond.length > 25) toRespond.length = 25;
interaction.respond(toRespond.map((w) => {
return {
name: w.reason.substring(0, 50),
value: w.id
};
}));
}
};
+251 -251
View File
@@ -1,252 +1,252 @@
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder, User } from 'discord.js';
import { Database } from '../shared/Database';
import { TicketPhrase } from '../types';
import { logCustomEvent } from '../utils';
const MIN_PHRASE_LENGTH = 1;
const MAX_PHRASE_LENGTH = 512;
type SubcommandGroup = 'admin' | 'personal';
type Subcommand = 'add' | 'remove' | 'list' | 'dump' | 'purge';
export default {
name: 'phrases',
data: new SlashCommandBuilder()
.setName('phrases')
.setDescription('Manage notified phrases.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addSubcommandGroup(subcommandGroup =>
subcommandGroup
.setName('admin')
.setDescription('Manage admin notified phrases.')
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add an admin notification phrase.')
.addStringOption(option =>
option
.setName('phrase')
.setDescription('The phrase to add.')
.setRequired(true)
.setMinLength(MIN_PHRASE_LENGTH)
.setMaxLength(MAX_PHRASE_LENGTH)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove an admin notification phrase.')
.addNumberOption(option =>
option
.setName('phrase')
.setDescription('The phrase to remove.')
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('Get a list of the current admin notification phrases.')
)
)
.addSubcommandGroup(subcommandGroup =>
subcommandGroup
.setName('personal')
.setDescription('Manage personal notified phrases.')
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add a personal notification phrase.')
.addStringOption(option =>
option
.setName('phrase')
.setDescription('The phrase to add.')
.setRequired(true)
.setMinLength(MIN_PHRASE_LENGTH)
.setMaxLength(MAX_PHRASE_LENGTH)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove a personal notification phrase.')
.addNumberOption(option =>
option
.setName('phrase')
.setDescription('The phrase to remove.')
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('Get a list of the current personal notification phrases.')
)
)
.addSubcommand(subcommand =>
subcommand
.setName('dump')
.setDescription('List all notification phrases.')
)
.addSubcommand(subcommand =>
subcommand
.setName('purge')
.setDescription("Purge a user's phrases.")
.addUserOption(option =>
option
.setName('user')
.setDescription('The user to purge the phrases of.')
.setRequired(true)
)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const subcommandGroup: SubcommandGroup | null = interaction.options.getSubcommandGroup() as SubcommandGroup;
const subcommand: Subcommand | null = interaction.options.getSubcommand() as Subcommand;
switch (subcommand) {
case 'add':
return addPhrase(interaction, interaction.options.getString('phrase', true), subcommandGroup!);
case 'remove':
return removePhrase(interaction, interaction.options.getNumber('phrase', true), subcommandGroup!);
case 'list':
return listPhrases(interaction, subcommandGroup!);
case 'dump':
return dumpPhrases(interaction);
case 'purge':
return purgePhrases(interaction, interaction.options.getUser('user', true));
}
},
autoComplete: async function (client: Client, interaction: AutocompleteInteraction) {
const subcommandGroup: SubcommandGroup | null = interaction.options.getSubcommandGroup() as SubcommandGroup;
if (!subcommandGroup) return interaction.respond([]);
const value = interaction.options.getFocused();
const phrases: TicketPhrase[] = await Database.getTicketPhrasesFor(subcommandGroup == 'admin' ? 'admin' : interaction.user.id);
const toRespond = phrases.filter(p => !value ? true : p.phrase.includes(value));
if (toRespond.length > 25) toRespond.length = 25;
interaction.respond(toRespond.map(p => ({
name: p.phrase,
value: p.id
})));
}
};
async function purgePhrases(interaction: ChatInputCommandInteraction, user: User) {
const phrases: TicketPhrase[] = await Database.getTicketPhrasesFor(user.id);
const count = await Database.removeAllTicketPhrasesFor(user.id);
logCustomEvent(interaction.guild!, {
title: 'Ticket Phrases Purged',
description: null,
color: 0xFF0000,
timestamp: new Date(),
fields: [
{
name: 'User',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Target User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'Count',
value: count.toString(),
inline: true
}
]
});
interaction.reply(`Purged the following phrases (${count}):\n${phrases.map(p => `- \`${p.phrase}\``).join('\n')}`);
}
async function dumpPhrases(interaction: ChatInputCommandInteraction) {
let content = '';
const guildSettings = await Database.getGuildSettings(interaction.guildId!);
await Database.getAllTicketPhrases((phrase: TicketPhrase) => {
if (phrase.user_id == 'admin' && (!guildSettings || !guildSettings.admin_role_id)) return;
const mention = phrase.user_id == 'admin' ? `<@&${guildSettings?.admin_role_id}>` : `<@${phrase.user_id}>`;
content += `${mention}: \`${phrase.phrase}\`\n`;
});
if (content.length == 0) return interaction.reply('No phrases found.');
interaction.reply('The following phrases are registered:\n\n' + content);
}
async function addPhrase(interaction: ChatInputCommandInteraction, phrase: string, group: SubcommandGroup) {
await Database.putTicketPhrase(group == 'admin' ? 'admin' : interaction.user.id, phrase);
logCustomEvent(interaction.guild!, {
title: `${group == 'admin' ? 'Admin ' : ''}Ticket Phrase Added`,
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'User',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Phrase',
value: phrase,
inline: true
}
]
});
interaction.reply(`Phrases matching "${phrase}" will now alert ${group == 'admin' ? 'admins' : 'you'}.`);
}
async function removePhrase(interaction: ChatInputCommandInteraction, phraseId: number, group: SubcommandGroup) {
const phrase = await Database.getTicketPhrase(phraseId);
if (!phrase) return interaction.reply('Phrase not found');
await Database.removeTicketPhrase(phraseId);
logCustomEvent(interaction.guild!, {
title: `${group == 'admin' ? 'Admin ' : ''}Ticket Phrase Removed`,
description: null,
color: 0xFF0000,
timestamp: new Date(),
fields: [
{
name: 'User',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Phrase',
value: phrase.phrase,
inline: true
}
]
});
interaction.reply(`Phrase will no longer alert ${group == 'admin' ? 'admins' : 'you'}.`);
}
async function listPhrases(interaction: ChatInputCommandInteraction, group: SubcommandGroup) {
const phrases = await Database.getTicketPhrasesFor(group == 'admin' ? 'admin' : interaction.user.id);
if (phrases.length == 0) return interaction.reply('No phrases registered');
interaction.reply(`The following phrases are registered:\n\n${phrases.map(p => (`- \`${p.phrase}\``)).join('\n')}`);
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder, User } from 'discord.js';
import { Database } from '../shared/Database';
import { TicketPhrase } from '../types';
import { logCustomEvent } from '../utils';
const MIN_PHRASE_LENGTH = 1;
const MAX_PHRASE_LENGTH = 512;
type SubcommandGroup = 'admin' | 'personal';
type Subcommand = 'add' | 'remove' | 'list' | 'dump' | 'purge';
export default {
name: 'phrases',
data: new SlashCommandBuilder()
.setName('phrases')
.setDescription('Manage notified phrases.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addSubcommandGroup(subcommandGroup =>
subcommandGroup
.setName('admin')
.setDescription('Manage admin notified phrases.')
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add an admin notification phrase.')
.addStringOption(option =>
option
.setName('phrase')
.setDescription('The phrase to add.')
.setRequired(true)
.setMinLength(MIN_PHRASE_LENGTH)
.setMaxLength(MAX_PHRASE_LENGTH)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove an admin notification phrase.')
.addNumberOption(option =>
option
.setName('phrase')
.setDescription('The phrase to remove.')
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('Get a list of the current admin notification phrases.')
)
)
.addSubcommandGroup(subcommandGroup =>
subcommandGroup
.setName('personal')
.setDescription('Manage personal notified phrases.')
.addSubcommand(subcommand =>
subcommand
.setName('add')
.setDescription('Add a personal notification phrase.')
.addStringOption(option =>
option
.setName('phrase')
.setDescription('The phrase to add.')
.setRequired(true)
.setMinLength(MIN_PHRASE_LENGTH)
.setMaxLength(MAX_PHRASE_LENGTH)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('remove')
.setDescription('Remove a personal notification phrase.')
.addNumberOption(option =>
option
.setName('phrase')
.setDescription('The phrase to remove.')
.setRequired(true)
.setAutocomplete(true)
)
)
.addSubcommand(subcommand =>
subcommand
.setName('list')
.setDescription('Get a list of the current personal notification phrases.')
)
)
.addSubcommand(subcommand =>
subcommand
.setName('dump')
.setDescription('List all notification phrases.')
)
.addSubcommand(subcommand =>
subcommand
.setName('purge')
.setDescription("Purge a user's phrases.")
.addUserOption(option =>
option
.setName('user')
.setDescription('The user to purge the phrases of.')
.setRequired(true)
)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const subcommandGroup: SubcommandGroup | null = interaction.options.getSubcommandGroup() as SubcommandGroup;
const subcommand: Subcommand | null = interaction.options.getSubcommand() as Subcommand;
switch (subcommand) {
case 'add':
return addPhrase(interaction, interaction.options.getString('phrase', true), subcommandGroup!);
case 'remove':
return removePhrase(interaction, interaction.options.getNumber('phrase', true), subcommandGroup!);
case 'list':
return listPhrases(interaction, subcommandGroup!);
case 'dump':
return dumpPhrases(interaction);
case 'purge':
return purgePhrases(interaction, interaction.options.getUser('user', true));
}
},
autoComplete: async function (client: Client, interaction: AutocompleteInteraction) {
const subcommandGroup: SubcommandGroup | null = interaction.options.getSubcommandGroup() as SubcommandGroup;
if (!subcommandGroup) return interaction.respond([]);
const value = interaction.options.getFocused();
const phrases: TicketPhrase[] = await Database.getTicketPhrasesFor(subcommandGroup == 'admin' ? 'admin' : interaction.user.id);
const toRespond = phrases.filter(p => !value ? true : p.phrase.includes(value));
if (toRespond.length > 25) toRespond.length = 25;
interaction.respond(toRespond.map(p => ({
name: p.phrase,
value: p.id
})));
}
};
async function purgePhrases(interaction: ChatInputCommandInteraction, user: User) {
const phrases: TicketPhrase[] = await Database.getTicketPhrasesFor(user.id);
const count = await Database.removeAllTicketPhrasesFor(user.id);
logCustomEvent(interaction.guild!, {
title: 'Ticket Phrases Purged',
description: null,
color: 0xFF0000,
timestamp: new Date(),
fields: [
{
name: 'User',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Target User',
value: `<@${user.id}>\n${user.username}`,
inline: true
},
{
name: 'Count',
value: count.toString(),
inline: true
}
]
});
interaction.reply(`Purged the following phrases (${count}):\n${phrases.map(p => `- \`${p.phrase}\``).join('\n')}`);
}
async function dumpPhrases(interaction: ChatInputCommandInteraction) {
let content = '';
const guildSettings = await Database.getGuildSettings(interaction.guildId!);
await Database.getAllTicketPhrases((phrase: TicketPhrase) => {
if (phrase.user_id == 'admin' && (!guildSettings || !guildSettings.admin_role_id)) return;
const mention = phrase.user_id == 'admin' ? `<@&${guildSettings?.admin_role_id}>` : `<@${phrase.user_id}>`;
content += `${mention}: \`${phrase.phrase}\`\n`;
});
if (content.length == 0) return interaction.reply('No phrases found.');
interaction.reply('The following phrases are registered:\n\n' + content);
}
async function addPhrase(interaction: ChatInputCommandInteraction, phrase: string, group: SubcommandGroup) {
await Database.putTicketPhrase(group == 'admin' ? 'admin' : interaction.user.id, phrase);
logCustomEvent(interaction.guild!, {
title: `${group == 'admin' ? 'Admin ' : ''}Ticket Phrase Added`,
description: null,
color: 0x00FF00,
timestamp: new Date(),
fields: [
{
name: 'User',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Phrase',
value: phrase,
inline: true
}
]
});
interaction.reply(`Phrases matching "${phrase}" will now alert ${group == 'admin' ? 'admins' : 'you'}.`);
}
async function removePhrase(interaction: ChatInputCommandInteraction, phraseId: number, group: SubcommandGroup) {
const phrase = await Database.getTicketPhrase(phraseId);
if (!phrase) return interaction.reply('Phrase not found');
await Database.removeTicketPhrase(phraseId);
logCustomEvent(interaction.guild!, {
title: `${group == 'admin' ? 'Admin ' : ''}Ticket Phrase Removed`,
description: null,
color: 0xFF0000,
timestamp: new Date(),
fields: [
{
name: 'User',
value: `<@${interaction.user.id}>\n${interaction.user.username}`,
inline: true
},
{
name: 'Phrase',
value: phrase.phrase,
inline: true
}
]
});
interaction.reply(`Phrase will no longer alert ${group == 'admin' ? 'admins' : 'you'}.`);
}
async function listPhrases(interaction: ChatInputCommandInteraction, group: SubcommandGroup) {
const phrases = await Database.getTicketPhrasesFor(group == 'admin' ? 'admin' : interaction.user.id);
if (phrases.length == 0) return interaction.reply('No phrases registered');
interaction.reply(`The following phrases are registered:\n\n${phrases.map(p => (`- \`${p.phrase}\``)).join('\n')}`);
}
+44 -44
View File
@@ -1,45 +1,45 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { deferInteraction } from '../utils';
import { getRecordMessageFromDiscordId } from '../utils/record-utils';
export default {
name: 'records',
data: new SlashCommandBuilder()
.setName('records')
.setDescription("Get a user's on-site records.")
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addUserOption(option =>
option
.setName('user')
.setDescription('The discord user to find the e621 user of.')
.setRequired(false)
)
.addStringOption(option =>
option
.setName('id')
.setDescription('The discord user id to find the e621 user of.')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await deferInteraction(interaction);
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
const user = interaction.options.getUser('user');
const id = interaction.options.getString('id');
if (!user && !id) {
return interaction.reply({ content: 'No user or id given.', flags: [MessageFlags.Ephemeral] });
}
const idToUse = (user?.id ?? id) as string;
const recordMessage = await getRecordMessageFromDiscordId(idToUse, 1, interaction.guild);
if (!recordMessage) return interaction.editReply('No records found on any linked accounts.');
interaction.editReply(recordMessage);
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { deferInteraction } from '../utils';
import { getRecordMessageFromDiscordId } from '../utils/record-utils';
export default {
name: 'records',
data: new SlashCommandBuilder()
.setName('records')
.setDescription("Get a user's on-site records.")
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addUserOption(option =>
option
.setName('user')
.setDescription('The discord user to find the e621 user of.')
.setRequired(false)
)
.addStringOption(option =>
option
.setName('id')
.setDescription('The discord user id to find the e621 user of.')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await deferInteraction(interaction);
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
const user = interaction.options.getUser('user');
const id = interaction.options.getString('id');
if (!user && !id) {
return interaction.reply({ content: 'No user or id given.', flags: [MessageFlags.Ephemeral] });
}
const idToUse = (user?.id ?? id) as string;
const recordMessage = await getRecordMessageFromDiscordId(idToUse, 1, interaction.guild);
if (!recordMessage) return interaction.editReply('No records found on any linked accounts.');
interaction.editReply(recordMessage);
}
};
+50 -50
View File
@@ -1,51 +1,51 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, PermissionFlagsBits, RateLimitError, SlashCommandBuilder } from 'discord.js';
import { msToHuman } from '../utils';
import { Database } from '../shared/Database';
export default {
name: 'rename',
data: new SlashCommandBuilder()
.setName('rename')
.setDescription('Rename the general channel.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addStringOption(option =>
option
.setName('new-name')
.setDescription('The new name of the general channel.')
.setRequired(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const guildSettings = await Database.getGuildSettings(interaction.guildId!);
if (!guildSettings || !guildSettings.general_chat_id) {
return interaction.reply('No general chat id found.');
}
const name = interaction.options.getString('new-name', true);
if (name.length > 100) {
return interaction.reply('Name must be less than 100 characters in length.');
}
const channel = await interaction.guild!.channels.fetch(guildSettings.general_chat_id)!;
if (!channel) {
return interaction.reply('No general chat id found.');
}
try {
await channel.setName(name);
interaction.reply(`Renamed general to ${channel.name}`);
} catch (e: any) {
if (e instanceof RateLimitError) {
return interaction.reply(`Name change limited. Try again in ${msToHuman(e.retryAfter)}`);
}
console.error(e);
return interaction.reply('An error has occurred.');
}
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, PermissionFlagsBits, RateLimitError, SlashCommandBuilder } from 'discord.js';
import { msToHuman } from '../utils';
import { Database } from '../shared/Database';
export default {
name: 'rename',
data: new SlashCommandBuilder()
.setName('rename')
.setDescription('Rename the general channel.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addStringOption(option =>
option
.setName('new-name')
.setDescription('The new name of the general channel.')
.setRequired(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const guildSettings = await Database.getGuildSettings(interaction.guildId!);
if (!guildSettings || !guildSettings.general_chat_id) {
return interaction.reply('No general chat id found.');
}
const name = interaction.options.getString('new-name', true);
if (name.length > 100) {
return interaction.reply('Name must be less than 100 characters in length.');
}
const channel = await interaction.guild!.channels.fetch(guildSettings.general_chat_id)!;
if (!channel) {
return interaction.reply('No general chat id found.');
}
try {
await channel.setName(name);
interaction.reply(`Renamed general to ${channel.name}`);
} catch (e: any) {
if (e instanceof RateLimitError) {
return interaction.reply(`Name change limited. Try again in ${msToHuman(e.retryAfter)}`);
}
console.error(e);
return interaction.reply('An error has occurred.');
}
}
};
+80 -80
View File
@@ -1,81 +1,81 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, GuildMember, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { deferInteraction } from '../utils';
export default {
name: 'softban',
data: new SlashCommandBuilder()
.setName('softban')
.setDescription('Bans and immediately unbans a user to purge messages.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
.addUserOption(option =>
option
.setName('user')
.setDescription('The discord user to softban.')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('reason')
.setDescription('The reason for the softban')
.setRequired(false)
.setMaxLength(400)
)
.addNumberOption(option =>
option
.setName('days')
.setDescription('How far back to delete messages (in days, default: 7 days).')
.setRequired(false)
.setMinValue(0)
.setMaxValue(7)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await deferInteraction(interaction);
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
if (!interaction.guild.members.me) return interaction.editReply('An error has occurred. Please try again later.');
const user = interaction.options.getUser('user', true);
const reason = interaction.options.getString('reason') ?? '';
const seconds = (interaction.options.getNumber('days') ?? 7) * 86400;
let banMember: GuildMember | null = null;
try {
banMember = await interaction.guild.members.fetch(user.id);
} catch (e) {
// Member not in server.
}
const member = await interaction.guild.members.fetch(interaction.user.id);
if (banMember && member.roles.highest.comparePositionTo(banMember.roles.highest) <= 0) {
return await interaction.editReply('You do not have permission to softban this user.');
}
if (banMember && !banMember.bannable) {
return await interaction.editReply('I do not have permission to softban this user.');
}
try {
await interaction.guild.bans.create(user, {
reason: (reason + ` Softban by ${interaction.user.username} (${interaction.user.id})`).trim(),
deleteMessageSeconds: seconds
});
} catch (e) {
console.error(e);
return await interaction.editReply("Error softbanning user (couldn't ban).");
}
try {
await interaction.guild.bans.remove(user);
} catch (e) {
console.error(e);
return await interaction.editReply("Error softbanning user (couldn't remove ban).");
}
await interaction.editReply('Softban successful');
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, GuildMember, InteractionContextType, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { deferInteraction } from '../utils';
export default {
name: 'softban',
data: new SlashCommandBuilder()
.setName('softban')
.setDescription('Bans and immediately unbans a user to purge messages.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.KickMembers)
.addUserOption(option =>
option
.setName('user')
.setDescription('The discord user to softban.')
.setRequired(true)
)
.addStringOption(option =>
option
.setName('reason')
.setDescription('The reason for the softban')
.setRequired(false)
.setMaxLength(400)
)
.addNumberOption(option =>
option
.setName('days')
.setDescription('How far back to delete messages (in days, default: 7 days).')
.setRequired(false)
.setMinValue(0)
.setMaxValue(7)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
await deferInteraction(interaction);
if (!interaction.guild) return interaction.editReply('This command must be used in a server');
if (!interaction.guild.members.me) return interaction.editReply('An error has occurred. Please try again later.');
const user = interaction.options.getUser('user', true);
const reason = interaction.options.getString('reason') ?? '';
const seconds = (interaction.options.getNumber('days') ?? 7) * 86400;
let banMember: GuildMember | null = null;
try {
banMember = await interaction.guild.members.fetch(user.id);
} catch (e) {
// Member not in server.
}
const member = await interaction.guild.members.fetch(interaction.user.id);
if (banMember && member.roles.highest.comparePositionTo(banMember.roles.highest) <= 0) {
return await interaction.editReply('You do not have permission to softban this user.');
}
if (banMember && !banMember.bannable) {
return await interaction.editReply('I do not have permission to softban this user.');
}
try {
await interaction.guild.bans.create(user, {
reason: (reason + ` Softban by ${interaction.user.username} (${interaction.user.id})`).trim(),
deleteMessageSeconds: seconds
});
} catch (e) {
console.error(e);
return await interaction.editReply("Error softbanning user (couldn't ban).");
}
try {
await interaction.guild.bans.remove(user);
} catch (e) {
console.error(e);
return await interaction.editReply("Error softbanning user (couldn't remove ban).");
}
await interaction.editReply('Softban successful');
}
};
+29 -29
View File
@@ -1,30 +1,30 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, GuildBasedChannel, InteractionContextType, MessageMentions, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { channelIsInStaffCategory, handleWhoIsInteraction } from '../utils';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'whois',
data: new SlashCommandBuilder()
.setName('whois')
.setDescription("Find a user's e621 account from their discord account, or vice versa.")
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to find the e621 user of.')
.setRequired(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const input = interaction.options.getString('user', true);
const matches = mentionRegex.exec(input);
mentionRegex.lastIndex = 0;
const valueToUse = matches ? matches.groups!.id : input;
handleWhoIsInteraction(interaction, valueToUse, !(await channelIsInStaffCategory(interaction.channel as GuildBasedChannel)));
}
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, GuildBasedChannel, InteractionContextType, MessageMentions, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { channelIsInStaffCategory, handleWhoIsInteraction } from '../utils';
const mentionRegex = new RegExp(MessageMentions.UsersPattern);
export default {
name: 'whois',
data: new SlashCommandBuilder()
.setName('whois')
.setDescription("Find a user's e621 account from their discord account, or vice versa.")
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)
.addStringOption(option =>
option
.setName('user')
.setDescription('The discord user mention, or ID, to find the e621 user of.')
.setRequired(true)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const input = interaction.options.getString('user', true);
const matches = mentionRegex.exec(input);
mentionRegex.lastIndex = 0;
const valueToUse = matches ? matches.groups!.id : input;
handleWhoIsInteraction(interaction, valueToUse, !(await channelIsInStaffCategory(interaction.channel as GuildBasedChannel)));
}
};