mirror of
https://github.com/informaticker/discord-jellyfin-bot.git
synced 2024-11-23 18:21:55 +01:00
✨ Add album playback
This commit is contained in:
parent
c218c1273d
commit
1a1e12506f
@ -1,12 +1,17 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { JellyfinService } from './jellyfin.service';
|
import { JellyfinService } from './jellyfin.service';
|
||||||
|
|
||||||
import { SearchHint } from '@jellyfin/sdk/lib/generated-client/models';
|
import {
|
||||||
|
BaseItemDto,
|
||||||
|
BaseItemKind,
|
||||||
|
SearchHint,
|
||||||
|
SearchHintResult,
|
||||||
|
} from '@jellyfin/sdk/lib/generated-client/models';
|
||||||
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
||||||
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
|
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
|
||||||
import { getSearchApi } from '@jellyfin/sdk/lib/utils/api/search-api';
|
import { getSearchApi } from '@jellyfin/sdk/lib/utils/api/search-api';
|
||||||
import { Logger } from '@nestjs/common/services';
|
import { Logger } from '@nestjs/common/services';
|
||||||
import { JellyfinAudioPlaylist } from '../../models/jellyfinAudioItems';
|
import { JellyfinAudioPlaylist, JellyfinMusicAlbum } from '../../models/jellyfinAudioItems';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JellyfinSearchService {
|
export class JellyfinSearchService {
|
||||||
@ -25,9 +30,15 @@ export class JellyfinSearchService {
|
|||||||
status,
|
status,
|
||||||
} = await searchApi.get({
|
} = await searchApi.get({
|
||||||
searchTerm: searchTerm,
|
searchTerm: searchTerm,
|
||||||
mediaTypes: ['Audio', 'MusicAlbum', 'Playlist'],
|
includeItemTypes: [
|
||||||
|
BaseItemKind.Audio,
|
||||||
|
BaseItemKind.MusicAlbum,
|
||||||
|
BaseItemKind.Playlist,
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(SearchHints);
|
||||||
|
|
||||||
if (status !== 200) {
|
if (status !== 200) {
|
||||||
this.logger.error(`Jellyfin Search failed with status code ${status}`);
|
this.logger.error(`Jellyfin Search failed with status code ${status}`);
|
||||||
return [];
|
return [];
|
||||||
@ -48,13 +59,35 @@ export class JellyfinSearchService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (axiosResponse.status !== 200) {
|
if (axiosResponse.status !== 200) {
|
||||||
this.logger.error(`Jellyfin Search failed with status code ${status}`);
|
this.logger.error(
|
||||||
|
`Jellyfin Search failed with status code ${axiosResponse.status}`,
|
||||||
|
);
|
||||||
return new JellyfinAudioPlaylist();
|
return new JellyfinAudioPlaylist();
|
||||||
}
|
}
|
||||||
|
|
||||||
return axiosResponse.data as JellyfinAudioPlaylist;
|
return axiosResponse.data as JellyfinAudioPlaylist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getItemsByAlbum(albumId: string): Promise<JellyfinMusicAlbum> {
|
||||||
|
const api = this.jellyfinService.getApi();
|
||||||
|
const searchApi = getSearchApi(api);
|
||||||
|
const axiosResponse = await searchApi.get({
|
||||||
|
parentId: albumId,
|
||||||
|
userId: this.jellyfinService.getUserId(),
|
||||||
|
mediaTypes: [BaseItemKind[BaseItemKind.Audio]],
|
||||||
|
searchTerm: '%',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (axiosResponse.status !== 200) {
|
||||||
|
this.logger.error(
|
||||||
|
`Jellyfin Search failed with status code ${axiosResponse.status}`,
|
||||||
|
);
|
||||||
|
return new JellyfinMusicAlbum();
|
||||||
|
}
|
||||||
|
|
||||||
|
return axiosResponse.data as JellyfinMusicAlbum;
|
||||||
|
}
|
||||||
|
|
||||||
async getById(id: string): Promise<SearchHint> {
|
async getById(id: string): Promise<SearchHint> {
|
||||||
const api = this.jellyfinService.getApi();
|
const api = this.jellyfinService.getApi();
|
||||||
|
|
||||||
|
@ -190,6 +190,21 @@ export class PlayItemCommand
|
|||||||
components: [],
|
components: [],
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'album':
|
||||||
|
const album = await this.jellyfinSearchService.getItemsByAlbum(id);
|
||||||
|
console.log(album);
|
||||||
|
album.SearchHints.forEach((item) => {
|
||||||
|
this.enqueueSingleTrack(item as BaseJellyfinAudioPlayable, bitrate);
|
||||||
|
});
|
||||||
|
interaction.update({
|
||||||
|
embeds: [
|
||||||
|
this.discordMessageService.buildMessage({
|
||||||
|
title: `Added ${album.TotalRecordCount} items from your album`,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
components: [],
|
||||||
|
});
|
||||||
|
break;
|
||||||
case 'playlist':
|
case 'playlist':
|
||||||
const playlist = await this.jellyfinSearchService.getPlaylistById(id);
|
const playlist = await this.jellyfinSearchService.getPlaylistById(id);
|
||||||
playlist.Items.forEach((item) => {
|
playlist.Items.forEach((item) => {
|
||||||
@ -209,7 +224,7 @@ export class PlayItemCommand
|
|||||||
embeds: [
|
embeds: [
|
||||||
this.discordMessageService.buildErrorMessage({
|
this.discordMessageService.buildErrorMessage({
|
||||||
title: 'Unable to process your selection',
|
title: 'Unable to process your selection',
|
||||||
description: `Sorry. I don't know the type you selected: \`\`${type}\`\`. Please report this bug to the developers.\n\nDebug Information:\`\`${interaction.values.join(
|
description: `Sorry. I don't know the type you selected: \`\`${type}\`\`. Please report this bug to the developers.\n\nDebug Information: \`\`${interaction.values.join(
|
||||||
', ',
|
', ',
|
||||||
)}\`\``,
|
)}\`\``,
|
||||||
}),
|
}),
|
||||||
|
@ -56,7 +56,7 @@ export class PlaylistCommand implements DiscordCommand {
|
|||||||
|
|
||||||
return point;
|
return point;
|
||||||
})
|
})
|
||||||
.join(',\n');
|
.join('\n');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
embeds: [
|
embeds: [
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
import { SearchHint } from '@jellyfin/sdk/lib/generated-client/models';
|
import {
|
||||||
|
BaseItemKind,
|
||||||
|
SearchHint,
|
||||||
|
} from '@jellyfin/sdk/lib/generated-client/models';
|
||||||
import { JellyfinStreamBuilderService } from '../clients/jellyfin/jellyfin.stream.builder.service';
|
import { JellyfinStreamBuilderService } from '../clients/jellyfin/jellyfin.stream.builder.service';
|
||||||
import { Track } from '../types/track';
|
import { Track } from '../types/track';
|
||||||
import { trimStringToFixedLength } from '../utils/stringUtils';
|
import { trimStringToFixedLength } from '../utils/stringUtils';
|
||||||
@ -161,22 +164,70 @@ export class JellyfinAudioPlaylist implements BaseJellyfinAudioPlayable {
|
|||||||
TotalRecordCount: number;
|
TotalRecordCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class JellyfinMusicAlbum implements BaseJellyfinAudioPlayable {
|
||||||
|
Id: string;
|
||||||
|
Name: string;
|
||||||
|
RunTimeTicks: number;
|
||||||
|
SearchHints: JellyfinAudioItem[];
|
||||||
|
TotalRecordCount: number;
|
||||||
|
|
||||||
|
async fromSearchHint(
|
||||||
|
jellyfinSearchService: JellyfinSearchService,
|
||||||
|
searchHint: SearchHint,
|
||||||
|
): Promise<JellyfinMusicAlbum> {
|
||||||
|
this.Id = searchHint.Id;
|
||||||
|
this.Name = searchHint.Name;
|
||||||
|
this.RunTimeTicks = searchHint.RunTimeTicks;
|
||||||
|
const album = await jellyfinSearchService.getItemsByAlbum(searchHint.Id);
|
||||||
|
this.SearchHints = album.SearchHints;
|
||||||
|
this.TotalRecordCount = album.TotalRecordCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
fetchTracks(
|
||||||
|
jellyfinStreamBuilder: JellyfinStreamBuilderService,
|
||||||
|
bitrate: number,
|
||||||
|
): Track[] {
|
||||||
|
return this.SearchHints.flatMap((item) =>
|
||||||
|
item.fetchTracks(jellyfinStreamBuilder, bitrate),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
prettyPrint(search: string): string {
|
||||||
|
return `${markSearchTermOverlap(this.Name, search)} (${
|
||||||
|
this.TotalRecordCount
|
||||||
|
} items) (Album)`;
|
||||||
|
}
|
||||||
|
getId(): string {
|
||||||
|
return this.Id;
|
||||||
|
}
|
||||||
|
getValueId(): string {
|
||||||
|
return `album_${this.getId()}`;
|
||||||
|
}
|
||||||
|
getEmoji(): string {
|
||||||
|
return '📀';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const searchResultAsJellyfinAudio = async (
|
export const searchResultAsJellyfinAudio = async (
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
jellyfinSearchService: JellyfinSearchService,
|
jellyfinSearchService: JellyfinSearchService,
|
||||||
searchHint: SearchHint,
|
searchHint: SearchHint,
|
||||||
) => {
|
) => {
|
||||||
switch (searchHint.Type) {
|
switch (searchHint.Type) {
|
||||||
case 'Audio':
|
case BaseItemKind[BaseItemKind.Audio]:
|
||||||
return await new JellyfinAudioItem().fromSearchHint(
|
return await new JellyfinAudioItem().fromSearchHint(
|
||||||
jellyfinSearchService,
|
jellyfinSearchService,
|
||||||
searchHint,
|
searchHint,
|
||||||
);
|
);
|
||||||
case 'Playlist':
|
case BaseItemKind[BaseItemKind.Playlist]:
|
||||||
return await new JellyfinAudioPlaylist().fromSearchHint(
|
return await new JellyfinAudioPlaylist().fromSearchHint(
|
||||||
jellyfinSearchService,
|
jellyfinSearchService,
|
||||||
searchHint,
|
searchHint,
|
||||||
);
|
);
|
||||||
|
case BaseItemKind[BaseItemKind.MusicAlbum]:
|
||||||
|
return await new JellyfinMusicAlbum().fromSearchHint(
|
||||||
|
jellyfinSearchService,
|
||||||
|
searchHint,
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed to parse Jellyfin response for item type ${searchHint.Type}`,
|
`Failed to parse Jellyfin response for item type ${searchHint.Type}`,
|
||||||
|
Loading…
Reference in New Issue
Block a user