Compare commits

..

1 Commits

Author SHA1 Message Date
fallenbagel
4377667b8f feat(settings): stores the jellyfin/emby servername in settings
Stores jellyfin/emby(?) server name in the settings file. This might come in handy in the future
once simultaneous multi-server sync is implemented.
2024-05-25 01:00:31 +05:00
9 changed files with 56 additions and 99 deletions

View File

@@ -367,15 +367,6 @@
"contributions": [
"translation"
]
},
{
"login": "ThowZzy",
"name": "ThowZzy",
"avatar_url": "https://avatars.githubusercontent.com/u/61882536?v=4",
"profile": "https://github.com/ThowZzy",
"contributions": [
"code"
]
}
]
}

View File

@@ -2,7 +2,7 @@ name: Publish Snap
# turn off edge snap builds temporarily and make it manual
# on:
# on:
# push:
# branches:
# - develop

View File

@@ -11,7 +11,7 @@
<a href="http://jellyseerr.borgcube.de/engage/jellyseerr/"><img src="http://jellyseerr.borgcube.de/widget/jellyseerr/jellyseerr-frontend/svg-badge.svg" alt="Translation status" /></a>
<a href="https://github.com/fallenbagel/jellyseerr/blob/develop/LICENSE"><img alt="GitHub" src="https://img.shields.io/github/license/fallenbagel/jellyseerr"></a>
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-40-orange.svg"/></a>
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-39-orange.svg"/></a>
<!-- ALL-CONTRIBUTORS-BADGE:END -->
**Jellyseerr** is a free and open source software application for managing requests for your media library.
@@ -236,7 +236,6 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Kara-Zor-El"><img src="https://avatars.githubusercontent.com/u/69772087?v=4?s=100" width="100px;" alt="Kara"/><br /><sub><b>Kara</b></sub></a><br /><a href="#infra-Kara-Zor-El" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://joaquinolivero.com"><img src="https://avatars.githubusercontent.com/u/66050823?v=4?s=100" width="100px;" alt="Joaquin Olivero"/><br /><sub><b>Joaquin Olivero</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=JoaquinOlivero" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Bretterteig"><img src="https://avatars.githubusercontent.com/u/47298401?v=4?s=100" width="100px;" alt="Julian Behr"/><br /><sub><b>Julian Behr</b></sub></a><br /><a href="#translation-Bretterteig" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ThowZzy"><img src="https://avatars.githubusercontent.com/u/61882536?v=4?s=100" width="100px;" alt="ThowZzy"/><br /><sub><b>ThowZzy</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=ThowZzy" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -44,7 +44,6 @@
"axios-rate-limit": "1.3.0",
"bcrypt": "5.1.0",
"bowser": "2.11.0",
"cacheable-lookup": "^7.0.0",
"connect-typeorm": "1.1.4",
"cookie-parser": "1.4.6",
"copy-to-clipboard": "3.3.3",

View File

