mirror of
https://github.com/informaticker/discord-jellyfin-bot.git
synced 2024-11-25 02:51:57 +01:00
Merge pull request #149 from manuel-rw/refactor/strict-typescript
♻️ Strict Typescript
This commit is contained in:
commit
65d401fdbb
@ -9,7 +9,7 @@ import { GatewayIntentBits } from 'discord.js';
|
||||
export class DiscordConfigService implements DiscordOptionsFactory {
|
||||
createDiscordOptions(): DiscordModuleOption {
|
||||
return {
|
||||
token: process.env.DISCORD_CLIENT_TOKEN,
|
||||
token: process.env.DISCORD_CLIENT_TOKEN ?? '',
|
||||
discordClientOptions: {
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
|
@ -103,6 +103,12 @@ export class DiscordVoiceService {
|
||||
}
|
||||
|
||||
changeVolume(volume: number) {
|
||||
if (!this.audioResource || !this.audioResource.volume) {
|
||||
this.logger.error(
|
||||
`Failed to change audio volume, AudioResource or volume was undefined`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.audioResource.volume.setVolume(volume);
|
||||
}
|
||||
|
||||
@ -235,6 +241,20 @@ export class DiscordVoiceService {
|
||||
}
|
||||
|
||||
private attachEventListenersToAudioPlayer() {
|
||||
if (!this.voiceConnection) {
|
||||
this.logger.error(
|
||||
`Unable to attach listener events, because the VoiceConnection was undefined`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.audioPlayer) {
|
||||
this.logger.error(
|
||||
`Unable to attach listener events, because the AudioPlayer was undefined`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.voiceConnection.on('debug', (message) => {
|
||||
if (process.env.DEBUG?.toLowerCase() !== 'true') {
|
||||
return;
|
||||
@ -252,6 +272,13 @@ export class DiscordVoiceService {
|
||||
this.logger.error(message);
|
||||
});
|
||||
this.audioPlayer.on('stateChange', (previousState) => {
|
||||
if (!this.audioPlayer) {
|
||||
this.logger.error(
|
||||
`Unable to process state change from audio player, because the current audio player in the callback was undefined`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
`Audio player changed state from ${previousState.status} to ${this.audioPlayer.state.status}`,
|
||||
);
|
||||
@ -268,9 +295,11 @@ export class DiscordVoiceService {
|
||||
|
||||
const playlist = this.playbackService.getPlaylistOrDefault();
|
||||
const finishedTrack = playlist.getActiveTrack();
|
||||
finishedTrack.playing = false;
|
||||
|
||||
if (finishedTrack) {
|
||||
finishedTrack.playing = false;
|
||||
this.eventEmitter.emit('internal.audio.track.finish', finishedTrack);
|
||||
}
|
||||
|
||||
const hasNextTrack = playlist.hasNextTrackInPlaylist();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
BaseItemDto,
|
||||
BaseItemKind,
|
||||
RemoteImageResult,
|
||||
SearchHint as JellyfinSearchHint,
|
||||
@ -57,9 +58,13 @@ export class JellyfinSearchService {
|
||||
|
||||
const { SearchHints } = data;
|
||||
|
||||
return SearchHints.map((hint) => this.transformToSearchHint(hint)).filter(
|
||||
(x) => x !== null,
|
||||
);
|
||||
if (!SearchHints) {
|
||||
throw new Error('SearchHints were undefined');
|
||||
}
|
||||
|
||||
return SearchHints.map((hint) =>
|
||||
this.transformToSearchHintFromHint(hint),
|
||||
).filter((x) => x !== null) as SearchHint[];
|
||||
} catch (err) {
|
||||
this.logger.error(`Failed to search on Jellyfin: ${err}`);
|
||||
return [];
|
||||
@ -82,8 +87,15 @@ export class JellyfinSearchService {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!axiosResponse.data.Items) {
|
||||
this.logger.error(
|
||||
`Jellyfin search returned no items: ${axiosResponse.data}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return axiosResponse.data.Items.map((hint) =>
|
||||
SearchHint.constructFromHint(hint),
|
||||
SearchHint.constructFromBaseItem(hint),
|
||||
);
|
||||
}
|
||||
|
||||
@ -104,6 +116,13 @@ export class JellyfinSearchService {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!axiosResponse.data.SearchHints) {
|
||||
this.logger.error(
|
||||
`Received an unexpected empty list but expected a list of tracks of the album`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...axiosResponse.data.SearchHints]
|
||||
.reverse()
|
||||
.map((hint) => SearchHint.constructFromHint(hint));
|
||||
@ -112,7 +131,7 @@ export class JellyfinSearchService {
|
||||
async getById(
|
||||
id: string,
|
||||
includeItemTypes: BaseItemKind[],
|
||||
): Promise<SearchHint> | undefined {
|
||||
): Promise<SearchHint | undefined> {
|
||||
const api = this.jellyfinService.getApi();
|
||||
|
||||
const searchApi = getItemsApi(api);
|
||||
@ -122,18 +141,18 @@ export class JellyfinSearchService {
|
||||
includeItemTypes: includeItemTypes,
|
||||
});
|
||||
|
||||
if (data.Items.length !== 1) {
|
||||
if (!data.Items || data.Items.length !== 1) {
|
||||
this.logger.warn(`Failed to retrieve item via id '${id}'`);
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.transformToSearchHint(data.Items[0]);
|
||||
return this.transformToSearchHintFromBaseItemDto(data.Items[0]);
|
||||
}
|
||||
|
||||
async getAllById(
|
||||
ids: string[],
|
||||
includeItemTypes: BaseItemKind[] = [BaseItemKind.Audio],
|
||||
): Promise<SearchHint[]> | undefined {
|
||||
): Promise<SearchHint[]> {
|
||||
const api = this.jellyfinService.getApi();
|
||||
|
||||
const searchApi = getItemsApi(api);
|
||||
@ -143,12 +162,14 @@ export class JellyfinSearchService {
|
||||
includeItemTypes: includeItemTypes,
|
||||
});
|
||||
|
||||
if (data.Items.length !== 1) {
|
||||
if (!data.Items || data.Items.length !== 1) {
|
||||
this.logger.warn(`Failed to retrieve item via id '${ids}'`);
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
|
||||
return data.Items.map((item) => this.transformToSearchHint(item));
|
||||
return data.Items.map((item) =>
|
||||
this.transformToSearchHintFromBaseItemDto(item),
|
||||
).filter((searchHint) => searchHint !== undefined) as SearchHint[];
|
||||
}
|
||||
|
||||
async getRemoteImageById(id: string, limit = 20): Promise<RemoteImageResult> {
|
||||
@ -204,18 +225,25 @@ export class JellyfinSearchService {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
if (!response.data.Items) {
|
||||
this.logger.error(
|
||||
`Received empty list of items but expected a random list of tracks`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.data.Items.map((item) => {
|
||||
return SearchHint.constructFromBaseItem(item);
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Unabele to retrieve random items from Jellyfin: ${err}`,
|
||||
`Unable to retrieve random items from Jellyfin: ${err}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private transformToSearchHint(jellyifnHint: JellyfinSearchHint) {
|
||||
private transformToSearchHintFromHint(jellyifnHint: JellyfinSearchHint) {
|
||||
switch (jellyifnHint.Type) {
|
||||
case BaseItemKind[BaseItemKind.Audio]:
|
||||
return SearchHint.constructFromHint(jellyifnHint);
|
||||
@ -227,7 +255,23 @@ export class JellyfinSearchService {
|
||||
this.logger.warn(
|
||||
`Received unexpected item type from Jellyfin search: ${jellyifnHint.Type}`,
|
||||
);
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private transformToSearchHintFromBaseItemDto(baseItemDto: BaseItemDto) {
|
||||
switch (baseItemDto.Type) {
|
||||
case BaseItemKind[BaseItemKind.Audio]:
|
||||
return SearchHint.constructFromBaseItem(baseItemDto);
|
||||
case BaseItemKind[BaseItemKind.MusicAlbum]:
|
||||
return AlbumSearchHint.constructFromBaseItem(baseItemDto);
|
||||
case BaseItemKind[BaseItemKind.Playlist]:
|
||||
return PlaylistSearchHint.constructFromBaseItem(baseItemDto);
|
||||
default:
|
||||
this.logger.warn(
|
||||
`Received unexpected item type from Jellyfin search: ${baseItemDto.Type}`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,18 +33,20 @@ export class JellyfinService {
|
||||
},
|
||||
});
|
||||
|
||||
this.api = this.jellyfin.createApi(process.env.JELLYFIN_SERVER_ADDRESS);
|
||||
this.api = this.jellyfin.createApi(
|
||||
process.env.JELLYFIN_SERVER_ADDRESS ?? '',
|
||||
);
|
||||
this.logger.debug('Created Jellyfin Client and Api');
|
||||
}
|
||||
|
||||
authenticate() {
|
||||
this.api
|
||||
.authenticateUserByName(
|
||||
process.env.JELLYFIN_AUTHENTICATION_USERNAME,
|
||||
process.env.JELLYFIN_AUTHENTICATION_USERNAME ?? '',
|
||||
process.env.JELLYFIN_AUTHENTICATION_PASSWORD,
|
||||
)
|
||||
.then(async (response) => {
|
||||
if (response.data.SessionInfo === undefined) {
|
||||
if (response.data.SessionInfo?.UserId === undefined) {
|
||||
this.logger.error(
|
||||
`Failed to authenticate with response code ${response.status}: '${response.data}'`,
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
import { convertToTracks } from 'src/utils/trackConverter';
|
||||
|
||||
import { WebSocket } from 'ws';
|
||||
|
||||
@ -104,15 +105,7 @@ export class JellyfinWebSocketService implements OnModuleDestroy {
|
||||
`Processing ${ids.length} ids received via websocket and adding them to the queue`,
|
||||
);
|
||||
const searchHints = await this.jellyfinSearchService.getAllById(ids);
|
||||
|
||||
const tracks = await Promise.all(
|
||||
searchHints.map(async (x) =>
|
||||
(
|
||||
await x.toTracks(this.jellyfinSearchService)
|
||||
).find((x) => x !== null),
|
||||
),
|
||||
);
|
||||
|
||||
const tracks = convertToTracks(searchHints, this.jellyfinSearchService);
|
||||
this.playbackService.getPlaylistOrDefault().enqueueTracks(tracks);
|
||||
break;
|
||||
case SessionMessageType[SessionMessageType.Playstate]:
|
||||
|
@ -48,12 +48,12 @@ export class PlayItemCommand {
|
||||
async handler(
|
||||
@InteractionEvent(SlashCommandPipe) dto: PlayCommandParams,
|
||||
@IA() interaction: CommandInteraction,
|
||||
): Promise<InteractionReplyOptions | string> {
|
||||
) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const baseItems = PlayCommandParams.getBaseItemKinds(dto.type);
|
||||
|
||||
let item: SearchHint;
|
||||
let item: SearchHint | undefined;
|
||||
if (dto.name.startsWith('native-')) {
|
||||
item = await this.jellyfinSearchService.getById(
|
||||
dto.name.replace('native-', ''),
|
||||
@ -104,9 +104,9 @@ export class PlayItemCommand {
|
||||
);
|
||||
this.playbackService.getPlaylistOrDefault().enqueueTracks(tracks);
|
||||
|
||||
const remoteImage: RemoteImageInfo | undefined = tracks
|
||||
.flatMap((track) => track.getRemoteImages())
|
||||
.find(() => true);
|
||||
const remoteImages = tracks.flatMap((track) => track.getRemoteImages());
|
||||
const remoteImage: RemoteImageInfo | undefined =
|
||||
remoteImages.length > 0 ? remoteImages[0] : undefined;
|
||||
|
||||
await interaction.followUp({
|
||||
embeds: [
|
||||
@ -117,7 +117,7 @@ export class PlayItemCommand {
|
||||
reducedDuration,
|
||||
)})`,
|
||||
mixin(embedBuilder) {
|
||||
if (!remoteImage) {
|
||||
if (!remoteImage?.Url) {
|
||||
return embedBuilder;
|
||||
}
|
||||
return embedBuilder.setThumbnail(remoteImage.Url);
|
||||
@ -135,7 +135,15 @@ export class PlayItemCommand {
|
||||
}
|
||||
|
||||
const focusedAutoCompleteAction = interaction.options.getFocused(true);
|
||||
const typeIndex: number | null = interaction.options.getInteger('type');
|
||||
const typeIndex = interaction.options.getInteger('type');
|
||||
|
||||
if (typeIndex === null) {
|
||||
this.logger.error(
|
||||
`Failed to get type integer from play command interaction autocomplete`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const type = Object.values(SearchType)[typeIndex] as SearchType;
|
||||
const searchQuery = focusedAutoCompleteAction.value;
|
||||
|
||||
|
@ -30,7 +30,10 @@ export class PlaylistInteractionCollector {
|
||||
|
||||
@Filter()
|
||||
filter(interaction: ButtonInteraction): boolean {
|
||||
return this.causeInteraction.id === interaction.message.interaction.id;
|
||||
return (
|
||||
interaction.message.interaction !== null &&
|
||||
this.causeInteraction.id === interaction.message.interaction.id
|
||||
);
|
||||
}
|
||||
|
||||
@On('collect')
|
||||
@ -55,7 +58,7 @@ export class PlaylistInteractionCollector {
|
||||
await interaction.update(reply as InteractionUpdateOptions);
|
||||
}
|
||||
|
||||
private getInteraction(interaction: ButtonInteraction): number | null {
|
||||
private getInteraction(interaction: ButtonInteraction): number | undefined {
|
||||
const current = this.playlistCommand.pageData.get(this.causeInteraction.id);
|
||||
|
||||
if (current === undefined) {
|
||||
|
@ -39,7 +39,7 @@ export class StatusCommand {
|
||||
const status = Status[this.client.ws.status];
|
||||
|
||||
const interval = intervalToDuration({
|
||||
start: this.client.uptime,
|
||||
start: this.client.uptime ?? 0,
|
||||
end: 0,
|
||||
});
|
||||
const formattedDuration = formatDuration(interval);
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
import {
|
||||
HealthCheckResult,
|
||||
HealthCheckService,
|
||||
@ -42,10 +43,14 @@ describe('HealthController', () => {
|
||||
}
|
||||
|
||||
if (token === HealthCheckService) {
|
||||
return new HealthCheckService(new HealthCheckExecutor(), null, null);
|
||||
return new HealthCheckService(
|
||||
new HealthCheckExecutor(),
|
||||
{ getErrorMessage: jest.fn() },
|
||||
{ log: jest.fn(), error: jest.fn(), warn: jest.fn() },
|
||||
);
|
||||
}
|
||||
|
||||
return useDefaultMockerToken(token);
|
||||
return useDefaultMockerToken(token as InjectionToken);
|
||||
})
|
||||
.compile();
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
import { HealthIndicatorResult } from '@nestjs/terminus';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { JellyfinService } from '../../clients/jellyfin/jellyfin.service';
|
||||
@ -16,7 +17,7 @@ describe('JellyfinHealthIndicator', () => {
|
||||
if (token === JellyfinService) {
|
||||
return { isConnected: jest.fn() };
|
||||
}
|
||||
return useDefaultMockerToken(token);
|
||||
return useDefaultMockerToken(token as InjectionToken);
|
||||
})
|
||||
.compile();
|
||||
|
||||
|
@ -4,6 +4,10 @@ import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
function getLoggingLevels(): LogLevel[] {
|
||||
if (!process.env.LOG_LEVEL) {
|
||||
return ['error', 'warn', 'log'];
|
||||
}
|
||||
|
||||
switch (process.env.LOG_LEVEL.toLowerCase()) {
|
||||
case 'error':
|
||||
return ['error'];
|
||||
|
@ -11,6 +11,12 @@ export class AlbumSearchHint extends SearchHint {
|
||||
}
|
||||
|
||||
static constructFromHint(hint: JellyfinSearchHint) {
|
||||
if (hint.Id === undefined || !hint.Name || !hint.RunTimeTicks) {
|
||||
throw new Error(
|
||||
'Unable to construct playlist search hint, required properties were undefined',
|
||||
);
|
||||
}
|
||||
|
||||
return new AlbumSearchHint(hint.Id, hint.Name, hint.RunTimeTicks / 10000);
|
||||
}
|
||||
|
||||
@ -24,7 +30,7 @@ export class AlbumSearchHint extends SearchHint {
|
||||
(await x.toTracks(searchService)).find((x) => x !== null),
|
||||
),
|
||||
);
|
||||
return tracks.map((track): Track => {
|
||||
return tracks.map((track: Track): Track => {
|
||||
track.remoteImages = remoteImages;
|
||||
return track;
|
||||
});
|
||||
|
@ -4,6 +4,7 @@ import { Track } from '../shared/Track';
|
||||
import { JellyfinSearchService } from '../../clients/jellyfin/jellyfin.search.service';
|
||||
|
||||
import { SearchHint } from './SearchHint';
|
||||
import { convertToTracks } from 'src/utils/trackConverter';
|
||||
|
||||
export class PlaylistSearchHint extends SearchHint {
|
||||
override toString(): string {
|
||||
@ -11,6 +12,12 @@ export class PlaylistSearchHint extends SearchHint {
|
||||
}
|
||||
|
||||
static constructFromHint(hint: JellyfinSearchHint) {
|
||||
if (hint.Id === undefined || !hint.Name || !hint.RunTimeTicks) {
|
||||
throw new Error(
|
||||
'Unable to construct playlist search hint, required properties were undefined',
|
||||
);
|
||||
}
|
||||
|
||||
return new PlaylistSearchHint(
|
||||
hint.Id,
|
||||
hint.Name,
|
||||
@ -22,9 +29,6 @@ export class PlaylistSearchHint extends SearchHint {
|
||||
searchService: JellyfinSearchService,
|
||||
): Promise<Track[]> {
|
||||
const playlistItems = await searchService.getPlaylistitems(this.id);
|
||||
const tracks = playlistItems.map(async (x) =>
|
||||
(await x.toTracks(searchService)).find((x) => x !== null),
|
||||
);
|
||||
return await Promise.all(tracks);
|
||||
return convertToTracks(playlistItems, searchService);
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,20 @@ export class SearchHint {
|
||||
}
|
||||
|
||||
static constructFromHint(hint: JellyfinSearchHint) {
|
||||
if (hint.Id === undefined || !hint.Name || !hint.RunTimeTicks) {
|
||||
throw new Error(
|
||||
'Unable to construct search hint, required properties were undefined',
|
||||
);
|
||||
}
|
||||
return new SearchHint(hint.Id, hint.Name, hint.RunTimeTicks / 10000);
|
||||
}
|
||||
|
||||
static constructFromBaseItem(baseItem: BaseItemDto) {
|
||||
if (baseItem.Id === undefined || !baseItem.Name || !baseItem.RunTimeTicks) {
|
||||
throw new Error(
|
||||
'Unable to construct search hint from base item, required properties were undefined',
|
||||
);
|
||||
}
|
||||
return new SearchHint(
|
||||
baseItem.Id,
|
||||
baseItem.Name,
|
||||
|
@ -24,7 +24,7 @@ export class Playlist {
|
||||
* @returns active track or undefined if there's none
|
||||
*/
|
||||
getActiveTrack(): Track | undefined {
|
||||
if (this.isActiveTrackOutOfSync()) {
|
||||
if (this.isActiveTrackOutOfSync() || this.activeTrackIndex === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return this.tracks[this.activeTrackIndex];
|
||||
@ -51,7 +51,10 @@ export class Playlist {
|
||||
setNextTrackAsActiveTrack(): boolean {
|
||||
this.announceTrackFinishIfSet();
|
||||
|
||||
if (this.activeTrackIndex >= this.tracks.length) {
|
||||
if (
|
||||
this.activeTrackIndex === undefined ||
|
||||
this.activeTrackIndex >= this.tracks.length
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -70,7 +73,7 @@ export class Playlist {
|
||||
setPreviousTrackAsActiveTrack(): boolean {
|
||||
this.announceTrackFinishIfSet();
|
||||
|
||||
if (this.activeTrackIndex <= 0) {
|
||||
if (this.activeTrackIndex === undefined || this.activeTrackIndex <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -120,7 +123,7 @@ export class Playlist {
|
||||
* @returns if there is a track next in the playlist
|
||||
*/
|
||||
hasNextTrackInPlaylist() {
|
||||
return this.activeTrackIndex + 1 < this.tracks.length;
|
||||
return (this.activeTrackIndex ?? 0) + 1 < this.tracks.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,7 +131,7 @@ export class Playlist {
|
||||
* @returns if there is a previous track in the playlist
|
||||
*/
|
||||
hasPreviousTrackInPlaylist() {
|
||||
return this.activeTrackIndex > 0;
|
||||
return this.activeTrackIndex !== undefined && this.activeTrackIndex > 0;
|
||||
}
|
||||
|
||||
clear() {
|
||||
@ -156,13 +159,20 @@ export class Playlist {
|
||||
}
|
||||
|
||||
const activeTrack = this.getActiveTrack();
|
||||
|
||||
if (!activeTrack) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeTrack.playing = true;
|
||||
this.eventEmitter.emit('internal.audio.track.announce', activeTrack);
|
||||
}
|
||||
|
||||
private isActiveTrackOutOfSync(): boolean {
|
||||
return (
|
||||
this.activeTrackIndex < 0 || this.activeTrackIndex >= this.tracks.length
|
||||
this.activeTrackIndex === undefined ||
|
||||
this.activeTrackIndex < 0 ||
|
||||
this.activeTrackIndex >= this.tracks.length
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,6 @@ export class Track {
|
||||
}
|
||||
|
||||
getRemoteImages(): RemoteImageInfo[] {
|
||||
return this.remoteImages.Images;
|
||||
return this.remoteImages?.Images ?? [];
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export class PlayNowCommand {
|
||||
}
|
||||
|
||||
getSelection(): string[] {
|
||||
if (this.hasSelection()) {
|
||||
if (this.hasSelection() && this.StartIndex !== undefined) {
|
||||
return [this.ItemIds[this.StartIndex]];
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { DiscordMessageService } from '../clients/discord/discord.message.servic
|
||||
import { GithubRelease } from '../models/github-release';
|
||||
import { useDefaultMockerToken } from '../utils/tests/defaultMockerToken';
|
||||
import { UpdatesService } from './updates.service';
|
||||
import { InjectionToken } from '@nestjs/common';
|
||||
|
||||
// mock axios: https://stackoverflow.com/questions/51275434/type-of-axios-mock-using-jest-typescript/55351900#55351900
|
||||
jest.mock('axios');
|
||||
@ -49,7 +50,7 @@ describe('UpdatesService', () => {
|
||||
};
|
||||
}
|
||||
|
||||
return useDefaultMockerToken(token);
|
||||
return useDefaultMockerToken(token as InjectionToken);
|
||||
})
|
||||
.compile();
|
||||
|
||||
|
@ -30,6 +30,14 @@ export class UpdatesService {
|
||||
this.logger.debug('Checking for available updates...');
|
||||
|
||||
const latestGitHubRelease = await this.fetchLatestGithubRelease();
|
||||
|
||||
if (!latestGitHubRelease) {
|
||||
this.logger.warn(
|
||||
`Aborting update check because api request failed. Please check your internet connection or disable the check`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentVersion = Constants.Metadata.Version.All();
|
||||
|
||||
if (latestGitHubRelease.tag_name <= currentVersion) {
|
||||
@ -95,21 +103,21 @@ export class UpdatesService {
|
||||
});
|
||||
}
|
||||
|
||||
private async fetchLatestGithubRelease(): Promise<null | GithubRelease> {
|
||||
private async fetchLatestGithubRelease(): Promise<GithubRelease | undefined> {
|
||||
return axios({
|
||||
method: 'GET',
|
||||
url: Constants.Links.Api.GetLatestRelease,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status !== 200) {
|
||||
return null;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return response.data as GithubRelease;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error('Error while checking for updates', err);
|
||||
return null;
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
15
src/utils/trackConverter.ts
Normal file
15
src/utils/trackConverter.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { JellyfinSearchService } from 'src/clients/jellyfin/jellyfin.search.service';
|
||||
import { SearchHint } from 'src/models/search/SearchHint';
|
||||
import { Track } from 'src/models/shared/Track';
|
||||
|
||||
export const convertToTracks = (
|
||||
hints: SearchHint[],
|
||||
jellyfinSearchService: JellyfinSearchService,
|
||||
): Track[] => {
|
||||
let tracks: Track[] = [];
|
||||
hints.forEach(async (hint) => {
|
||||
const searchedTracks = await hint.toTracks(jellyfinSearchService);
|
||||
tracks = [...tracks, ...searchedTracks];
|
||||
});
|
||||
return tracks;
|
||||
};
|
@ -12,7 +12,7 @@
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": false,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
|
Loading…
Reference in New Issue
Block a user