mirror of
https://github.com/informaticker/discord-jellyfin-bot.git
synced 2024-11-23 18:21:55 +01:00
✨ Add search command and service
This commit is contained in:
parent
17eee92404
commit
4693b2f75f
@ -1,16 +1,20 @@
|
|||||||
import { Module, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
|
import { Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||||
import { JellyfinService } from "./jellyfin.service";
|
import { JellyfinSearchService } from './jellyfin.search.service';
|
||||||
|
import { JellyfinService } from './jellyfin.service';
|
||||||
|
import { JellyinWebsocketService } from './jellyfin.websocket.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [JellyfinService],
|
providers: [JellyfinService, JellyinWebsocketService, JellyfinSearchService],
|
||||||
exports: [],
|
exports: [JellyfinService, JellyfinSearchService],
|
||||||
})
|
})
|
||||||
export class JellyfinClientModule implements OnModuleInit, OnModuleDestroy {
|
export class JellyfinClientModule implements OnModuleInit, OnModuleDestroy {
|
||||||
|
constructor(
|
||||||
constructor(private jellyfinService: JellyfinService) {}
|
private jellyfinService: JellyfinService,
|
||||||
|
private readonly jellyfinWebsocketService: JellyinWebsocketService,
|
||||||
|
) {}
|
||||||
|
|
||||||
onModuleDestroy() {
|
onModuleDestroy() {
|
||||||
this.jellyfinService.destroy();
|
this.jellyfinService.destroy();
|
||||||
}
|
}
|
||||||
@ -18,5 +22,9 @@ export class JellyfinClientModule implements OnModuleInit, OnModuleDestroy {
|
|||||||
onModuleInit() {
|
onModuleInit() {
|
||||||
this.jellyfinService.init();
|
this.jellyfinService.init();
|
||||||
this.jellyfinService.authenticate();
|
this.jellyfinService.authenticate();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.jellyfinWebsocketService.openSocket();
|
||||||
|
}, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
src/clients/jellyfin/jellyfin.search.service.ts
Normal file
31
src/clients/jellyfin/jellyfin.search.service.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { JellyfinService } from './jellyfin.service';
|
||||||
|
|
||||||
|
import { SearchHint } from '@jellyfin/sdk/lib/generated-client/models';
|
||||||
|
import { getSearchApi } from '@jellyfin/sdk/lib/utils/api/search-api';
|
||||||
|
import { Logger } from '@nestjs/common/services';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JellyfinSearchService {
|
||||||
|
private readonly logger = new Logger(JellyfinSearchService.name);
|
||||||
|
|
||||||
|
constructor(private readonly jellyfinService: JellyfinService) {}
|
||||||
|
|
||||||
|
async search(searchTerm: string): Promise<SearchHint[]> {
|
||||||
|
const api = this.jellyfinService.getApi();
|
||||||
|
|
||||||
|
this.logger.debug(`Searching for '${searchTerm}'`);
|
||||||
|
|
||||||
|
const searchApi = getSearchApi(api);
|
||||||
|
const {
|
||||||
|
data: { SearchHints, TotalRecordCount },
|
||||||
|
} = await searchApi.get({
|
||||||
|
searchTerm: searchTerm,
|
||||||
|
mediaTypes: ['Audio', 'Album'],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logger.debug(`Found ${TotalRecordCount} results for '${searchTerm}'`);
|
||||||
|
|
||||||
|
return SearchHints;
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,16 @@ import { Injectable, Logger } from '@nestjs/common';
|
|||||||
|
|
||||||
import { Api, Jellyfin } from '@jellyfin/sdk';
|
import { Api, Jellyfin } from '@jellyfin/sdk';
|
||||||
import { Constants } from '../../utils/constants';
|
import { Constants } from '../../utils/constants';
|
||||||
|
import { SystemApi } from '@jellyfin/sdk/lib/generated-client/api/system-api';
|
||||||
|
import { getSystemApi } from '@jellyfin/sdk/lib/utils/api/system-api';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JellyfinService {
|
export class JellyfinService {
|
||||||
private readonly logger = new Logger(JellyfinService.name);
|
private readonly logger = new Logger(JellyfinService.name);
|
||||||
private jellyfin: Jellyfin;
|
private jellyfin: Jellyfin;
|
||||||
private api: Api;
|
private api: Api;
|
||||||
|
private systemApi: SystemApi;
|
||||||
|
private userId: string;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.jellyfin = new Jellyfin({
|
this.jellyfin = new Jellyfin({
|
||||||
@ -42,6 +46,9 @@ export class JellyfinService {
|
|||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Connected using user '${response.data.SessionInfo.UserId}'`,
|
`Connected using user '${response.data.SessionInfo.UserId}'`,
|
||||||
);
|
);
|
||||||
|
this.userId = response.data.SessionInfo.UserId;
|
||||||
|
|
||||||
|
this.systemApi = getSystemApi(this.api);
|
||||||
})
|
})
|
||||||
.catch((test) => {
|
.catch((test) => {
|
||||||
this.logger.error(test);
|
this.logger.error(test);
|
||||||
@ -57,4 +64,20 @@ export class JellyfinService {
|
|||||||
}
|
}
|
||||||
this.api.logout();
|
this.api.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getApi() {
|
||||||
|
return this.api;
|
||||||
|
}
|
||||||
|
|
||||||
|
getJellyfin() {
|
||||||
|
return this.jellyfin;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSystemApi() {
|
||||||
|
return this.systemApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserId() {
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
15
src/clients/jellyfin/jellyfin.websocket.service.ts
Normal file
15
src/clients/jellyfin/jellyfin.websocket.service.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { JellyfinService } from './jellyfin.service';
|
||||||
|
|
||||||
|
import { getPlaystateApi } from '@jellyfin/sdk/lib/utils/api/playstate-api';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JellyinWebsocketService {
|
||||||
|
constructor(private readonly jellyfinClientManager: JellyfinService) {}
|
||||||
|
|
||||||
|
async openSocket() {
|
||||||
|
const systemApi = getPlaystateApi(this.jellyfinClientManager.getApi());
|
||||||
|
|
||||||
|
// TODO: Write socket playstate api to report playback progress
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,23 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { DiscordModule } from '@discord-nestjs/core';
|
import { DiscordModule } from '@discord-nestjs/core';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { HelpCommand } from './help.command';
|
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
||||||
import { StatusCommand } from './status.command';
|
import { JellyfinClientModule } from '../clients/jellyfin/jellyfin.module';
|
||||||
|
import { PlaybackService } from '../playback/playback.service';
|
||||||
import { CurrentTrackCommand } from './current.command';
|
import { CurrentTrackCommand } from './current.command';
|
||||||
import { DisconnectCommand } from './disconnect.command';
|
import { DisconnectCommand } from './disconnect.command';
|
||||||
import { EnqueueCommand } from './enqueue.command';
|
import { EnqueueCommand } from './enqueue.command';
|
||||||
|
import { HelpCommand } from './help.command';
|
||||||
import { PausePlaybackCommand } from './pause.command';
|
import { PausePlaybackCommand } from './pause.command';
|
||||||
import { PlayCommand } from './play.command';
|
import { PlayCommand } from './play.command';
|
||||||
|
import { SearchItemCommand } from './search.comands';
|
||||||
import { SkipTrackCommand } from './skip.command';
|
import { SkipTrackCommand } from './skip.command';
|
||||||
|
import { StatusCommand } from './status.command';
|
||||||
import { StopPlaybackCommand } from './stop.command';
|
import { StopPlaybackCommand } from './stop.command';
|
||||||
import { SummonCommand } from './summon.command';
|
import { SummonCommand } from './summon.command';
|
||||||
import { DiscordMessageService } from '../clients/discord/discord.message.service';
|
|
||||||
import { PlaybackService } from '../playback/playback.service';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DiscordModule.forFeature()],
|
imports: [DiscordModule.forFeature(), JellyfinClientModule],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [
|
providers: [
|
||||||
HelpCommand,
|
HelpCommand,
|
||||||
@ -28,6 +30,7 @@ import { PlaybackService } from '../playback/playback.service';
|
|||||||
SkipTrackCommand,
|
SkipTrackCommand,
|
||||||
StopPlaybackCommand,
|
StopPlaybackCommand,
|
||||||
SummonCommand,
|
SummonCommand,
|
||||||
|
SearchItemCommand,
|
||||||
DiscordMessageService,
|
DiscordMessageService,
|
||||||
PlaybackService,
|
PlaybackService,
|
||||||
],
|
],
|
||||||
|
113
src/commands/search.comands.ts
Normal file
113
src/commands/search.comands.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { TransformPipe } from '@discord-nestjs/common';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
DiscordTransformedCommand,
|
||||||
|
Param,
|
||||||
|
Payload,
|
||||||
|
TransformedCommandExecutionContext,
|
||||||
|
UsePipes,
|
||||||
|
} from '@discord-nestjs/core';
|
||||||
|
import {
|
||||||
|
APIEmbedField,
|
||||||
|
ComponentType,
|
||||||
|
EmbedBuilder,
|
||||||
|
InteractionReplyOptions,
|
||||||
|
} from 'discord.js';
|
||||||
|
import { JellyfinSearchService } from '../clients/jellyfin/jellyfin.search.service';
|
||||||
|
import { TrackRequestDto } from '../models/track-request.dto';
|
||||||
|
import { DefaultJellyfinColor } from '../types/colors';
|
||||||
|
|
||||||
|
@Command({
|
||||||
|
name: 'search',
|
||||||
|
description: 'Search for an item on your Jellyfin instance',
|
||||||
|
})
|
||||||
|
@UsePipes(TransformPipe)
|
||||||
|
export class SearchItemCommand
|
||||||
|
implements DiscordTransformedCommand<TrackRequestDto>
|
||||||
|
{
|
||||||
|
constructor(private readonly jellyfinSearchService: JellyfinSearchService) {}
|
||||||
|
|
||||||
|
async handler(
|
||||||
|
@Payload() dto: TrackRequestDto,
|
||||||
|
executionContext: TransformedCommandExecutionContext<any>,
|
||||||
|
): Promise<InteractionReplyOptions | string> {
|
||||||
|
const items = await this.jellyfinSearchService.search(dto.search);
|
||||||
|
|
||||||
|
const firstItems = items.slice(0, 10);
|
||||||
|
|
||||||
|
const lines: string[] = firstItems.map(
|
||||||
|
(item) =>
|
||||||
|
`:white_small_square: ${this.markSearchTermOverlap(
|
||||||
|
item.Name,
|
||||||
|
dto.search,
|
||||||
|
)} *(${item.Type})*`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const description = `I have found **${
|
||||||
|
items.length
|
||||||
|
}** results for your search \`\`${
|
||||||
|
dto.search
|
||||||
|
}\`\`.\nFor better readability, I have limited the search results to 10\n\n ${lines.join(
|
||||||
|
'\n',
|
||||||
|
)}`;
|
||||||
|
|
||||||
|
const emojiForType = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'Audio':
|
||||||
|
return '🎵';
|
||||||
|
case 'Playlist':
|
||||||
|
return '📚';
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectOptions: { label: string; value: string; emoji?: string }[] =
|
||||||
|
firstItems.map((item) => ({
|
||||||
|
label: item.Name,
|
||||||
|
value: item.Id,
|
||||||
|
emoji: emojiForType(item.Type),
|
||||||
|
}));
|
||||||
|
|
||||||
|
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(),
|
||||||
|
],
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.ActionRow,
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
type: ComponentType.StringSelect,
|
||||||
|
customId: 'cool',
|
||||||
|
options: selectOptions,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private markSearchTermOverlap(value: string, searchTerm: string) {
|
||||||
|
const startIndex = value.indexOf(searchTerm);
|
||||||
|
const actualValue = value.substring(
|
||||||
|
startIndex,
|
||||||
|
startIndex + 1 + searchTerm.length,
|
||||||
|
);
|
||||||
|
return `${value.substring(
|
||||||
|
0,
|
||||||
|
startIndex,
|
||||||
|
)}**${actualValue}**${value.substring(
|
||||||
|
startIndex + 1 + actualValue.length,
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user