mirror of
https://github.com/BluemediaGER/muse.git
synced 2024-11-22 16:55:30 +01:00
fix command permission handling and push discord to v10 (#640)
Co-authored-by: Max Isom <hi@maxisom.me>
This commit is contained in:
parent
1ef05aba9d
commit
eb2885b206
|
@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
### Changed
|
||||
- Migrated to the v10 API
|
||||
- Command permissions are now configured differently: you can now configure permissions in Discord's UI rather than through the bot. See the [wiki page](https://github.com/codetheweb/muse/wiki/Configuring-Bot-Permissions) for details.
|
||||
- 🚨 when you upgrade to this version, the role you manually set with `/config set-role` will no longer be respected. Check the above link for how to re-configure permissions.
|
||||
|
||||
## [1.9.0] - 2022-04-23
|
||||
### Changed
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `invitedByUserId` on the `Setting` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Setting" (
|
||||
"guildId" TEXT NOT NULL PRIMARY KEY,
|
||||
"playlistLimit" INTEGER NOT NULL DEFAULT 50,
|
||||
"secondsToWaitAfterQueueEmpties" INTEGER NOT NULL DEFAULT 30,
|
||||
"leaveIfNoListeners" BOOLEAN NOT NULL DEFAULT true,
|
||||
"roleId" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL
|
||||
);
|
||||
INSERT INTO "new_Setting" ("createdAt", "guildId", "leaveIfNoListeners", "playlistLimit", "roleId", "secondsToWaitAfterQueueEmpties", "updatedAt") SELECT "createdAt", "guildId", "leaveIfNoListeners", "playlistLimit", "roleId", "secondsToWaitAfterQueueEmpties", "updatedAt" FROM "Setting";
|
||||
DROP TABLE "Setting";
|
||||
ALTER TABLE "new_Setting" RENAME TO "Setting";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
13
package.json
13
package.json
|
@ -49,7 +49,7 @@
|
|||
"prisma": "^3.11.0",
|
||||
"release-it": "^14.11.8",
|
||||
"type-fest": "^2.12.0",
|
||||
"typescript": "^4.6.2"
|
||||
"typescript": "^4.6.4"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
@ -74,17 +74,17 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.12.0",
|
||||
"@discordjs/builders": "0.14.0-dev.1652443433-d522320",
|
||||
"@discordjs/opus": "^0.7.0",
|
||||
"@discordjs/rest": "^0.3.0",
|
||||
"@discordjs/voice": "^0.8.0",
|
||||
"@discordjs/rest": "0.5.0-dev.1651147752-679dcda",
|
||||
"@discordjs/voice": "0.10.0-dev.1651147759-679dcda",
|
||||
"@prisma/client": "^3.11.0",
|
||||
"@types/libsodium-wrappers": "^0.7.9",
|
||||
"array-shuffle": "^3.0.0",
|
||||
"debug": "^4.3.3",
|
||||
"delay": "^5.0.0",
|
||||
"discord-api-types": "^0.29.0",
|
||||
"discord.js": "^13.6.0",
|
||||
"discord-api-types": "0.32.1",
|
||||
"discord.js": "14.0.0-dev.1652443445-d522320",
|
||||
"dotenv": "^16.0.0",
|
||||
"esmo": "0.14.1",
|
||||
"execa": "^6.1.0",
|
||||
|
@ -108,6 +108,7 @@
|
|||
"reflect-metadata": "^0.1.13",
|
||||
"spotify-uri": "^2.2.0",
|
||||
"spotify-web-api-node": "^5.0.2",
|
||||
"sync-fetch": "^0.3.1",
|
||||
"xbytes": "^1.7.0",
|
||||
"youtube.ts": "^0.2.8",
|
||||
"ytdl-core": "^4.11.0",
|
||||
|
|
|
@ -25,7 +25,6 @@ model KeyValueCache {
|
|||
|
||||
model Setting {
|
||||
guildId String @id
|
||||
invitedByUserId String?
|
||||
playlistLimit Int @default(50)
|
||||
secondsToWaitAfterQueueEmpties Int @default(30)
|
||||
leaveIfNoListeners Boolean @default(true)
|
||||
|
|
47
src/bot.ts
47
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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}});
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.');
|
||||
};
|
||||
|
|
|
@ -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;
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ export const TYPES = {
|
|||
ThirdParty: Symbol('ThirdParty'),
|
||||
Managers: {
|
||||
Player: Symbol('PlayerManager'),
|
||||
UpdatingQueueEmbed: Symbol('UpdatingQueueEmbed'),
|
||||
},
|
||||
Services: {
|
||||
AddQueryToQueue: Symbol('AddQueryToQueue'),
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
19
src/utils/register-commands-on-guild.ts
Normal file
19
src/utils/register-commands-on-guild.ts
Normal file
|
@ -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;
|
|
@ -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;
|
|
@ -1,10 +1,9 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2019", "DOM"],
|
||||
"lib": ["esnext"],
|
||||
"target": "es2018",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
|
|
Loading…
Reference in a new issue