Add embed to display playlist

This commit is contained in:
Manuel Ruwe 2022-12-17 19:52:32 +01:00
parent 9c23ef293f
commit feeb09a17d
11 changed files with 208 additions and 94 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -22,8 +22,7 @@ export class DiscordMessageService {
return embedBuilder
.setAuthor({
name: title,
iconURL:
'https://github.com/manuel-rw/jellyfin-discord-music-bot/blob/nestjs-migration/images/icons/alert-circle.png?raw=true',
iconURL: Constants.Design.Icons.ErrorIcon,
})
.setFooter({
text: `${date} - Report an issue: ${Constants.Links.ReportIssue}`,
@ -48,8 +47,7 @@ export class DiscordMessageService {
.setColor(DefaultJellyfinColor)
.setAuthor({
name: title,
iconURL:
'https://github.com/manuel-rw/jellyfin-discord-music-bot/blob/nestjs-migration/images/icons/circle-check.png?raw=true',
iconURL: Constants.Design.Icons.JellyfinLogo,
})
.setFooter({
text: `${date}`,

View File

@ -10,8 +10,8 @@ import { DisconnectCommand } from './disconnect.command';
import { EnqueueCommand } from './enqueue.command';
import { HelpCommand } from './help.command';
import { PausePlaybackCommand } from './pause.command';
import { PlayCommand } from './play.command';
import { SearchItemCommand } from './search.comands';
import { PreviousTrackCommand } from './previous.command';
import { PlayItemCommand } from './play.comands';
import { SkipTrackCommand } from './skip.command';
import { StatusCommand } from './status.command';
import { StopPlaybackCommand } from './stop.command';
@ -31,11 +31,11 @@ import { SummonCommand } from './summon.command';
DisconnectCommand,
EnqueueCommand,
PausePlaybackCommand,
PlayCommand,
SkipTrackCommand,
StopPlaybackCommand,
SummonCommand,
SearchItemCommand,
PlayItemCommand,
PreviousTrackCommand,
DiscordMessageService,
PlaybackService,
],

View File

@ -4,6 +4,9 @@ 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 { formatMillisecondsAsHumanReadable } from '../utils/timeUtils';
@Command({
name: 'current',
@ -11,15 +14,55 @@ import { GenericCustomReply } from '../models/generic-try-handler';
})
@UsePipes(TransformPipe)
export class CurrentTrackCommand implements DiscordCommand {
constructor(private readonly discordMessageService: DiscordMessageService) {}
constructor(
private readonly discordMessageService: DiscordMessageService,
private readonly playbackService: PlaybackService,
) {}
handler(interaction: CommandInteraction): GenericCustomReply {
const playList = this.playbackService.getPlaylist();
if (playList.tracks.length === 0) {
return {
embeds: [
this.discordMessageService.buildMessage({
title: 'Your Playlist',
description:
'You do not have any tracks in your playlist.\nUse the play command to add new tracks to your playlist',
}),
],
};
}
const tracklist = playList.tracks
.slice(0, 10)
.map((track) => {
const isCurrent = track.id === playList.activeTrack;
return `${this.getListPoint(isCurrent)} ${
track.track.name
}\n${Constants.Design.InvisibleSpace.repeat(
3,
)}${formatMillisecondsAsHumanReadable(
track.track.durationInMilliseconds,
)} ${isCurrent && ' *(active track)*'}`;
})
.join(',\n');
return {
embeds: [
this.discordMessageService.buildErrorMessage({
title: 'NOT IMPLEMENTED',
this.discordMessageService.buildMessage({
title: 'Your Playlist',
description: tracklist,
}),
],
};
}
private getListPoint(isCurrent: boolean) {
if (isCurrent) {
return ':black_small_square:';
}
return ':white_small_square:';
}
}

View File

@ -28,16 +28,17 @@ import { formatDuration, intervalToDuration } from 'date-fns';
import { DiscordVoiceService } from '../clients/discord/discord.voice.service';
import { JellyfinStreamBuilderService } from '../clients/jellyfin/jellyfin.stream.builder.service';
import { PlaybackService } from '../playback/playback.service';
import { Constants } from '../utils/constants';
@Command({
name: 'search',
name: 'play',
description: 'Search for an item on your Jellyfin instance',
})
@UsePipes(TransformPipe)
export class SearchItemCommand
export class PlayItemCommand
implements DiscordTransformedCommand<TrackRequestDto>
{
private readonly logger: Logger = new Logger(SearchItemCommand.name);
private readonly logger: Logger = new Logger(PlayItemCommand.name);
constructor(
private readonly jellyfinSearchService: JellyfinSearchService,
@ -102,15 +103,15 @@ export class SearchItemCommand
return {
embeds: [
new EmbedBuilder()
.setAuthor({
name: 'Jellyfin Search Results',
iconURL:
'https://github.com/walkxcode/dashboard-icons/blob/main/png/jellyfin.png?raw=true',
})
.setColor(DefaultJellyfinColor)
.setDescription(description)
.toJSON(),
this.discordMessageService.buildMessage({
title: '',
mixin(embedBuilder) {
return embedBuilder.setAuthor({
name: 'Jellyfin Search Results',
iconURL: Constants.Design.Icons.JellyfinLogo,
});
},
}),
],
components: [
{

View File

@ -1,60 +0,0 @@
import { TransformPipe } from '@discord-nestjs/common';
import {
Command,
DiscordTransformedCommand,
Payload,
TransformedCommandExecutionContext,
UsePipes,
} from '@discord-nestjs/core';
import { GuildMember, InteractionReplyOptions } from 'discord.js';
import { createAudioResource } from '@discordjs/voice';
import { Injectable } from '@nestjs/common';
import { Logger } from '@nestjs/common/services';
import { DiscordMessageService } from '../clients/discord/discord.message.service';
import { DiscordVoiceService } from '../clients/discord/discord.voice.service';
import { TrackRequestDto } from '../models/track-request.dto';
@Command({
name: 'play',
description: 'Immediately play a track',
})
@Injectable()
@UsePipes(TransformPipe)
export class PlayCommand implements DiscordTransformedCommand<TrackRequestDto> {
private readonly logger = new Logger(PlayCommand.name);
constructor(
private readonly discordMessageService: DiscordMessageService,
private readonly discordVoiceService: DiscordVoiceService,
) {}
handler(
@Payload() dto: TrackRequestDto,
executionContext: TransformedCommandExecutionContext<any>,
):
| string
| InteractionReplyOptions
| Promise<string | InteractionReplyOptions> {
const guildMember = executionContext.interaction.member as GuildMember;
const joinVoiceChannel =
this.discordVoiceService.tryJoinChannelAndEstablishVoiceConnection(
guildMember,
);
if (!joinVoiceChannel.success) {
return joinVoiceChannel.reply;
}
this.discordVoiceService.playResource(createAudioResource(dto.search));
return {
embeds: [
this.discordMessageService.buildMessage({
title: `Playing ${dto.search}`,
}),
],
};
}
}

View File

@ -0,0 +1,40 @@
import { TransformPipe } from '@discord-nestjs/common';
import { Command, DiscordCommand, UsePipes } from '@discord-nestjs/core';
import { CommandInteraction, InteractionReplyOptions } from 'discord.js';
import { DiscordMessageService } from '../clients/discord/discord.message.service';
import { PlaybackService } from '../playback/playback.service';
@Command({
name: 'previous',
description: 'Go to the previous track',
})
@UsePipes(TransformPipe)
export class PreviousTrackCommand implements DiscordCommand {
constructor(
private readonly playbackService: PlaybackService,
private readonly discordMessageService: DiscordMessageService,
) {}
handler(
dcommandInteraction: CommandInteraction,
): InteractionReplyOptions | string {
if (!this.playbackService.previousTrack()) {
return {
embeds: [
this.discordMessageService.buildErrorMessage({
title: 'There is no previous track',
}),
],
};
}
return {
embeds: [
this.discordMessageService.buildMessage({
title: 'Went to previous track',
}),
],
};
}
}

View File

@ -1,23 +1,40 @@
import { TransformPipe } from '@discord-nestjs/common';
import {
Command,
DiscordTransformedCommand,
TransformedCommandExecutionContext,
UsePipes,
} from '@discord-nestjs/core';
import { InteractionReplyOptions } from 'discord.js';
import { Command, DiscordCommand, UsePipes } from '@discord-nestjs/core';
import { CommandInteraction, InteractionReplyOptions } from 'discord.js';
import { DiscordMessageService } from '../clients/discord/discord.message.service';
import { PlaybackService } from '../playback/playback.service';
@Command({
name: 'skip',
description: 'Skip the current track',
})
@UsePipes(TransformPipe)
export class SkipTrackCommand implements DiscordTransformedCommand<unknown> {
export class SkipTrackCommand implements DiscordCommand {
constructor(
private readonly playbackService: PlaybackService,
private readonly discordMessageService: DiscordMessageService,
) {}
handler(
dto: unknown,
executionContext: TransformedCommandExecutionContext<any>,
interactionCommand: CommandInteraction,
): InteractionReplyOptions | string {
return 'nice';
if (!this.playbackService.nextTrack()) {
return {
embeds: [
this.discordMessageService.buildErrorMessage({
title: 'There is no next track',
}),
],
};
}
return {
embeds: [
this.discordMessageService.buildMessage({
title: 'Skipped to the next track',
}),
],
};
}
}

View File

@ -25,12 +25,49 @@ export class PlaybackService {
this.playlist.activeTrack = track.id;
}
nextTrack() {
const keys = this.getTrackIds();
const index = this.getActiveIndex();
console.log(keys);
console.log(index);
if (!this.hasActiveTrack() || index >= keys.length) {
return false;
}
const newKey = keys[index + 1];
this.setActiveTrack(newKey);
return true;
}
previousTrack() {
const index = this.getActiveIndex();
if (!this.hasActiveTrack() || index < 1) {
return false;
}
const keys = this.getTrackIds();
const newKey = keys[index - 1];
this.setActiveTrack(newKey);
return true;
}
eneuqueTrack(track: Track) {
const uuid = uuidv4();
const emptyBefore = this.playlist.tracks.length === 0;
this.playlist.tracks.push({
id: uuid,
track: track,
});
if (emptyBefore) {
this.setActiveTrack(this.playlist.tracks.find((x) => x.id === uuid).id);
}
return this.playlist.tracks.findIndex((x) => x.id === uuid);
}
@ -45,7 +82,23 @@ export class PlaybackService {
this.playlist.tracks = [];
}
hasActiveTrack() {
return this.playlist.activeTrack !== null;
}
getPlaylist(): Playlist {
return this.playlist;
}
private getTrackById(id: string) {
return this.playlist.tracks.find((x) => x.id === id);
}
private getTrackIds() {
return Object.keys(this.playlist.tracks);
}
private getActiveIndex() {
return this.getTrackIds().indexOf(this.playlist.activeTrack);
}
}

View File

@ -8,4 +8,15 @@ export const Constants = {
ReportIssue:
'https://github.com/manuel-rw/jellyfin-discord-music-bot/issues/new/choose',
},
Design: {
InvisibleSpace: '\u1CBC',
Icons: {
JellyfinLogo:
'https://github.com/manuel-rw/jellyfin-discord-music-bot/blob/nestjs-migration/images/icons/jellyfin-icon-squared.png?raw=true',
SuccessIcon:
'https://github.com/manuel-rw/jellyfin-discord-music-bot/blob/nestjs-migration/images/icons/circle-check.png?raw=true',
ErrorIcon:
'https://github.com/manuel-rw/jellyfin-discord-music-bot/blob/nestjs-migration/images/icons/alert-circle.png?raw=true',
},
},
};

11
src/utils/timeUtils.ts Normal file
View File

@ -0,0 +1,11 @@
import { formatDuration, intervalToDuration } from 'date-fns';
export const formatMillisecondsAsHumanReadable = (milliseconds: number) => {
const duration = formatDuration(
intervalToDuration({
start: milliseconds,
end: 0,
}),
);
return duration;
};