Compare commits

...

36 Commits

Author SHA1 Message Date
fallenbagel
6caa0456bf feat: use gravatar for jellyfin users' with missing jellyfin avatars 2024-02-23 09:26:45 +05:00
fallenbagel
d9da3a2e3f refactor: jellyfin authentication
This refactor standardizes the authentication approach in Jellyfin to mirror the method employed in
Plex authentication for consistency
2024-02-23 09:18:13 +05:00
InvalidArgumentException
db84f6529a fix(jellyfin.ts): process virtual seasons if they have non virtual episodes (#639)
All seasons are processed now, but those without any episodes are filtered out again as unavailable.
This fixes in issue where jellyfin reports all seasons as virtual
2024-02-01 16:10:06 +05:00
Fallenbagel
4f81788386 Merge pull request #640 from Fallenbagel/all-contributors/add-Danish-H
docs: add Danish-H as a contributor for code
2024-01-29 00:54:02 +05:00
allcontributors[bot]
72d3f9b908 docs: update .all-contributorsrc [skip ci] 2024-01-28 19:53:51 +00:00
allcontributors[bot]
333ffed7f0 docs: update README.md [skip ci] 2024-01-28 19:53:50 +00:00
Fallenbagel
8641a26771 Merge pull request #636 from Danish-H/feature-letterboxd-links
feat: added Letterboxd links for the external link blocks for movies
2024-01-29 00:53:29 +05:00
Fallenbagel
7329524868 Merge pull request #638 from Fallenbagel/all-contributors/add-aleksasiriski
docs: add aleksasiriski as a contributor for infra
2024-01-28 01:10:56 +05:00
allcontributors[bot]
908dcb487a docs: update .all-contributorsrc [skip ci] 2024-01-27 20:10:44 +00:00
allcontributors[bot]
d486d58d3d docs: update README.md [skip ci] 2024-01-27 20:10:43 +00:00
Fallenbagel
d8b08f4c6b Merge pull request #637 from aleksasiriski/patch-1
ci(preview): added arm support for preview tags
2024-01-28 00:50:23 +05:00
Aleksa Siriški
a48a337e0f ci(preview): added arm support for preview tags 2024-01-27 16:58:35 +01:00
Danish Humair
981f5e679c feat: added Letterboxd links for the external link blocks for movies 2024-01-27 03:25:03 +05:00
Fallenbagel
7af193b8f6 docs: fix weblate link 2024-01-13 22:05:03 +05:00
Fallenbagel
6040e16645 update discord badge 2024-01-04 02:02:16 +05:00
Fallenbagel
3877301fc8 add translation percentage badge 2024-01-04 02:00:49 +05:00
Fallenbagel
092a1458a4 move weblate details to contributing.md 2024-01-03 14:25:23 +05:00
Fallenbagel
1c68111b12 update weblate link 2024-01-03 14:24:45 +05:00
Fallenbagel
0e777ddb1e Merge pull request #612 from Fallenbagel/feat-readme-weblate
Add more badges and weblate status
2024-01-03 14:20:29 +05:00
Fallenbagel
52c689b080 Merge pull request #613 from Fallenbagel/all-contributors/add-xeruf
docs: add xeruf as a contributor for doc
2024-01-03 14:12:32 +05:00
allcontributors[bot]
1a11f085ba docs: update .all-contributorsrc [skip ci] 2024-01-03 09:12:19 +00:00
allcontributors[bot]
c0234582a6 docs: update README.md [skip ci] 2024-01-03 09:12:18 +00:00
Fallenbagel
fd958d6347 Merge pull request #611 from xeruf/patch-1
Link related projects in README.md
2024-01-03 14:10:17 +05:00
Fallenbagel
6586db52dc Add more badges and weblate status 2024-01-03 14:04:17 +05:00
Janek
a41cb8b004 Link related projects in README.md 2024-01-03 07:39:48 +01:00
Fallenbagel
de66222e7a Merge pull request #590 from Fallenbagel/all-contributors/add-mdll23
docs: add mdll23 as a contributor for translation
2023-12-03 21:04:28 +05:00
allcontributors[bot]
eb790cb466 docs: update .all-contributorsrc [skip ci] 2023-12-03 16:03:26 +00:00
allcontributors[bot]
0680931332 docs: update README.md [skip ci] 2023-12-03 16:03:26 +00:00
Fallenbagel
ff2821471e Merge pull request #589 from mdll23/develop
fix: translation de.json
2023-12-03 21:02:59 +05:00
mdll23
e032c02f5f fix: fix german translation for "components.Discover.FilterSlideover.tmdbuservotecount" 2023-12-03 15:13:19 +01:00
Fallenbagel
f8c4def229 Merge pull request #565 from notquitenothing/custom-jellyfin-password-reset
feat: Custom jellyfin password reset setting
2023-11-30 14:08:20 +05:00
fallenbagel
a0415e7b6b Merge branch 'develop' into custom-jellyfin-password-reset 2023-11-30 09:26:14 +05:00
fallenbagel
b5f672785a docs: reverted two unrelated files to its develop branch state 2023-11-30 09:25:34 +05:00
Derek Paschal
0dfe050ba1 Fixing code formatting, prettier 2023-11-15 06:59:02 -06:00
Derek Paschal
13dd3cad54 Making the new setting optional 2023-11-14 08:51:29 -06:00
Derek Paschal
ce9802d5d4 Adding Jellyfin Setting for Custom "Forgot Password" URL
Adding Jellyfin Setting for Custom "Forgot Password" URL.  Useful in cases where you are using a custom authentication provider such as the LDAP plugin, Authelia, lldap, or any other external auth scheme with its own password reset page.
2023-11-14 08:20:28 -06:00
17 changed files with 245 additions and 87 deletions

View File

@@ -277,6 +277,42 @@
"contributions": [ "contributions": [
"doc" "doc"
] ]
},
{
"login": "mdll23",
"name": "Michael Dallinger",
"avatar_url": "https://avatars.githubusercontent.com/u/142844478?v=4",
"profile": "https://github.com/mdll23",
"contributions": [
"translation"
]
},
{
"login": "xeruf",
"name": "Janek",
"avatar_url": "https://avatars.githubusercontent.com/u/13354331?v=4",
"profile": "https://github.com/xeruf",
"contributions": [
"doc"
]
},
{
"login": "aleksasiriski",
"name": "Aleksa Siriški",
"avatar_url": "https://avatars.githubusercontent.com/u/31509435?v=4",
"profile": "https://aleksasiriski.dev",
"contributions": [
"infra"
]
},
{
"login": "Danish-H",
"name": "Danish Humair",
"avatar_url": "https://avatars.githubusercontent.com/u/121830048?v=4",
"profile": "http://danishhumair.com",
"contributions": [
"code"
]
} }
] ]
} }

