Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added music support through Lidarr #1238

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
940 changes: 888 additions & 52 deletions overseerr-api.yml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"csurf": "1.11.0",
"date-fns": "2.29.3",
"dayjs": "1.11.7",
"dompurify": "^3.2.3",
"email-templates": "9.0.0",
"email-validator": "2.0.4",
"express": "4.18.2",
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions server/api/coverartarchive/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import ExternalAPI from '@server/api/externalapi';
import cacheManager from '@server/lib/cache';
import type { CoverArtResponse } from './interfaces';

class CoverArtArchive extends ExternalAPI {
constructor() {
super(
'https://coverartarchive.org',
{},
{
nodeCache: cacheManager.getCache('covertartarchive').data,
rateLimit: {
maxRPS: 50,
id: 'covertartarchive',
},
}
);
}

public async getCoverArt(id: string): Promise<CoverArtResponse> {
try {
const data = await this.get<CoverArtResponse>(
`/release-group/${id}`,
undefined,
43200
);
return data;
} catch (e) {
throw new Error(
`[CoverArtArchive] Failed to fetch cover art: ${e.message}`
);
}
}
}

export default CoverArtArchive;
24 changes: 24 additions & 0 deletions server/api/coverartarchive/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
interface CoverArtThumbnails {
1200: string;
250: string;
500: string;
large: string;
small: string;
}

interface CoverArtImage {
approved: boolean;
back: boolean;
comment: string;
edit: number;
front: boolean;
id: number;
image: string;
thumbnails: CoverArtThumbnails;
types: string[];
}

export interface CoverArtResponse {
images: CoverArtImage[];
release: string;
}
32 changes: 21 additions & 11 deletions server/api/jellyfin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ interface JellyfinMediaFolder {
}

export interface JellyfinLibrary {
type: 'show' | 'movie';
type: 'show' | 'movie' | 'music';
key: string;
title: string;
agent: string;
Expand All @@ -47,7 +47,13 @@ export interface JellyfinLibraryItem {
Name: string;
Id: string;
HasSubtitles: boolean;
Type: 'Movie' | 'Episode' | 'Season' | 'Series';
Type:
| 'Movie'
| 'Episode'
| 'Season'
| 'Series'
| 'MusicAlbum'
| 'MusicArtist';
LocationType: 'FileSystem' | 'Offline' | 'Remote' | 'Virtual';
SeriesName?: string;
SeriesId?: string;
Expand All @@ -57,6 +63,8 @@ export interface JellyfinLibraryItem {
IndexNumberEnd?: number;
ParentIndexNumber?: number;
MediaType: string;
AlbumId?: string;
ArtistId?: string;
}

export interface JellyfinMediaStream {
Expand Down Expand Up @@ -84,6 +92,9 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem {
Tmdb?: string;
Imdb?: string;
Tvdb?: string;
MusicBrainzReleaseGroup: string | undefined;
MusicBrainzAlbum?: string;
MusicBrainzArtistId?: string;
};
MediaSources?: JellyfinMediaSource[];
Width?: number;
Expand Down Expand Up @@ -265,13 +276,7 @@ class JellyfinAPI extends ExternalAPI {
}

private mapLibraries(mediaFolders: JellyfinMediaFolder[]): JellyfinLibrary[] {
const excludedTypes = [
'music',
'books',
'musicvideos',
'homevideos',
'boxsets',
];
const excludedTypes = ['books', 'musicvideos', 'homevideos', 'boxsets'];

return mediaFolders
.filter((Item: JellyfinMediaFolder) => {
Expand All @@ -284,7 +289,12 @@ class JellyfinAPI extends ExternalAPI {
return <JellyfinLibrary>{
key: Item.Id,
title: Item.Name,
type: Item.CollectionType === 'movies' ? 'movie' : 'show',
type:
Item.CollectionType === 'movies'
? 'movie'
: Item.CollectionType === 'tvshows'
? 'show'
: 'music',
agent: 'jellyfin',
};
});
Expand All @@ -297,7 +307,7 @@ class JellyfinAPI extends ExternalAPI {
{
SortBy: 'SortName',
SortOrder: 'Ascending',
IncludeItemTypes: 'Series,Movie,Others',
IncludeItemTypes: 'Series,Movie,MusicAlbum,MusicArtist,Others',
Recursive: 'true',
StartIndex: '0',
ParentId: id,
Expand Down
76 changes: 76 additions & 0 deletions server/api/listenbrainz/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import ExternalAPI from '@server/api/externalapi';
import cacheManager from '@server/lib/cache';
import type {
LbSimilarArtistResponse,
LbTopAlbumsResponse,
} from './interfaces';

class ListenBrainzAPI extends ExternalAPI {
constructor() {
super(
'https://api.listenbrainz.org/1',
{},
{
nodeCache: cacheManager.getCache('listenbrainz').data,
rateLimit: {
maxRPS: 50,
id: 'listenbrainz',
},
}
);
}

public async getSimilarArtists(
artistMbid: string,
options: {
days?: number;
session?: number;
contribution?: number;
threshold?: number;
limit?: number;
skip?: number;
} = {}
): Promise<LbSimilarArtistResponse[]> {
const {
days = 9000,
session = 300,
contribution = 5,
threshold = 15,
limit = 50,
skip = 30,
} = options;

return this.getRolling<LbSimilarArtistResponse[]>(
'/similar-artists/json',
{
artist_mbids: artistMbid,
algorithm: `session_based_days_${days}_session_${session}_contribution_${contribution}_threshold_${threshold}_limit_${limit}_skip_${skip}`,
},
43200,
undefined,
'https://labs.api.listenbrainz.org'
);
}

public async getTopAlbums({
offset = 0,
range = 'week',
count = 20,
}: {
offset?: number;
range?: string;
count?: number;
}): Promise<LbTopAlbumsResponse> {
return this.get<LbTopAlbumsResponse>(
'/stats/sitewide/release-groups',
{
offset: offset.toString(),
range,
count: count.toString(),
},
43200
);
}
}

export default ListenBrainzAPI;
31 changes: 31 additions & 0 deletions server/api/listenbrainz/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export interface LbSimilarArtistResponse {
artist_mbid: string;
name: string;
comment: string;
type: string | null;
gender: string | null;
score: number;
reference_mbid: string;
}

export interface LbReleaseGroup {
artist_mbids: string[];
artist_name: string;
caa_id: number;
caa_release_mbid: string;
listen_count: number;
release_group_mbid: string;
release_group_name: string;
}

export interface LbTopAlbumsResponse {
payload: {
count: number;
from_ts: number;
last_updated: number;
offset: number;
range: string;
release_groups: LbReleaseGroup[];
to_ts: number;
};
}
Loading
Loading