mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
fix(jellyfinapi): refactors jellyfin library sync to support automatic grouping and collections (#700)
* fix(jellyfinapi): refactors jellyfin library sync to support automatic grouping and collections Previously, #450 added support for automatic library grouping. However, some users reported that they were getting a 401 when using custom authentication such as LDAP. Therefore, that PR was reverted (#524). This PR adds back the support for automatic library grouping for jellyfin authentication users using the endpoint `/Library/MediaFolders` and fallsback to User views endpoint if they're unable to sync the libraries (some, not all LDAP users had issues. Some reported that it worked despite having custom authentication). Once it falls back to user views endpoint for syncing, now it will detect if automatic grouping is enabled giving a warning that its not supported when using some custom authentication methods. This PR also fixed collection syncing by expanding the boxsets when syncing. fix #256, fix #489, re #450, #524, fix #515, fix #474, fix #473 * refactor(i18n): adds the suffix "jellyfin" to jellyfin library sync message keys * refactor(i18n): extract translation keys * refactor: remove console logs * refactor: remove more console logs * refactor: apply review suggestions * chore: fix prettier failing on .github file
This commit is contained in:
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
|||||||
github: [Fallenbagel]
|
github: [Fallenbagel]
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ export interface JellyfinUserResponse {
|
|||||||
ServerId: string;
|
ServerId: string;
|
||||||
ServerName: string;
|
ServerName: string;
|
||||||
Id: string;
|
Id: string;
|
||||||
|
Configuration: {
|
||||||
|
GroupedFolders: string[];
|
||||||
|
};
|
||||||
Policy: {
|
Policy: {
|
||||||
IsAdministrator: boolean;
|
IsAdministrator: boolean;
|
||||||
};
|
};
|
||||||
@@ -24,6 +27,13 @@ export interface JellyfinUserListResponse {
|
|||||||
users: JellyfinUserResponse[];
|
users: JellyfinUserResponse[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface JellyfinMediaFolder {
|
||||||
|
Name: string;
|
||||||
|
Id: string;
|
||||||
|
Type: string;
|
||||||
|
CollectionType: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface JellyfinLibrary {
|
export interface JellyfinLibrary {
|
||||||
type: 'show' | 'movie';
|
type: 'show' | 'movie';
|
||||||
key: string;
|
key: string;
|
||||||
@@ -175,24 +185,45 @@ class JellyfinAPI {
|
|||||||
|
|
||||||
public async getLibraries(): Promise<JellyfinLibrary[]> {
|
public async getLibraries(): Promise<JellyfinLibrary[]> {
|
||||||
try {
|
try {
|
||||||
// TODO: Try to fix automatic grouping without fucking up LDAP users
|
const mediaFolders = await this.axios.get<any>(`/Library/MediaFolders`);
|
||||||
// const libraries = await this.axios.get<any>('/Library/VirtualFolders');
|
|
||||||
|
|
||||||
const account = await this.axios.get<any>(
|
return this.mapLibraries(mediaFolders.data.Items);
|
||||||
`/Users/${this.userId ?? 'Me'}/Views`
|
} catch (mediaFoldersError) {
|
||||||
);
|
// fallback to user views to get libraries
|
||||||
|
// this only affects LDAP users
|
||||||
|
try {
|
||||||
|
const mediaFolders = await this.axios.get<any>(
|
||||||
|
`/Users/${this.userId ?? 'Me'}/Views`
|
||||||
|
);
|
||||||
|
|
||||||
const response: JellyfinLibrary[] = account.data.Items.filter(
|
return this.mapLibraries(mediaFolders.data.Items);
|
||||||
(Item: any) => {
|
} catch (e) {
|
||||||
return (
|
logger.error(
|
||||||
Item.Type === 'CollectionFolder' &&
|
`Something went wrong while getting libraries from the Jellyfin server: ${e.message}`,
|
||||||
Item.CollectionType !== 'music' &&
|
{ label: 'Jellyfin API' }
|
||||||
Item.CollectionType !== 'books' &&
|
);
|
||||||
Item.CollectionType !== 'musicvideos' &&
|
return [];
|
||||||
Item.CollectionType !== 'homevideos'
|
}
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
).map((Item: any) => {
|
|
||||||
|
private mapLibraries(mediaFolders: JellyfinMediaFolder[]): JellyfinLibrary[] {
|
||||||
|
const excludedTypes = [
|
||||||
|
'music',
|
||||||
|
'books',
|
||||||
|
'musicvideos',
|
||||||
|
'homevideos',
|
||||||
|
'boxsets',
|
||||||
|
];
|
||||||
|
|
||||||
|
return mediaFolders
|
||||||
|
.filter((Item: JellyfinMediaFolder) => {
|
||||||
|
return (
|
||||||
|
Item.Type === 'CollectionFolder' &&
|
||||||
|
!excludedTypes.includes(Item.CollectionType)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((Item: JellyfinMediaFolder) => {
|
||||||
return <JellyfinLibrary>{
|
return <JellyfinLibrary>{
|
||||||
key: Item.Id,
|
key: Item.Id,
|
||||||
title: Item.Name,
|
title: Item.Name,
|
||||||
@@ -200,21 +231,12 @@ class JellyfinAPI {
|
|||||||
agent: 'jellyfin',
|
agent: 'jellyfin',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(
|
|
||||||
`Something went wrong while getting libraries from the Jellyfin server: ${e.message}`,
|
|
||||||
{ label: 'Jellyfin API' }
|
|
||||||
);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getLibraryContents(id: string): Promise<JellyfinLibraryItem[]> {
|
public async getLibraryContents(id: string): Promise<JellyfinLibraryItem[]> {
|
||||||
try {
|
try {
|
||||||
const contents = await this.axios.get<any>(
|
const contents = await this.axios.get<any>(
|
||||||
`/Users/${this.userId}/Items?SortBy=SortName&SortOrder=Ascending&IncludeItemTypes=Series,Movie,Others&Recursive=true&StartIndex=0&ParentId=${id}`
|
`/Users/${this.userId}/Items?SortBy=SortName&SortOrder=Ascending&IncludeItemTypes=Series,Movie,Others&Recursive=true&StartIndex=0&ParentId=${id}&collapseBoxSetItems=false`
|
||||||
);
|
);
|
||||||
|
|
||||||
return contents.data.Items.filter(
|
return contents.data.Items.filter(
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ settingsRoutes.post('/jellyfin', (req, res) => {
|
|||||||
return res.status(200).json(settings.jellyfin);
|
return res.status(200).json(settings.jellyfin);
|
||||||
});
|
});
|
||||||
|
|
||||||
settingsRoutes.get('/jellyfin/library', async (req, res) => {
|
settingsRoutes.get('/jellyfin/library', async (req, res, next) => {
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
|
|
||||||
if (req.query.sync) {
|
if (req.query.sync) {
|
||||||
@@ -281,6 +281,19 @@ settingsRoutes.get('/jellyfin/library', async (req, res) => {
|
|||||||
|
|
||||||
const libraries = await jellyfinClient.getLibraries();
|
const libraries = await jellyfinClient.getLibraries();
|
||||||
|
|
||||||
|
if (libraries.length === 0) {
|
||||||
|
// Check if no libraries are found due to the fallback to user views
|
||||||
|
// This only affects LDAP users
|
||||||
|
const account = await jellyfinClient.getUser();
|
||||||
|
|
||||||
|
// Automatic Library grouping is not supported when user views are used to get library
|
||||||
|
if (account.Configuration.GroupedFolders.length > 0) {
|
||||||
|
return next({ status: 501, message: 'SYNC_ERROR_GROUPED_FOLDERS' });
|
||||||
|
}
|
||||||
|
|
||||||
|
return next({ status: 404, message: 'SYNC_ERROR_NO_LIBRARIES' });
|
||||||
|
}
|
||||||
|
|
||||||
const newLibraries: Library[] = libraries.map((library) => {
|
const newLibraries: Library[] = libraries.map((library) => {
|
||||||
const existing = settings.jellyfin.libraries.find(
|
const existing = settings.jellyfin.libraries.find(
|
||||||
(l) => l.id === library.key && l.name === library.title
|
(l) => l.id === library.key && l.name === library.title
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ const messages = defineMessages({
|
|||||||
externalUrl: 'External URL',
|
externalUrl: 'External URL',
|
||||||
internalUrl: 'Internal URL',
|
internalUrl: 'Internal URL',
|
||||||
jellyfinForgotPasswordUrl: 'Forgot Password URL',
|
jellyfinForgotPasswordUrl: 'Forgot Password URL',
|
||||||
|
jellyfinSyncFailedNoLibrariesFound: 'No libraries were found',
|
||||||
|
jellyfinSyncFailedAutomaticGroupedFolders:
|
||||||
|
'Custom authentication with Automatic Library Grouping not supported',
|
||||||
|
jellyfinSyncFailedGenericError:
|
||||||
|
'Something went wrong while syncing libraries',
|
||||||
validationUrl: 'You must provide a valid URL',
|
validationUrl: 'You must provide a valid URL',
|
||||||
syncing: 'Syncing',
|
syncing: 'Syncing',
|
||||||
syncJellyfin: 'Sync Libraries',
|
syncJellyfin: 'Sync Libraries',
|
||||||
@@ -70,6 +75,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
|||||||
showAdvancedSettings,
|
showAdvancedSettings,
|
||||||
}) => {
|
}) => {
|
||||||
const [isSyncing, setIsSyncing] = useState(false);
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
|
const toasts = useToasts();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
@@ -117,11 +123,43 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
|
|||||||
params.enable = activeLibraries.join(',');
|
params.enable = activeLibraries.join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
await axios.get('/api/v1/settings/jellyfin/library', {
|
try {
|
||||||
params,
|
await axios.get('/api/v1/settings/jellyfin/library', {
|
||||||
});
|
params,
|
||||||
setIsSyncing(false);
|
});
|
||||||
revalidate();
|
setIsSyncing(false);
|
||||||
|
revalidate();
|
||||||
|
} catch (e) {
|
||||||
|
if (e.response.data.message === 'SYNC_ERROR_GROUPED_FOLDERS') {
|
||||||
|
toasts.addToast(
|
||||||
|
intl.formatMessage(
|
||||||
|
messages.jellyfinSyncFailedAutomaticGroupedFolders
|
||||||
|
),
|
||||||
|
{
|
||||||
|
autoDismiss: true,
|
||||||
|
appearance: 'warning',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else if (e.response.data.message === 'SYNC_ERROR_NO_LIBRARIES') {
|
||||||
|
toasts.addToast(
|
||||||
|
intl.formatMessage(messages.jellyfinSyncFailedNoLibrariesFound),
|
||||||
|
{
|
||||||
|
autoDismiss: true,
|
||||||
|
appearance: 'error',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toasts.addToast(
|
||||||
|
intl.formatMessage(messages.jellyfinSyncFailedGenericError),
|
||||||
|
{
|
||||||
|
autoDismiss: true,
|
||||||
|
appearance: 'error',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setIsSyncing(false);
|
||||||
|
revalidate();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startScan = async () => {
|
const startScan = async () => {
|
||||||
|
|||||||
@@ -946,6 +946,9 @@
|
|||||||
"components.Settings.jellyfinlibrariesDescription": "The libraries {mediaServerName} scans for titles. Click the button below if no libraries are listed.",
|
"components.Settings.jellyfinlibrariesDescription": "The libraries {mediaServerName} scans for titles. Click the button below if no libraries are listed.",
|
||||||
"components.Settings.jellyfinsettings": "{mediaServerName} Settings",
|
"components.Settings.jellyfinsettings": "{mediaServerName} Settings",
|
||||||
"components.Settings.jellyfinsettingsDescription": "Configure the settings for your {mediaServerName} server. {mediaServerName} scans your {mediaServerName} libraries to see what content is available.",
|
"components.Settings.jellyfinsettingsDescription": "Configure the settings for your {mediaServerName} server. {mediaServerName} scans your {mediaServerName} libraries to see what content is available.",
|
||||||
|
"components.Settings.jellyfinSyncFailedNoLibrariesFound": "No libraries were found",
|
||||||
|
"components.Settings.jellyfinSyncFailedAutomaticGroupedFolders": "Custom authentication with Automatic Library Grouping not supported",
|
||||||
|
"components.Settings.jellyfinSyncFailedGenericError": "Something went wrong while syncing libraries",
|
||||||
"components.Settings.librariesRemaining": "Libraries Remaining: {count}",
|
"components.Settings.librariesRemaining": "Libraries Remaining: {count}",
|
||||||
"components.Settings.manualscan": "Manual Library Scan",
|
"components.Settings.manualscan": "Manual Library Scan",
|
||||||
"components.Settings.manualscanDescription": "Normally, this will only be run once every 24 hours. Jellyseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one-time full manual library scan is recommended!",
|
"components.Settings.manualscanDescription": "Normally, this will only be run once every 24 hours. Jellyseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one-time full manual library scan is recommended!",
|
||||||
|
|||||||
Reference in New Issue
Block a user