Compare commits

..

48 Commits

Author SHA1 Message Date
fallenbagel
4ea9779a60 Revert "fix: disable seasonfolder option in sonarr for jellyfin/Emby users"
This reverts commit 8ec8f2ac57.
Disabling seasonfolder is no longer needed as we now allow
virtualFolders from jellyfin api.
2024-04-17 15:18:50 +05:00
Gauvino
9c68616343 Update action (#717)
- Update action version to latest (remove all warnings)
 - Update nodejs version on action
 - Update ubuntu to latest on support action
2024-04-16 02:41:08 +05:00
Fallenbagel
0c2713213c feat: jellyseerr makeover (#715) 2024-04-16 00:22:12 +05:00
Fallenbagel
3856061fe1 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
2024-04-16 00:04:11 +05:00
Fallenbagel
0900a95532 fix: nullable type for jellyfinMediaId(4k) (#702)
The jellyfinMediaId(4k) properties were inferred as string | undefined, causing them to be set to
undefined when assigning null. This prevented the media from being saved correctly to the SQLite
database, as it doesn't accept undefined values. This resolves the availabilitySync job issue where
the "play on" button wasn't being removed for all media server types.

fix #668
2024-03-31 16:26:09 +05:00
Fallenbagel
0c86684bc2 refactor(i18n): change the user-facing identity of the application in i18n (#703) 2024-03-31 16:25:45 +05:00
Danish Humair
010df62776 feat: check if first jellyfin user is admin (#635)
* feat: merge check if first jellyfin user is admin

re #610

* refactor(i18n): extract admin error message into en locale

---------

Co-authored-by: fallenbagel <98979876+Fallenbagel@users.noreply.github.com>
2024-03-30 05:53:14 +05:00
Fallenbagel
530be4272c fix(jellyfinscanner): conditionally assign the jellyfinMediaId and jellyfinMediaId4k (#686)
Previously `jellyfinMediaId4k` was being assigned even if 4k server was not setup or even if 4k
content were not present. This fixes it by conditionally assigning the jellyfinMediaId and
JellyfinMediaId4k

fix #681
2024-03-14 03:11:53 +05:00
Fallenbagel
c2e87714b4 fix(embyauth): remove the accidentally added mediaServerType change code from another PR (#684)
Accidentally added the mediaServerType change code from another feature branch/PR during the auth
logic refactor that broke emby logins.
2024-03-14 01:08:09 +05:00
Gauvino
eee9a025d2 fix: typos on readme (#655)
* Fix typo

* Apply suggestions

* Apply suggestions

---------

Co-authored-by: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com>
2024-02-23 12:57:57 +05:00
allcontributors[bot]
aed011a557 docs: add trackmastersteve as a contributor for doc (#665)
* docs: update README.md [skip ci]

* docs: update .all-contributorsrc [skip ci]

---------

Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
2024-02-23 09:39:53 +05:00
Stephen Harris
ea47dd3571 Fixed a typo (#654)
Just a simple typo fix.
2024-02-23 09:38:45 +05:00
Fallenbagel
4c9013729e refactor: jellyfin authentication and add gravatar for missing avatars of jellyfin users (#664)
* refactor: jellyfin authentication

This refactor standardizes the authentication approach in Jellyfin to mirror the method employed in
Plex authentication for consistency

* feat: use gravatar for jellyfin users' with missing jellyfin avatars
2024-02-23 09:38:18 +05:00
Fallenbagel
3eb1bb3d8f feat(job): media availability support for jellyfin/emby (#522)
* feat(job): media availability support for jellyfin/emby

This refactors the media availability job to support jellyfin/emby for media removal automatically.
Needs further testing on 4k items (as I have not yet tested with 4k), however, non-4k items work as
intended.

fix #406, fix #193, fix #516, fix #362, fix #84

* fix(availabilitysync): use the correct 4k jellyfinMediaId

* fix: season mapping for plex

Fixes a bug introduced with this PR where media availability sync job removed the seasons from all
series even when those seasons existed
2024-02-23 07:42:59 +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
89 changed files with 953 additions and 463 deletions

View File

@@ -277,6 +277,51 @@
"contributions": [
"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"
]
},
{
"login": "trackmastersteve",
"name": "Stephen Harris",
"avatar_url": "https://avatars.githubusercontent.com/u/16858514?v=4",
"profile": "https://arm0.red",
"contributions": [
"doc"
]
}
]
}

2
.github/FUNDING.yml vendored
View File

@@ -1 +1 @@
github: [Fallenbagel]
github: [Fallenbagel]

View File

@@ -16,7 +16,7 @@ jobs:
container: node:18.18-alpine
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install dependencies
env:
HUSKY: 0
@@ -34,18 +34,18 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -56,7 +56,7 @@ jobs:
env:
OWNER: ${{ github.repository_owner }}
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile

View File

@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v2

View File

@@ -13,9 +13,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Cypress run
uses: cypress-io/github-action@v4
uses: cypress-io/github-action@v6
with:
build: yarn cypress:build
start: yarn start

View File

@@ -11,25 +11,25 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Get the version
id: get_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
build-args: |
COMMIT_TAG=${{ github.sha }}

View File

@@ -10,19 +10,19 @@ jobs:
HUSKY: 0
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 16
node-version: 18
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
@@ -48,7 +48,7 @@ jobs:
- armhf
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Switch to main branch
@@ -65,7 +65,7 @@ jobs:
echo "RELEASE=edge" >> $GITHUB_OUTPUT
fi
- name: Set Up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
with:
image: tonistiigi/binfmt@sha256:df15403e06a03c2f461c1f7938b171fda34a5849eb63a70e2a2109ed5a778bde
- name: Build Snap Package
@@ -74,7 +74,7 @@ jobs:
with:
architecture: ${{ matrix.architecture }}
- name: Upload Snap Package
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: jellyseerr-snap-package-${{ matrix.architecture }}
path: ${{ steps.build.outputs.snap }}

View File

@@ -12,7 +12,7 @@ jobs:
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.10.0
uses: styfle/cancel-workflow-action@0.12.1
with:
access_token: ${{ secrets.GITHUB_TOKEN }}
@@ -29,7 +29,7 @@ jobs:
- armhf
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Prepare
id: prepare
run: |
@@ -40,7 +40,7 @@ jobs:
echo "RELEASE=edge" >> $GITHUB_OUTPUT
fi
- name: Set Up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Configure Git
run: git config --add safe.directory /data/parts/jellyseerr/src
- name: Build Snap Package
@@ -49,7 +49,7 @@ jobs:
with:
architecture: ${{ matrix.architecture }}
- name: Upload Snap Package
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: jellyseerr-snap-package-${{ matrix.architecture }}
path: ${{ steps.build.outputs.snap }}

View File

@@ -6,9 +6,9 @@ on:
jobs:
support:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: dessant/support-requests@v2
- uses: dessant/support-requests@v4
with:
github-token: ${{ github.token }}
support-label: 'support'

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...
@@ -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:
```bash
git clone https://github.com/YOUR_USERNAME/overseerr.git
git clone https://github.com/YOUR_USERNAME/jellyseerr.git
cd overseerr/
```
@@ -97,9 +97,9 @@ When adding new UI text, please try to adhere to the following guidelines:
## 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

View File

@@ -2,23 +2,28 @@
<img src="./public/logo_full.svg" alt="Jellyseerr" style="margin: 20px 0;">
</p>
<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="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-29-orange.svg"/></a>
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-34-orange.svg"/></a>
<!-- 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 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!_
## Current Features
- Full Jellyfin/Emby/Plex integration. Authenticate and manage user access with Jellyfin/Emby/Plex!
- Supports Movies, Shows, Mixed Libraries!
- Full Jellyfin/Emby/Plex integration including authentication with user import & management
- Supports Movies, Shows and Mixed Libraries
- 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!
- 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.
@@ -35,11 +40,11 @@ With more features on the way! Check out our [issue tracker](https://github.com/
#### Pre-requisite (Important)
_*On Jellyfin/Emby, ensure the `settings > Home > Automatically group content from the following folders into views such as 'Movies', 'Music' and 'TV'` is turned off*_
_*On Jellyfin/Emby, ensure the `Settings > Home > Automatically group content from the following folders into views such as 'Movies', 'Music' and 'TV'` is turned off*_
### Launching Jellyseerr using Docker (Recommended)
Check out our dockerhub for instructions on how to install and run Jellyseerr:
Check out our docker hub for instructions on how to install and run Jellyseerr:
https://hub.docker.com/r/fallenbagel/jellyseerr
### Building from source (ADVANCED):
@@ -49,7 +54,7 @@ https://hub.docker.com/r/fallenbagel/jellyseerr
Pre-requisites:
- 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)
```cmd
@@ -59,16 +64,17 @@ yarn install --frozen-lockfile --network-timeout 1000000
yarn run build
yarn start
```
(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_
(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_
#### Linux
**Pre-requisites:**
- Nodejs [v18](https://nodejs.org/en/download/package-manager)
- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) (on debian based distros, the package manager provided `yarn` is different and is a package called cmdlet. You can remove that using `apt-remove cmdlet` then install yarn using `npm install -g yarn`)
- [Yarn](https://classic.yarnpkg.com/lang/en/docs/install) (on Debian based distros, the package manager provided `yarn` is different and is a package called cmdlet. You can remove that using `apt-remove cmdlet` then install yarn using `npm install -g yarn`)
- Git
**Steps:**
@@ -79,7 +85,7 @@ _to set env variables such as `JELLYFIN_TYPE=emby` create a file called `.env` i
cd /opt
```
2. Then clone the follow commands to clone and checkout to the stable version
2. Then execute the following commands to clone and checkout to the stable version
```bash
git clone https://github.com/Fallenbagel/jellyseerr.git && cd jellyseerr
@@ -98,9 +104,9 @@ yarn run build
5. If you want to run jellyseerr as a _Systemd-service:_
- assuming jellyseerr was cloned to `/opt/`
- first create the environmentfile at `/etc/jellyseerr/jellyseerr.conf`
- first create the environment file at `/etc/jellyseerr/jellyseerr.conf`
Environmentfile:
Environment file:
```
# Jellyseerr's default port is 5055, if you want to use both, change this.
@@ -136,6 +142,7 @@ ExecStart=/usr/bin/node dist/index.js
[Install]
WantedBy=multi-user.target
```
### Packages:
Archlinux: [AUR](https://aur.archlinux.org/packages/jellyseerr)
@@ -217,6 +224,11 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
</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://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>
<td align="center" valign="top" width="14.28%"><a href="https://arm0.red"><img src="https://avatars.githubusercontent.com/u/16858514?v=4?s=100" width="100px;" alt="Stephen Harris"/><br /><sub><b>Stephen Harris</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=trackmastersteve" title="Documentation">📖</a></td>
</tr>
</tbody>
</table>

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 137 KiB

View File

@@ -9,6 +9,12 @@ export interface JellyfinUserResponse {
ServerId: string;
ServerName: string;
Id: string;
Configuration: {
GroupedFolders: string[];
};
Policy: {
IsAdministrator: boolean;
};
PrimaryImageTag?: string;
}
@@ -21,6 +27,13 @@ export interface JellyfinUserListResponse {
users: JellyfinUserResponse[];
}
interface JellyfinMediaFolder {
Name: string;
Id: string;
Type: string;
CollectionType: string;
}
export interface JellyfinLibrary {
type: 'show' | 'movie';
key: string;
@@ -172,24 +185,45 @@ class JellyfinAPI {
public async getLibraries(): Promise<JellyfinLibrary[]> {
try {
// TODO: Try to fix automatic grouping without fucking up LDAP users
// const libraries = await this.axios.get<any>('/Library/VirtualFolders');
const mediaFolders = await this.axios.get<any>(`/Library/MediaFolders`);
const account = await this.axios.get<any>(
`/Users/${this.userId ?? 'Me'}/Views`
);
return this.mapLibraries(mediaFolders.data.Items);
} 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(
(Item: any) => {
return (
Item.Type === 'CollectionFolder' &&
Item.CollectionType !== 'music' &&
Item.CollectionType !== 'books' &&
Item.CollectionType !== 'musicvideos' &&
Item.CollectionType !== 'homevideos'
);
}
).map((Item: any) => {
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 [];
}
}
}
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>{
key: Item.Id,
title: Item.Name,
@@ -197,21 +231,12 @@ class JellyfinAPI {
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[]> {
try {
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(
@@ -269,9 +294,7 @@ class JellyfinAPI {
try {
const contents = await this.axios.get<any>(`/Shows/${seriesID}/Seasons`);
return contents.data.Items.filter(
(item: JellyfinLibraryItem) => item.LocationType !== 'Virtual'
);
return contents.data.Items;
} catch (e) {
logger.error(
`Something went wrong while getting the list of seasons from the Jellyfin server: ${e.message}`,

View File

@@ -151,11 +151,11 @@ class Media {
@Column({ nullable: true, type: 'varchar' })
public ratingKey4k?: string | null;
@Column({ nullable: true })
public jellyfinMediaId?: string;
@Column({ nullable: true, type: 'varchar' })
public jellyfinMediaId?: string | null;
@Column({ nullable: true })
public jellyfinMediaId4k?: string;
@Column({ nullable: true, type: 'varchar' })
public jellyfinMediaId4k?: string | null;
public serviceUrl?: string;
public serviceUrl4k?: string;

View File

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

View File

@@ -299,45 +299,42 @@ class AvailabilitySync {
const finalSeasons: Map<number, boolean> = new Map();
if (mediaServerType === MediaServerType.PLEX) {
const plexMap = new Map([
...plexSeasonsMap,
...filteredSeasonsMap,
...sonarrSeasonsMap,
]);
plexMap.forEach((value, key) => {
plexSeasonsMap.forEach((value, key) => {
finalSeasons.set(key, value);
});
filteredSeasonsMap.forEach((value, key) => {
if (!finalSeasons.has(key)) {
finalSeasons.set(key, value);
}
});
sonarrSeasonsMap.forEach((value, key) => {
if (!finalSeasons.has(key)) {
finalSeasons.set(key, value);
}
});
} else if (
mediaServerType === MediaServerType.JELLYFIN ||
mediaServerType === MediaServerType.EMBY
) {
// Adding values from jellyfinSeasonsMap
jellyfinSeasonsMap.forEach((value, key) => {
finalSeasons.set(key, value);
});
// Adding values from filteredSeasonsMap and handling missing keys
filteredSeasonsMap.forEach((value, key) => {
// Check if the key is missing in jellyfinSeasonsMap
if (!finalSeasons.has(key)) {
finalSeasons.set(key, value);
}
});
// Adding values from sonarrSeasonsMap and handling missing keys
sonarrSeasonsMap.forEach((value, key) => {
// Check if the key is missing in jellyfinSeasonsMap and filteredSeasonsMap
if (!finalSeasons.has(key)) {
finalSeasons.set(key, value);
}
});
}
// ...(mediaServerType === MediaServerType.PLEX ? plexSeasonsMap : []),
// ...(mediaServerType === MediaServerType.JELLYFIN ||
// mediaServerType === MediaServerType.EMBY ? jellyfinSeasonsMap
// : []),
const filteredSeasonsMap4k: Map<number, boolean> = new Map();
media.seasons
@@ -350,50 +347,40 @@ class AvailabilitySync {
filteredSeasonsMap4k.set(season.seasonNumber, false)
);
// const finalSeasons4k: Map<any, any> = new Map<any, any>([
// ...(mediaServerType === MediaServerType.PLEX
// ? plexSeasonsMap4k
// : []),
// ...(mediaServerType === MediaServerType.JELLYFIN ||
// mediaServerType === MediaServerType.EMBY
// ? jellyfinSeasonsMap4k
// : []),
// ...filteredSeasonsMap4k,
// ...sonarrSeasonsMap4k,
// ]);
// 4k
const finalSeasons4k: Map<number, boolean> = new Map();
if (mediaServerType === MediaServerType.PLEX) {
const plexMap4k = new Map([
...plexSeasonsMap4k,
...filteredSeasonsMap4k,
...sonarrSeasonsMap4k,
]);
plexMap4k.forEach((value, key) => {
finalSeasons4k.set(key, value);
});
} else if (
mediaServerType === MediaServerType.JELLYFIN ||
mediaServerType === MediaServerType.EMBY
) {
// Adding values from jellyfinSeasonsMap
jellyfinSeasonsMap4k.forEach((value, key) => {
plexSeasonsMap4k.forEach((value, key) => {
finalSeasons4k.set(key, value);
});
filteredSeasonsMap4k.forEach((value, key) => {
if (!finalSeasons4k.has(key)) {
finalSeasons4k.set(key, value);
}
});
sonarrSeasonsMap4k.forEach((value, key) => {
if (!finalSeasons4k.has(key)) {
finalSeasons4k.set(key, value);
}
});
} else if (
mediaServerType === MediaServerType.JELLYFIN ||
mediaServerType === MediaServerType.EMBY
) {
jellyfinSeasonsMap4k.forEach((value, key) => {
finalSeasons4k.set(key, value);
});
// Adding values from filteredSeasonsMap and handling missing keys
filteredSeasonsMap4k.forEach((value, key) => {
// Check if the key is missing in jellyfinSeasonsMap
if (!finalSeasons4k.has(key)) {
finalSeasons4k.set(key, value);
}
});
// Adding values from sonarrSeasonsMap and handling missing keys
sonarrSeasonsMap4k.forEach((value, key) => {
// Check if the key is missing in jellyfinSeasonsMap and filteredSeasonsMap
if (!finalSeasons4k.has(key)) {
finalSeasons4k.set(key, value);
}
@@ -561,7 +548,7 @@ class AvailabilitySync {
media[is4k ? 'ratingKey4k' : 'ratingKey'] =
mediaStatus === MediaStatus.PROCESSING
? media[is4k ? 'ratingKey4k' : 'ratingKey']
: undefined;
: null;
} else if (
mediaServerType === MediaServerType.JELLYFIN ||
mediaServerType === MediaServerType.EMBY
@@ -569,7 +556,7 @@ class AvailabilitySync {
media[is4k ? 'jellyfinMediaId4k' : 'jellyfinMediaId'] =
mediaStatus === MediaStatus.PROCESSING
? media[is4k ? 'jellyfinMediaId4k' : 'jellyfinMediaId']
: undefined;
: null;
}
logger.info(
`The ${is4k ? '4K' : 'non-4K'} ${

View File

@@ -168,9 +168,9 @@ class JellyfinScanner {
newMedia.jellyfinMediaId =
hasOtherResolution || (!this.enable4kMovie && has4k)
? metadata.Id
: undefined;
: null;
newMedia.jellyfinMediaId4k =
has4k && this.enable4kMovie ? metadata.Id : undefined;
has4k && this.enable4kMovie ? metadata.Id : null;
await mediaRepository.save(newMedia);
this.log(`Saved ${metadata.Name}`);
}
@@ -461,8 +461,9 @@ class JellyfinScanner {
tmdbId: tvShow.id,
tvdbId: tvShow.external_ids.tvdb_id,
mediaAddedAt: new Date(metadata.DateCreated ?? ''),
jellyfinMediaId: Id,
jellyfinMediaId4k: Id,
jellyfinMediaId: isAllStandardSeasons ? Id : null,
jellyfinMediaId4k:
isAll4kSeasons && this.enable4kShow ? Id : null,
status: isAllStandardSeasons
? MediaStatus.AVAILABLE
: newSeasons.some(

View File

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

View File

@@ -11,6 +11,7 @@ import logger from '@server/logger';
import { isAuthenticated } from '@server/middleware/auth';
import * as EmailValidator from 'email-validator';
import { Router } from 'express';
import gravatarUrl from 'gravatar-url';
const authRoutes = Router();
@@ -274,24 +275,87 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
where: { jellyfinUserId: account.User.Id },
});
if (user) {
if (!user && !(await userRepository.count())) {
// Check if user is admin on jellyfin
if (account.User.Policy.IsAdministrator === false) {
throw new Error('not_admin');
}
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
if (user.jellyfinAuthToken !== account.AccessToken) {
user.jellyfinAuthToken = account.AccessToken;
}
// Update the users avatar with their jellyfin profile pic (incase it changed)
if (account.User.PrimaryImageTag) {
user.avatar = `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`;
} else {
user.avatar = '/os_logo_square.png';
user.avatar = gravatarUrl(user.email, {
default: 'mm',
size: 200,
});
}
user.jellyfinUsername = account.User.Name;
if (user.username === account.User.Name) {
user.username = '';
}
// TODO: 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);
} else if (!settings.main.newPlexLogin) {
logger.warn(
@@ -307,69 +371,38 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
status: 403,
message: 'Access denied.',
});
} else {
// Here we check if it's the first user. If it is, we create the user with no check
// and give them admin permissions
const totalUsers = await userRepository.count();
if (totalUsers === 0) {
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 = new User({
email: body.email,
} else if (!user) {
logger.info(
'Sign-in attempt from Jellyfin user with access to the media server; creating new Overseerr user',
{
label: 'API',
ip: req.ip,
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) {
if (!body.email) {
throw new Error('add_email');
}
user = new User({
email: body.email,
jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: settings.main.defaultPermissions,
avatar: account.User.PrimaryImageTag
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
: '/os_logo_square.png',
userType: UserType.JELLYFIN,
});
//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);
user = new User({
email: body.email,
jellyfinUsername: account.User.Name,
jellyfinUserId: account.User.Id,
jellyfinDeviceId: deviceId,
jellyfinAuthToken: account.AccessToken,
permissions: settings.main.defaultPermissions,
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,
});
//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);
}
// Set logged in session
@@ -395,11 +428,21 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
status: 401,
message: 'Unauthorized',
});
} else if (e.message === 'not_admin') {
return next({
status: 403,
message: 'CREDENTIAL_ERROR_NOT_ADMIN',
});
} else if (e.message === 'add_email') {
return next({
status: 406,
message: 'CREDENTIAL_ERROR_ADD_EMAIL',
});
} else if (e.message === 'select_server_type') {
return next({
status: 406,
message: 'CREDENTIAL_ERROR_NO_SERVER_TYPE',
});
} else {
logger.error(e.message, { label: 'Auth' });
return next({

View File

@@ -29,6 +29,7 @@ import { getAppVersion } from '@server/utils/appVersion';
import { Router } from 'express';
import rateLimit from 'express-rate-limit';
import fs from 'fs';
import gravatarUrl from 'gravatar-url';
import { escapeRegExp, merge, omit, set, sortBy } from 'lodash';
import { rescheduleJob } from 'node-schedule';
import path from 'path';
@@ -260,7 +261,7 @@ settingsRoutes.post('/jellyfin', (req, res) => {
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();
if (req.query.sync) {
@@ -280,6 +281,19 @@ settingsRoutes.get('/jellyfin/library', async (req, res) => {
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 existing = settings.jellyfin.libraries.find(
(l) => l.id === library.key && l.name === library.title
@@ -337,7 +351,7 @@ settingsRoutes.get('/jellyfin/users', async (req, res) => {
id: user.Id,
thumb: user.PrimaryImageTag
? `${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,
}));

View File

@@ -537,7 +537,10 @@ router.post(
permissions: settings.main.defaultPermissions,
avatar: jellyfinUser?.PrimaryImageTag
? `${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,
});

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 ImdbLogo from '@app/assets/services/imdb.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 RTLogo from '@app/assets/services/rt.svg';
import TmdbLogo from '@app/assets/services/tmdb.svg';
@@ -103,6 +104,16 @@ const ExternalLinkBlock = ({
<TraktLogo />
</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>
);
};

View File

@@ -24,6 +24,7 @@ const messages = defineMessages({
validationusernamerequired: 'Username required',
validationpasswordrequired: 'Password required',
loginerror: 'Something went wrong while trying to sign in.',
adminerror: 'You must use an admin account to sign in.',
credentialerror: 'The username or password is incorrect.',
signingin: 'Signing in…',
signin: 'Sign In',
@@ -94,6 +95,8 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
intl.formatMessage(
e.message == 'Request failed with status code 401'
? messages.credentialerror
: e.message == 'Request failed with status code 403'
? messages.adminerror
: messages.loginerror
),
{
@@ -222,6 +225,8 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
const baseUrl = settings.currentSettings.jellyfinExternalHost
? settings.currentSettings.jellyfinExternalHost
: settings.currentSettings.jellyfinHost;
const jellyfinForgotPasswordUrl =
settings.currentSettings.jellyfinForgotPasswordUrl;
return (
<div>
<Formik
@@ -298,11 +303,15 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
<Button
as="a"
buttonType="ghost"
href={`${baseUrl}/web/index.html#!/${
process.env.JELLYFIN_TYPE === 'emby'
? 'startup/'
: ''
}forgotpassword.html`}
href={
jellyfinForgotPasswordUrl
? `${jellyfinForgotPasswordUrl}`
: `${baseUrl}/web/index.html#!/${
process.env.JELLYFIN_TYPE === 'emby'
? 'startup/'
: ''
}forgotpassword.html`
}
>
{intl.formatMessage(messages.forgotpassword)}
</Button>

View File

@@ -30,9 +30,15 @@ const messages = defineMessages({
jellyfinSettingsSuccess: '{mediaServerName} settings saved successfully!',
jellyfinSettings: '{mediaServerName} 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.',
'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',
internalUrl: 'Internal 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',
syncing: 'Syncing',
syncJellyfin: 'Sync Libraries',
@@ -69,6 +75,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
showAdvancedSettings,
}) => {
const [isSyncing, setIsSyncing] = useState(false);
const toasts = useToasts();
const {
data,
@@ -94,6 +101,10 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
/^(https?:\/\/)?(?:[\w-]+\.)*[\w-]+(?::\d{2,5})?(?:\/[\w-]+)*(?:\/)?$/gm,
intl.formatMessage(messages.validationUrl)
),
jellyfinForgotPasswordUrl: Yup.string().matches(
/^(https?:\/\/)?(?:[\w-]+\.)*[\w-]+(?::\d{2,5})?(?:\/[\w-]+)*(?:\/)?$/gm,
intl.formatMessage(messages.validationUrl)
),
});
const activeLibraries =
@@ -112,11 +123,43 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
params.enable = activeLibraries.join(',');
}
await axios.get('/api/v1/settings/jellyfin/library', {
params,
});
setIsSyncing(false);
revalidate();
try {
await axios.get('/api/v1/settings/jellyfin/library', {
params,
});
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 () => {
@@ -353,6 +396,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
initialValues={{
jellyfinInternalUrl: data?.hostname || '',
jellyfinExternalUrl: data?.externalHostname || '',
jellyfinForgotPasswordUrl: data?.jellyfinForgotPasswordUrl || '',
}}
validationSchema={JellyfinSettingsSchema}
onSubmit={async (values) => {
@@ -360,6 +404,7 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
await axios.post('/api/v1/settings/jellyfin', {
hostname: values.jellyfinInternalUrl,
externalHostname: values.jellyfinExternalUrl,
jellyfinForgotPasswordUrl: values.jellyfinForgotPasswordUrl,
} as JellyfinSettings);
addToast(
@@ -437,6 +482,30 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
)}
</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="flex justify-end">
<span className="ml-3 inline-flex rounded-md shadow-sm">

View File

@@ -1,10 +1,8 @@
import Modal from '@app/components/Common/Modal';
import SensitiveInput from '@app/components/Common/SensitiveInput';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import { Transition } from '@headlessui/react';
import { MediaServerType } from '@server/constants/server';
import { type SonarrSettings } from '@server/lib/settings';
import type { SonarrSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { useCallback, useEffect, useRef, useState } from 'react';
@@ -111,7 +109,6 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
const { addToast } = useToasts();
const [isValidated, setIsValidated] = useState(sonarr ? true : false);
const [isTesting, setIsTesting] = useState(false);
const settings = useSettings();
const [testResponse, setTestResponse] = useState<TestResponse>({
profiles: [],
rootFolders: [],
@@ -258,9 +255,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
animeTags: sonarr?.animeTags ?? [],
isDefault: sonarr?.isDefault ?? false,
is4k: sonarr?.is4k ?? false,
enableSeasonFolders:
sonarr?.enableSeasonFolders ??
settings.currentSettings.mediaServerType !== MediaServerType.PLEX,
enableSeasonFolders: sonarr?.enableSeasonFolders ?? false,
externalUrl: sonarr?.externalUrl,
syncEnabled: sonarr?.syncEnabled ?? false,
enableSearch: !sonarr?.preventSearch,
@@ -966,24 +961,11 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
>
{intl.formatMessage(messages.seasonfolders)}
</label>
<div
className={`form-input-area ${
settings.currentSettings.mediaServerType ===
MediaServerType.JELLYFIN ||
settings.currentSettings.mediaServerType ===
MediaServerType.EMBY
? 'opacity-50'
: 'opacity-100'
}`}
>
<div className="form-input-area">
<Field
type="checkbox"
id="enableSeasonFolders"
name="enableSeasonFolders"
disabled={
settings.currentSettings.mediaServerType !==
MediaServerType.PLEX
}
/>
</div>
</div>

View File

@@ -155,7 +155,7 @@
"components.TvDetails.overview": "Přehled",
"components.TvDetails.cast": "Obsazení",
"components.TvDetails.anime": "Anime",
"components.StatusChacker.reloadJellyseerr": "Znovu načíst",
"components.StatusChacker.reloadOverseerr": "Znovu načíst",
"components.Setup.tip": "Tip",
"components.Setup.setup": "Konfigurace",
"components.Setup.finishing": "Dokončování…",

View File

@@ -724,7 +724,7 @@
"components.StatusBadge.status4k": "4K {status}",
"components.Setup.tip": "Tip",
"components.Setup.welcome": "Velkommen til Jellyseerr",
"components.StatusChacker.reloadJellyseerr": "Genindlæs",
"components.StatusChacker.reloadOverseerr": "Genindlæs",
"components.TvDetails.anime": "Anime",
"components.TvDetails.cast": "Roller",
"components.TvDetails.episodeRuntimeMinutes": "{runtime} minutter",

View File

@@ -889,7 +889,7 @@
"components.StatusBadge.status4k": "4K {status}",
"components.StatusChacker.newversionDescription": "Jellyseerr wurde aktualisiert! Bitte klicke auf die Schaltfläche unten, um die Seite neu zu laden.",
"components.StatusChacker.newversionavailable": "Anwendungsaktualisierung",
"components.StatusChacker.reloadJellyseerr": "Jellyseerr neu laden",
"components.StatusChacker.reloadOverseerr": "Jellyseerr neu laden",
"components.StatusChecker.appUpdated": "{applicationTitle} aktualisiert",
"components.StatusChecker.appUpdatedDescription": "Klicke bitte auf die Schaltfläche unten, um die Anwendung neu zu laden.",
"components.StatusChecker.reloadApp": "{applicationTitle} neu laden",
@@ -1235,7 +1235,7 @@
"components.Discover.tmdbmoviestreamingservices": "TMDB Film-Streaming-Dienste",
"components.Discover.tmdbtvstreamingservices": "TMDB TV-Streaming-Dienste",
"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.MovieDetails.imdbuserscore": "IMDB Nutzer Bewertung",
"components.Settings.SonarrModal.tagRequests": "Tag Anforderungen",

View File

@@ -634,7 +634,7 @@
"components.TvDetails.anime": "Anime",
"components.TvDetails.TvCrew.fullseriescrew": "Όλο το Πλήρωμα της Σειράς",
"components.TvDetails.TvCast.fullseriescast": "Όλοι οι Ηθοποιοί της Σειράς",
"components.StatusChacker.reloadJellyseerr": "Επαναφόρτωση",
"components.StatusChacker.reloadOverseerr": "Επαναφόρτωση",
"components.StatusChacker.newversionavailable": "Ενημέρωση εφαρμογής",
"components.StatusChacker.newversionDescription": "Το Jellyseerr έχει ενημερωθεί! Κάνε κλικ στο παρακάτω κουμπί για να φορτώσει ξανά η σελίδα.",
"components.StatusBadge.status4k": "4K {status}",

View File

@@ -217,9 +217,10 @@
"components.Layout.UserWarnings.passwordRequired": "A password is required.",
"components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} behind",
"components.Layout.VersionStatus.outofdate": "Out of Date",
"components.Layout.VersionStatus.streamdevelop": "Overseerr Develop",
"components.Layout.VersionStatus.streamstable": "Overseerr Stable",
"components.Layout.VersionStatus.streamdevelop": "Jellyseerr Develop",
"components.Layout.VersionStatus.streamstable": "Jellyseerr Stable",
"components.Login.credentialerror": "The username or password is incorrect.",
"components.Login.adminerror": "You must use an admin account to sign in.",
"components.Login.description": "Since this is your first time logging into {applicationName}, you are required to add a valid email address.",
"components.Login.email": "Email Address",
"components.Login.emailtooltip": "Address does not need to be associated with your {mediaServerName} instance.",
@@ -582,7 +583,7 @@
"components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "You must provide an access token",
"components.Settings.Notifications.NotificationsPushbullet.validationTypes": "You must select at least one notification type",
"components.Settings.Notifications.NotificationsPushover.accessToken": "Application API Token",
"components.Settings.Notifications.NotificationsPushover.accessTokenTip": "<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr",
"components.Settings.Notifications.NotificationsPushover.accessTokenTip": "<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Jellyseerr",
"components.Settings.Notifications.NotificationsPushover.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsPushover.deviceDefault": "Device Default",
"components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover notification settings failed to save.",
@@ -607,7 +608,7 @@
"components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL",
"components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Create an <WebhookLink>Incoming Webhook</WebhookLink> integration",
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent",
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "In order to receive web push notifications, Overseerr must be served over HTTPS.",
"components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "In order to receive web push notifications, Jellyseerr must be served over HTTPS.",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Web push test notification failed to send.",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Sending web push test notification…",
"components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Web push test notification sent!",
@@ -633,7 +634,7 @@
"components.Settings.Notifications.authPass": "SMTP Password",
"components.Settings.Notifications.authUser": "SMTP Username",
"components.Settings.Notifications.botAPI": "Bot Authorization Token",
"components.Settings.Notifications.botApiTip": "<CreateBotLink>Create a bot</CreateBotLink> for use with Overseerr",
"components.Settings.Notifications.botApiTip": "<CreateBotLink>Create a bot</CreateBotLink> for use with Jellyseerr",
"components.Settings.Notifications.botAvatarUrl": "Bot Avatar URL",
"components.Settings.Notifications.botUsername": "Bot Username",
"components.Settings.Notifications.botUsernameTip": "Allow users to also start a chat with your bot and configure their own notifications",
@@ -748,9 +749,9 @@
"components.Settings.SettingsAbout.githubdiscussions": "GitHub Discussions",
"components.Settings.SettingsAbout.helppaycoffee": "Help Pay for Coffee",
"components.Settings.SettingsAbout.outofdate": "Out of Date",
"components.Settings.SettingsAbout.overseerrinformation": "About Overseerr",
"components.Settings.SettingsAbout.overseerrinformation": "About Jellyseerr",
"components.Settings.SettingsAbout.preferredmethod": "Preferred",
"components.Settings.SettingsAbout.runningDevelop": "You are running the <code>develop</code> branch of Overseerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.",
"components.Settings.SettingsAbout.runningDevelop": "You are running the <code>develop</code> branch of Jellyseerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.",
"components.Settings.SettingsAbout.supportoverseerr": "Support Overseerr",
"components.Settings.SettingsAbout.supportjellyseerr": "Support Jellyseerr",
"components.Settings.SettingsAbout.timezone": "Time Zone",
@@ -760,7 +761,7 @@
"components.Settings.SettingsAbout.version": "Version",
"components.Settings.SettingsJobsCache.availability-sync": "Media Availability Sync",
"components.Settings.SettingsJobsCache.cache": "Cache",
"components.Settings.SettingsJobsCache.cacheDescription": "Overseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.",
"components.Settings.SettingsJobsCache.cacheDescription": "Jellyseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.",
"components.Settings.SettingsJobsCache.cacheflushed": "{cachename} cache flushed.",
"components.Settings.SettingsJobsCache.cachehits": "Hits",
"components.Settings.SettingsJobsCache.cachekeys": "Total Keys",
@@ -781,7 +782,7 @@
"components.Settings.SettingsJobsCache.flushcache": "Flush Cache",
"components.Settings.SettingsJobsCache.image-cache-cleanup": "Image Cache Cleanup",
"components.Settings.SettingsJobsCache.imagecache": "Image Cache",
"components.Settings.SettingsJobsCache.imagecacheDescription": "When enabled in settings, Overseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.",
"components.Settings.SettingsJobsCache.imagecacheDescription": "When enabled in settings, Jellyseerr will proxy and cache images from pre-configured external sources. Cached images are saved into your config folder. You can find the files in <code>{appDataPath}/cache/images</code>.",
"components.Settings.SettingsJobsCache.imagecachecount": "Images Cached",
"components.Settings.SettingsJobsCache.imagecachesize": "Total Cache Size",
"components.Settings.SettingsJobsCache.jellyfin-full-scan": "Jellyfin Full Library Scan",
@@ -791,7 +792,7 @@
"components.Settings.SettingsJobsCache.jobcancelled": "{jobname} canceled.",
"components.Settings.SettingsJobsCache.jobname": "Job Name",
"components.Settings.SettingsJobsCache.jobs": "Jobs",
"components.Settings.SettingsJobsCache.jobsDescription": "Overseerr performs certain maintenance tasks as regularly-scheduled jobs, but they can also be manually triggered below. Manually running a job will not alter its schedule.",
"components.Settings.SettingsJobsCache.jobsDescription": "Jellyseerr performs certain maintenance tasks as regularly-scheduled jobs, but they can also be manually triggered below. Manually running a job will not alter its schedule.",
"components.Settings.SettingsJobsCache.jobsandcache": "Jobs & Cache",
"components.Settings.SettingsJobsCache.jobstarted": "{jobname} started.",
"components.Settings.SettingsJobsCache.jobtype": "Type",
@@ -832,7 +833,7 @@
"components.Settings.SettingsMain.csrfProtectionTip": "Set external API access to read-only (requires HTTPS)",
"components.Settings.SettingsMain.general": "General",
"components.Settings.SettingsMain.generalsettings": "General Settings",
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Overseerr.",
"components.Settings.SettingsMain.generalsettingsDescription": "Configure global and default settings for Jellyseerr.",
"components.Settings.SettingsMain.hideAvailable": "Hide Available Media",
"components.Settings.SettingsMain.locale": "Display Language",
"components.Settings.SettingsMain.originallanguage": "Discover Language",
@@ -845,7 +846,7 @@
"components.Settings.SettingsMain.toastSettingsFailure": "Something went wrong while saving settings.",
"components.Settings.SettingsMain.toastSettingsSuccess": "Settings saved successfully!",
"components.Settings.SettingsMain.trustProxy": "Enable Proxy Support",
"components.Settings.SettingsMain.trustProxyTip": "Allow Overseerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsMain.trustProxyTip": "Allow Jellyseerr to correctly register client IP addresses behind a proxy",
"components.Settings.SettingsMain.validationApplicationTitle": "You must provide an application title",
"components.Settings.SettingsMain.validationApplicationUrl": "You must provide a valid URL",
"components.Settings.SettingsMain.validationApplicationUrlTrailingSlash": "URL must not end in a trailing slash",
@@ -938,16 +939,19 @@
"components.Settings.internalUrl": "Internal URL",
"components.Settings.is4k": "4K",
"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.jellyfinSettingsSuccess": "{mediaServerName} settings saved successfully!",
"components.Settings.jellyfinlibraries": "{mediaServerName} Libraries",
"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.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.manualscan": "Manual Library Scan",
"components.Settings.manualscanDescription": "Normally, this will only be run once every 24 hours. Overseerr 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!",
"components.Settings.manualscanDescriptionJellyfin": "Normally, this will only be run once every 24 hours. Jellyseerr will check your {mediaServerName} server's recently added more aggressively. If this is your first time configuring Jellyseerr, a one-time full manual library scan is recommended!",
"components.Settings.manualscanJellyfin": "Manual Library Scan",
"components.Settings.mediaTypeMovie": "movie",
@@ -970,12 +974,12 @@
"components.Settings.notrunning": "Not Running",
"components.Settings.plex": "Plex",
"components.Settings.plexlibraries": "Plex Libraries",
"components.Settings.plexlibrariesDescription": "The libraries Overseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.",
"components.Settings.plexlibrariesDescription": "The libraries Jellyseerr scans for titles. Set up and save your Plex connection settings, then click the button below if no libraries are listed.",
"components.Settings.plexsettings": "Plex Settings",
"components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Overseerr scans your Plex libraries to determine content availability.",
"components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Jellyseerr scans your Plex libraries to determine content availability.",
"components.Settings.port": "Port",
"components.Settings.radarrsettings": "Radarr Settings",
"components.Settings.restartrequiredTooltip": "Overseerr must be restarted for changes to this setting to take effect",
"components.Settings.restartrequiredTooltip": "Jellyseerr must be restarted for changes to this setting to take effect",
"components.Settings.save": "Save Changes",
"components.Settings.saving": "Saving…",
"components.Settings.scan": "Sync Libraries",
@@ -997,7 +1001,7 @@
"components.Settings.syncing": "Syncing",
"components.Settings.tautulliApiKey": "API Key",
"components.Settings.tautulliSettings": "Tautulli Settings",
"components.Settings.tautulliSettingsDescription": "Optionally configure the settings for your Tautulli server. Overseerr fetches watch history data for your Plex media from Tautulli.",
"components.Settings.tautulliSettingsDescription": "Optionally configure the settings for your Tautulli server. Jellyseerr fetches watch history data for your Plex media from Tautulli.",
"components.Settings.timeout": "Timeout",
"components.Settings.toastPlexConnecting": "Attempting to connect to Plex…",
"components.Settings.toastPlexConnectingFailure": "Failed to connect to Plex.",

View File

@@ -1152,7 +1152,7 @@
"components.Settings.SettingsMain.applicationurl": "URL applicazione",
"components.Settings.SettingsMain.validationApplicationTitle": "Devi fornire un titolo dell'applicazione",
"components.Settings.SettingsMain.validationApplicationUrl": "Devi fornire un URL valido",
"components.Settings.restartrequiredTooltip": "Overseerr deve essere riavviato per rendere effettive le modifiche",
"components.Settings.restartrequiredTooltip": "Jellyseerr deve essere riavviato per rendere effettive le modifiche",
"components.TitleCard.tvdbid": "TheTVDB ID",
"components.UserProfile.emptywatchlist": "I media aggiunti alla tua <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink> appariranno qui.",
"components.TvDetails.status4k": "4K {status}",

View File

@@ -324,13 +324,13 @@
"components.Settings.SettingsAbout.appDataPath": "데이터 디렉토리",
"components.Settings.SettingsAbout.documentation": "문서",
"components.Settings.SettingsAbout.gettingsupport": "지원 받기",
"components.Settings.SettingsAbout.overseerrinformation": "Overseerr 정보",
"components.Settings.SettingsAbout.overseerrinformation": "Jellyseerr 정보",
"components.Settings.SettingsAbout.preferredmethod": "선호",
"components.Settings.SettingsAbout.runningDevelop": "당신은 <code>개발</code>에 기여하거나 최신 테스트를 지원하는 사람들에게만 권장되는 Overserr 분기를 실행하고 있습니다.",
"components.Settings.SettingsAbout.timezone": "시간대",
"components.Settings.SettingsJobsCache.availability-sync": "사용가능한 미디어 동기화",
"components.Settings.SettingsJobsCache.cache": "캐시",
"components.Settings.SettingsJobsCache.cacheDescription": "Overseerr는 외부 API 엔드포인트에 대한 요청을 캐시하여 성능을 최적화하고 불필요한 API 호출을 방지합니다.",
"components.Settings.SettingsJobsCache.cacheDescription": "Jellyseerr는 외부 API 엔드포인트에 대한 요청을 캐시하여 성능을 최적화하고 불필요한 API 호출을 방지합니다.",
"components.Settings.SettingsJobsCache.cacheflushed": "{cachename} 캐시가 플러시되었습니다.",
"components.Settings.SettingsJobsCache.cachekeys": "전체 키",
"components.Settings.SettingsJobsCache.cacheksize": "키 크기",
@@ -342,11 +342,11 @@
"components.Settings.SettingsJobsCache.editJobSchedulePrompt": "새 주파수",
"components.Settings.SettingsJobsCache.editJobScheduleSelectorSeconds": "매 {jobScheduleSeconds, plural, one {초} other {{jobScheduleSeconds} 초}}",
"components.Settings.SettingsJobsCache.imagecache": "이미지 캐시",
"components.Settings.SettingsJobsCache.imagecacheDescription": "설정에서 활성화하면 Overseerr는 미리 구성된 외부 소스에서 이미지를 프록시하고 캐시합니다. 캐시된 이미지는 구성 폴더에 저장됩니다. <code>{appDataPath}/cache/images</code> 에서 파일을 찾을 수 있습니다.",
"components.Settings.SettingsJobsCache.imagecacheDescription": "설정에서 활성화하면 Jellyseerr는 미리 구성된 외부 소스에서 이미지를 프록시하고 캐시합니다. 캐시된 이미지는 구성 폴더에 저장됩니다. <code>{appDataPath}/cache/images</code> 에서 파일을 찾을 수 있습니다.",
"components.Settings.SettingsJobsCache.jobScheduleEditFailed": "작업을 저장하는 동안 문제가 발생했습니다.",
"components.Settings.SettingsJobsCache.jobScheduleEditSaved": "작업이 성공적으로 수정되었습니다!",
"components.Settings.SettingsJobsCache.jobs": "작업",
"components.Settings.SettingsJobsCache.jobsDescription": "Overseerr는 특정 유지 관리 작업을 정기적으로 예약된 작업으로 수행하지만 아래에서 수동으로 트리거할 수도 있습니다. 작업을 수동으로 실행해도 일정이 변경되지는 않습니다.",
"components.Settings.SettingsJobsCache.jobsDescription": "Jellyseerr는 특정 유지 관리 작업을 정기적으로 예약된 작업으로 수행하지만 아래에서 수동으로 트리거할 수도 있습니다. 작업을 수동으로 실행해도 일정이 변경되지는 않습니다.",
"components.Settings.SettingsJobsCache.jobtype": "유형",
"components.Settings.SettingsJobsCache.plex-full-scan": "Plex 전체 라이브러리 스캔",
"components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex 최근 추가 스캔",
@@ -420,7 +420,7 @@
"components.Settings.is4k": "4K",
"components.Settings.manualscan": "수동으로 라이브러리 스캔",
"components.Settings.mediaTypeMovie": "영화",
"components.Settings.manualscanDescription": "일반적으로 이는 24시간에 한 번 실행됩니다. Overseerr는 Plex 서버의 최근 추가 항목을 더 적극적으로 확인합니다. 만약 Plex를 처음 구성하는 경우, 한번은 전체 수동 라이브러리 스캔을 권장합니다!",
"components.Settings.manualscanDescription": "일반적으로 이는 24시간에 한 번 실행됩니다. Jellyseerr는 Plex 서버의 최근 추가 항목을 더 적극적으로 확인합니다. 만약 Plex를 처음 구성하는 경우, 한번은 전체 수동 라이브러리 스캔을 권장합니다!",
"components.Settings.mediaTypeSeries": "시리즈",
"components.Settings.menuAbout": "정보",
"components.Settings.menuGeneralSettings": "일반",
@@ -431,10 +431,10 @@
"components.Settings.noDefaultNon4kServer": "비-4K 및 4K 콘텐츠를 전부 처리하는 유일한 {serverType} 서버가 있는 경우(또는 4K 콘텐츠만 다운로드하는 경우), {serverType} 서버는 4K 서버로 지정되어서는 <strong>안됩니다</strong>.",
"components.Settings.noDefaultServer": "{mediaType} 요청을 처리하기 위해서는 적어도 하나 이상의 {serverType} 서버를 기본 설정해야 합니다.",
"components.Settings.notifications": "알림",
"components.Settings.plexlibrariesDescription": "Overseerr에서 타이틀을 스캔하는 라이브러리입니다. Plex 연결 설정을 설정하고 저장한 후, 라이브러리가 표시되지 않는 경우 아래 버튼을 클릭하세요.",
"components.Settings.plexlibrariesDescription": "Jellyseerr에서 타이틀을 스캔하는 라이브러리입니다. Plex 연결 설정을 설정하고 저장한 후, 라이브러리가 표시되지 않는 경우 아래 버튼을 클릭하세요.",
"components.Settings.port": "포트",
"components.Settings.radarrsettings": "Radarr 설정",
"components.Settings.restartrequiredTooltip": "이 변경된 설정이 적용되려면 Overseerr를 재시작해야 합니다",
"components.Settings.restartrequiredTooltip": "이 변경된 설정이 적용되려면 Jellyseerr를 재시작해야 합니다",
"components.Settings.serverRemote": "원격",
"components.Settings.serverSecure": "보안",
"components.Settings.serverpreset": "서버",
@@ -598,7 +598,7 @@
"components.IssueList.showallissues": "모든 이슈 표시",
"components.IssueModal.CreateIssueModal.toastFailedCreate": "이슈를 제출하는 동안에 문제가 발생했습니다.",
"components.Layout.LanguagePicker.displaylanguage": "표시 언어",
"components.Layout.VersionStatus.streamdevelop": "Overseerr 개발",
"components.Layout.VersionStatus.streamdevelop": "Jellyseerr 개발",
"components.ManageSlideOver.tvshow": "시리즈",
"components.ManageSlideOver.plays": "<strong>{playCount, number}</strong> {playCount, plural, one {재생} other {재생}}",
"components.MovieDetails.rtaudiencescore": "Rotten Tomatoes 시청자 점수",
@@ -690,7 +690,7 @@
"components.Layout.Sidebar.issues": "이슈",
"components.Layout.UserDropdown.myprofile": "프로필",
"components.Login.loginerror": "로그인을 시도하는 중에 문제가 발생했습니다.",
"components.Layout.VersionStatus.streamstable": "Overseerr 안정",
"components.Layout.VersionStatus.streamstable": "Jellyseerr 안정",
"components.Login.signingin": "로그인 중…",
"components.ManageSlideOver.manageModalIssues": "진행 중인 이슈",
"components.ManageSlideOver.manageModalMedia": "미디어",
@@ -780,7 +780,7 @@
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet 테스트 알림이 전송되었습니다!",
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Pushbullet 테스트 알림을 보내지 못했습니다.",
"components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Pushbullet 테스트 알림 보내기…",
"components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Overseerr와 함께 사용할 <ApplicationRegistrationLink>애플리케이션 등록</ApplicationRegistrationLink>",
"components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Jellyseerr와 함께 사용할 <ApplicationRegistrationLink>애플리케이션 등록</ApplicationRegistrationLink>",
"components.Settings.Notifications.NotificationsPushover.agentenabled": "에이전트 활성화",
"components.Settings.Notifications.NotificationsPushbullet.validationTypes": "적어도 하나 이상의 알림 유형을 선택해야 합니다",
"components.Settings.Notifications.NotificationsPushover.accessToken": "애플리케이션 API 토큰",
@@ -796,7 +796,7 @@
"components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "유효한 JSON 페이로드를 입력해야 합니다",
"components.Settings.Notifications.allowselfsigned": "자체 서명된 인증서 허용",
"components.Settings.Notifications.authUser": "SMTP 사용자 이름",
"components.Settings.Notifications.botApiTip": "Overseerr와 함께 사용할 <CreateBotLink>봇 생성</CreateBotLink>이 필요합니다",
"components.Settings.Notifications.botApiTip": "Jellyseerr와 함께 사용할 <CreateBotLink>봇 생성</CreateBotLink>이 필요합니다",
"components.Settings.Notifications.botUsername": "봇 사용자 이름",
"components.Settings.Notifications.chatIdTip": "봇과 채팅을 시작하고 <GetIdBotLink>@get_id_bot</GetIdBotLink>, <code>/my_id</code> 명령을 실행하세요",
"components.Settings.Notifications.discordsettingsfailed": "Discord 알림 설정을 저장하지 못했습니다.",
@@ -840,7 +840,7 @@
"components.Settings.SettingsMain.originallanguageTip": "원작 언어로 콘텐츠 필터링",
"components.Settings.SettingsMain.partialRequestsEnabled": "부분 시리즈 요청 허용",
"components.Settings.SettingsMain.trustProxy": "프록시 지원 활성화",
"components.Settings.SettingsMain.trustProxyTip": "프록시 뒤에서 클라이언트 IP 주소를 정확하게 등록하도록 Overseerr에 허용",
"components.Settings.SettingsMain.trustProxyTip": "프록시 뒤에서 클라이언트 IP 주소를 정확하게 등록하도록 Jellyseerr에 허용",
"components.Settings.SettingsUsers.localLoginTip": "사용자가 Plex OAuth 대신 이메일 주소와 암호를 사용하여 로그인하도록 허용",
"components.Settings.SettingsUsers.movieRequestLimitLabel": "전역 영화 요청 제한",
"components.Settings.SettingsUsers.toastSettingsSuccess": "사용자 설정이 성공적으로 저장되었습니다!",
@@ -1081,6 +1081,7 @@
"components.Settings.Notifications.encryptionTip": "대부분의 경우 암시적 TLS는 465 포트를 사용하고 STARTTLS는 587 포트를 사용합니다",
"components.Settings.SettingsAbout.Releases.versionChangelog": "{version} 변경 로그",
"components.Settings.SettingsAbout.supportoverseerr": "Overseerr 지원",
"components.Settings.SettingsAbout.supportjellyseerr": "Jellyseerr 지원",
"components.Settings.SettingsAbout.uptodate": "최신",
"components.Settings.SettingsAbout.githubdiscussions": "GitHub 토론",
"components.Settings.SettingsAbout.version": "버전",
@@ -1098,7 +1099,7 @@
"components.Settings.SettingsLogs.extraData": "추가 데이터",
"components.Settings.SettingsLogs.time": "타임스탬프",
"components.Settings.SettingsMain.cacheImages": "이미지 캐싱 활성화",
"components.Settings.SettingsMain.generalsettingsDescription": "Overseerr에 대한 전역 및 기본 설정을 구성합니다.",
"components.Settings.SettingsMain.generalsettingsDescription": "Jellyseerr에 대한 전역 및 기본 설정을 구성합니다.",
"components.Settings.SettingsLogs.viewdetails": "세부 정보 보기",
"components.Settings.SettingsMain.applicationurl": "애플리케이션 URL",
"components.Settings.SettingsMain.apikey": "API 키",
@@ -1138,7 +1139,7 @@
"components.Settings.librariesRemaining": "남은 라이브러리: {count}",
"components.Settings.noDefault4kServer": "4K {serverType} 서버는 사용자가 4K {mediaType} 요청을 제출할 수 있도록 기본 설정되어야 합니다.",
"components.Settings.scan": "라이브러리 동기화",
"components.Settings.plexsettingsDescription": "Plex 서버의 설정을 구성하세요. Overseerr는 Plex 라이브러리를 스캔하여 콘텐츠의 사용 가능성을 판단합니다.",
"components.Settings.plexsettingsDescription": "Plex 서버의 설정을 구성하세요. Jellyseerr는 Plex 라이브러리를 스캔하여 콘텐츠의 사용 가능성을 판단합니다.",
"components.Settings.notificationsettings": "알림 설정",
"components.Settings.plexsettings": "Plex 설정",
"components.Settings.notificationAgentSettingsDescription": "알림 에이전트를 구성하고 활성화합니다.",
@@ -1155,7 +1156,7 @@
"components.Settings.webAppUrlTip": "사용자에게 \"호스팅된\" 웹 앱 대신 서버의 웹 앱으로 이동하도록 선택적으로 설정할 수 있습니다",
"components.Setup.setup": "설정",
"components.Setup.tip": "팁",
"components.Setup.welcome": "Overseerr에 오신 것을 환영합니다",
"components.Setup.welcome": "Jellyseerr에 오신 것을 환영합니다",
"components.StatusBadge.status4k": "4K {status}",
"components.StatusBadge.status": "{status}",
"components.TvDetails.play4konplex": "Plex에서 4K로 재생",
@@ -1226,7 +1227,7 @@
"pages.errormessagewithcode": "{statusCode} - {error}",
"pages.serviceunavailable": "서비스를 사용할 수 없음",
"components.Settings.settingUpPlexDescription": "Plex를 설정하려면, 세부 정보를 수동으로 입력하거나 <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>에서 검색된 서버를 선택할 수 있습니다. 사용 가능한 서버 목록을 불러오려면 드롭다운 오른쪽에 있는 버튼을 누르세요.",
"components.Settings.tautulliSettingsDescription": "선택적으로 Tautulli 서버의 설정을 구성하세요. Overseerr는 Tautulli로부터 Plex 미디어의 시청 기록 데이터를 불러옵니다.",
"components.Settings.tautulliSettingsDescription": "선택적으로 Tautulli 서버의 설정을 구성하세요. Jellyseerr는 Tautulli로부터 Plex 미디어의 시청 기록 데이터를 불러옵니다.",
"components.RequestBlock.requestdate": "요청 일자",
"components.Discover.DiscoverMovies.activefilters": "{count, plural, one {# 선택한 필터} other {# 선택한 필터}}",
"components.QuotaSelector.seasons": "{count, plural, one {시즌} other {시즌}}",

View File

@@ -223,7 +223,7 @@
"components.TvDetails.network": "{networkCount, plural, one {Netwerk} other {Netwerken}}",
"components.TvDetails.firstAirDate": "Datum eerste uitzending",
"components.TvDetails.anime": "Anime",
"components.StatusChacker.reloadJellyseerr": "Herladen",
"components.StatusChacker.reloadOverseerr": "Herladen",
"components.StatusChacker.newversionavailable": "Toepassingsupdate",
"components.StatusChacker.newversionDescription": "Jellyseerr is geüpdatet! Klik op de onderstaande knop om de pagina opnieuw te laden.",
"components.Settings.toastSettingsSuccess": "Instellingen succesvol opgeslagen!",

View File

@@ -332,7 +332,7 @@
"components.TvDetails.anime": "Anime",
"components.TvDetails.TvCrew.fullseriescrew": "Equipa Técnica Completa da Série",
"components.TvDetails.TvCast.fullseriescast": "Elenco Completo da Série",
"components.StatusChacker.reloadJellyseerr": "Recarregar",
"components.StatusChacker.reloadOverseerr": "Recarregar",
"components.StatusChacker.newversionavailable": "Atualização de Aplicação",
"components.StatusChacker.newversionDescription": "Jellyseerr foi atualizado! Clique no botão abaixo para recarregar a página.",
"components.StatusBadge.status4k": "4K {status}",

View File

@@ -1092,7 +1092,7 @@
"components.RequestList.RequestItem.tvdbid": "TheTVDB ID",
"components.Settings.SettingsJobsCache.plex-watchlist-sync": "Synkronisering av Plex Watchlist",
"components.Settings.advancedTooltip": "Om du konfigurerar den här inställningen felaktigt kan det leda till att funktionerna inte fungerar",
"components.Settings.restartrequiredTooltip": "Overseerr måste startas om för att ändringarna i den här inställningen ska träda i kraft",
"components.Settings.restartrequiredTooltip": "Jellyseerr måste startas om för att ändringarna i den här inställningen ska träda i kraft",
"components.TvDetails.reportissue": "Rapportera ett problem",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseries": "Begär automatiskt serier",
"components.UserProfile.UserSettings.UserGeneralSettings.plexwatchlistsyncseriestip": "Begär automatiskt serier på din <PlexWatchlistSupportLink>Plex Watchlist</PlexWatchlistSupportLink>",
@@ -1103,7 +1103,7 @@
"components.StatusBadge.playonplex": "Spela upp på Plex",
"components.TitleCard.tvdbid": "TheTVDB ID",
"components.Settings.SettingsLogs.viewdetails": "Visa detaljer",
"components.Settings.SettingsJobsCache.imagecacheDescription": "När det är aktiverat i inställningarna kommer Overseerr att göra proxy- och cache-bilder från förkonfigurerade externa källor. Cachade bilder sparas i din konfigurationsmapp. Du hittar filerna i <code>{appDataPath}/cache/images</code>.",
"components.Settings.SettingsJobsCache.imagecacheDescription": "När det är aktiverat i inställningarna kommer Jellyseerrr att göra proxy- och cache-bilder från förkonfigurerade externa källor. Cachade bilder sparas i din konfigurationsmapp. Du hittar filerna i <code>{appDataPath}/cache/images</code>.",
"components.TitleCard.cleardata": "Rensa data",
"components.TitleCard.mediaerror": "{mediaType} Hittades inte",
"components.TvDetails.rtcriticsscore": "Rotten Tomatoes Tomatometer",
@@ -1183,7 +1183,7 @@
"components.Settings.SettingsMain.csrfProtectionTip": "Ställ in extern API-åtkomst till skrivskyddad (kräver HTTPS)",
"components.Settings.SettingsMain.general": "Allmänt",
"components.Settings.SettingsMain.generalsettings": "Generella inställningar",
"components.Settings.SettingsMain.generalsettingsDescription": "Konfigurera globala och standard-inställningar för Overseerr.",
"components.Settings.SettingsMain.generalsettingsDescription": "Konfigurera globala och standard-inställningar för Jellyseerr.",
"components.Settings.SettingsMain.locale": "Visningsspråk",
"components.Settings.SettingsMain.originallanguage": "Upptäck språk",
"components.Settings.SettingsMain.originallanguageTip": "Filtrera innehållet efter originalspråk",
@@ -1192,7 +1192,7 @@
"components.Settings.SettingsMain.regionTip": "Filtrera innehållet efter regional tillgänglighet",
"components.Settings.SettingsMain.toastApiKeyFailure": "Något gick fel när du genererade en ny API-nyckel.",
"components.Settings.SettingsMain.toastApiKeySuccess": "Ny API-nyckel genererades framgångsrikt!",
"components.Settings.SettingsMain.trustProxyTip": "Tillåt Overseerr att korrekt registrera klienternas IP-adresser bakom en proxy",
"components.Settings.SettingsMain.trustProxyTip": "Tillåt Jellyseerr att korrekt registrera klienternas IP-adresser bakom en proxy",
"components.Discover.resetwarning": "Återställer alla skjutreglage till standardvärdet. Detta raderar också alla anpassade skjutreglage!",
"components.Discover.stopediting": "Sluta redigera",
"components.Discover.tmdbmoviegenre": "TMDB filmgenre",