From 8acc4f23f245d1c8fc80992a7117d5f6af168357 Mon Sep 17 00:00:00 2001 From: xHyperElectric <71343435+xHyperElectric@users.noreply.github.com> Date: Sun, 26 Feb 2023 21:50:36 -0500 Subject: [PATCH 1/6] Add /replay command (#901) Co-authored-by: xHyperElectric Co-authored-by: xHyperElectric Co-authored-by: Max Isom --- CHANGELOG.md | 2 ++ src/commands/replay.ts | 42 +++++++++++++++++++++++++++++++++++++++++ src/inversify.config.ts | 12 +++++++----- 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 src/commands/replay.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e46c188..d05eedd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ 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] +### Added +- Added a '/replay' to restart the current song. Alias for '/seek time: 0' ## [2.1.9] - 2023-02-14 ### Fixed diff --git a/src/commands/replay.ts b/src/commands/replay.ts new file mode 100644 index 0000000..43b1057 --- /dev/null +++ b/src/commands/replay.ts @@ -0,0 +1,42 @@ +import {ChatInputCommandInteraction} from 'discord.js'; +import {TYPES} from '../types.js'; +import {inject, injectable} from 'inversify'; +import PlayerManager from '../managers/player.js'; +import Command from '.'; +import {SlashCommandBuilder} from '@discordjs/builders'; + +@injectable() +export default class implements Command { + public readonly slashCommand = new SlashCommandBuilder() + .setName('replay') + .setDescription('replay the current song'); + + public requiresVC = true; + + private readonly playerManager: PlayerManager; + + constructor(@inject(TYPES.Managers.Player) playerManager: PlayerManager) { + this.playerManager = playerManager; + } + + public async execute(interaction: ChatInputCommandInteraction): Promise { + const player = this.playerManager.get(interaction.guild!.id); + + const currentSong = player.getCurrent(); + + if (!currentSong) { + throw new Error('nothing is playing'); + } + + if (currentSong.isLive) { + throw new Error('can\'t replay a livestream'); + } + + await Promise.all([ + player.seek(0), + interaction.deferReply(), + ]); + + await interaction.editReply('👍 replayed the current song'); + } +} diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 002d72c..1a3b85c 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -14,30 +14,31 @@ import GetSongs from './services/get-songs.js'; import YoutubeAPI from './services/youtube-api.js'; import SpotifyAPI from './services/spotify-api.js'; -// Comands +// Commands import Command from './commands'; import Clear from './commands/clear.js'; import Config from './commands/config.js'; import Disconnect from './commands/disconnect.js'; import Favorites from './commands/favorites.js'; import ForwardSeek from './commands/fseek.js'; +import Loop from './commands/loop'; import Move from './commands/move.js'; +import Next from './commands/next.js'; import NowPlaying from './commands/now-playing.js'; import Pause from './commands/pause.js'; import Play from './commands/play.js'; import QueueCommand from './commands/queue.js'; import Remove from './commands/remove.js'; +import Replay from './commands/replay.js'; import Resume from './commands/resume.js'; import Seek from './commands/seek.js'; import Shuffle from './commands/shuffle.js'; import Skip from './commands/skip.js'; -import Next from './commands/next.js'; import Stop from './commands/stop.js'; import Unskip from './commands/unskip.js'; import ThirdParty from './services/third-party.js'; import FileCacheProvider from './services/file-cache.js'; import KeyValueCacheProvider from './services/key-value-cache.js'; -import Loop from './commands/loop'; const container = new Container(); @@ -67,20 +68,21 @@ container.bind(TYPES.Services.SpotifyAPI).to(SpotifyAPI).inSingleton Disconnect, Favorites, ForwardSeek, + Loop, Move, + Next, NowPlaying, Pause, Play, QueueCommand, Remove, + Replay, Resume, Seek, Shuffle, Skip, - Next, Stop, Unskip, - Loop, ].forEach(command => { container.bind(TYPES.Command).to(command).inSingletonScope(); }); From f7c3d87722f098ee0e3d518261a269992c5e8604 Mon Sep 17 00:00:00 2001 From: Max Isom Date: Sun, 26 Feb 2023 18:54:57 -0800 Subject: [PATCH 2/6] Release 2.2.0 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d05eedd..8e82628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ 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] + +## [2.2.0] - 2023-02-26 ### Added - Added a '/replay' to restart the current song. Alias for '/seek time: 0' @@ -217,7 +219,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release -[unreleased]: https://github.com/codetheweb/muse/compare/v2.1.9...HEAD +[unreleased]: https://github.com/codetheweb/muse/compare/v2.2.0...HEAD +[2.2.0]: https://github.com/codetheweb/muse/compare/v2.1.9...v2.2.0 [2.1.9]: https://github.com/codetheweb/muse/compare/v2.1.8...v2.1.9 [2.1.8]: https://github.com/codetheweb/muse/compare/v2.1.7...v2.1.8 [2.1.7]: https://github.com/codetheweb/muse/compare/v2.1.6...v2.1.7 diff --git a/package.json b/package.json index 530c782..c61c4ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "muse", - "version": "2.1.9", + "version": "2.2.0", "description": "🎧 a self-hosted Discord music bot that doesn't suck ", "repository": "git@github.com:codetheweb/muse.git", "author": "Max Isom ", From 6926e39c5679e75c9ca6ef1a123abdce3d7ff992 Mon Sep 17 00:00:00 2001 From: Federico Rapetti Date: Sat, 4 Mar 2023 22:38:36 +0100 Subject: [PATCH 3/6] Temporary workaround for VoiceConnection stuck in signalling state (#907) --- CHANGELOG.md | 2 ++ src/services/player.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e82628..0386d30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ 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] +### Fixed +- Add temporary workaround to avoid VoiceConnection being stuck in signalling state ## [2.2.0] - 2023-02-26 ### Added diff --git a/src/services/player.ts b/src/services/player.ts index 78f72b2..0b57ad7 100644 --- a/src/services/player.ts +++ b/src/services/player.ts @@ -84,6 +84,22 @@ export default class { guildId: channel.guild.id, adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator, }); + + // Workaround to disable keepAlive + this.voiceConnection.on('stateChange', (oldState, newState) => { + /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */ + const oldNetworking = Reflect.get(oldState, 'networking'); + const newNetworking = Reflect.get(newState, 'networking'); + + const networkStateChangeHandler = (_: any, newNetworkState: any) => { + const newUdp = Reflect.get(newNetworkState, 'udp'); + clearInterval(newUdp?.keepAliveInterval); + }; + + oldNetworking?.off('stateChange', networkStateChangeHandler); + newNetworking?.on('stateChange', networkStateChangeHandler); + /* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */ + }); } disconnect(): void { From 02ee8aefc86b3a9902827874260d59115e73af13 Mon Sep 17 00:00:00 2001 From: Federico Rapetti Date: Sat, 4 Mar 2023 22:39:33 +0100 Subject: [PATCH 4/6] Create guild settings if not found (#911) Co-authored-by: Max Isom --- CHANGELOG.md | 1 + src/commands/config.ts | 7 ++----- src/events/guild-create.ts | 8 ++++---- src/events/voice-state-update.ts | 8 ++------ src/services/add-query-to-queue.ts | 8 ++------ src/services/player.ts | 8 ++------ src/utils/get-guild-settings.ts | 12 ++++++++++++ 7 files changed, 25 insertions(+), 27 deletions(-) create mode 100644 src/utils/get-guild-settings.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 0386d30..b7aa1c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed +- Create the guild settings when not found instead of returning an error - Add temporary workaround to avoid VoiceConnection being stuck in signalling state ## [2.2.0] - 2023-02-26 diff --git a/src/commands/config.ts b/src/commands/config.ts index 9ed78d7..158f4f4 100644 --- a/src/commands/config.ts +++ b/src/commands/config.ts @@ -3,6 +3,7 @@ import {ChatInputCommandInteraction, EmbedBuilder, PermissionFlagsBits} from 'di import {injectable} from 'inversify'; import {prisma} from '../utils/db.js'; import Command from './index.js'; +import {getGuildSettings} from '../utils/get-guild-settings'; @injectable() export default class implements Command { @@ -96,11 +97,7 @@ export default class implements Command { case 'get': { const embed = new EmbedBuilder().setTitle('Config'); - const config = await prisma.setting.findUnique({where: {guildId: interaction.guild!.id}}); - - if (!config) { - throw new Error('no config found'); - } + const config = await getGuildSettings(interaction.guild!.id); const settingsToShow = { 'Playlist Limit': config.playlistLimit, diff --git a/src/events/guild-create.ts b/src/events/guild-create.ts index af195ce..d566c13 100644 --- a/src/events/guild-create.ts +++ b/src/events/guild-create.ts @@ -8,20 +8,20 @@ import {REST} from '@discordjs/rest'; import {Setting} from '@prisma/client'; import registerCommandsOnGuild from '../utils/register-commands-on-guild.js'; -export async function createGuildSettings(guild: Guild): Promise { +export async function createGuildSettings(guildId: string): Promise { return prisma.setting.upsert({ where: { - guildId: guild.id, + guildId, }, create: { - guildId: guild.id, + guildId, }, update: {}, }); } export default async (guild: Guild): Promise => { - await createGuildSettings(guild); + await createGuildSettings(guild.id); const config = container.get(TYPES.Config); diff --git a/src/events/voice-state-update.ts b/src/events/voice-state-update.ts index 60a40b1..e941a70 100644 --- a/src/events/voice-state-update.ts +++ b/src/events/voice-state-update.ts @@ -3,7 +3,7 @@ import container from '../inversify.config.js'; import {TYPES} from '../types.js'; import PlayerManager from '../managers/player.js'; import {getSizeWithoutBots} from '../utils/channels.js'; -import {prisma} from '../utils/db.js'; +import {getGuildSettings} from '../utils/get-guild-settings'; export default async (oldState: VoiceState, _: VoiceState): Promise => { const playerManager = container.get(TYPES.Managers.Player); @@ -12,11 +12,7 @@ export default async (oldState: VoiceState, _: VoiceState): Promise => { if (player.voiceConnection) { const voiceChannel: VoiceChannel = oldState.guild.channels.cache.get(player.voiceConnection.joinConfig.channelId!) as VoiceChannel; - const settings = await prisma.setting.findUnique({where: {guildId: player.guildId}}); - - if (!settings) { - throw new Error('Could not find settings for guild'); - } + const settings = await getGuildSettings(player.guildId); const {leaveIfNoListeners} = settings; if (!voiceChannel || (getSizeWithoutBots(voiceChannel) === 0 && leaveIfNoListeners)) { diff --git a/src/services/add-query-to-queue.ts b/src/services/add-query-to-queue.ts index 15192b4..dc972cd 100644 --- a/src/services/add-query-to-queue.ts +++ b/src/services/add-query-to-queue.ts @@ -6,9 +6,9 @@ import {TYPES} from '../types.js'; import GetSongs from '../services/get-songs.js'; import {SongMetadata, STATUS} from './player.js'; import PlayerManager from '../managers/player.js'; -import {prisma} from '../utils/db.js'; import {buildPlayingMessageEmbed} from '../utils/build-embed.js'; import {getMemberVoiceChannel, getMostPopularVoiceChannel} from '../utils/channels.js'; +import {getGuildSettings} from '../utils/get-guild-settings'; @injectable() export default class AddQueryToQueue { @@ -34,11 +34,7 @@ export default class AddQueryToQueue { const [targetVoiceChannel] = getMemberVoiceChannel(interaction.member as GuildMember) ?? getMostPopularVoiceChannel(interaction.guild!); - const settings = await prisma.setting.findUnique({where: {guildId}}); - - if (!settings) { - throw new Error('Could not find settings for guild'); - } + const settings = await getGuildSettings(guildId); const {playlistLimit} = settings; diff --git a/src/services/player.ts b/src/services/player.ts index 0b57ad7..9c1a122 100644 --- a/src/services/player.ts +++ b/src/services/player.ts @@ -18,7 +18,7 @@ import { } from '@discordjs/voice'; import FileCacheProvider from './file-cache.js'; import debug from '../utils/debug.js'; -import {prisma} from '../utils/db.js'; +import {getGuildSettings} from '../utils/get-guild-settings'; export enum MediaSource { Youtube, @@ -272,11 +272,7 @@ export default class { this.audioPlayer?.stop(); this.status = STATUS.IDLE; - const settings = await prisma.setting.findUnique({where: {guildId: this.guildId}}); - - if (!settings) { - throw new Error('Could not find settings for guild'); - } + const settings = await getGuildSettings(this.guildId); const {secondsToWaitAfterQueueEmpties} = settings; if (secondsToWaitAfterQueueEmpties !== 0) { diff --git a/src/utils/get-guild-settings.ts b/src/utils/get-guild-settings.ts new file mode 100644 index 0000000..f001aab --- /dev/null +++ b/src/utils/get-guild-settings.ts @@ -0,0 +1,12 @@ +import {Setting} from '@prisma/client'; +import {prisma} from './db'; +import {createGuildSettings} from '../events/guild-create'; + +export async function getGuildSettings(guildId: string): Promise { + const config = await prisma.setting.findUnique({where: {guildId}}); + if (!config) { + return createGuildSettings(guildId); + } + + return config; +} From 02611f2ae1722a93f05f80d94a0f6820b4e4ea0f Mon Sep 17 00:00:00 2001 From: Federico Rapetti Date: Sat, 4 Mar 2023 22:40:26 +0100 Subject: [PATCH 5/6] Fix all lint errors (#909) Co-authored-by: Max Isom --- CHANGELOG.md | 1 + src/bot.ts | 4 +--- src/services/add-query-to-queue.ts | 1 + src/services/get-songs.ts | 6 ++---- src/services/youtube-api.ts | 5 +---- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7aa1c1..043e996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed +- Fixed all lint errors - Create the guild settings when not found instead of returning an error - Add temporary workaround to avoid VoiceConnection being stuck in signalling state diff --git a/src/bot.ts b/src/bot.ts index 596c54e..949d9f0 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -23,9 +23,7 @@ export default class { private readonly commandsByName!: Collection; private readonly commandsByButtonId!: Collection; - constructor( - @inject(TYPES.Client) client: Client, - @inject(TYPES.Config) config: Config) { + constructor(@inject(TYPES.Client) client: Client, @inject(TYPES.Config) config: Config) { this.client = client; this.config = config; this.shouldRegisterCommandsOnBot = config.REGISTER_COMMANDS_ON_BOT; diff --git a/src/services/add-query-to-queue.ts b/src/services/add-query-to-queue.ts index dc972cd..b5367a2 100644 --- a/src/services/add-query-to-queue.ts +++ b/src/services/add-query-to-queue.ts @@ -1,5 +1,6 @@ /* eslint-disable complexity */ import {ChatInputCommandInteraction, GuildMember} from 'discord.js'; +import {URL} from 'node:url'; import {inject, injectable} from 'inversify'; import shuffle from 'array-shuffle'; import {TYPES} from '../types.js'; diff --git a/src/services/get-songs.ts b/src/services/get-songs.ts index fbb4937..1c68e7e 100644 --- a/src/services/get-songs.ts +++ b/src/services/get-songs.ts @@ -1,6 +1,6 @@ import {inject, injectable} from 'inversify'; import * as spotifyURI from 'spotify-uri'; -import {SongMetadata, QueuedPlaylist, MediaSource} from '../services/player.js'; +import {SongMetadata, QueuedPlaylist, MediaSource} from './player'; import {TYPES} from '../types.js'; import ffmpeg from 'fluent-ffmpeg'; import YoutubeAPI from './youtube-api.js'; @@ -11,9 +11,7 @@ export default class { private readonly youtubeAPI: YoutubeAPI; private readonly spotifyAPI: SpotifyAPI; - constructor( - @inject(TYPES.Services.YoutubeAPI) youtubeAPI: YoutubeAPI, - @inject(TYPES.Services.SpotifyAPI) spotifyAPI: SpotifyAPI) { + constructor(@inject(TYPES.Services.YoutubeAPI) youtubeAPI: YoutubeAPI, @inject(TYPES.Services.SpotifyAPI) spotifyAPI: SpotifyAPI) { this.youtubeAPI = youtubeAPI; this.spotifyAPI = spotifyAPI; } diff --git a/src/services/youtube-api.ts b/src/services/youtube-api.ts index 01a29dc..d2528d4 100644 --- a/src/services/youtube-api.ts +++ b/src/services/youtube-api.ts @@ -29,10 +29,7 @@ export default class { private readonly ytsrQueue: PQueue; - constructor( - @inject(TYPES.ThirdParty) thirdParty: ThirdParty, - @inject(TYPES.Config) config: Config, - @inject(TYPES.KeyValueCache) cache: KeyValueCacheProvider) { + constructor(@inject(TYPES.ThirdParty) thirdParty: ThirdParty, @inject(TYPES.Config) config: Config, @inject(TYPES.KeyValueCache) cache: KeyValueCacheProvider) { this.youtube = thirdParty.youtube; this.youtubeKey = config.YOUTUBE_API_KEY; this.cache = cache; From 3940ca7da0dbbbb93ec2f9fc16111386e8369cd7 Mon Sep 17 00:00:00 2001 From: Max Isom Date: Sat, 4 Mar 2023 13:41:09 -0800 Subject: [PATCH 6/6] Release 2.2.1 --- CHANGELOG.md | 5 ++++- package.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 043e996..8af6490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ 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] + +## [2.2.1] - 2023-03-04 ### Fixed - Fixed all lint errors - Create the guild settings when not found instead of returning an error @@ -223,7 +225,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release -[unreleased]: https://github.com/codetheweb/muse/compare/v2.2.0...HEAD +[unreleased]: https://github.com/codetheweb/muse/compare/v2.2.1...HEAD +[2.2.1]: https://github.com/codetheweb/muse/compare/v2.2.0...v2.2.1 [2.2.0]: https://github.com/codetheweb/muse/compare/v2.1.9...v2.2.0 [2.1.9]: https://github.com/codetheweb/muse/compare/v2.1.8...v2.1.9 [2.1.8]: https://github.com/codetheweb/muse/compare/v2.1.7...v2.1.8 diff --git a/package.json b/package.json index c61c4ea..642217e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "muse", - "version": "2.2.0", + "version": "2.2.1", "description": "🎧 a self-hosted Discord music bot that doesn't suck ", "repository": "git@github.com:codetheweb/muse.git", "author": "Max Isom ",