aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKevin Kendzia <[email protected]>2022-05-14 02:44:14 +0200
committerGitHub <[email protected]>2022-05-13 19:44:14 -0500
commiteb2885b2061708415e46929d21bc5991724ce441 (patch)
treee77496a2d2fdd249cc505ca1e06d7b17cce957ac /src
parent1ef05aba9d2e692ef365721f725be2d2a4e464d9 (diff)
downloadmuse-eb2885b2061708415e46929d21bc5991724ce441.tar.xz
muse-eb2885b2061708415e46929d21bc5991724ce441.zip
fix command permission handling and push discord to v10 (#640)
Co-authored-by: Max Isom <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/bot.ts47
-rw-r--r--src/commands/clear.ts4
-rw-r--r--src/commands/config.ts29
-rw-r--r--src/commands/disconnect.ts4
-rw-r--r--src/commands/favorites.ts16
-rw-r--r--src/commands/fseek.ts4
-rw-r--r--src/commands/index.ts6
-rw-r--r--src/commands/move.ts4
-rw-r--r--src/commands/now-playing.ts4
-rw-r--r--src/commands/pause.ts4
-rw-r--r--src/commands/play.ts5
-rw-r--r--src/commands/queue.ts4
-rw-r--r--src/commands/remove.ts4
-rw-r--r--src/commands/resume.ts5
-rw-r--r--src/commands/seek.ts4
-rw-r--r--src/commands/shuffle.ts4
-rw-r--r--src/commands/skip.ts4
-rw-r--r--src/commands/stop.ts4
-rw-r--r--src/commands/unskip.ts4
-rw-r--r--src/events/guild-create.ts52
-rw-r--r--src/events/guild-update.ts10
-rw-r--r--src/inversify.config.ts12
-rw-r--r--src/services/add-query-to-queue.ts13
-rw-r--r--src/services/config.ts2
-rw-r--r--src/services/player.ts4
-rw-r--r--src/services/spotify-api.ts2
-rw-r--r--src/types.ts1
-rw-r--r--src/utils/build-embed.ts22
-rw-r--r--src/utils/channels.ts8
-rw-r--r--src/utils/get-youtube-and-spotify-suggestions-for.ts6
-rw-r--r--src/utils/register-commands-on-guild.ts19
-rw-r--r--src/utils/update-permissions-for-guild.ts53
32 files changed, 138 insertions, 226 deletions
diff --git a/src/bot.ts b/src/bot.ts
index 37be7f9..94b3aa6 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -1,4 +1,4 @@
-import {Client, Collection, ExcludeEnum, PresenceStatusData, User} from 'discord.js';
+import {Client, Collection, PresenceStatusData, User} from 'discord.js';
import {inject, injectable} from 'inversify';
import ora from 'ora';
import {TYPES} from './types.js';
@@ -7,32 +7,27 @@ import Command from './commands/index.js';
import debug from './utils/debug.js';
import handleGuildCreate from './events/guild-create.js';
import handleVoiceStateUpdate from './events/voice-state-update.js';
-import handleGuildUpdate from './events/guild-update.js';
import errorMsg from './utils/error-msg.js';
import {isUserInVoice} from './utils/channels.js';
import Config from './services/config.js';
import {generateDependencyReport} from '@discordjs/voice';
import {REST} from '@discordjs/rest';
-import {Routes} from 'discord-api-types/v9';
-import updatePermissionsForGuild from './utils/update-permissions-for-guild.js';
-import {ActivityTypes} from 'discord.js/typings/enums';
+import {Routes, ActivityType} from 'discord-api-types/v10';
+import registerCommandsOnGuild from './utils/register-commands-on-guild.js';
@injectable()
export default class {
private readonly client: Client;
private readonly config: Config;
- private readonly token: string;
private readonly shouldRegisterCommandsOnBot: boolean;
private readonly commandsByName!: Collection<string, Command>;
private readonly commandsByButtonId!: Collection<string, Command>;
constructor(
@inject(TYPES.Client) client: Client,
- @inject(TYPES.Config) config: Config,
- ) {
+ @inject(TYPES.Config) config: Config) {
this.client = client;
this.config = config;
- this.token = config.DISCORD_TOKEN;
this.shouldRegisterCommandsOnBot = config.REGISTER_COMMANDS_ON_BOT;
this.commandsByName = new Collection();
this.commandsByButtonId = new Collection();
@@ -61,12 +56,13 @@ export default class {
}
// Register event handlers
+ // eslint-disable-next-line complexity
this.client.on('interactionCreate', async interaction => {
try {
if (interaction.isCommand()) {
const command = this.commandsByName.get(interaction.commandName);
- if (!command) {
+ if (!command || !interaction.isChatInputCommand()) {
return;
}
@@ -76,7 +72,6 @@ export default class {
}
const requiresVC = command.requiresVC instanceof Function ? command.requiresVC(interaction) : command.requiresVC;
-
if (requiresVC && interaction.member && !isUserInVoice(interaction.guild, interaction.member.user as User)) {
await interaction.reply({content: errorMsg('gotta be in a voice channel'), ephemeral: true});
return;
@@ -111,9 +106,9 @@ export default class {
// This can fail if the message was deleted, and we don't want to crash the whole bot
try {
- if ((interaction.isApplicationCommand() || interaction.isButton()) && (interaction.replied || interaction.deferred)) {
+ if ((interaction.isCommand() || interaction.isButton()) && (interaction.replied || interaction.deferred)) {
await interaction.editReply(errorMsg(error as Error));
- } else if (interaction.isApplicationCommand() || interaction.isButton()) {
+ } else if (interaction.isCommand() || interaction.isButton()) {
await interaction.reply({content: errorMsg(error as Error), ephemeral: true});
}
} catch {}
@@ -126,11 +121,9 @@ export default class {
debug(generateDependencyReport());
// Update commands
- const rest = new REST({version: '9'}).setToken(this.token);
-
+ const rest = new REST({version: '10'}).setToken(this.config.DISCORD_TOKEN);
if (this.shouldRegisterCommandsOnBot) {
spinner.text = '📡 updating commands on bot...';
-
await rest.put(
Routes.applicationCommands(this.client.user!.id),
{body: this.commandsByName.map(command => command.slashCommand.toJSON())},
@@ -140,10 +133,12 @@ export default class {
await Promise.all([
...this.client.guilds.cache.map(async guild => {
- await rest.put(
- Routes.applicationGuildCommands(this.client.user!.id, guild.id),
- {body: this.commandsByName.map(command => command.slashCommand.toJSON())},
- );
+ await registerCommandsOnGuild({
+ rest,
+ guildId: guild.id,
+ applicationId: this.client.user!.id,
+ commands: this.commandsByName.map(c => c.slashCommand),
+ });
}),
// Remove commands registered on bot (if they exist)
rest.put(Routes.applicationCommands(this.client.user!.id), {body: []}),
@@ -155,18 +150,14 @@ export default class {
activities: [
{
name: this.config.BOT_ACTIVITY,
- type: this.config.BOT_ACTIVITY_TYPE as unknown as ExcludeEnum<typeof ActivityTypes, 'CUSTOM'>,
+ type: this.config.BOT_ACTIVITY_TYPE as unknown as Exclude<ActivityType, ActivityType.Custom>,
url: this.config.BOT_ACTIVITY_URL === '' ? undefined : this.config.BOT_ACTIVITY_URL,
},
],
status: this.config.BOT_STATUS as PresenceStatusData,
});
- // Update permissions
- spinner.text = '📡 updating permissions...';
- await Promise.all(this.client.guilds.cache.map(async guild => updatePermissionsForGuild(guild)));
-
- spinner.succeed(`Ready! Invite the bot with https://discordapp.com/oauth2/authorize?client_id=${this.client.user?.id ?? ''}&scope=bot%20applications.commands&permissions=36700288`);
+ spinner.succeed(`Ready! Invite the bot with https://discordapp.com/oauth2/authorize?client_id=${this.client.user?.id ?? ''}&scope=bot%20applications.commands&permissions=36700160`);
});
this.client.on('error', console.error);
@@ -174,8 +165,6 @@ export default class {
this.client.on('guildCreate', handleGuildCreate);
this.client.on('voiceStateUpdate', handleVoiceStateUpdate);
- this.client.on('guildUpdate', handleGuildUpdate);
-
- await this.client.login(this.token);
+ await this.client.login();
}
}
diff --git a/src/commands/clear.ts b/src/commands/clear.ts
index 2b258cb..8de8764 100644
--- a/src/commands/clear.ts
+++ b/src/commands/clear.ts
@@ -1,5 +1,5 @@
import {inject, injectable} from 'inversify';
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {SlashCommandBuilder} from '@discordjs/builders';
import {TYPES} from '../types.js';
import PlayerManager from '../managers/player.js';
@@ -19,7 +19,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction) {
+ public async execute(interaction: ChatInputCommandInteraction) {
this.playerManager.get(interaction.guild!.id).clear();
await interaction.reply('clearer than a field after a fresh harvest');
diff --git a/src/commands/config.ts b/src/commands/config.ts
index 5ed286f..55dab71 100644
--- a/src/commands/config.ts
+++ b/src/commands/config.ts
@@ -1,8 +1,7 @@
import {SlashCommandBuilder} from '@discordjs/builders';
-import {CommandInteraction, MessageEmbed} from 'discord.js';
+import {ChatInputCommandInteraction, EmbedBuilder, PermissionFlagsBits} from 'discord.js';
import {injectable} from 'inversify';
import {prisma} from '../utils/db.js';
-import updatePermissionsForGuild from '../utils/update-permissions-for-guild.js';
import Command from './index.js';
@injectable()
@@ -10,6 +9,7 @@ export default class implements Command {
public readonly slashCommand = new SlashCommandBuilder()
.setName('config')
.setDescription('configure bot settings')
+ .setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild.toString() as any)
.addSubcommand(subcommand => subcommand
.setName('set-playlist-limit')
.setDescription('set the maximum number of tracks that can be added from a playlist')
@@ -43,10 +43,10 @@ export default class implements Command {
.setName('get')
.setDescription('show all settings'));
- async execute(interaction: CommandInteraction) {
+ async execute(interaction: ChatInputCommandInteraction) {
switch (interaction.options.getSubcommand()) {
case 'set-playlist-limit': {
- const limit = interaction.options.getInteger('limit')!;
+ const limit: number = interaction.options.getInteger('limit')!;
if (limit < 1) {
throw new Error('invalid limit');
@@ -66,25 +66,6 @@ export default class implements Command {
break;
}
- case 'set-role': {
- const role = interaction.options.getRole('role')!;
-
- await prisma.setting.update({
- where: {
- guildId: interaction.guild!.id,
- },
- data: {
- roleId: role.id,
- },
- });
-
- await updatePermissionsForGuild(interaction.guild!);
-
- await interaction.reply('👍 role updated');
-
- break;
- }
-
case 'set-wait-after-queue-empties': {
const delay = interaction.options.getInteger('delay')!;
@@ -120,7 +101,7 @@ export default class implements Command {
}
case 'get': {
- const embed = new MessageEmbed().setTitle('Config');
+ const embed = new EmbedBuilder().setTitle('Config');
const config = await prisma.setting.findUnique({where: {guildId: interaction.guild!.id}});
diff --git a/src/commands/disconnect.ts b/src/commands/disconnect.ts
index fd6b984..d9f025a 100644
--- a/src/commands/disconnect.ts
+++ b/src/commands/disconnect.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {SlashCommandBuilder} from '@discordjs/builders';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
@@ -19,7 +19,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction) {
+ public async execute(interaction: ChatInputCommandInteraction) {
const player = this.playerManager.get(interaction.guild!.id);
if (!player.voiceConnection) {
diff --git a/src/commands/favorites.ts b/src/commands/favorites.ts
index 31b1f79..3cfd716 100644
--- a/src/commands/favorites.ts
+++ b/src/commands/favorites.ts
@@ -1,5 +1,5 @@
import {SlashCommandBuilder} from '@discordjs/builders';
-import {AutocompleteInteraction, CommandInteraction, MessageEmbed} from 'discord.js';
+import {AutocompleteInteraction, ChatInputCommandInteraction, EmbedBuilder} from 'discord.js';
import {inject, injectable} from 'inversify';
import Command from '.';
import AddQueryToQueue from '../services/add-query-to-queue.js';
@@ -56,9 +56,9 @@ export default class implements Command {
constructor(@inject(TYPES.Services.AddQueryToQueue) private readonly addQueryToQueue: AddQueryToQueue) {}
- requiresVC = (interaction: CommandInteraction) => interaction.options.getSubcommand() === 'use';
+ requiresVC = (interaction: ChatInputCommandInteraction) => interaction.options.getSubcommand() === 'use';
- async execute(interaction: CommandInteraction) {
+ async execute(interaction: ChatInputCommandInteraction) {
switch (interaction.options.getSubcommand()) {
case 'use':
await this.use(interaction);
@@ -100,7 +100,7 @@ export default class implements Command {
})));
}
- private async use(interaction: CommandInteraction) {
+ private async use(interaction: ChatInputCommandInteraction) {
const name = interaction.options.getString('name')!.trim();
const favorite = await prisma.favoriteQuery.findFirst({
@@ -123,7 +123,7 @@ export default class implements Command {
});
}
- private async list(interaction: CommandInteraction) {
+ private async list(interaction: ChatInputCommandInteraction) {
const favorites = await prisma.favoriteQuery.findMany({
where: {
guildId: interaction.guild!.id,
@@ -135,7 +135,7 @@ export default class implements Command {
return;
}
- const embed = new MessageEmbed().setTitle('Favorites');
+ const embed = new EmbedBuilder().setTitle('Favorites');
let description = '';
for (const favorite of favorites) {
@@ -149,7 +149,7 @@ export default class implements Command {
});
}
- private async create(interaction: CommandInteraction) {
+ private async create(interaction: ChatInputCommandInteraction) {
const name = interaction.options.getString('name')!.trim();
const query = interaction.options.getString('query')!.trim();
@@ -174,7 +174,7 @@ export default class implements Command {
await interaction.reply('👍 favorite created');
}
- private async remove(interaction: CommandInteraction) {
+ private async remove(interaction: ChatInputCommandInteraction) {
const name = interaction.options.getString('name')!.trim();
const favorite = await prisma.favoriteQuery.findFirst({where: {
diff --git a/src/commands/fseek.ts b/src/commands/fseek.ts
index 7d0e553..c469948 100644
--- a/src/commands/fseek.ts
+++ b/src/commands/fseek.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {SlashCommandBuilder} from '@discordjs/builders';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
@@ -25,7 +25,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const player = this.playerManager.get(interaction.guild!.id);
const currentSong = player.getCurrent();
diff --git a/src/commands/index.ts b/src/commands/index.ts
index 02349d2..60cf6ce 100644
--- a/src/commands/index.ts
+++ b/src/commands/index.ts
@@ -1,11 +1,11 @@
import {SlashCommandBuilder, SlashCommandSubcommandsOnlyBuilder} from '@discordjs/builders';
-import {AutocompleteInteraction, ButtonInteraction, CommandInteraction} from 'discord.js';
+import {AutocompleteInteraction, ButtonInteraction, ChatInputCommandInteraction} from 'discord.js';
export default interface Command {
readonly slashCommand: Partial<SlashCommandBuilder | SlashCommandSubcommandsOnlyBuilder> & Pick<SlashCommandBuilder, 'toJSON'>;
readonly handledButtonIds?: readonly string[];
- readonly requiresVC?: boolean | ((interaction: CommandInteraction) => boolean);
- execute: (interaction: CommandInteraction) => Promise<void>;
+ readonly requiresVC?: boolean | ((interaction: ChatInputCommandInteraction) => boolean);
+ execute: (interaction: ChatInputCommandInteraction) => Promise<void>;
handleButtonInteraction?: (interaction: ButtonInteraction) => Promise<void>;
handleAutocompleteInteraction?: (interaction: AutocompleteInteraction) => Promise<void>;
}
diff --git a/src/commands/move.ts b/src/commands/move.ts
index 9855c59..9063473 100644
--- a/src/commands/move.ts
+++ b/src/commands/move.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {inject, injectable} from 'inversify';
import {TYPES} from '../types.js';
import PlayerManager from '../managers/player.js';
@@ -26,7 +26,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const player = this.playerManager.get(interaction.guild!.id);
const from = interaction.options.getInteger('from') ?? 1;
diff --git a/src/commands/now-playing.ts b/src/commands/now-playing.ts
index 999bb69..55060ed 100644
--- a/src/commands/now-playing.ts
+++ b/src/commands/now-playing.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
import PlayerManager from '../managers/player.js';
@@ -18,7 +18,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const player = this.playerManager.get(interaction.guild!.id);
if (!player.getCurrent()) {
diff --git a/src/commands/pause.ts b/src/commands/pause.ts
index 7b9d295..b0381ae 100644
--- a/src/commands/pause.ts
+++ b/src/commands/pause.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {SlashCommandBuilder} from '@discordjs/builders';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
@@ -20,7 +20,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction) {
+ public async execute(interaction: ChatInputCommandInteraction) {
const player = this.playerManager.get(interaction.guild!.id);
if (player.status !== STATUS.PLAYING) {
diff --git a/src/commands/play.ts b/src/commands/play.ts
index ca23d61..d6149c4 100644
--- a/src/commands/play.ts
+++ b/src/commands/play.ts
@@ -1,4 +1,4 @@
-import {AutocompleteInteraction, CommandInteraction} from 'discord.js';
+import {AutocompleteInteraction, ChatInputCommandInteraction} from 'discord.js';
import {URL} from 'url';
import {SlashCommandBuilder} from '@discordjs/builders';
import {inject, injectable} from 'inversify';
@@ -43,8 +43,7 @@ export default class implements Command {
this.addQueryToQueue = addQueryToQueue;
}
- // eslint-disable-next-line complexity
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const query = interaction.options.getString('query')!;
await this.addQueryToQueue.addToQueue({
diff --git a/src/commands/queue.ts b/src/commands/queue.ts
index 4d75d42..9115ee6 100644
--- a/src/commands/queue.ts
+++ b/src/commands/queue.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {SlashCommandBuilder} from '@discordjs/builders';
import {inject, injectable} from 'inversify';
import {TYPES} from '../types.js';
@@ -22,7 +22,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction) {
+ public async execute(interaction: ChatInputCommandInteraction) {
const player = this.playerManager.get(interaction.guild!.id);
const embed = buildQueueEmbed(player, interaction.options.getInteger('page') ?? 1);
diff --git a/src/commands/remove.ts b/src/commands/remove.ts
index 55c416f..5b470c6 100644
--- a/src/commands/remove.ts
+++ b/src/commands/remove.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {inject, injectable} from 'inversify';
import {TYPES} from '../types.js';
import PlayerManager from '../managers/player.js';
@@ -26,7 +26,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const player = this.playerManager.get(interaction.guild!.id);
const position = interaction.options.getInteger('position') ?? 1;
diff --git a/src/commands/resume.ts b/src/commands/resume.ts
index eddc762..42758f2 100644
--- a/src/commands/resume.ts
+++ b/src/commands/resume.ts
@@ -6,7 +6,7 @@ import PlayerManager from '../managers/player.js';
import {STATUS} from '../services/player.js';
import {buildPlayingMessageEmbed} from '../utils/build-embed.js';
import {getMemberVoiceChannel, getMostPopularVoiceChannel} from '../utils/channels.js';
-import {CommandInteraction, GuildMember} from 'discord.js';
+import {ChatInputCommandInteraction, GuildMember} from 'discord.js';
@injectable()
export default class implements Command {
@@ -22,8 +22,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- // eslint-disable-next-line complexity
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const player = this.playerManager.get(interaction.guild!.id);
const [targetVoiceChannel] = getMemberVoiceChannel(interaction.member as GuildMember) ?? getMostPopularVoiceChannel(interaction.guild!);
if (player.status === STATUS.PLAYING) {
diff --git a/src/commands/seek.ts b/src/commands/seek.ts
index 00e9c2e..4be51a2 100644
--- a/src/commands/seek.ts
+++ b/src/commands/seek.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
import PlayerManager from '../managers/player.js';
@@ -26,7 +26,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const player = this.playerManager.get(interaction.guild!.id);
const currentSong = player.getCurrent();
diff --git a/src/commands/shuffle.ts b/src/commands/shuffle.ts
index 819c01c..a4a1497 100644
--- a/src/commands/shuffle.ts
+++ b/src/commands/shuffle.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
import PlayerManager from '../managers/player.js';
@@ -19,7 +19,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const player = this.playerManager.get(interaction.guild!.id);
if (player.isQueueEmpty()) {
diff --git a/src/commands/skip.ts b/src/commands/skip.ts
index eb2746a..d8be5d8 100644
--- a/src/commands/skip.ts
+++ b/src/commands/skip.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
import PlayerManager from '../managers/player.js';
@@ -24,7 +24,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const numToSkip = interaction.options.getInteger('number') ?? 1;
if (numToSkip < 1) {
diff --git a/src/commands/stop.ts b/src/commands/stop.ts
index 3c32c7f..71b33db 100644
--- a/src/commands/stop.ts
+++ b/src/commands/stop.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {SlashCommandBuilder} from '@discordjs/builders';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
@@ -20,7 +20,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction) {
+ public async execute(interaction: ChatInputCommandInteraction) {
const player = this.playerManager.get(interaction.guild!.id);
if (!player.voiceConnection) {
diff --git a/src/commands/unskip.ts b/src/commands/unskip.ts
index 936f4ef..31805d3 100644
--- a/src/commands/unskip.ts
+++ b/src/commands/unskip.ts
@@ -1,4 +1,4 @@
-import {CommandInteraction} from 'discord.js';
+import {ChatInputCommandInteraction} from 'discord.js';
import {TYPES} from '../types.js';
import {inject, injectable} from 'inversify';
import PlayerManager from '../managers/player.js';
@@ -20,7 +20,7 @@ export default class implements Command {
this.playerManager = playerManager;
}
- public async execute(interaction: CommandInteraction): Promise<void> {
+ public async execute(interaction: ChatInputCommandInteraction): Promise<void> {
const player = this.playerManager.get(interaction.guild!.id);
try {
diff --git a/src/events/guild-create.ts b/src/events/guild-create.ts
index d71acda..f509e45 100644
--- a/src/events/guild-create.ts
+++ b/src/events/guild-create.ts
@@ -1,58 +1,44 @@
-import {Guild, Client} from 'discord.js';
+import {Client, Guild} from 'discord.js';
import container from '../inversify.config.js';
import Command from '../commands';
import {TYPES} from '../types.js';
import Config from '../services/config.js';
import {prisma} from '../utils/db.js';
import {REST} from '@discordjs/rest';
-import {Routes} from 'discord-api-types/v9';
-import updatePermissionsForGuild from '../utils/update-permissions-for-guild.js';
+import {Setting} from '@prisma/client';
+import registerCommandsOnGuild from '../utils/register-commands-on-guild.js';
-export default async (guild: Guild): Promise<void> => {
- let invitedBy;
- try {
- const logs = await guild.fetchAuditLogs({type: 'BOT_ADD'});
- invitedBy = logs.entries.find(entry => entry.target?.id === guild.client.user?.id)?.executor;
- } catch {}
-
- if (!invitedBy) {
- console.warn(`Could not find user who invited Muse to ${guild.name} from the audit logs.`);
- }
-
- await prisma.setting.upsert({
+export async function createGuildSettings(guild: Guild): Promise<Setting> {
+ return prisma.setting.upsert({
where: {
guildId: guild.id,
},
create: {
guildId: guild.id,
- invitedByUserId: invitedBy?.id,
- },
- update: {
- invitedByUserId: invitedBy?.id,
},
+ update: {},
});
+}
+
+export default async (guild: Guild): Promise<void> => {
+ await createGuildSettings(guild);
const config = container.get<Config>(TYPES.Config);
// Setup slash commands
if (!config.REGISTER_COMMANDS_ON_BOT) {
- const token = container.get<Config>(TYPES.Config).DISCORD_TOKEN;
const client = container.get<Client>(TYPES.Client);
- const rest = new REST({version: '9'}).setToken(token);
+ const rest = new REST({version: '10'}).setToken(config.DISCORD_TOKEN);
- await rest.put(
- Routes.applicationGuildCommands(client.user!.id, guild.id),
- {body: container.getAll<Command>(TYPES.Command).map(command => command.slashCommand.toJSON())},
- );
+ await registerCommandsOnGuild({
+ rest,
+ applicationId: client.user!.id,
+ guildId: guild.id,
+ commands: container.getAll<Command>(TYPES.Command).map(command => command.slashCommand),
+ });
}
- await updatePermissionsForGuild(guild);
-
- if (invitedBy) {
- await invitedBy.send('👋 Hi! You just invited me to a server. I can\'t be used by your server members until you complete setup by running /config set-role in your server.');
- } else {
- const owner = await guild.fetchOwner();
- await owner.send('👋 Hi! Someone (probably you) just invited me to a server you own. I can\'t be used by your server members until you complete setup by running /config set-role in your server.');
- }
+ const owner = await guild.fetchOwner();
+ await owner.send('👋 Hi! Someone (probably you) just invited me to a server you own. I can\'t be used by your server members until you complete setup by running /config set-role in your server.');
};
diff --git a/src/events/guild-update.ts b/src/events/guild-update.ts
deleted file mode 100644
index 837bbbe..0000000
--- a/src/events/guild-update.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import {Guild} from 'discord.js';
-import updatePermissionsForGuild from '../utils/update-permissions-for-guild.js';
-
-const handleGuildUpdate = async (oldGuild: Guild, newGuild: Guild) => {
- if (oldGuild.ownerId !== newGuild.ownerId) {
- await updatePermissionsForGuild(newGuild);
- }
-};
-
-export default handleGuildUpdate;
diff --git a/src/inversify.config.ts b/src/inversify.config.ts
index e4c30e3..f420071 100644
--- a/src/inversify.config.ts
+++ b/src/inversify.config.ts
@@ -2,7 +2,7 @@ import 'reflect-metadata';
import {Container} from 'inversify';
import {TYPES} from './types.js';
import Bot from './bot.js';
-import {Client, Intents} from 'discord.js';
+import {Client, GatewayIntentBits} from 'discord.js';
import ConfigProvider from './services/config.js';
// Managers
@@ -41,12 +41,10 @@ import KeyValueCacheProvider from './services/key-value-cache.js';
const container = new Container();
// Intents
-const intents = new Intents();
-intents.add(Intents.FLAGS.GUILDS); // To listen for guildCreate event
-intents.add(Intents.FLAGS.GUILD_MESSAGES); // To listen for messages (messageCreate event)
-intents.add(Intents.FLAGS.DIRECT_MESSAGE_REACTIONS); // To listen for message reactions (messageReactionAdd event)
-intents.add(Intents.FLAGS.DIRECT_MESSAGES); // To receive the prefix message
-intents.add(Intents.FLAGS.GUILD_VOICE_STATES); // To listen for voice state changes (voiceStateUpdate event)
+const intents: GatewayIntentBits[] = [];
+intents.push(GatewayIntentBits.Guilds); // To listen for guildCreate event
+intents.push(GatewayIntentBits.GuildMessageReactions); // To listen for message reactions (messageReactionAdd event)
+intents.push(GatewayIntentBits.GuildVoiceStates); // To listen for voice state changes (voiceStateUpdate event)
// Bot
container.bind<Bot>(TYPES.Bot).to(Bot).inSingletonScope();
diff --git a/src/services/add-query-to-queue.ts b/src/services/add-query-to-queue.ts
index 7980ca0..15192b4 100644
--- a/src/services/add-query-to-queue.ts
+++ b/src/services/add-query-to-queue.ts
@@ -1,5 +1,5 @@
/* eslint-disable complexity */
-import {CommandInteraction, GuildMember} from 'discord.js';
+import {ChatInputCommandInteraction, GuildMember} from 'discord.js';
import {inject, injectable} from 'inversify';
import shuffle from 'array-shuffle';
import {TYPES} from '../types.js';
@@ -12,7 +12,8 @@ import {getMemberVoiceChannel, getMostPopularVoiceChannel} from '../utils/channe
@injectable()
export default class AddQueryToQueue {
- constructor(@inject(TYPES.Services.GetSongs) private readonly getSongs: GetSongs, @inject(TYPES.Managers.Player) private readonly playerManager: PlayerManager) {}
+ constructor(@inject(TYPES.Services.GetSongs) private readonly getSongs: GetSongs, @inject(TYPES.Managers.Player) private readonly playerManager: PlayerManager) {
+ }
public async addToQueue({
query,
@@ -25,7 +26,7 @@ export default class AddQueryToQueue {
addToFrontOfQueue: boolean;
shuffleAdditions: boolean;
shouldSplitChapters: boolean;
- interaction: CommandInteraction;
+ interaction: ChatInputCommandInteraction;
}): Promise<void> {
const guildId = interaction.guild!.id;
const player = this.playerManager.get(guildId);
@@ -121,7 +122,11 @@ export default class AddQueryToQueue {
}
newSongs.forEach(song => {
- player.add({...song, addedInChannelId: interaction.channel!.id, requestedBy: interaction.member!.user.id}, {immediate: addToFrontOfQueue ?? false});
+ player.add({
+ ...song,
+ addedInChannelId: interaction.channel!.id,
+ requestedBy: interaction.member!.user.id,
+ }, {immediate: addToFrontOfQueue ?? false});
});
const firstSong = newSongs[0];
diff --git a/src/services/config.ts b/src/services/config.ts
index 1b4f4c9..ca4fc68 100644
--- a/src/services/config.ts
+++ b/src/services/config.ts
@@ -18,7 +18,7 @@ const CONFIG_MAP = {
CACHE_DIR: path.join(DATA_DIR, 'cache'),
CACHE_LIMIT_IN_BYTES: xbytes.parseSize(process.env.CACHE_LIMIT ?? '2GB'),
BOT_STATUS: process.env.BOT_STATUS ?? 'online',
- BOT_ACTIVITY_TYPE: process.env.BOT_ACTIVITY_TYPE ?? 'LISTENING',
+ BOT_ACTIVITY_TYPE: process.env.BOT_ACTIVITY_TYPE ?? 'Listening',
BOT_ACTIVITY_URL: process.env.BOT_ACTIVITY_URL ?? '',
BOT_ACTIVITY: process.env.BOT_ACTIVITY ?? 'music',
} as const;
diff --git a/src/services/player.ts b/src/services/player.ts
index abcc8ca..c8a95a4 100644
--- a/src/services/player.ts
+++ b/src/services/player.ts
@@ -10,7 +10,7 @@ import {
AudioPlayerState,
AudioPlayerStatus,
createAudioPlayer,
- createAudioResource,
+ createAudioResource, DiscordGatewayAdapterCreator,
joinVoiceChannel,
StreamType,
VoiceConnection,
@@ -82,7 +82,7 @@ export default class {
this.voiceConnection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
- adapterCreator: channel.guild.voiceAdapterCreator,
+ adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator,
});
}
diff --git a/src/services/spotify-api.ts b/src/services/spotify-api.ts
index 7f90aff..4347df2 100644
--- a/src/services/spotify-api.ts
+++ b/src/services/spotify-api.ts
@@ -47,7 +47,7 @@ export default class {
items.push(...tracksResponse.items.map(playlistItem => playlistItem.track));
}
- const tracks = this.limitTracks(items, playlistLimit).map(this.toSpotifyTrack);
+ const tracks = this.limitTracks(items.filter(i => i !== null) as SpotifyApi.TrackObjectSimplified[], playlistLimit).map(this.toSpotifyTrack);
return [tracks, playlist];
}
diff --git a/src/types.ts b/src/types.ts
index fc1b5e2..3b64ea1 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,7 +8,6 @@ export const TYPES = {
ThirdParty: Symbol('ThirdParty'),
Managers: {
Player: Symbol('PlayerManager'),
- UpdatingQueueEmbed: Symbol('UpdatingQueueEmbed'),
},
Services: {
AddQueryToQueue: Symbol('AddQueryToQueue'),
diff --git a/src/utils/build-embed.ts b/src/utils/build-embed.ts
index c6e9551..3ee17ce 100644
--- a/src/utils/build-embed.ts
+++ b/src/utils/build-embed.ts
@@ -1,5 +1,5 @@
import getYouTubeID from 'get-youtube-id';
-import {MessageEmbed} from 'discord.js';
+import {EmbedBuilder} from 'discord.js';
import Player, {MediaSource, QueuedSong, STATUS} from '../services/player.js';
import getProgressBar from './get-progress-bar.js';
import {prettyTime} from './time.js';
@@ -50,7 +50,7 @@ const getPlayerUI = (player: Player) => {
return `${button} ${progressBar} \`[${elapsedTime}]\` 🔉`;
};
-export const buildPlayingMessageEmbed = (player: Player): MessageEmbed => {
+export const buildPlayingMessageEmbed = (player: Player): EmbedBuilder => {
const currentlyPlaying = player.getCurrent();
if (!currentlyPlaying) {
@@ -58,10 +58,9 @@ export const buildPlayingMessageEmbed = (player: Player): MessageEmbed => {
}
const {artist, thumbnailUrl, requestedBy} = currentlyPlaying;
- const message = new MessageEmbed();
-
+ const message = new EmbedBuilder();
message
- .setColor(player.status === STATUS.PLAYING ? 'DARK_GREEN' : 'DARK_RED')
+ .setColor(player.status === STATUS.PLAYING ? 'DarkGreen' : 'DarkRed')
.setTitle(player.status === STATUS.PLAYING ? 'Now Playing' : 'Paused')
.setDescription(`
**${getSongTitle(currentlyPlaying)}**
@@ -77,7 +76,7 @@ export const buildPlayingMessageEmbed = (player: Player): MessageEmbed => {
return message;
};
-export const buildQueueEmbed = (player: Player, page: number): MessageEmbed => {
+export const buildQueueEmbed = (player: Player, page: number): EmbedBuilder => {
const currentlyPlaying = player.getCurrent();
if (!currentlyPlaying) {
@@ -108,7 +107,7 @@ export const buildQueueEmbed = (player: Player, page: number): MessageEmbed => {
const playlistTitle = playlist ? `(${playlist.title})` : '';
const totalLength = player.getQueue().reduce((accumulator, current) => accumulator + current.length, 0);
- const message = new MessageEmbed();
+ const message = new EmbedBuilder();
let description = `**${getSongTitle(currentlyPlaying)}**\n`;
description += `Requested by: <@${requestedBy}>\n\n`;
@@ -121,11 +120,11 @@ export const buildQueueEmbed = (player: Player, page: number): MessageEmbed => {
message
.setTitle(player.status === STATUS.PLAYING ? 'Now Playing' : 'Queued songs')
- .setColor(player.status === STATUS.PLAYING ? 'DARK_GREEN' : 'NOT_QUITE_BLACK')
+ .setColor(player.status === STATUS.PLAYING ? 'DarkGreen' : 'NotQuiteBlack')
.setDescription(description)
- .addField('In queue', getQueueInfo(player), true)
- .addField('Total length', `${totalLength > 0 ? prettyTime(totalLength) : '-'}`, true)
- .addField('Page', `${page} out of ${maxQueuePage}`, true)
+ .addFields([{name: 'In queue', value: getQueueInfo(player), inline: true}, {
+ name: 'Total length', value: `${totalLength > 0 ? prettyTime(totalLength) : '-'}`, inline: true,
+ }, {name: 'Page', value: `${page} out of ${maxQueuePage}`, inline: true}])
.setFooter({text: `Source: ${artist} ${playlistTitle}`});
if (thumbnailUrl) {
@@ -134,3 +133,4 @@ export const buildQueueEmbed = (player: Player, page: number): MessageEmbed => {
return message;
};
+
diff --git a/src/utils/channels.ts b/src/utils/channels.ts
index f4e576a..25f3eb8 100644
--- a/src/utils/channels.ts
+++ b/src/utils/channels.ts
@@ -1,9 +1,9 @@
-import {Guild, VoiceChannel, User, GuildMember} from 'discord.js';
+import {ChannelType, Guild, GuildMember, User, VoiceChannel} from 'discord.js';
export const isUserInVoice = (guild: Guild, user: User): boolean => {
let inVoice = false;
- guild.channels.cache.filter(channel => channel.type === 'GUILD_VOICE').forEach(channel => {
+ guild.channels.cache.filter(channel => channel.type === ChannelType.GuildVoice).forEach(channel => {
if ((channel as VoiceChannel).members.find(member => member.id === user.id)) {
inVoice = true;
}
@@ -22,7 +22,7 @@ export const getSizeWithoutBots = (channel: VoiceChannel): number => channel.mem
export const getMemberVoiceChannel = (member?: GuildMember): [VoiceChannel, number] | null => {
const channel = member?.voice?.channel;
- if (channel && channel.type === 'GUILD_VOICE') {
+ if (channel && channel.type === ChannelType.GuildVoice) {
return [
channel,
getSizeWithoutBots(channel),
@@ -41,7 +41,7 @@ export const getMostPopularVoiceChannel = (guild: Guild): [VoiceChannel, number]
const voiceChannels: PopularResult[] = [];
for (const [_, channel] of guild.channels.cache) {
- if (channel.type === 'GUILD_VOICE') {
+ if (channel.type === ChannelType.GuildVoice) {
const size = getSizeWithoutBots(channel);
voiceChannels.push({
diff --git a/src/utils/get-youtube-and-spotify-suggestions-for.ts b/src/utils/get-youtube-and-spotify-suggestions-for.ts
index 10f9394..6594b52 100644
--- a/src/utils/get-youtube-and-spotify-suggestions-for.ts
+++ b/src/utils/get-youtube-and-spotify-suggestions-for.ts
@@ -1,4 +1,4 @@
-import {ApplicationCommandOptionChoice} from 'discord.js';
+import {APIApplicationCommandOptionChoice} from 'discord-api-types/v10';
import SpotifyWebApi from 'spotify-web-api-node';
import getYouTubeSuggestionsFor from './get-youtube-suggestions-for.js';
@@ -14,7 +14,7 @@ const filterDuplicates = <T extends {name: string}>(items: T[]) => {
return results;
};
-const getYouTubeAndSpotifySuggestionsFor = async (query: string, spotify: SpotifyWebApi, limit = 10): Promise<ApplicationCommandOptionChoice[]> => {
+const getYouTubeAndSpotifySuggestionsFor = async (query: string, spotify: SpotifyWebApi, limit = 10): Promise<APIApplicationCommandOptionChoice[]> => {
const [youtubeSuggestions, spotifyResults] = await Promise.all([
getYouTubeSuggestionsFor(query),
spotify.search(query, ['track', 'album'], {limit: 5}),
@@ -35,7 +35,7 @@ const getYouTubeAndSpotifySuggestionsFor = async (query: string, spotify: Spotif
const maxYouTubeSuggestions = limit - numOfSpotifySuggestions;
const numOfYouTubeSuggestions = Math.min(maxYouTubeSuggestions, totalYouTubeResults);
- const suggestions: ApplicationCommandOptionChoice[] = [];
+ const suggestions: APIApplicationCommandOptionChoice[] = [];
suggestions.push(
...youtubeSuggestions
diff --git a/src/utils/register-commands-on-guild.ts b/src/utils/register-commands-on-guild.ts
new file mode 100644
index 0000000..ebd94e4
--- /dev/null
+++ b/src/utils/register-commands-on-guild.ts
@@ -0,0 +1,19 @@
+import {REST} from '@discordjs/rest';
+import {Routes} from 'discord-api-types/v10';
+import Command from '../commands';
+
+interface RegisterCommandsOnGuildOptions {
+ rest: REST;
+ applicationId: string;
+ guildId: string;
+ commands: Array<Command['slashCommand']>;
+}
+
+const registerCommandsOnGuild = async ({rest, applicationId, guildId, commands}: RegisterCommandsOnGuildOptions) => {
+ await rest.put(
+ Routes.applicationGuildCommands(applicationId, guildId),
+ {body: commands.map(command => command.toJSON())},
+ );
+};
+
+export default registerCommandsOnGuild;
diff --git a/src/utils/update-permissions-for-guild.ts b/src/utils/update-permissions-for-guild.ts
deleted file mode 100644
index 64110a7..0000000
--- a/src/utils/update-permissions-for-guild.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import {ApplicationCommandPermissionData, Guild} from 'discord.js';
-import {prisma} from './db.js';
-
-const COMMANDS_TO_LIMIT_TO_GUILD_OWNER = ['config'];
-
-const updatePermissionsForGuild = async (guild: Guild) => {
- const settings = await prisma.setting.findUnique({
- where: {
- guildId: guild.id,
- },
- });
-
- if (!settings) {
- throw new Error('could not find settings for guild');
- }
-
- const permissions: ApplicationCommandPermissionData[] = [
- {
- id: guild.ownerId,
- type: 'USER',
- permission: true,
- },
- {
- id: guild.roles.everyone.id,
- type: 'ROLE',
- permission: false,
- },
- ];
-
- if (settings.invitedByUserId) {
- permissions.push({
- id: settings.invitedByUserId,
- type: 'USER',
- permission: true,
- });
- }
-
- const commands = await guild.commands.fetch();
-
- await guild.commands.permissions.set({fullPermissions: commands.map(command => ({
- id: command.id,
- permissions: COMMANDS_TO_LIMIT_TO_GUILD_OWNER.includes(command.name) ? permissions : [
- ...permissions,
- ...(settings.roleId ? [{
- id: settings.roleId,
- type: 'ROLE' as const,
- permission: true,
- }] : []),
- ],
- }))});
-};
-
-export default updatePermissionsForGuild;