mirror of
https://github.com/informaticker/discord-jellyfin-bot.git
synced 2024-11-25 02:51:57 +01:00
♻️ Refactor from synchronous replies to asynchronous deferred replies with edits #64
This commit is contained in:
parent
38d48ec708
commit
066d27b351
@ -4,7 +4,6 @@ import { Command, DiscordCommand, UsePipes } from '@discord-nestjs/core';
|
||||
import { CommandInteraction } from 'discord.js';
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
import { DiscordVoiceService } from '../clients/discord/discord.voice.service';
|
||||
import { GenericCustomReply } from '../models/generic-try-handler';
|
||||
|
||||
@Command({
|
||||
name: 'disconnect',
|
||||
@ -17,20 +16,28 @@ export class DisconnectCommand implements DiscordCommand {
|
||||
private readonly discordMessageService: DiscordMessageService,
|
||||
) {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
handler(interaction: CommandInteraction): GenericCustomReply {
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Disconnecting...',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const disconnect = this.discordVoiceService.disconnect();
|
||||
|
||||
if (!disconnect.success) {
|
||||
return disconnect.reply;
|
||||
await interaction.editReply(disconnect.reply);
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Disconnected from your channel',
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { TransformPipe } from '@discord-nestjs/common';
|
||||
import { Command, DiscordCommand, UsePipes } from '@discord-nestjs/core';
|
||||
import { CommandInteraction } from 'discord.js';
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
import { GenericCustomReply } from '../models/generic-try-handler';
|
||||
|
||||
@Command({
|
||||
name: 'help',
|
||||
@ -13,9 +12,8 @@ import { GenericCustomReply } from '../models/generic-try-handler';
|
||||
export class HelpCommand implements DiscordCommand {
|
||||
constructor(private readonly discordMessageService: DiscordMessageService) {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
handler(commandInteraction: CommandInteraction): GenericCustomReply {
|
||||
return {
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Jellyfin Discord Bot',
|
||||
@ -40,6 +38,6 @@ export class HelpCommand implements DiscordCommand {
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { TransformPipe } from '@discord-nestjs/common';
|
||||
|
||||
import { Command, DiscordCommand, UsePipes } from '@discord-nestjs/core';
|
||||
import { CommandInteraction, InteractionReplyOptions } from 'discord.js';
|
||||
import { CommandInteraction } from 'discord.js';
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
import { PlaybackService } from '../playback/playback.service';
|
||||
|
||||
@ -16,26 +16,23 @@ export class SkipTrackCommand implements DiscordCommand {
|
||||
private readonly discordMessageService: DiscordMessageService,
|
||||
) {}
|
||||
|
||||
handler(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interactionCommand: CommandInteraction,
|
||||
): InteractionReplyOptions | string {
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
if (!this.playbackService.nextTrack()) {
|
||||
return {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildErrorMessage({
|
||||
title: 'There is no next track',
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Skipped to the next track',
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,18 +16,15 @@ export class PausePlaybackCommand implements DiscordCommand {
|
||||
private readonly discordMessageService: DiscordMessageService,
|
||||
) {}
|
||||
|
||||
handler(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
commandInteraction: CommandInteraction,
|
||||
): string | InteractionReplyOptions {
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
const shouldBePaused = this.discordVoiceService.togglePaused();
|
||||
|
||||
return {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: shouldBePaused ? 'Paused' : 'Unpaused',
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import { TrackRequestDto } from '../models/track-request.dto';
|
||||
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
|
||||
import { RemoteImageResult } from '@jellyfin/sdk/lib/generated-client/models';
|
||||
import { DiscordVoiceService } from '../clients/discord/discord.voice.service';
|
||||
import { JellyfinStreamBuilderService } from '../clients/jellyfin/jellyfin.stream.builder.service';
|
||||
import {
|
||||
@ -28,7 +29,6 @@ import {
|
||||
searchResultAsJellyfinAudio,
|
||||
} from '../models/jellyfinAudioItems';
|
||||
import { PlaybackService } from '../playback/playback.service';
|
||||
import { RemoteImageResult } from '@jellyfin/sdk/lib/generated-client/models';
|
||||
import { chooseSuitableRemoteImage } from '../utils/remoteImages/remoteImages';
|
||||
import { trimStringToFixedLength } from '../utils/stringUtils/stringUtils';
|
||||
|
||||
@ -52,9 +52,10 @@ export class PlayItemCommand
|
||||
|
||||
async handler(
|
||||
@Payload() dto: TrackRequestDto,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
executionContext: TransformedCommandExecutionContext<any>,
|
||||
): Promise<InteractionReplyOptions | string> {
|
||||
await executionContext.interaction.deferReply();
|
||||
|
||||
const items = await this.jellyfinSearchService.search(dto.search);
|
||||
const parsedItems = await Promise.all(
|
||||
items.map(
|
||||
@ -68,14 +69,15 @@ export class PlayItemCommand
|
||||
);
|
||||
|
||||
if (parsedItems.length === 0) {
|
||||
return {
|
||||
await executionContext.interaction.followUp({
|
||||
embeds: [
|
||||
this.discordMessageService.buildErrorMessage({
|
||||
title: 'No results for your search query found',
|
||||
description: `I was not able to find any matches for your query \`\`${dto.search}\`\`. Please check that I have access to the desired libraries and that your query is not misspelled`,
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const firstItems = parsedItems.slice(0, 10);
|
||||
@ -107,7 +109,7 @@ export class PlayItemCommand
|
||||
emoji: item.getEmoji(),
|
||||
}));
|
||||
|
||||
return {
|
||||
await executionContext.interaction.followUp({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Jellyfin Search Results',
|
||||
@ -126,7 +128,7 @@ export class PlayItemCommand
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@On(Events.InteractionCreate)
|
||||
@ -144,6 +146,18 @@ export class PlayItemCommand
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferUpdate();
|
||||
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Applying your selection to the queue...',
|
||||
description: `This may take a moment. Please wait`,
|
||||
}),
|
||||
],
|
||||
components: [],
|
||||
});
|
||||
|
||||
const guildMember = interaction.member as GuildMember;
|
||||
|
||||
const tryResult =
|
||||
@ -156,7 +170,7 @@ export class PlayItemCommand
|
||||
`Unable to process select result because the member was not in a voice channcel`,
|
||||
);
|
||||
const replyOptions = tryResult.reply as InteractionReplyOptions;
|
||||
await interaction.update({
|
||||
await interaction.editReply({
|
||||
embeds: replyOptions.embeds,
|
||||
content: undefined,
|
||||
components: [],
|
||||
@ -183,7 +197,7 @@ export class PlayItemCommand
|
||||
bitrate,
|
||||
remoteImagesOfCurrentAlbum,
|
||||
);
|
||||
interaction.update({
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: item.Name,
|
||||
@ -212,7 +226,7 @@ export class PlayItemCommand
|
||||
remoteImages,
|
||||
);
|
||||
});
|
||||
interaction.update({
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: `Added ${album.TotalRecordCount} items from your album`,
|
||||
@ -247,7 +261,7 @@ export class PlayItemCommand
|
||||
}
|
||||
const bestPlaylistRemoteImage =
|
||||
chooseSuitableRemoteImage(addedRemoteImages);
|
||||
interaction.update({
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: `Added ${playlist.TotalRecordCount} items from your playlist`,
|
||||
@ -267,7 +281,7 @@ export class PlayItemCommand
|
||||
});
|
||||
break;
|
||||
default:
|
||||
interaction.update({
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildErrorMessage({
|
||||
title: 'Unable to process your selection',
|
||||
|
@ -3,7 +3,6 @@ import { TransformPipe } from '@discord-nestjs/common';
|
||||
import { Command, DiscordCommand, UsePipes } from '@discord-nestjs/core';
|
||||
import { CommandInteraction } from 'discord.js';
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
import { GenericCustomReply } from '../models/generic-try-handler';
|
||||
import { PlaybackService } from '../playback/playback.service';
|
||||
import { Constants } from '../utils/constants';
|
||||
import { chooseSuitableRemoteImageFromTrack } from '../utils/remoteImages/remoteImages';
|
||||
@ -21,12 +20,11 @@ export class PlaylistCommand implements DiscordCommand {
|
||||
private readonly playbackService: PlaybackService,
|
||||
) {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
handler(interaction: CommandInteraction): GenericCustomReply {
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
const playList = this.playbackService.getPlaylist();
|
||||
|
||||
if (playList.tracks.length === 0) {
|
||||
return {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Your Playlist',
|
||||
@ -34,7 +32,7 @@ export class PlaylistCommand implements DiscordCommand {
|
||||
'You do not have any tracks in your playlist.\nUse the play command to add new tracks to your playlist',
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const tracklist = playList.tracks
|
||||
@ -63,7 +61,7 @@ export class PlaylistCommand implements DiscordCommand {
|
||||
const activeTrack = this.playbackService.getActiveTrack();
|
||||
const remoteImage = chooseSuitableRemoteImageFromTrack(activeTrack.track);
|
||||
|
||||
return {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Your Playlist',
|
||||
@ -77,7 +75,7 @@ export class PlaylistCommand implements DiscordCommand {
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getListPoint(isCurrent: boolean, index: number) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { TransformPipe } from '@discord-nestjs/common';
|
||||
|
||||
import { Command, DiscordCommand, UsePipes } from '@discord-nestjs/core';
|
||||
import { CommandInteraction, InteractionReplyOptions } from 'discord.js';
|
||||
import { CommandInteraction } from 'discord.js';
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
import { PlaybackService } from '../playback/playback.service';
|
||||
|
||||
@ -16,26 +16,23 @@ export class PreviousTrackCommand implements DiscordCommand {
|
||||
private readonly discordMessageService: DiscordMessageService,
|
||||
) {}
|
||||
|
||||
handler(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
dcommandInteraction: CommandInteraction,
|
||||
): InteractionReplyOptions | string {
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
if (!this.playbackService.previousTrack()) {
|
||||
return {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildErrorMessage({
|
||||
title: 'There is no previous track',
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Went to previous track',
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,7 @@ import {
|
||||
InjectDiscordClient,
|
||||
UsePipes,
|
||||
} from '@discord-nestjs/core';
|
||||
import {
|
||||
Client,
|
||||
CommandInteraction,
|
||||
InteractionReplyOptions,
|
||||
Status,
|
||||
} from 'discord.js';
|
||||
import { Client, CommandInteraction, Status } from 'discord.js';
|
||||
|
||||
import { formatDuration, intervalToDuration } from 'date-fns';
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
@ -33,10 +28,15 @@ export class StatusCommand implements DiscordCommand {
|
||||
private readonly jellyfinService: JellyfinService,
|
||||
) {}
|
||||
|
||||
async handler(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
commandInteraction: CommandInteraction,
|
||||
): Promise<string | InteractionReplyOptions> {
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Retrieving status information...',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const ping = this.client.ws.ping;
|
||||
const status = Status[this.client.ws.status];
|
||||
|
||||
@ -49,7 +49,7 @@ export class StatusCommand implements DiscordCommand {
|
||||
const jellyfinSystemApi = getSystemApi(this.jellyfinService.getApi());
|
||||
const jellyfinSystemInformation = await jellyfinSystemApi.getSystemInfo();
|
||||
|
||||
return {
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Discord Bot Status',
|
||||
@ -90,6 +90,6 @@ export class StatusCommand implements DiscordCommand {
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { Command, DiscordCommand, UsePipes } from '@discord-nestjs/core';
|
||||
import { CommandInteraction } from 'discord.js';
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
import { DiscordVoiceService } from '../clients/discord/discord.voice.service';
|
||||
import { GenericCustomReply } from '../models/generic-try-handler';
|
||||
import { PlaybackService } from '../playback/playback.service';
|
||||
|
||||
@Command({
|
||||
@ -19,22 +18,28 @@ export class StopPlaybackCommand implements DiscordCommand {
|
||||
private readonly discordVoiceService: DiscordVoiceService,
|
||||
) {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
handler(CommandInteraction: CommandInteraction): GenericCustomReply {
|
||||
const hasActiveTrack = this.playbackService.hasActiveTrack()
|
||||
const title = hasActiveTrack ? 'Playback stopped successfully' : 'Playback failed to stop'
|
||||
const description = hasActiveTrack ? 'In addition, your playlist has been cleared' : 'There is no active track in the queue'
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
const hasActiveTrack = this.playbackService.hasActiveTrack();
|
||||
const title = hasActiveTrack
|
||||
? 'Playback stopped successfully'
|
||||
: 'Playback failed to stop';
|
||||
const description = hasActiveTrack
|
||||
? 'In addition, your playlist has been cleared'
|
||||
: 'There is no active track in the queue';
|
||||
if (hasActiveTrack) {
|
||||
this.playbackService.clear();
|
||||
this.discordVoiceService.stop(false);
|
||||
}
|
||||
|
||||
return {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService[hasActiveTrack ? 'buildMessage' : 'buildErrorMessage']({
|
||||
this.discordMessageService[
|
||||
hasActiveTrack ? 'buildMessage' : 'buildErrorMessage'
|
||||
]({
|
||||
title: title,
|
||||
description: description,
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import { Logger } from '@nestjs/common';
|
||||
import { CommandInteraction, GuildMember } from 'discord.js';
|
||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||
import { DiscordVoiceService } from '../clients/discord/discord.voice.service';
|
||||
import { GenericCustomReply } from '../models/generic-try-handler';
|
||||
|
||||
@Command({
|
||||
name: 'summon',
|
||||
@ -20,7 +19,15 @@ export class SummonCommand implements DiscordCommand {
|
||||
private readonly discordMessageService: DiscordMessageService,
|
||||
) {}
|
||||
|
||||
handler(interaction: CommandInteraction): GenericCustomReply {
|
||||
async handler(interaction: CommandInteraction): Promise<void> {
|
||||
await interaction.reply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Joining your voice channel...',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const guildMember = interaction.member as GuildMember;
|
||||
|
||||
const tryResult =
|
||||
@ -29,10 +36,11 @@ export class SummonCommand implements DiscordCommand {
|
||||
);
|
||||
|
||||
if (!tryResult.success) {
|
||||
return tryResult.reply;
|
||||
interaction.editReply(tryResult.reply);
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
await interaction.editReply({
|
||||
embeds: [
|
||||
this.discordMessageService.buildMessage({
|
||||
title: 'Joined your voicehannel',
|
||||
@ -40,6 +48,6 @@ export class SummonCommand implements DiscordCommand {
|
||||
"I'm ready to play media. Use ``Cast to device`` in Jellyfin or the ``/play`` command to get started.",
|
||||
}),
|
||||
],
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { InteractionReplyOptions } from 'discord.js';
|
||||
import { InteractionEditReplyOptions, MessagePayload } from 'discord.js';
|
||||
|
||||
export interface GenericTryHandler {
|
||||
success: boolean;
|
||||
reply: GenericCustomReply;
|
||||
reply: string | MessagePayload | InteractionEditReplyOptions;
|
||||
}
|
||||
|
||||
export type GenericCustomReply =
|
||||
| string
|
||||
| InteractionReplyOptions
|
||||
| Promise<string | InteractionReplyOptions>;
|
||||
|
Loading…
Reference in New Issue
Block a user