diff --git a/package.json b/package.json index b788a56..06d8d2e 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "discord.js": "^14.7.1", "jellyfin-apiclient": "^1.10.0", "joi": "^17.7.0", + "libsodium-wrappers": "^0.7.10", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^7.2.0" diff --git a/src/clients/discord/discord.config.service.ts b/src/clients/discord/discord.config.service.ts index c17f74b..870d5ee 100644 --- a/src/clients/discord/discord.config.service.ts +++ b/src/clients/discord/discord.config.service.ts @@ -16,6 +16,7 @@ export class DiscordConfigService implements DiscordOptionsFactory { GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.GuildIntegrations, + GatewayIntentBits.GuildVoiceStates, ], }, }; diff --git a/src/clients/discord/discord.module.ts b/src/clients/discord/discord.module.ts index abad467..27d4a92 100644 --- a/src/clients/discord/discord.module.ts +++ b/src/clients/discord/discord.module.ts @@ -1,10 +1,18 @@ import { Module } from '@nestjs/common'; +import { OnModuleDestroy } from '@nestjs/common/interfaces/hooks'; import { DiscordConfigService } from './discord.config.service'; +import { DiscordVoiceService } from './discord.voice.service'; @Module({ imports: [], controllers: [], - providers: [DiscordConfigService], + providers: [DiscordConfigService, DiscordVoiceService], exports: [DiscordConfigService], }) -export class DiscordClientModule {} +export class DiscordClientModule implements OnModuleDestroy { + constructor(private readonly discordVoiceService: DiscordVoiceService) {} + + onModuleDestroy() { + this.discordVoiceService.disconnectGracefully(); + } +} diff --git a/src/clients/discord/discord.voice.service.ts b/src/clients/discord/discord.voice.service.ts index a969208..552d37b 100644 --- a/src/clients/discord/discord.voice.service.ts +++ b/src/clients/discord/discord.voice.service.ts @@ -1,14 +1,20 @@ -import { Injectable } from "@nestjs/common"; -import { VoiceChannel } from "discord.js"; +import { getVoiceConnections } from '@discordjs/voice'; +import { Injectable } from '@nestjs/common'; +import { Logger } from '@nestjs/common/services'; @Injectable() export class DiscordVoiceService { - - summonClient(voiceChannel: VoiceChannel) { - // voiceChannel.join(''); - } - - startPlayback() { + private readonly logger = new Logger(DiscordVoiceService.name); + disconnectGracefully() { + const connections = getVoiceConnections(); + this.logger.debug( + `Disonnecting gracefully from ${ + Object.keys(connections).length + } connections`, + ); + connections.forEach((connection) => { + connection.destroy(); + }); } -} \ No newline at end of file +} diff --git a/src/commands/play.command.ts b/src/commands/play.command.ts index 57219b5..137cb84 100644 --- a/src/commands/play.command.ts +++ b/src/commands/play.command.ts @@ -1,23 +1,61 @@ import { TransformPipe } from '@discord-nestjs/common'; - import { Command, DiscordTransformedCommand, + Payload, TransformedCommandExecutionContext, UsePipes, } from '@discord-nestjs/core'; -import { InteractionReplyOptions } from 'discord.js'; +import { InteractionReplyOptions, MessagePayload } from 'discord.js'; + +import { Injectable } from '@nestjs/common'; +import { TrackRequestDto } from '../models/track-request.dto'; +import { + createAudioPlayer, + createAudioResource, + getVoiceConnection, +} from '@discordjs/voice'; +import { Logger } from '@nestjs/common/services'; @Command({ name: 'play', description: 'Immediately play a track', }) +@Injectable() @UsePipes(TransformPipe) -export class PlayCommand implements DiscordTransformedCommand { +export class PlayCommand implements DiscordTransformedCommand { + private readonly logger = new Logger(PlayCommand.name); + handler( - dto: unknown, + @Payload() dto: TrackRequestDto, executionContext: TransformedCommandExecutionContext, - ): InteractionReplyOptions | string { - return 'nice'; + ): + | string + | void + | MessagePayload + | InteractionReplyOptions + | Promise { + const player = createAudioPlayer(); + + this.logger.debug('bruh'); + + player.on('error', (error) => { + this.logger.error(error); + }); + + player.on('debug', (error) => { + this.logger.debug(error); + }); + + const resource = createAudioResource(dto.search); + + const connection = getVoiceConnection(executionContext.interaction.guildId); + + connection.subscribe(player); + + player.play(resource); + player.unpause(); + + return 'Playing Audio...'; } } diff --git a/src/models/track-request.dto.ts b/src/models/track-request.dto.ts new file mode 100644 index 0000000..8daf21e --- /dev/null +++ b/src/models/track-request.dto.ts @@ -0,0 +1,6 @@ +import { Param } from '@discord-nestjs/core'; + +export class TrackRequestDto { + @Param({ required: true, description: 'Track name to search' }) + search: string; +} diff --git a/yarn.lock b/yarn.lock index 69ddeb0..46c427b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4475,6 +4475,7 @@ __metadata: jellyfin-apiclient: ^1.10.0 jest: 28.1.3 joi: ^17.7.0 + libsodium-wrappers: ^0.7.10 prettier: ^2.3.2 reflect-metadata: ^0.1.13 rimraf: ^3.0.2 @@ -5093,6 +5094,22 @@ __metadata: languageName: node linkType: hard +"libsodium-wrappers@npm:^0.7.10": + version: 0.7.10 + resolution: "libsodium-wrappers@npm:0.7.10" + dependencies: + libsodium: ^0.7.0 + checksum: 294ac098895a15f99e65431c62478f149e9e5cbbcd1fa1b41e832b65e0ead63856cc964b3b7c14447a48701e3334661dea9223442834ae7dd0d34285991616cd + languageName: node + linkType: hard + +"libsodium@npm:^0.7.0": + version: 0.7.10 + resolution: "libsodium@npm:0.7.10" + checksum: 243794a0b3b753fafb304a82e9ff777eaccf11785bde6965e7f25171fd2fb35da302a89f009a91c1e922817d37724f7afc86592b128b2b58ed657d7fbe5259e6 + languageName: node + linkType: hard + "lines-and-columns@npm:^1.1.6": version: 1.2.4 resolution: "lines-and-columns@npm:1.2.4"