Initial commit.

This commit is contained in:
Tarrgon
2025-05-28 10:44:41 -04:00
commit e0efb86dce
50 changed files with 8579 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
import { getE621User } from '../utils';
import { config } from '../config';
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('username')
.setDescription('The e621 username to find the discord user of.')
.setRequired(false)
)
.addStringOption(option =>
option
.setName('id')
.setDescription('The e621 user id to find the discord user of.')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
const username = interaction.options.getString('username');
const id = interaction.options.getString('id');
if (!username && !id) {
return interaction.reply({ content: 'No username or id given.', flags: [MessageFlags.Ephemeral] });
}
try {
const e621User = await getE621User((id ?? username) as string);
if (!e621User) {
return interaction.reply('I got lost along the way. Who again?');
}
const results = await Database.getDiscordIds(e621User.id);
const mappedResults = results.map(id => `- <@${id}>\n`);
interaction.reply(`[${e621User.name}](${config.E621_BASE_URL}/users/${e621User.id})<${e621User.id}>'s discord account(s):\n${mappedResults}`);
} catch (e) {
console.error(e);
interaction.reply('I got lost in the net.');
}
}
};
+52
View File
@@ -0,0 +1,52 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
import { getE621User } from '../utils';
import { config } from '../config';
import { E621User } from '../types';
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) {
const id = interaction.options.getInteger('id');
const availableIds = await Database.getE621Ids(interaction.user.id);
let e621User: E621User | null;
if (!id || !availableIds.includes(id)) {
e621User = await getE621User(availableIds[0]);
} else {
e621User = await getE621User(id);
}
if (!e621User) {
return interaction.reply({ content: "Couldn't figure out what your name was. Please contact an administrator.", flags: [MessageFlags.Ephemeral] });
}
const guild = await client.guilds.fetch(config.DISCORD_GUILD_ID!);
if (!guild) {
return interaction.reply({ content: 'An error has occurred. Please try again later', flags: [MessageFlags.Ephemeral] });
}
const member = await guild.members.fetch(interaction.user.id);
if (!member) {
return interaction.reply({ content: 'An error has occurred. Please try again later', flags: [MessageFlags.Ephemeral] });
}
await member.setNickname(e621User.name);
interaction.reply({ content: `Nickname set to: ${e621User.name}`, flags: [MessageFlags.Ephemeral] });
}
};
+165
View File
@@ -0,0 +1,165 @@
import { ApplicationIntegrationType, AutocompleteInteraction, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
import { getE621User } from '../utils';
import { config } from '../config';
import { TicketPhrase } from '../types';
const MIN_PHRASE_LENGTH = 1;
const MAX_PHRASE_LENGTH = 512;
type SubcommandGroup = 'admin' | 'personal';
type Subcommand = 'add' | 'remove' | 'list' | 'dump';
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.')
),
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;
if (subcommand == 'dump') {
return dumpPhrases(interaction);
}
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!);
}
},
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 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.addTicketPhrase(group == 'admin' ? 'admin' : interaction.user.id, phrase);
interaction.reply(`Phrases matching "${phrase}" will now alert ${group == 'admin' ? 'admins' : 'you'}.`);
}
async function removePhrase(interaction: ChatInputCommandInteraction, phraseId: number, group: SubcommandGroup) {
await Database.removeTicketPhrase(phraseId);
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')}`);
}
+42
View File
@@ -0,0 +1,42 @@
import { ActionRow, ActionRowBuilder, ApplicationIntegrationType, ButtonBuilder, ButtonStyle, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, RateLimitError, SlashCommandBuilder } from 'discord.js';
export default {
name: 'private-help',
data: new SlashCommandBuilder()
.setName('private-help')
.setDescription('Setup a private help button.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.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') ?? 'Get in contact';
const button = new ButtonBuilder()
.setCustomId('private-help')
.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.' });
}
};
+46
View File
@@ -0,0 +1,46 @@
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);
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.');
}
}
};
+160
View File
@@ -0,0 +1,160 @@
import { ApplicationIntegrationType, ChannelType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, RateLimitError, SlashCommandBuilder } from 'discord.js';
import { msToHuman } from '../utils';
import { Database } from '../shared/Database';
export default {
name: 'settings',
data: new SlashCommandBuilder()
.setName('settings')
.setDescription('Change server settings.')
.setIntegrationTypes(ApplicationIntegrationType.GuildInstall)
.setContexts(InteractionContextType.Guild)
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.addChannelOption(option =>
option
.setName('general-channel')
.setDescription('Set the general channel.')
.setRequired(false)
)
.addChannelOption(option =>
option
.setName('tickets-channel')
.setDescription('Set the ticket logs channel.')
.setRequired(false)
)
.addChannelOption(option =>
option
.setName('message-logs-channel')
.setDescription('Set the message logs channel.')
.setRequired(false)
)
.addChannelOption(option =>
option
.setName('audit-logs-channel')
.setDescription('Set the audit logs channel.')
.setRequired(false)
)
.addChannelOption(option =>
option
.setName('voice-logs-channel')
.setDescription('Set the voice logs channel.')
.setRequired(false)
)
.addRoleOption(option =>
option
.setName('admin-role')
.setDescription('Set the admin role.')
.setRequired(false)
)
.addRoleOption(option =>
option
.setName('private-helper-role')
.setDescription('Set the private helper role.')
.setRequired(false)
)
.addChannelOption(option =>
option
.setName('add-staff-category')
.setDescription('Add a category to staff categories.')
.setRequired(false)
)
.addChannelOption(option =>
option
.setName('remove-staff-category')
.setDescription('Remove a category from staff categories.')
.setRequired(false)
),
handler: async function (client: Client, interaction: ChatInputCommandInteraction) {
let response = '';
const settings = await Database.getGuildSettings(interaction.guildId!);
if (!settings) {
await Database.addGuild(interaction.guildId!);
}
const generalChannel = interaction.options.getChannel('general-channel');
if (generalChannel) {
await Database.setGuildGeneralChatId(interaction.guildId!, generalChannel.id);
response += `General channel set to ${generalChannel}.\n`;
}
const ticketsChannel = interaction.options.getChannel('tickets-channel');
if (ticketsChannel) {
await Database.setGuildTicketsLogsChannelId(interaction.guildId!, ticketsChannel.id);
response += `Tickets logs channel set to ${ticketsChannel}.\n`;
}
const messageLogsChannel = interaction.options.getChannel('message-logs-channel');
if (messageLogsChannel) {
await Database.setGuildEventsLogsChannelId(interaction.guildId!, messageLogsChannel.id);
response += `Message logs channel set to ${messageLogsChannel}.\n`;
}
const auditLogsChannel = interaction.options.getChannel('audit-logs-channel');
if (auditLogsChannel) {
await Database.setGuildAuditLogsChannelId(interaction.guildId!, auditLogsChannel.id);
response += `Audit logs channel set to ${auditLogsChannel}.\n`;
}
const voiceLogsChannel = interaction.options.getChannel('voice-logs-channel');
if (voiceLogsChannel) {
await Database.setGuildVoiceLogsChannelId(interaction.guildId!, voiceLogsChannel.id);
response += `Voice logs channel set to ${voiceLogsChannel}.\n`;
}
const adminRole = interaction.options.getRole('admin-role');
if (adminRole) {
await Database.setGuildAdminRole(interaction.guildId!, adminRole.id);
response += `Admin role set to ${adminRole}.\n`;
}
const privateHelperRole = interaction.options.getRole('private-helper-role');
if (privateHelperRole) {
await Database.setGuildPrivateHelperRole(interaction.guildId!, privateHelperRole.id);
response += `Private helper role set to ${privateHelperRole}.\n`;
}
const addCategory = interaction.options.getChannel('add-staff-category');
if (addCategory) {
if (addCategory.type == ChannelType.GuildCategory) {
await Database.addGuildStaffCategory(interaction.guildId!, addCategory.id);
response += `Added ${addCategory.toString()} as a staff category.\n`;
} else {
response += `Error adding staff category: ${addCategory.toString()} isn't a category.`;
}
}
const removeCategory = interaction.options.getChannel('remove-staff-category');
if (removeCategory) {
if (removeCategory.type == ChannelType.GuildCategory) {
if (await Database.removeGuildStaffCategory(interaction.guildId!, removeCategory.id)) {
response += `Removed ${removeCategory.toString()} as a staff category\n`;
} else {
response += `Error removing staff category: ${removeCategory.toString()} isn't a staff category.`;
}
} else {
response += `Error removing staff category: ${removeCategory.toString()} isn't a category.`;
}
}
if (response.length == 0) return interaction.reply({ content: 'No settings provided.', flags: [MessageFlags.Ephemeral] });
interaction.reply({ content: response, flags: [MessageFlags.Ephemeral] });
}
};
+63
View File
@@ -0,0 +1,63 @@
import { ApplicationIntegrationType, ChatInputCommandInteraction, Client, InteractionContextType, MessageFlags, PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
import { Database } from '../shared/Database';
import { getE621User } from '../utils';
import { config } from '../config';
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)
.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) {
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 results = await Database.getE621Ids(idToUse);
if (results.length > 0) {
const mappedResults = results.map(e => `- ${config.E621_BASE_URL}/users/${e}\n`);
const alts: string[] = [];
for (const e621Id of results) {
const discordIds = await Database.getDiscordIds(e621Id);
for (const discordId of discordIds) {
if (discordId != idToUse && !alts.includes(discordId)) alts.push(discordId);
}
}
let content = `<@${idToUse}>'s e621 account(s):\n${mappedResults}`;
if (alts.length > 0) {
const mappedAlts = alts.map(id => `- <@${id}>\n`);
content += `\n\nDiscord alts found:\n${mappedAlts}`;
}
interaction.reply(content);
} else {
interaction.reply('No e621 accounts found for this user');
}
}
};