aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHellyson Rodrigo Parteka <[email protected]>2021-11-12 16:30:18 -0300
committerHellyson Rodrigo Parteka <[email protected]>2021-11-12 16:30:18 -0300
commit8448feebb6e8154ed0338989a0646be7918eb595 (patch)
treeaa19f5f08c3208be70898f044f15ae694cf34b8b /src
parenta4e67d63839154916783e7b4a5f6c65006e759f3 (diff)
downloadmuse-8448feebb6e8154ed0338989a0646be7918eb595.tar.xz
muse-8448feebb6e8154ed0338989a0646be7918eb595.zip
feat: discord.js v13
Diffstat (limited to 'src')
-rw-r--r--src/bot.ts7
-rw-r--r--src/commands/config.ts9
-rw-r--r--src/commands/help.ts12
-rw-r--r--src/commands/queue.ts2
-rw-r--r--src/commands/shortcuts.ts4
-rw-r--r--src/events/guild-create.ts6
-rw-r--r--src/events/voice-state-update.ts5
-rw-r--r--src/inversify.config.ts11
-rw-r--r--src/services/player.ts53
-rw-r--r--src/utils/channels.ts10
10 files changed, 73 insertions, 46 deletions
diff --git a/src/bot.ts b/src/bot.ts
index 5d2e97e..b49c1f6 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -11,6 +11,7 @@ import handleVoiceStateUpdate from './events/voice-state-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';
@injectable()
export default class {
@@ -34,7 +35,7 @@ export default class {
commandNames.forEach(commandName => this.commands.set(commandName, command));
});
- this.client.on('message', async (msg: Message) => {
+ this.client.on('messageCreate', async (msg: Message) => {
// Get guild settings
if (!msg.guild) {
return;
@@ -44,7 +45,8 @@ export default class {
if (!settings) {
// Got into a bad state, send owner welcome message
- return this.client.emit('guildCreate', msg.guild);
+ this.client.emit('guildCreate', msg.guild);
+ return;
}
const {prefix, channel} = settings;
@@ -95,6 +97,7 @@ export default class {
});
this.client.on('ready', async () => {
+ debug(generateDependencyReport());
console.log(`Ready! Invite the bot with https://discordapp.com/oauth2/authorize?client_id=${this.client.user?.id ?? ''}&scope=bot&permissions=36752448`);
});
diff --git a/src/commands/config.ts b/src/commands/config.ts
index 0e5728a..8f3e0aa 100644
--- a/src/commands/config.ts
+++ b/src/commands/config.ts
@@ -1,4 +1,4 @@
-import {TextChannel, Message, GuildChannel} from 'discord.js';
+import {TextChannel, Message, GuildChannel, ThreadChannel} from 'discord.js';
import {injectable} from 'inversify';
import {Settings} from '../models/index.js';
import errorMsg from '../utils/error-msg.js';
@@ -20,6 +20,7 @@ export default class implements Command {
if (settings) {
let response = `prefix: \`${settings.prefix}\`\n`;
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
response += `channel: ${msg.guild!.channels.cache.get(settings.channel)!.toString()}`;
await msg.channel.send(response);
@@ -35,7 +36,7 @@ export default class implements Command {
return;
}
- if (msg.author.id !== msg.guild!.owner!.id) {
+ if (msg.author.id !== msg.guild!.ownerId) {
await msg.channel.send(errorMsg('not authorized'));
return;
}
@@ -51,7 +52,7 @@ export default class implements Command {
}
case 'channel': {
- let channel: GuildChannel | undefined;
+ let channel: GuildChannel | ThreadChannel | undefined;
if (args[1].includes('<#') && args[1].includes('>')) {
channel = msg.guild!.channels.cache.find(c => c.id === args[1].slice(2, args[1].indexOf('>')));
@@ -59,7 +60,7 @@ export default class implements Command {
channel = msg.guild!.channels.cache.find(c => c.name === args[1]);
}
- if (channel && channel.type === 'text') {
+ if (channel && channel.type === 'GUILD_TEXT') {
await Settings.update({channel: channel.id}, {where: {guildId: msg.guild!.id}});
await Promise.all([
diff --git a/src/commands/help.ts b/src/commands/help.ts
index f150c54..7058efd 100644
--- a/src/commands/help.ts
+++ b/src/commands/help.ts
@@ -1,4 +1,4 @@
-import {Message} from 'discord.js';
+import {Message, Util} from 'discord.js';
import {injectable} from 'inversify';
import Command from '.';
import {TYPES} from '../types.js';
@@ -29,7 +29,7 @@ export default class implements Command {
const {prefix} = settings;
- const res = this.commands.sort((a, b) => a.name.localeCompare(b.name)).reduce((content, command) => {
+ const res = Util.splitMessage(this.commands.sort((a, b) => a.name.localeCompare(b.name)).reduce((content, command) => {
const aliases = command.aliases.reduce((str, alias, i) => {
str += alias;
@@ -53,9 +53,13 @@ export default class implements Command {
content += '\n';
return content;
- }, '');
+ }, ''));
+
+ for (const r of res) {
+ // eslint-disable-next-line no-await-in-loop
+ await msg.author.send(r);
+ }
- await msg.author.send(res, {split: true});
await msg.react('🇩');
await msg.react('🇲');
}
diff --git a/src/commands/queue.ts b/src/commands/queue.ts
index 85420e8..1c0735c 100644
--- a/src/commands/queue.ts
+++ b/src/commands/queue.ts
@@ -74,7 +74,7 @@ export default class implements Command {
embed.addField('Page', `${queuePage} out of ${maxQueuePage}`, false);
- await msg.channel.send(embed);
+ await msg.channel.send({embeds: [embed]});
} else {
await msg.channel.send('queue empty');
}
diff --git a/src/commands/shortcuts.ts b/src/commands/shortcuts.ts
index 109d5fc..929a5c1 100644
--- a/src/commands/shortcuts.ts
+++ b/src/commands/shortcuts.ts
@@ -55,7 +55,7 @@ export default class implements Command {
const newShortcut = {shortcut: shortcutName, command, guildId: msg.guild!.id, authorId: msg.author.id};
if (shortcut) {
- if (shortcut.authorId !== msg.author.id && msg.author.id !== msg.guild!.owner!.id) {
+ if (shortcut.authorId !== msg.author.id && msg.author.id !== msg.guild!.ownerId) {
await msg.channel.send(errorMsg('you do\'nt have permission to do that'));
return;
}
@@ -80,7 +80,7 @@ export default class implements Command {
}
// Check permissions
- if (shortcut.authorId !== msg.author.id && msg.author.id !== msg.guild!.owner!.id) {
+ if (shortcut.authorId !== msg.author.id && msg.author.id !== msg.guild!.ownerId) {
await msg.channel.send(errorMsg('you don\'t have permission to do that'));
return;
}
diff --git a/src/events/guild-create.ts b/src/events/guild-create.ts
index 3bcf281..01f0910 100644
--- a/src/events/guild-create.ts
+++ b/src/events/guild-create.ts
@@ -9,7 +9,7 @@ const DEFAULT_PREFIX = '!';
export default async (guild: Guild): Promise<void> => {
await Settings.upsert({guildId: guild.id, prefix: DEFAULT_PREFIX});
- const owner = await guild.client.users.fetch(guild.ownerID);
+ const owner = await guild.client.users.fetch(guild.ownerId);
let firstStep = '👋 Hi!\n';
firstStep += 'I just need to ask a few questions before you start listening to music.\n\n';
@@ -27,7 +27,7 @@ export default async (guild: Guild): Promise<void> => {
const emojiChannels: EmojiChannel[] = [];
for (const [channelId, channel] of guild.channels.cache) {
- if (channel.type === 'text') {
+ if (channel.type === 'GUILD_TEXT') {
emojiChannels.push({
name: channel.name,
id: channelId,
@@ -65,7 +65,7 @@ export default async (guild: Guild): Promise<void> => {
await owner.send(secondStep);
- const prefixResponses = await firstStepMsg.channel.awaitMessages((r: Message) => r.content.length === 1, {max: 1});
+ const prefixResponses = await firstStepMsg.channel.awaitMessages({filter: (r: Message) => r.content.length === 1, max: 1});
const prefixCharacter = prefixResponses.first()!.content;
diff --git a/src/events/voice-state-update.ts b/src/events/voice-state-update.ts
index 6440949..69b4504 100644
--- a/src/events/voice-state-update.ts
+++ b/src/events/voice-state-update.ts
@@ -1,4 +1,4 @@
-import {VoiceState} from 'discord.js';
+import {VoiceChannel, VoiceState} from 'discord.js';
import container from '../inversify.config.js';
import {TYPES} from '../types.js';
import PlayerManager from '../managers/player.js';
@@ -10,7 +10,8 @@ export default (oldState: VoiceState, _: VoiceState): void => {
const player = playerManager.get(oldState.guild.id);
if (player.voiceConnection) {
- if (getSizeWithoutBots(player.voiceConnection.channel) === 0) {
+ const voiceChannel: VoiceChannel = oldState.guild.channels.cache.get(player.voiceConnection.joinConfig.channelId!) as VoiceChannel;
+ if (!voiceChannel || getSizeWithoutBots(voiceChannel) === 0) {
player.disconnect();
}
}
diff --git a/src/inversify.config.ts b/src/inversify.config.ts
index 00a2507..c6809d2 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} from 'discord.js';
+import {Client, Intents} from 'discord.js';
import ConfigProvider from './services/config.js';
// Managers
@@ -32,9 +32,16 @@ import CacheProvider from './services/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.GUILD_VOICE_STATES); // To listen for voice state changes (voiceStateUpdate event)
+
// Bot
container.bind<Bot>(TYPES.Bot).to(Bot).inSingletonScope();
-container.bind<Client>(TYPES.Client).toConstantValue(new Client());
+container.bind<Client>(TYPES.Client).toConstantValue(new Client({intents}));
// Managers
container.bind<PlayerManager>(TYPES.Managers.Player).to(PlayerManager).inSingletonScope();
diff --git a/src/services/player.ts b/src/services/player.ts
index 74f409b..9faef18 100644
--- a/src/services/player.ts
+++ b/src/services/player.ts
@@ -1,4 +1,4 @@
-import {VoiceConnection, VoiceChannel, StreamDispatcher, Snowflake, Client, TextChannel} from 'discord.js';
+import {VoiceChannel, Snowflake, Client, TextChannel} from 'discord.js';
import {promises as fs, createWriteStream} from 'fs';
import {Readable, PassThrough} from 'stream';
import path from 'path';
@@ -8,6 +8,7 @@ import {WriteStream} from 'fs-capacitor';
import ffmpeg from 'fluent-ffmpeg';
import shuffle from 'array-shuffle';
import errorMsg from '../utils/error-msg.js';
+import {AudioPlayer, AudioPlayerStatus, createAudioPlayer, createAudioResource, joinVoiceChannel, StreamType, VoiceConnection, VoiceConnectionStatus} from '@discordjs/voice';
export interface QueuedPlaylist {
title: string;
@@ -35,7 +36,7 @@ export default class {
private queue: QueuedSong[] = [];
private queuePosition = 0;
private readonly cacheDir: string;
- private dispatcher: StreamDispatcher | null = null;
+ private audioPlayer: AudioPlayer | null = null;
private nowPlaying: QueuedSong | null = null;
private playPositionInterval: NodeJS.Timeout | undefined;
private lastSongURL = '';
@@ -50,23 +51,26 @@ export default class {
}
async connect(channel: VoiceChannel): Promise<void> {
- const conn = await channel.join();
+ const conn = joinVoiceChannel({
+ channelId: channel.id,
+ guildId: channel.guild.id,
+ adapterCreator: channel.guild.voiceAdapterCreator,
+ });
this.voiceConnection = conn;
}
- disconnect(breakConnection = true): void {
+ disconnect(): void {
if (this.voiceConnection) {
if (this.status === STATUS.PLAYING) {
this.pause();
}
- if (breakConnection) {
- this.voiceConnection.disconnect();
- }
+ this.voiceConnection.destroy();
+ this.audioPlayer?.stop();
this.voiceConnection = null;
- this.dispatcher = null;
+ this.audioPlayer = null;
}
}
@@ -88,8 +92,11 @@ export default class {
}
const stream = await this.getStream(currentSong.url, {seek: positionSeconds});
- this.dispatcher = this.voiceConnection.play(stream, {type: 'webm/opus', bitrate: 'auto'});
-
+ this.audioPlayer = createAudioPlayer();
+ this.voiceConnection.subscribe(this.audioPlayer);
+ this.audioPlayer.play(createAudioResource(stream, {
+ inputType: StreamType.WebmOpus,
+ }));
this.attachListeners();
this.startTrackingPosition(positionSeconds);
@@ -117,8 +124,8 @@ export default class {
// Resume from paused state
if (this.status === STATUS.PAUSED && currentSong.url === this.nowPlaying?.url) {
- if (this.dispatcher) {
- this.dispatcher.resume();
+ if (this.audioPlayer) {
+ this.audioPlayer.unpause();
this.status = STATUS.PLAYING;
this.startTrackingPosition();
return;
@@ -132,7 +139,11 @@ export default class {
try {
const stream = await this.getStream(currentSong.url);
- this.dispatcher = this.voiceConnection.play(stream, {type: 'webm/opus'});
+ this.audioPlayer = createAudioPlayer();
+ this.voiceConnection.subscribe(this.audioPlayer);
+ this.audioPlayer.play(createAudioResource(stream, {
+ inputType: StreamType.WebmOpus,
+ }));
this.attachListeners();
@@ -170,8 +181,8 @@ export default class {
this.status = STATUS.PAUSED;
- if (this.dispatcher) {
- this.dispatcher.pause();
+ if (this.audioPlayer) {
+ this.audioPlayer.pause();
}
this.stopTrackingPosition();
@@ -441,22 +452,22 @@ export default class {
return;
}
- this.voiceConnection.on('disconnect', this.onVoiceConnectionDisconnect.bind(this));
+ this.voiceConnection.on(VoiceConnectionStatus.Disconnected, this.onVoiceConnectionDisconnect.bind(this));
- if (!this.dispatcher) {
+ if (!this.audioPlayer) {
return;
}
- this.dispatcher.on('speaking', this.onVoiceConnectionSpeaking.bind(this));
+ this.audioPlayer.on('stateChange', this.onAudioPlayerStateChange.bind(this));
}
private onVoiceConnectionDisconnect(): void {
- this.disconnect(false);
+ this.disconnect();
}
- private async onVoiceConnectionSpeaking(isSpeaking: boolean): Promise<void> {
+ private async onAudioPlayerStateChange(_oldState: {status: AudioPlayerStatus}, newState: {status: AudioPlayerStatus}): Promise<void> {
// Automatically advance queued song at end
- if (!isSpeaking && this.status === STATUS.PLAYING) {
+ if (newState.status === AudioPlayerStatus.Idle && this.status === STATUS.PLAYING) {
await this.forward(1);
}
}
diff --git a/src/utils/channels.ts b/src/utils/channels.ts
index 4c5b6b0..2d074b4 100644
--- a/src/utils/channels.ts
+++ b/src/utils/channels.ts
@@ -3,8 +3,8 @@ import {Guild, VoiceChannel, User, GuildMember} from 'discord.js';
export const isUserInVoice = (guild: Guild, user: User): boolean => {
let inVoice = false;
- guild.channels.cache.filter(channel => channel.type === 'voice').forEach(channel => {
- if (channel.members.array().find(member => member.id === user.id)) {
+ guild.channels.cache.filter(channel => channel.type === 'GUILD_VOICE').forEach(channel => {
+ if ((channel as VoiceChannel).members.find(member => member.id === user.id)) {
inVoice = true;
}
});
@@ -12,7 +12,7 @@ export const isUserInVoice = (guild: Guild, user: User): boolean => {
return inVoice;
};
-export const getSizeWithoutBots = (channel: VoiceChannel): number => channel.members.array().reduce((s, member) => {
+export const getSizeWithoutBots = (channel: VoiceChannel): number => channel.members.reduce((s, member) => {
if (!member.user.bot) {
s++;
}
@@ -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 === 'voice') {
+ if (channel && channel.type === 'GUILD_VOICE') {
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 === 'voice') {
+ if (channel.type === 'GUILD_VOICE') {
const size = getSizeWithoutBots(channel as VoiceChannel);
voiceChannels.push({