386 lines
11 KiB
TypeScript
386 lines
11 KiB
TypeScript
import { ButtonInteraction, Client, CommandInteraction, GuildMember, Interaction, Message, MessageActionRow, MessageButton, MessageSelectMenu, MessageSelectOptionData, PartialDMChannel, SelectMenuInteraction, TextBasedChannels } from "discord.js";
|
|
import { TextChannel } from "discord.js";
|
|
import * as Opt from "../types/Opt";
|
|
import refreshCommands from "./slash";
|
|
|
|
import { InteractionRoles, GuildId } from "./data";
|
|
|
|
type ReplyableInteraction = ButtonInteraction | CommandInteraction | SelectMenuInteraction;
|
|
|
|
const isReplyableInteraction = (res: any): boolean => res instanceof ButtonInteraction || res instanceof CommandInteraction || res instanceof SelectMenuInteraction;
|
|
|
|
const sendMessage = (res: ReplyableInteraction | TextBasedChannels, msg: any, ephemeral?: boolean) => {
|
|
|
|
let arg: any = {};
|
|
if (typeof msg === 'object')
|
|
arg = msg;
|
|
else
|
|
arg.content = msg;
|
|
|
|
if (isReplyableInteraction(res) && ephemeral)
|
|
arg.ephemeral = true;
|
|
|
|
if (isReplyableInteraction(res))
|
|
(res as ReplyableInteraction).reply(arg);
|
|
else
|
|
(res as TextBasedChannels).send(arg);
|
|
}
|
|
|
|
//initialize discord
|
|
const Discord = require('discord.js'),
|
|
client: Client = new Discord.Client({ intents: [
|
|
'GUILD_MEMBERS'
|
|
] });
|
|
|
|
/**
|
|
* send message containing menu
|
|
* @param res text channel to send message or interaction to reply to
|
|
* @param menuOpts menu data object
|
|
*/
|
|
function sendMenu(res: ReplyableInteraction|TextBasedChannels, menuOpts: Opt.Menu, ephemeral?: boolean) {
|
|
|
|
let row = new MessageActionRow()
|
|
|
|
let select = new MessageSelectMenu()
|
|
.setCustomId(menuOpts.id)
|
|
.setPlaceholder(menuOpts.palceholder)
|
|
.setMaxValues(menuOpts.max === 'all' ? menuOpts.options.length : menuOpts.max)
|
|
.setMinValues(0);
|
|
|
|
let options: MessageSelectOptionData[] = [];
|
|
|
|
for (let i = 0; i < menuOpts.options.length; i++) {
|
|
|
|
let opts: Opt.MenuOptions = menuOpts.options[i];
|
|
|
|
let option: MessageSelectOptionData = {
|
|
label: opts.label,
|
|
value: opts.value
|
|
};
|
|
|
|
if (opts.emoji && opts.emoji.length)
|
|
option.emoji = opts.emoji;
|
|
|
|
if (opts.description && opts.description.length)
|
|
option.description = opts.description;
|
|
|
|
if (opts.default)
|
|
option.default = true;
|
|
|
|
options.push(option);
|
|
|
|
}
|
|
|
|
select.addOptions(options);
|
|
row.addComponents(select)
|
|
|
|
let arg = {
|
|
content: menuOpts.message,
|
|
components: [row]
|
|
};
|
|
|
|
sendMessage(res, arg, ephemeral);
|
|
|
|
}
|
|
|
|
/**
|
|
* send message containing row
|
|
* @param c text channel to send message
|
|
* @param rowOpts row data object
|
|
*/
|
|
function sendRow(res: ReplyableInteraction|TextBasedChannels, rowOpts: Opt.Row, ephemeral?: boolean) {
|
|
|
|
let row = new MessageActionRow();
|
|
|
|
let options: MessageButton[] = [];
|
|
|
|
for (let i = 0; i < rowOpts.buttons.length; i++) {
|
|
|
|
let opts: Opt.RowButton = rowOpts.buttons[i];
|
|
|
|
let option = new MessageButton()
|
|
.setStyle(opts.style)
|
|
.setLabel(opts.label)
|
|
.setCustomId(opts.id);
|
|
|
|
options.push(option);
|
|
|
|
}
|
|
|
|
row.addComponents(options);
|
|
|
|
let arg = {
|
|
content: rowOpts.message,
|
|
components: [row]
|
|
};
|
|
|
|
sendMessage(res, arg, ephemeral);
|
|
|
|
}
|
|
|
|
//on interaction event
|
|
client.on('interactionCreate', (interaction: Interaction) => {
|
|
|
|
if (interaction.inGuild()) {
|
|
|
|
if (interaction.isSelectMenu())
|
|
handleSelect(interaction as SelectMenuInteraction);
|
|
|
|
else if (interaction.isButton())
|
|
handleButton(interaction as ButtonInteraction);
|
|
|
|
else if (interaction.isCommand())
|
|
handleCommand(interaction as CommandInteraction);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
async function handleButton(interaction: ButtonInteraction) {
|
|
|
|
//start typing
|
|
await interaction.deferReply({ ephemeral: true });
|
|
|
|
//get custom id
|
|
let customId: string;
|
|
if (interaction.component instanceof MessageButton)
|
|
customId = interaction.component.customId;
|
|
else if ('custom_id' in interaction.component)
|
|
customId = interaction.component.custom_id;
|
|
|
|
//list of button rows from data
|
|
let rows = InteractionRoles.filter(opt => opt.type === 'row');
|
|
|
|
//loop through each the buttons rows
|
|
for (let i = 0; i < rows.length; i++) {
|
|
|
|
let row = rows[i] as Opt.Row;
|
|
|
|
//filter to just buttons with matching id
|
|
let matchingButtons = row.buttons.filter(opt => opt.id === customId);
|
|
|
|
if (matchingButtons.length) {
|
|
|
|
//if there's no role id, then nothing can be done
|
|
if (matchingButtons[0].roleid && interaction.member instanceof GuildMember) {
|
|
|
|
//since it is a GuildMember, let's force typescript to rememeber it
|
|
let member = interaction.member as GuildMember;
|
|
|
|
//get corresponding role
|
|
let role = await interaction.guild.roles.fetch(matchingButtons[0].roleid);
|
|
|
|
//if clicker (member) does have the role
|
|
if (member.roles.cache.some(r => r.id === role.id)) {
|
|
|
|
//remove role
|
|
await member.roles.remove(role.id);
|
|
interaction.editReply(`Removed \`${role.name}\``);
|
|
|
|
} else {
|
|
|
|
//add role
|
|
await member.roles.add(role.id);
|
|
interaction.editReply(`Added \`${role.name}\``);
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
//on menu click
|
|
async function handleSelect(interaction: SelectMenuInteraction) {
|
|
|
|
//start typing
|
|
await interaction.deferReply({ ephemeral: true });
|
|
|
|
//get custom id
|
|
let customId: string;
|
|
if (interaction.component instanceof MessageSelectMenu)
|
|
customId = interaction.component.customId;
|
|
else if ('custom_id' in interaction.component)
|
|
customId = interaction.component.custom_id;
|
|
|
|
//list of menus from data with matching id
|
|
let menus = InteractionRoles.filter(opt => opt.type === 'menu' && opt.id === customId);
|
|
|
|
if (menus.length) {
|
|
|
|
//use first menu with matching id
|
|
let m = menus[0] as Opt.Menu,
|
|
vals = interaction.values || [];
|
|
|
|
//list of changes
|
|
let changed: {
|
|
add: string[],
|
|
rem: string[]
|
|
} = {
|
|
add: [],
|
|
rem: []
|
|
};
|
|
|
|
//loop through all the options in the menu
|
|
for (let i = 0; i < m.options.length; i++) {
|
|
|
|
let opt: Opt.MenuOptions = m.options[i],
|
|
{roleid} = opt;
|
|
|
|
//if there's no role id, then nothing can be done
|
|
if (roleid && interaction.member instanceof GuildMember) {
|
|
|
|
//since it is a GuildMember, let's force typescript to rememeber it
|
|
let member = interaction.member as GuildMember;
|
|
|
|
//get corresponding role
|
|
let role = await interaction.guild.roles.fetch(roleid);
|
|
|
|
//if enabled
|
|
if (vals && vals.includes(opt.value)) {
|
|
|
|
//if member doesn't have the role
|
|
if (!member.roles.cache.some(r => r.id === role.id)) {
|
|
|
|
//give role
|
|
await member.roles.add(role);
|
|
changed.add.push(role.name);
|
|
|
|
}
|
|
|
|
//else if member does have the role
|
|
} else if (member.roles.cache.some(r => r.id === role.id)) {
|
|
|
|
//remove role
|
|
await member.roles.remove(role);
|
|
changed.rem.push(role.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//convert changes to a message
|
|
let message = [];
|
|
|
|
if (changed.add.length)
|
|
message.push(`Added \`${changed.add.join(', ')}\``);
|
|
|
|
if (changed.rem.length)
|
|
message.push(`Removed \`${changed.rem.join(', ')}\``);
|
|
|
|
interaction.editReply(message.length ? message.join('\n') : 'No Changes');
|
|
|
|
} else
|
|
interaction.editReply('Error: Unknown menu');
|
|
|
|
};
|
|
|
|
//on slash command
|
|
async function handleCommand(interaction: CommandInteraction) {
|
|
|
|
console.log(interaction.commandName)
|
|
|
|
let category: number;
|
|
|
|
switch (interaction.commandName) {
|
|
|
|
case 'roles':
|
|
|
|
category = interaction.options.getInteger('category', true);
|
|
sendRoles(interaction, InteractionRoles[category].id, true);
|
|
break;
|
|
|
|
case 'rolesall':
|
|
if (interaction.user.id === '167336999844315137') {
|
|
|
|
let givenChannel = interaction.options.getChannel('channel'),
|
|
channel: TextChannel = await client.channels.fetch(interaction.channelId) as TextChannel;
|
|
|
|
if (givenChannel && givenChannel.type === 'GUILD_TEXT')
|
|
channel = givenChannel as TextChannel;
|
|
|
|
if (channel) {
|
|
category = interaction.options.getInteger('category');
|
|
sendRoles(channel, category ? InteractionRoles[category].id : undefined);
|
|
interaction.reply({
|
|
content: `sending message in <#${channel.id}>`,
|
|
ephemeral: true
|
|
})
|
|
|
|
} else
|
|
interaction.reply({
|
|
content: 'error finding channel to send message in',
|
|
ephemeral: true
|
|
})
|
|
|
|
} else
|
|
interaction.reply({
|
|
content: 'missing permissions',
|
|
ephemeral: true
|
|
})
|
|
break;
|
|
|
|
case 'updateroles':
|
|
//TODO reload data.ts
|
|
//TODO reload slash.ts
|
|
interaction.reply({
|
|
content: 'command incomplete',
|
|
ephemeral: true
|
|
})
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
async function sendRoles(res: ReplyableInteraction|TextBasedChannels, id?: string, ephemeral?: boolean) {
|
|
|
|
if (id) {
|
|
|
|
let rowarr = InteractionRoles.filter(r => r.id === id);
|
|
if (rowarr.length) {
|
|
|
|
let row = rowarr[0];
|
|
|
|
if (row.type === 'menu')
|
|
sendMenu(res, row as Opt.Menu, ephemeral);
|
|
else if (row.type === 'row')
|
|
sendRow(res, row as Opt.Row, ephemeral);
|
|
|
|
} else
|
|
sendMessage(res, `invalid id ${id}`, ephemeral);
|
|
|
|
} else if (isReplyableInteraction(res))
|
|
sendMessage(res, `missing id`, ephemeral);
|
|
|
|
else {
|
|
|
|
for (let i = 0; i < InteractionRoles.length; i++) {
|
|
|
|
switch (InteractionRoles[i].type) {
|
|
case 'menu':
|
|
sendMenu(res, InteractionRoles[i] as Opt.Menu, ephemeral);
|
|
break;
|
|
case 'row':
|
|
sendRow(res, InteractionRoles[i] as Opt.Row, ephemeral);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//register slash commands
|
|
client.on('ready', () => {
|
|
console.log('Ready')
|
|
setInterval(() => refreshCommands(client.user.id), 1000*60*60*24);
|
|
refreshCommands(client.user.id)
|
|
});
|
|
|
|
//login with token
|
|
client.login(require('fs').readFileSync('../token').toString()); |