Compare commits
2 Commits
d751db58f7
...
e21d5573b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e21d5573b1 | ||
|
|
bc835922a5 |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -6,8 +6,8 @@
|
||||
"": {
|
||||
"name": "ikea-canada-support",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.5.0",
|
||||
"@discordjs/rest": "^0.1.0-canary.0",
|
||||
"discord-api-types": "^0.22.0",
|
||||
"discord.js": "^13.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"ts-node": "^10.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.5.0",
|
||||
"@discordjs/rest": "^0.1.0-canary.0",
|
||||
"discord-api-types": "^0.22.0",
|
||||
"discord.js": "^13.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import * as Opt from "../types/Opt";
|
||||
|
||||
//array of Opt.Row|Opt.Menu, each one is a message that will be sent/understood
|
||||
// - types/Opt.d.ts
|
||||
const InteractionRoles: (Opt.Row|Opt.Menu)[] = [
|
||||
export const InteractionRoles: (Opt.Row|Opt.Menu)[] = [
|
||||
{
|
||||
type: 'menu',
|
||||
message: 'Pronouns',
|
||||
palceholder: '',
|
||||
id: 'rolemenu',
|
||||
id: 'pronounsmenu',
|
||||
max: 'all',
|
||||
options: [
|
||||
{
|
||||
@@ -106,7 +106,7 @@ const InteractionRoles: (Opt.Row|Opt.Menu)[] = [
|
||||
type: 'menu',
|
||||
message: 'Other Roles',
|
||||
palceholder: '',
|
||||
id: 'otherrolesmenu',
|
||||
id: 'rolesmenu',
|
||||
max: 'all',
|
||||
options: [
|
||||
{
|
||||
@@ -123,4 +123,4 @@ const InteractionRoles: (Opt.Row|Opt.Menu)[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export default InteractionRoles;
|
||||
export const GuildId = '861404201645244416';
|
||||
@@ -1,21 +1,43 @@
|
||||
import { ButtonInteraction, GuildMember, Interaction, Message, MessageActionRow, MessageButton, MessageSelectMenu, MessageSelectOptionData, SelectMenuInteraction } from "discord.js";
|
||||
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 from "./data";
|
||||
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 = new Discord.Client({ intents: [
|
||||
client: Client = new Discord.Client({ intents: [
|
||||
'GUILD_MEMBERS'
|
||||
] });
|
||||
|
||||
/**
|
||||
* send message containing menu
|
||||
* @param c text channel to send message
|
||||
* @param res text channel to send message or interaction to reply to
|
||||
* @param menuOpts menu data object
|
||||
*/
|
||||
function sendMenu(c: TextChannel, menuOpts: Opt.Menu) {
|
||||
function sendMenu(res: ReplyableInteraction|TextBasedChannels, menuOpts: Opt.Menu, ephemeral?: boolean) {
|
||||
|
||||
let row = new MessageActionRow()
|
||||
|
||||
@@ -52,10 +74,13 @@ function sendMenu(c: TextChannel, menuOpts: Opt.Menu) {
|
||||
select.addOptions(options);
|
||||
row.addComponents(select)
|
||||
|
||||
c.send({
|
||||
let arg = {
|
||||
content: menuOpts.message,
|
||||
components: [row]
|
||||
});
|
||||
};
|
||||
|
||||
sendMessage(res, arg, ephemeral);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +88,7 @@ function sendMenu(c: TextChannel, menuOpts: Opt.Menu) {
|
||||
* @param c text channel to send message
|
||||
* @param rowOpts row data object
|
||||
*/
|
||||
function sendRow(c: TextChannel, rowOpts: Opt.Row) {
|
||||
function sendRow(res: ReplyableInteraction|TextBasedChannels, rowOpts: Opt.Row, ephemeral?: boolean) {
|
||||
|
||||
let row = new MessageActionRow();
|
||||
|
||||
@@ -84,42 +109,87 @@ function sendRow(c: TextChannel, rowOpts: Opt.Row) {
|
||||
|
||||
row.addComponents(options);
|
||||
|
||||
c.send({
|
||||
let arg = {
|
||||
content: rowOpts.message,
|
||||
components: [row]
|
||||
});
|
||||
};
|
||||
|
||||
sendMessage(res, arg, ephemeral);
|
||||
|
||||
}
|
||||
|
||||
//uncomment if you want to create a message, will change to slash command
|
||||
client.on('message', (m: Message) => {
|
||||
//on interaction event
|
||||
client.on('interactionCreate', (interaction: Interaction) => {
|
||||
|
||||
if (m.author.id === '167336999844315137' && m.content === '.') {
|
||||
if (interaction.inGuild()) {
|
||||
|
||||
for (let i = 0; i < InteractionRoles.length; i++) {
|
||||
switch (InteractionRoles[i].type) {
|
||||
case 'menu':
|
||||
sendMenu(m.channel as TextChannel, InteractionRoles[i] as Opt.Menu);
|
||||
break;
|
||||
case 'row':
|
||||
sendRow(m.channel as TextChannel, InteractionRoles[i] as Opt.Row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (interaction.isSelectMenu())
|
||||
handleSelect(interaction as SelectMenuInteraction);
|
||||
|
||||
else if (interaction.isButton())
|
||||
handleButton(interaction as ButtonInteraction);
|
||||
|
||||
else if (interaction.isCommand())
|
||||
handleCommand(interaction as CommandInteraction);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//on interaction event
|
||||
client.on('interactionCreate', (interaction: Interaction) => {
|
||||
async function handleButton(interaction: ButtonInteraction) {
|
||||
|
||||
if (interaction.isSelectMenu())
|
||||
handleSelect(interaction as SelectMenuInteraction);
|
||||
//start typing
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
else if (interaction.isButton())
|
||||
handleButton(interaction as ButtonInteraction);
|
||||
//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) {
|
||||
@@ -208,60 +278,109 @@ async function handleSelect(interaction: SelectMenuInteraction) {
|
||||
|
||||
};
|
||||
|
||||
async function handleButton(interaction: ButtonInteraction) {
|
||||
//on slash command
|
||||
async function handleCommand(interaction: CommandInteraction) {
|
||||
|
||||
//start typing
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
console.log(interaction.commandName)
|
||||
|
||||
//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;
|
||||
let category: number;
|
||||
|
||||
//list of button rows from data
|
||||
let rows = InteractionRoles.filter(opt => opt.type === 'row');
|
||||
switch (interaction.commandName) {
|
||||
|
||||
//loop through each the buttons rows
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
case 'roles':
|
||||
|
||||
let row = rows[i] as Opt.Row;
|
||||
category = interaction.options.getInteger('category', true);
|
||||
sendRoles(interaction, InteractionRoles[category].id, true);
|
||||
break;
|
||||
|
||||
//filter to just buttons with matching id
|
||||
let matchingButtons = row.buttons.filter(opt => opt.id === customId);
|
||||
case 'rolesall':
|
||||
if (interaction.user.id === '167336999844315137') {
|
||||
|
||||
if (matchingButtons.length) {
|
||||
let givenChannel = interaction.options.getChannel('channel'),
|
||||
channel: TextChannel = await client.channels.fetch(interaction.channelId) as TextChannel;
|
||||
|
||||
//if there's no role id, then nothing can be done
|
||||
if (matchingButtons[0].roleid && interaction.member instanceof GuildMember) {
|
||||
if (givenChannel && givenChannel.type === 'GUILD_TEXT')
|
||||
channel = givenChannel as TextChannel;
|
||||
|
||||
//since it is a GuildMember, let's force typescript to rememeber it
|
||||
let member = interaction.member as GuildMember;
|
||||
if (channel) {
|
||||
category = interaction.options.getInteger('category');
|
||||
sendRoles(channel, category ? InteractionRoles[category].id : undefined);
|
||||
interaction.reply({
|
||||
content: `sending message in <#${channel.id}>`,
|
||||
ephemeral: true
|
||||
})
|
||||
|
||||
//get corresponding role
|
||||
let role = await interaction.guild.roles.fetch(matchingButtons[0].roleid);
|
||||
} else
|
||||
interaction.reply({
|
||||
content: 'error finding channel to send message in',
|
||||
ephemeral: true
|
||||
})
|
||||
|
||||
//if clicker (member) does have the role
|
||||
if (member.roles.cache.some(r => r.id === role.id)) {
|
||||
} else
|
||||
interaction.reply({
|
||||
content: 'missing permissions',
|
||||
ephemeral: true
|
||||
})
|
||||
break;
|
||||
|
||||
//remove role
|
||||
await member.roles.remove(role.id);
|
||||
interaction.editReply(`Removed \`${role.name}\``);
|
||||
case 'updateroles':
|
||||
//TODO reload data.ts
|
||||
//TODO reload slash.ts
|
||||
interaction.reply({
|
||||
content: 'command incomplete',
|
||||
ephemeral: true
|
||||
})
|
||||
break;
|
||||
|
||||
} else {
|
||||
|
||||
//add role
|
||||
await member.roles.add(role.id);
|
||||
interaction.editReply(`Added \`${role.name}\``);
|
||||
|
||||
}
|
||||
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());
|
||||
@@ -3,7 +3,7 @@
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"start": "ts-node .",
|
||||
"debug": "ts-node .",
|
||||
"ts-node": "ts-node",
|
||||
"watchdog": "./run.sh",
|
||||
"tmux": "tmux new-session -d -s $npm_package_name \"./run.sh\"",
|
||||
"resume": "tmux a -t $npm_package_name",
|
||||
|
||||
67
rolemanager/slash.ts
Normal file
67
rolemanager/slash.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { SlashCommandBuilder } from "@discordjs/builders";
|
||||
import { REST } from '@discordjs/rest';
|
||||
import { Routes } from 'discord-api-types/v9';
|
||||
import { GuildId, InteractionRoles } from './data';
|
||||
|
||||
const commands = [
|
||||
|
||||
//roles ======
|
||||
new SlashCommandBuilder()
|
||||
.setName('roles')
|
||||
.setDescription('displays the role selections')
|
||||
.addIntegerOption((option) => {
|
||||
option
|
||||
.setName('category')
|
||||
.setDescription('the category of roles that will be displayed')
|
||||
.setRequired(true)
|
||||
|
||||
InteractionRoles.forEach((opt, i) => option.addChoice(opt.message, i));
|
||||
|
||||
return option;
|
||||
})
|
||||
.toJSON(),
|
||||
|
||||
//rolesall ======
|
||||
new SlashCommandBuilder()
|
||||
.setName('rolesall')
|
||||
.setDescription('displays the role selections for everyone')
|
||||
.addIntegerOption((option) => {
|
||||
|
||||
option
|
||||
.setName('category')
|
||||
.setDescription('the category of roles that will be displayed')
|
||||
|
||||
InteractionRoles.forEach((opt, i) => option.addChoice(opt.message, i));
|
||||
|
||||
return option;
|
||||
})
|
||||
.addChannelOption(option =>
|
||||
option
|
||||
.setName('channel')
|
||||
.setDescription('the channel the message(s) will be sent in'))
|
||||
.toJSON(),
|
||||
|
||||
//updateroles ======
|
||||
new SlashCommandBuilder()
|
||||
.setName('updateroles')
|
||||
.setDescription('updates the cached role options')
|
||||
.toJSON()
|
||||
|
||||
];
|
||||
|
||||
const rest = new REST({ version: '9' }).setToken(require('fs').readFileSync('../token').toString());
|
||||
|
||||
export default async function refreshCommands(ClientId: string) {
|
||||
|
||||
try {
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(ClientId, GuildId),
|
||||
{ body: commands },
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
}
|
||||
1
types/Opt.d.ts
vendored
1
types/Opt.d.ts
vendored
@@ -31,5 +31,6 @@ export interface RowButton {
|
||||
export interface Row {
|
||||
type: 'row';
|
||||
message: string;
|
||||
id: string; //not needed for api, just needed for slash commands
|
||||
buttons: [RowButton?, RowButton?, RowButton?, RowButton?, RowButton?];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user