@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import ExternalAPI from '@server/api/externalapi';
import { ApiErrorCode } from '@server/constants/error';
import availabilitySync from '@server/lib/availabilitySync';
import logger from '@server/logger';
import { ApiError } from '@server/types/error';
import { getAppVersion } from '@server/utils/appVersion';
import type { AxiosInstance } from 'axios';
import axios from 'axios';
export interface JellyfinUserResponse {
Name: string;
@@ -92,33 +92,31 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem {
DateCreated?: string;
}
class JellyfinAPI extends ExternalAPI {
class JellyfinAPI {
private authToken?: string;
private userId?: string;
private jellyfinHost: string;
private axios: AxiosInstance;
constructor(jellyfinHost: string, authToken?: string, deviceId?: string) {
let authHeaderVal: string;
if (authToken) {
authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="${getAppVersion()}", Token="${authToken}"`;
} else {
authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="${getAppVersion()}"`;
}
super(
jellyfinHost,
{},
{
headers: {
'X-Emby-Authorization': authHeaderVal,
'Content-Type': 'application/json',
Accept: 'application/json',
},
}
);
this.jellyfinHost = jellyfinHost;
this.authToken = authToken;
let authHeaderVal = '';
if (this.authToken) {
authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="10.8.0", Token="${authToken}"`;
} else {
authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="10.8.0"`;
}
this.axios = axios.create({
baseURL: this.jellyfinHost,
headers: {
'X-Emby-Authorization': authHeaderVal,
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
}
public async login(
@@ -132,8 +130,7 @@ class JellyfinAPI extends ExternalAPI {
'X-Forwarded-For': ClientIP,
}
: {};
const authResponse = await this.post<JellyfinLoginResponse>(
const account = await this.axios.post<JellyfinLoginResponse>(
'/Users/AuthenticateByName',
{
Username: Username,
@@ -144,7 +141,7 @@ class JellyfinAPI extends ExternalAPI {
}
);
return authResponse;
return account.data;
} catch (e) {
const status = e.response?.status;
@@ -180,72 +177,66 @@ class JellyfinAPI extends ExternalAPI {
public async getServerName(): Promise<string> {
try {
const serverResponse = await this.get<JellyfinUserResponse>(
'/System/Info/Public'
const account = await this.axios.get<JellyfinUserResponse>(
"/System/Info/Public'}"
);
return serverResponse.ServerName;
return account.data.ServerName;
} catch (e) {
logger.error(
`Something went wrong while getting the server name from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
throw new ApiError(e.response?.status, ApiErrorCode.Unknown);
throw new Error('girl idk');
}
}
public async getUsers(): Promise<JellyfinUserListResponse> {
try {
const userReponse = await this.get<JellyfinUserResponse[]>(`/Users`);
return { users: userReponse };
const account = await this.axios.get(`/Users`);
return { users: account.data };
} catch (e) {
logger.error(
`Something went wrong while getting the account from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
throw new Error('Invalid auth token');
}
}
public async getUser(): Promise<JellyfinUserResponse> {
try {
const userReponse = await this.get<JellyfinUserResponse>(
const account = await this.axios.get<JellyfinUserResponse>(
`/Users/${this.userId ?? 'Me'}`
);
return userReponse;
return account.data;
} catch (e) {
logger.error(
`Something went wrong while getting the account from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
throw new Error('Invalid auth token');
}
}
public async getLibraries(): Promise<JellyfinLibrary[]> {
try {
const mediaFolderResponse = await this.get<any>(`/Library/MediaFolders`);
const mediaFolders = await this.axios.get<any>(`/Library/MediaFolders`);
return this.mapLibraries(mediaFolderResponse.Items);
} catch (mediaFoldersResponseError) {
return this.mapLibraries(mediaFolders.data.Items);
} catch (mediaFoldersError) {
// fallback to user views to get libraries
// this only and maybe/depending on factors affects LDAP users
// this only affects LDAP users
try {
const mediaFolderResponse = await this.get<any>(
const mediaFolders = await this.axios.get<any>(
`/Users/${this.userId ?? 'Me'}/Views`
);
return this.mapLibraries(mediaFolderResponse.Items);
return this.mapLibraries(mediaFolders.data.Items);
} catch (e) {
logger.error(
`Something went wrong while getting libraries from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
return [];
}
}
@@ -279,11 +270,11 @@ class JellyfinAPI extends ExternalAPI {
public async getLibraryContents(id: string): Promise<JellyfinLibraryItem[]> {
try {
const libraryItemsResponse = await this.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}&collapseBoxSetItems=false`
);
return libraryItemsResponse.Items.filter(
return contents.data.Items.filter(
(item: JellyfinLibraryItem) => item.LocationType !== 'Virtual'
);
} catch (e) {
@@ -291,25 +282,23 @@ class JellyfinAPI extends ExternalAPI {
`Something went wrong while getting library content from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
throw new Error('Invalid auth token');
}
}
public async getRecentlyAdded(id: string): Promise<JellyfinLibraryItem[]> {
try {
const itemResponse = await this.get<any>(
const contents = await this.axios.get<any>(
`/Users/${this.userId}/Items/Latest?Limit=12&ParentId=${id}`
);
return itemResponse;
return contents.data;
} catch (e) {
logger.error(
`Something went wrong while getting library content from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
throw new Error('Invalid auth token');
}
}
@@ -317,38 +306,36 @@ class JellyfinAPI extends ExternalAPI {
id: string
): Promise<JellyfinLibraryItemExtended | undefined> {
try {
const itemResponse = await this.get<any>(
const contents = await this.axios.get<any>(
`/Users/${this.userId}/Items/${id}`
);
return itemResponse;
return contents.data;
} catch (e) {
if (availabilitySync.running) {
if (e.response && e.response.status === 500) {
return undefined;
}
}
logger.error(
`Something went wrong while getting library content from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
throw new Error('Invalid auth token');
}
}
public async getSeasons(seriesID: string): Promise<JellyfinLibraryItem[]> {
try {
const seasonResponse = await this.get<any>(`/Shows/${seriesID}/Seasons`);
const contents = await this.axios.get<any>(`/Shows/${seriesID}/Seasons`);
return seasonResponse.Items;
return contents.data.Items;
} catch (e) {
logger.error(
`Something went wrong while getting the list of seasons from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
throw new Error('Invalid auth token');
}
}
@@ -357,11 +344,11 @@ class JellyfinAPI extends ExternalAPI {
seasonID: string
): Promise<JellyfinLibraryItem[]> {
try {
const episodeResponse = await this.get<any>(
const contents = await this.axios.get<any>(
`/Shows/${seriesID}/Episodes?seasonId=${seasonID}`
);
return episodeResponse.Items.filter(
return contents.data.Items.filter(
(item: JellyfinLibraryItem) => item.LocationType !== 'Virtual'
);
} catch (e) {
@@ -369,8 +356,7 @@ class JellyfinAPI extends ExternalAPI {
`Something went wrong while getting the list of episodes from the Jellyfin server: ${e.message}`,
{ label: 'Jellyfin API' }
);
throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken);
throw new Error('Invalid auth token');
}
}
}

View File

@@ -1,7 +1,5 @@
export enum ApiErrorCode {
InvalidUrl = 'INVALID_URL',
InvalidCredentials = 'INVALID_CREDENTIALS',
InvalidAuthToken = 'INVALID_AUTH_TOKEN',
NotAdmin = 'NOT_ADMIN',
Unknown = 'UNKNOWN',
}

View File

@@ -23,7 +23,6 @@ import imageproxy from '@server/routes/imageproxy';
import { getAppVersion } from '@server/utils/appVersion';
import restartFlag from '@server/utils/restartFlag';
import { getClientIp } from '@supercharge/request-ip';
import type CacheableLookupType from 'cacheable-lookup';
import { TypeormStore } from 'connect-typeorm/out';
import cookieParser from 'cookie-parser';
import csurf from 'csurf';
@@ -33,14 +32,10 @@ import * as OpenApiValidator from 'express-openapi-validator';
import type { Store } from 'express-session';
import session from 'express-session';
import next from 'next';
import http from 'node:http';
import https from 'node:https';
import path from 'path';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
const _importDynamic = new Function('modulePath', 'return import(modulePath)');
const API_SPEC_PATH = path.join(__dirname, '../overseerr-api.yml');
logger.info(`Starting Overseerr version ${getAppVersion()}`);
@@ -51,12 +46,6 @@ const handle = app.getRequestHandler();
app
.prepare()
.then(async () => {
const CacheableLookup = (await _importDynamic('cacheable-lookup'))
.default as typeof CacheableLookupType;
const cacheable = new CacheableLookup();
cacheable.install(http.globalAgent);
cacheable.install(https.globalAgent);
const dbConnection = await dataSource.initialize();
// Run migrations in production

View File

@@ -325,7 +325,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
await userRepository.save(user);
}
// User already exists, let's update their information
else if (account.User.Id === user?.jellyfinUserId) {
else if (body.username === user?.jellyfinUsername) {
logger.info(
`Found matching ${
settings.main.mediaServerType === MediaServerType.JELLYFIN

View File

@@ -5033,11 +5033,6 @@ cacache@^16.0.0, cacache@^16.1.0, cacache@^16.1.3:
tar "^6.1.11"
unique-filename "^2.0.0"
cacheable-lookup@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz#3476a8215d046e5a3202a9209dd13fec1f933a27"
integrity sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==
cachedir@2.3.0, cachedir@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8"