View File

@@ -29,7 +29,7 @@ jobs:
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
platforms: linux/amd64 platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true push: true
build-args: | build-args: |
COMMIT_TAG=${{ github.sha }} COMMIT_TAG=${{ github.sha }}

View File

@@ -1,4 +1,4 @@
# Contributing to Overseerr # Contributing to Jellyseerr
All help is welcome and greatly appreciated! If you would like to contribute to the project, the following instructions should get you started... All help is welcome and greatly appreciated! If you would like to contribute to the project, the following instructions should get you started...
@@ -17,7 +17,7 @@ All help is welcome and greatly appreciated! If you would like to contribute to
1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository to your own GitHub account and [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device: 1. [Fork](https://help.github.com/articles/fork-a-repo/) the repository to your own GitHub account and [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device:
```bash ```bash
git clone https://github.com/YOUR_USERNAME/overseerr.git git clone https://github.com/YOUR_USERNAME/jellyseerr.git
cd overseerr/ cd overseerr/
``` ```
@@ -97,9 +97,9 @@ When adding new UI text, please try to adhere to the following guidelines:
## Translation ## Translation
We use [Weblate](https://hosted.weblate.org/engage/overseerr/) for our translations, and your help with localizing Overseerr would be greatly appreciated! If your language is not listed below, please [open a feature request](https://github.com/fallenbagel/jellyseerr/issues/new/choose). We use [Weblate](https://jellyseerr.borgcube.de/projects/jellyseerr/jellyseerr-frontend/) for our translations, and your help with localizing Overseerr would be greatly appreciated! If your language is not listed below, please [open a feature request](https://github.com/fallenbagel/jellyseerr/issues/new/choose).
<a href="https://hosted.weblate.org/engage/overseerr/"><img src="https://hosted.weblate.org/widgets/overseerr/-/overseerr-frontend/multi-auto.svg" alt="Translation status" /></a> <a href="https://jellyseerr.borgcube.de/engage/jellysseerr/"><img src="https://jellyseerr.borgcube.de/widget/jellyseerr/multi-auto.svg" alt="Translation status" /></a>
## Attribution ## Attribution

View File

@@ -2,23 +2,28 @@
<img src="./public/logo_full.svg" alt="Jellyseerr" style="margin: 20px 0;"> <img src="./public/logo_full.svg" alt="Jellyseerr" style="margin: 20px 0;">
</p> </p>
<p align="center"> <p align="center">
<a href="https://discord.gg/ckbvBtDJgC"><img src="https://img.shields.io/badge/Discord-Chat-lightgrey" alt="Discord"></a> <img src="https://github.com/Fallenbagel/jellyseerr/actions/workflows/release.yml/badge.svg" alt="Jellyseerr Release" />
<img src="https://github.com/Fallenbagel/jellyseerr/actions/workflows/ci.yml/badge.svg" alt="Jellyseerr CI">
</p>
<p align="center">
<a href="https://discord.gg/ckbvBtDJgC"><img src="https://img.shields.io/discord/952656177924300932" alt="Discord"></a>
<a href="https://hub.docker.com/r/fallenbagel/jellyseerr"><img src="https://img.shields.io/docker/pulls/fallenbagel/jellyseerr" alt="Docker pulls"></a> <a href="https://hub.docker.com/r/fallenbagel/jellyseerr"><img src="https://img.shields.io/docker/pulls/fallenbagel/jellyseerr" alt="Docker pulls"></a>
<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> <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 --> <!-- 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-29-orange.svg"/></a> <a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-33-orange.svg"/></a>
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
**Jellyseerr** is a free and open source software application for managing requests for your media library. It is a a fork of Overseerr built to bring support for Jellyfin & Emby media servers! **Jellyseerr** is a free and open source software application for managing requests for your media library.
It is a a fork of [Overseerr](https://github.com/sct/overseerr) built to bring support for [Jellyfin](https://github.com/jellyfin/jellyfin) & [Emby](https://github.com/MediaBrowser/Emby) media servers!
_The original Overseerr team have been busy and Jellyfin/Emby support aren't on their roadmap, so we started this project as we wanted to bring the Overseerr experience to the Jellyfin/Emby Community!_ _The original Overseerr team have been busy and Jellyfin/Emby support aren't on their roadmap, so we started this project as we wanted to bring the Overseerr experience to the Jellyfin/Emby Community!_
## Current Features ## Current Features
- Full Jellyfin/Emby/Plex integration. Authenticate and manage user access with Jellyfin/Emby/Plex! - Full Jellyfin/Emby/Plex integration including authentication with user import & management
- Supports Movies, Shows, Mixed Libraries! - Supports Movies, Shows and Mixed Libraries
- Ability to change email addresses for smtp purposes - Ability to change email addresses for smtp purposes
- Ability to import all jellyfin/emby users
- Easy integration with your existing services. Currently, Jellyseerr supports Sonarr and Radarr. More to come! - Easy integration with your existing services. Currently, Jellyseerr supports Sonarr and Radarr. More to come!
- Jellyfin/Emby/Plex library scan, to keep track of the titles which are already available. - Jellyfin/Emby/Plex library scan, to keep track of the titles which are already available.
- Customizable request system, which allows users to request individual seasons or movies in a friendly, easy-to-use interface. - Customizable request system, which allows users to request individual seasons or movies in a friendly, easy-to-use interface.
@@ -49,7 +54,7 @@ https://hub.docker.com/r/fallenbagel/jellyseerr
Pre-requisites: Pre-requisites:
- Nodejs [v18](https://nodejs.org/download/release/v18.18.2) - Nodejs [v18](https://nodejs.org/download/release/v18.18.2)
- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) - [Yarn](https://classic.yarnpkg.com/lang/en/docs/install)
- Download/git clone the source code from the github (Either develop branch or main for stable) - Download/git clone the source code from the github (Either develop branch or main for stable)
```cmd ```cmd
@@ -59,6 +64,7 @@ yarn install --frozen-lockfile --network-timeout 1000000
yarn run build yarn run build
yarn start yarn start
``` ```
(you can use task scheduler to run a bat script with `@echo off` and `yarn start` to run jellyseerr in the background) (you can use task scheduler to run a bat script with `@echo off` and `yarn start` to run jellyseerr in the background)
_to set env variables such as `JELLYFIN_TYPE=emby` create a file called `.env` in the root directory of jellyseerr_ _to set env variables such as `JELLYFIN_TYPE=emby` create a file called `.env` in the root directory of jellyseerr_
@@ -136,6 +142,7 @@ ExecStart=/usr/bin/node dist/index.js
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
### Packages: ### Packages:
Archlinux: [AUR](https://aur.archlinux.org/packages/jellyseerr) Archlinux: [AUR](https://aur.archlinux.org/packages/jellyseerr)
@@ -217,6 +224,10 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
</tr> </tr>
<tr> <tr>
<td align="center" valign="top" width="14.28%"><a href="https://athfan.com"><img src="https://avatars.githubusercontent.com/u/13810742?v=4?s=100" width="100px;" alt="Athfan Khaleel"/><br /><sub><b>Athfan Khaleel</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=athphane" title="Documentation">📖</a></td> <td align="center" valign="top" width="14.28%"><a href="https://athfan.com"><img src="https://avatars.githubusercontent.com/u/13810742?v=4?s=100" width="100px;" alt="Athfan Khaleel"/><br /><sub><b>Athfan Khaleel</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=athphane" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mdll23"><img src="https://avatars.githubusercontent.com/u/142844478?v=4?s=100" width="100px;" alt="Michael Dallinger"/><br /><sub><b>Michael Dallinger</b></sub></a><br /><a href="#translation-mdll23" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/xeruf"><img src="https://avatars.githubusercontent.com/u/13354331?v=4?s=100" width="100px;" alt="Janek"/><br /><sub><b>Janek</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=xeruf" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://aleksasiriski.dev"><img src="https://avatars.githubusercontent.com/u/31509435?v=4?s=100" width="100px;" alt="Aleksa Siriški"/><br /><sub><b>Aleksa Siriški</b></sub></a><br /><a href="#infra-aleksasiriski" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://danishhumair.com"><img src="https://avatars.githubusercontent.com/u/121830048?v=4?s=100" width="100px;" alt="Danish Humair"/><br /><sub><b>Danish Humair</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=Danish-H" title="Code">💻</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -368,6 +368,9 @@ components:
externalHostname: externalHostname:
type: string type: string
example: 'http://my.jellyfin.host' example: 'http://my.jellyfin.host'
jellyfinForgotPasswordUrl:
type: string
example: 'http://my.jellyfin.host/web/index.html#!/forgotpassword.html'
adminUser: adminUser:
type: string type: string
example: 'admin' example: 'admin'

View File

@@ -261,9 +261,7 @@ class JellyfinAPI {
try { try {
const contents = await this.axios.get<any>(`/Shows/${seriesID}/Seasons`); const contents = await this.axios.get<any>(`/Shows/${seriesID}/Seasons`);
return contents.data.Items.filter( return contents.data.Items;
(item: JellyfinLibraryItem) => item.LocationType !== 'Virtual'
);
} catch (e) { } catch (e) {
logger.error( logger.error(
`Something went wrong while getting the list of seasons from the Jellyfin server: ${e.message}`, `Something went wrong while getting the list of seasons from the Jellyfin server: ${e.message}`,

View File

@@ -24,6 +24,7 @@ export interface PublicSettingsResponse {
jellyfinHost?: string; jellyfinHost?: string;
jellyfinExternalHost?: string; jellyfinExternalHost?: string;
jellyfinServerName?: string; jellyfinServerName?: string;
jellyfinForgotPasswordUrl?: string;
initialized: boolean; initialized: boolean;
applicationTitle: string; applicationTitle: string;
applicationUrl: string; applicationUrl: string;

View File

@@ -40,6 +40,7 @@ export interface JellyfinSettings {
name: string; name: string;
hostname: string; hostname: string;
externalHostname?: string; externalHostname?: string;
jellyfinForgotPasswordUrl?: string;
libraries: Library[]; libraries: Library[];
serverId: string; serverId: string;
} }
@@ -131,6 +132,7 @@ interface FullPublicSettings extends PublicSettings {
mediaServerType: number; mediaServerType: number;
jellyfinHost?: string; jellyfinHost?: string;
jellyfinExternalHost?: string; jellyfinExternalHost?: string;
jellyfinForgotPasswordUrl?: string;
jellyfinServerName?: string; jellyfinServerName?: string;
partialRequestsEnabled: boolean; partialRequestsEnabled: boolean;
cacheImages: boolean; cacheImages: boolean;
@@ -331,6 +333,7 @@ class Settings {
name: '', name: '',
hostname: '', hostname: '',
externalHostname: '', externalHostname: '',
jellyfinForgotPasswordUrl: '',
libraries: [], libraries: [],
serverId: '', serverId: '',
}, },
@@ -534,6 +537,7 @@ class Settings {
applicationUrl: this.data.main.applicationUrl, applicationUrl: this.data.main.applicationUrl,
hideAvailable: this.data.main.hideAvailable, hideAvailable: this.data.main.hideAvailable,
localLogin: this.data.main.localLogin, localLogin: this.data.main.localLogin,
jellyfinForgotPasswordUrl: this.data.jellyfin.jellyfinForgotPasswordUrl,
movie4kEnabled: this.data.radarr.some( movie4kEnabled: this.data.radarr.some(
(radarr) => radarr.is4k && radarr.isDefault (radarr) => radarr.is4k && radarr.isDefault
), ),

View File

@@ -11,6 +11,7 @@ import logger from '@server/logger';
import { isAuthenticated } from '@server/middleware/auth'; import { isAuthenticated } from '@server/middleware/auth';
import * as EmailValidator from 'email-validator'; import * as EmailValidator from 'email-validator';
import { Router } from 'express'; import { Router } from 'express';
import gravatarUrl from 'gravatar-url';
const authRoutes = Router(); const authRoutes = Router();
@@ -274,24 +275,82 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
where: { jellyfinUserId: account.User.Id }, where: { jellyfinUserId: account.User.Id },
}); });
if (user) { if (!user && !(await userRepository.count())) {
logger.info(
'Sign-in attempt from Jellyfin user with access to the media server; creating initial admin user for Overseerr',
{
label: 'API',
ip: req.ip,
jellyfinUsername: account.User.Name,
}
);
// User doesn't exist, and there are no users in the database, we'll create the user
// with admin permission
settings.main.mediaServerType = MediaServerType.JELLYFIN;
user = new User({
email: body.email,
jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: account.User.PrimaryImageTag
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: gravatarUrl(body.email ?? '', { default: 'mm', size: 200 }),
userType: UserType.JELLYFIN,
});
settings.jellyfin.hostname = body.hostname ?? '';
settings.jellyfin.serverId = account.User.ServerId;
settings.save();
startJobs();
await userRepository.save(user);
}
// User already exists, let's update their information
else if (body.username === user?.jellyfinUsername) {
logger.info(
`Found matching ${
settings.main.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby'
} user; updating user with ${
settings.main.mediaServerType === MediaServerType.JELLYFIN
? 'Jellyfin'
: 'Emby'
}`,
{
label: 'API',
ip: req.ip,
jellyfinUsername: account.User.Name,
}
);
// Let's check if their authtoken is up to date // Let's check if their authtoken is up to date
if (user.jellyfinAuthToken !== account.AccessToken) { if (user.jellyfinAuthToken !== account.AccessToken) {
user.jellyfinAuthToken = account.AccessToken; user.jellyfinAuthToken = account.AccessToken;
} }
// Update the users avatar with their jellyfin profile pic (incase it changed) // Update the users avatar with their jellyfin profile pic (incase it changed)
if (account.User.PrimaryImageTag) { if (account.User.PrimaryImageTag) {
user.avatar = `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`; user.avatar = `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`;
} else { } else {
user.avatar = '/os_logo_square.png'; user.avatar = gravatarUrl(user.email, {
default: 'mm',
size: 200,
});
} }
user.jellyfinUsername = account.User.Name; user.jellyfinUsername = account.User.Name;
if (user.username === account.User.Name) { if (user.username === account.User.Name) {
user.username = ''; user.username = '';
} }
// If JELLYFIN_TYPE is set to 'emby' then set mediaServerType to EMBY
if (process.env.JELLYFIN_TYPE === 'emby') {
settings.main.mediaServerType = MediaServerType.EMBY;
settings.save();
}
await userRepository.save(user); await userRepository.save(user);
} else if (!settings.main.newPlexLogin) { } else if (!settings.main.newPlexLogin) {
logger.warn( logger.warn(
@@ -307,69 +366,38 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
status: 403, status: 403,
message: 'Access denied.', message: 'Access denied.',
}); });
} else { } else if (!user) {
// Here we check if it's the first user. If it is, we create the user with no check logger.info(
// and give them admin permissions 'Sign-in attempt from Jellyfin user with access to the media server; creating new Overseerr user',
const totalUsers = await userRepository.count(); {
if (totalUsers === 0) { label: 'API',
logger.info( ip: req.ip,
'Sign-in attempt from Jellyfin user with access to the media server; creating initial admin user for Overseerr',
{
label: 'API',
ip: req.ip,
jellyfinUsername: account.User.Name,
}
);
user = new User({
email: body.email,
jellyfinUsername: account.User.Name, jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: Permission.ADMIN,
avatar: account.User.PrimaryImageTag
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: '/os_logo_square.png',
userType: UserType.JELLYFIN,
});
await userRepository.save(user);
//Update hostname in settings if it doesn't exist (initial configuration)
//Also set mediaservertype to JELLYFIN
if (settings.jellyfin.hostname === '') {
settings.main.mediaServerType = MediaServerType.JELLYFIN;
settings.jellyfin.hostname = body.hostname ?? '';
settings.jellyfin.serverId = account.User.ServerId;
settings.save();
startJobs();
} }
);
if (!body.email) {
throw new Error('add_email');
} }
if (!user) { user = new User({
if (!body.email) { email: body.email,
throw new Error('add_email'); jellyfinUsername: account.User.Name,
} jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
user = new User({ jellyfinAuthToken: account.AccessToken,
email: body.email, permissions: settings.main.defaultPermissions,
jellyfinUsername: account.User.Name, avatar: account.User.PrimaryImageTag
jellyfinUserId: account.User.Id, ? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
jellyfinDeviceId: deviceId, : gravatarUrl(body.email, { default: 'mm', size: 200 }),
jellyfinAuthToken: account.AccessToken, userType: UserType.JELLYFIN,
permissions: settings.main.defaultPermissions, });
avatar: account.User.PrimaryImageTag //initialize Jellyfin/Emby users with local login
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90` const passedExplicitPassword = body.password && body.password.length > 0;
: '/os_logo_square.png', if (passedExplicitPassword) {
userType: UserType.JELLYFIN, await user.setPassword(body.password ?? '');
});
//initialize Jellyfin/Emby users with local login
const passedExplicitPassword =
body.password && body.password.length > 0;
if (passedExplicitPassword) {
await user.setPassword(body.password ?? '');
}
await userRepository.save(user);
} }
await userRepository.save(user);
} }
// Set logged in session // Set logged in session
@@ -400,6 +428,11 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
status: 406, status: 406,
message: 'CREDENTIAL_ERROR_ADD_EMAIL', message: 'CREDENTIAL_ERROR_ADD_EMAIL',
}); });
} else if (e.message === 'select_server_type') {
return next({
status: 406,
message: 'CREDENTIAL_ERROR_NO_SERVER_TYPE',
});
} else { } else {
logger.error(e.message, { label: 'Auth' }); logger.error(e.message, { label: 'Auth' });
return next({ return next({

View File

@@ -29,6 +29,7 @@ import { getAppVersion } from '@server/utils/appVersion';
import { Router } from 'express'; import { Router } from 'express';
import rateLimit from 'express-rate-limit'; import rateLimit from 'express-rate-limit';
import fs from 'fs'; import fs from 'fs';
import gravatarUrl from 'gravatar-url';
import { escapeRegExp, merge, omit, set, sortBy } from 'lodash'; import { escapeRegExp, merge, omit, set, sortBy } from 'lodash';
import { rescheduleJob } from 'node-schedule'; import { rescheduleJob } from 'node-schedule';
import path from 'path'; import path from 'path';
@@ -337,7 +338,7 @@ settingsRoutes.get('/jellyfin/users', async (req, res) => {
id: user.Id, id: user.Id,
thumb: user.PrimaryImageTag thumb: user.PrimaryImageTag
? `${jellyfinHost}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90` ? `${jellyfinHost}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90`
: '/os_logo_square.png', : gravatarUrl(user.Name, { default: 'mm', size: 200 }),
email: user.Name, email: user.Name,
})); }));

View File

@@ -537,7 +537,10 @@ router.post(
permissions: settings.main.defaultPermissions, permissions: settings.main.defaultPermissions,
avatar: jellyfinUser?.PrimaryImageTag avatar: jellyfinUser?.PrimaryImageTag
? `${jellyfinHost}/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90` ? `${jellyfinHost}/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90`
: '/os_logo_square.png', : gravatarUrl(jellyfinUser?.Name ?? '', {
default: 'mm',
size: 200,
}),
userType: UserType.JELLYFIN, userType: UserType.JELLYFIN,
}); });

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -1,6 +1,7 @@
import EmbyLogo from '@app/assets/services/emby.svg'; import EmbyLogo from '@app/assets/services/emby.svg';
import ImdbLogo from '@app/assets/services/imdb.svg'; import ImdbLogo from '@app/assets/services/imdb.svg';
import JellyfinLogo from '@app/assets/services/jellyfin.svg'; import JellyfinLogo from '@app/assets/services/jellyfin.svg';
import LetterboxdLogo from '@app/assets/services/letterboxd.svg';
import PlexLogo from '@app/assets/services/plex.svg'; import PlexLogo from '@app/assets/services/plex.svg';
import RTLogo from '@app/assets/services/rt.svg'; import RTLogo from '@app/assets/services/rt.svg';
import TmdbLogo from '@app/assets/services/tmdb.svg'; import TmdbLogo from '@app/assets/services/tmdb.svg';
@@ -103,6 +104,16 @@ const ExternalLinkBlock = ({
<TraktLogo /> <TraktLogo />
</a> </a>
)} )}
{tmdbId && mediaType === MediaType.MOVIE && (
<a
href={`https://letterboxd.com/tmdb/${tmdbId}`}
className="w-8 opacity-50 transition duration-300 hover:opacity-100"
target="_blank"
rel="noreferrer"
>
<LetterboxdLogo />
</a>
)}
</div> </div>
); );
}; };

View File

@@ -222,6 +222,8 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
const baseUrl = settings.currentSettings.jellyfinExternalHost const baseUrl = settings.currentSettings.jellyfinExternalHost
? settings.currentSettings.jellyfinExternalHost ? settings.currentSettings.jellyfinExternalHost
: settings.currentSettings.jellyfinHost; : settings.currentSettings.jellyfinHost;
const jellyfinForgotPasswordUrl =
settings.currentSettings.jellyfinForgotPasswordUrl;
return ( return (
<div> <div>
<Formik <Formik
@@ -298,11 +300,15 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
<Button <Button
as="a" as="a"
buttonType="ghost" buttonType="ghost"
href={`${baseUrl}/web/index.html#!/${ href={
process.env.JELLYFIN_TYPE === 'emby' jellyfinForgotPasswordUrl
? 'startup/' ? `${jellyfinForgotPasswordUrl}`
: '' : `${baseUrl}/web/index.html#!/${
}forgotpassword.html`} process.env.JELLYFIN_TYPE === 'emby'
? 'startup/'
: ''
}forgotpassword.html`
}
> >
{intl.formatMessage(messages.forgotpassword)} {intl.formatMessage(messages.forgotpassword)}
</Button> </Button>

View File

@@ -30,9 +30,10 @@ const messages = defineMessages({
jellyfinSettingsSuccess: '{mediaServerName} settings saved successfully!', jellyfinSettingsSuccess: '{mediaServerName} settings saved successfully!',
jellyfinSettings: '{mediaServerName} Settings', jellyfinSettings: '{mediaServerName} Settings',
jellyfinSettingsDescription: jellyfinSettingsDescription:
'Optionally configure the internal and external endpoints for your {mediaServerName} server. In most cases, the external URL is different to the internal URL.', 'Optionally configure the internal and external endpoints for your {mediaServerName} server. In most cases, the external URL is different to the internal URL. A custom password reset URL can also be set for {mediaServerName} login, in case you would like to redirect to a different password reset page.',
externalUrl: 'External URL', externalUrl: 'External URL',
internalUrl: 'Internal URL', internalUrl: 'Internal URL',
jellyfinForgotPasswordUrl: 'Forgot Password URL',
validationUrl: 'You must provide a valid URL', validationUrl: 'You must provide a valid URL',
syncing: 'Syncing', syncing: 'Syncing',
syncJellyfin: 'Sync Libraries', syncJellyfin: 'Sync Libraries',
@@ -94,6 +95,10 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
/^(https?:\/\/)?(?:[\w-]+\.)*[\w-]+(?::\d{2,5})?(?:\/[\w-]+)*(?:\/)?$/gm, /^(https?:\/\/)?(?:[\w-]+\.)*[\w-]+(?::\d{2,5})?(?:\/[\w-]+)*(?:\/)?$/gm,
intl.formatMessage(messages.validationUrl) intl.formatMessage(messages.validationUrl)
), ),
jellyfinForgotPasswordUrl: Yup.string().matches(
/^(https?:\/\/)?(?:[\w-]+\.)*[\w-]+(?::\d{2,5})?(?:\/[\w-]+)*(?:\/)?$/gm,
intl.formatMessage(messages.validationUrl)
),
}); });
const activeLibraries = const activeLibraries =
@@ -353,6 +358,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
initialValues={{ initialValues={{
jellyfinInternalUrl: data?.hostname || '', jellyfinInternalUrl: data?.hostname || '',
jellyfinExternalUrl: data?.externalHostname || '', jellyfinExternalUrl: data?.externalHostname || '',
jellyfinForgotPasswordUrl: data?.jellyfinForgotPasswordUrl || '',
}} }}
validationSchema={JellyfinSettingsSchema} validationSchema={JellyfinSettingsSchema}
onSubmit={async (values) => { onSubmit={async (values) => {
@@ -360,6 +366,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
await axios.post('/api/v1/settings/jellyfin', { await axios.post('/api/v1/settings/jellyfin', {
hostname: values.jellyfinInternalUrl, hostname: values.jellyfinInternalUrl,
externalHostname: values.jellyfinExternalUrl, externalHostname: values.jellyfinExternalUrl,
jellyfinForgotPasswordUrl: values.jellyfinForgotPasswordUrl,
} as JellyfinSettings); } as JellyfinSettings);
addToast( addToast(
@@ -437,6 +444,30 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
)} )}
</div> </div>
</div> </div>
<div className="form-row">
<label
htmlFor="jellyfinForgotPasswordUrl"
className="text-label"
>
{intl.formatMessage(messages.jellyfinForgotPasswordUrl)}
</label>
<div className="form-input-area">
<div className="form-input-field">
<Field
type="text"
inputMode="url"
id="jellyfinForgotPasswordUrl"
name="jellyfinForgotPasswordUrl"
/>
</div>
{errors.jellyfinForgotPasswordUrl &&
touched.jellyfinForgotPasswordUrl && (
<div className="error">
{errors.jellyfinForgotPasswordUrl}
</div>
)}
</div>
</div>
<div className="actions"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm"> <span className="ml-3 inline-flex rounded-md shadow-sm">

View File

@@ -1235,7 +1235,7 @@
"components.Discover.tmdbmoviestreamingservices": "TMDB Film-Streaming-Dienste", "components.Discover.tmdbmoviestreamingservices": "TMDB Film-Streaming-Dienste",
"components.Discover.tmdbtvstreamingservices": "TMDB TV-Streaming-Dienste", "components.Discover.tmdbtvstreamingservices": "TMDB TV-Streaming-Dienste",
"i18n.collection": "Sammlung", "i18n.collection": "Sammlung",
"components.Discover.FilterSlideover.tmdbuservotecount": "TMDB Kullanıcı Oy Sayısı", "components.Discover.FilterSlideover.tmdbuservotecount": "Anzahl an TMDB Benutzerbewertungen",
"components.Settings.RadarrModal.tagRequestsInfo": "Füge automatisch ein Tag hinzu mit der ID und dem Namen des anfordernden Nutzers", "components.Settings.RadarrModal.tagRequestsInfo": "Füge automatisch ein Tag hinzu mit der ID und dem Namen des anfordernden Nutzers",
"components.MovieDetails.imdbuserscore": "IMDB Nutzer Bewertung", "components.MovieDetails.imdbuserscore": "IMDB Nutzer Bewertung",
"components.Settings.SonarrModal.tagRequests": "Tag Anforderungen", "components.Settings.SonarrModal.tagRequests": "Tag Anforderungen",

View File

@@ -938,7 +938,7 @@
"components.Settings.internalUrl": "Internal URL", "components.Settings.internalUrl": "Internal URL",
"components.Settings.is4k": "4K", "components.Settings.is4k": "4K",
"components.Settings.jellyfinSettings": "{mediaServerName} Settings", "components.Settings.jellyfinSettings": "{mediaServerName} Settings",
"components.Settings.jellyfinSettingsDescription": "Optionally configure the internal and external endpoints for your {mediaServerName} server. In most cases, the external URL is different to the internal URL.", "components.Settings.jellyfinSettingsDescription": "Optionally configure the internal and external endpoints for your {mediaServerName} server. In most cases, the external URL is different to the internal URL. A custom password reset URL can also be set for {mediaServerName} login, in case you would like to redirect to a different password reset page.",
"components.Settings.jellyfinSettingsFailure": "Something went wrong while saving {mediaServerName} settings.", "components.Settings.jellyfinSettingsFailure": "Something went wrong while saving {mediaServerName} settings.",
"components.Settings.jellyfinSettingsSuccess": "{mediaServerName} settings saved successfully!", "components.Settings.jellyfinSettingsSuccess": "{mediaServerName} settings saved successfully!",
"components.Settings.jellyfinlibraries": "{mediaServerName} Libraries", "components.Settings.jellyfinlibraries": "{mediaServerName} Libraries",