Compare commits
19 Commits
v2.0.0
...
preview-mo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23e959acc1 | ||
|
|
2000c2ddf6 | ||
|
|
4a3fb5e6c8 | ||
|
|
4f14e057c7 | ||
|
|
d16e399011 | ||
|
|
4b4eeb6ec7 | ||
|
|
d331798b28 | ||
|
|
f2b63156d1 | ||
|
|
326001c3ec | ||
|
|
0bbcfcbd5e | ||
|
|
32e0b129fe | ||
|
|
a2b3408c9a | ||
|
|
cbb1a74526 | ||
|
|
26c37ec067 | ||
|
|
4e48fdf2cb | ||
|
|
a351264b87 | ||
|
|
9de304d17a | ||
|
|
4945b54298 | ||
|
|
a0f80fe764 |
@@ -439,6 +439,15 @@
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "M0NsTeRRR",
|
||||
"name": "Ludovic Ortega",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/37785089?v=4",
|
||||
"profile": "https://github.com/M0NsTeRRR",
|
||||
"contributions": [
|
||||
"security"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -34,6 +34,7 @@ yarn-error.log*
|
||||
# database
|
||||
config/db/*.sqlite3*
|
||||
config/settings.json
|
||||
config/settings.old.json
|
||||
|
||||
# logs
|
||||
config/logs/*.log*
|
||||
|
||||
418
CHANGELOG.md
418
CHANGELOG.md
@@ -1,421 +1,3 @@
|
||||
# [2.0.0](https://github.com/fallenbagel/jellyseerr/compare/v1.9.2...v2.0.0) (2024-10-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* abort availability sync job if auth token invalid/connection lost ([#845](https://github.com/fallenbagel/jellyseerr/issues/845)) ([bdee340](https://github.com/fallenbagel/jellyseerr/commit/bdee34053080c8975a88ba16a9e8f402e10fe7e1))
|
||||
* add an error message to say when an email is already taken ([#947](https://github.com/fallenbagel/jellyseerr/issues/947)) ([89e0a83](https://github.com/fallenbagel/jellyseerr/commit/89e0a831ec85a6905f539f59b7523bb1feb90bcf))
|
||||
* add missing brackets ([#888](https://github.com/fallenbagel/jellyseerr/issues/888)) ([6cea8bb](https://github.com/fallenbagel/jellyseerr/commit/6cea8bba592b8db566b4d8147630385f5c377f1b))
|
||||
* add missing content-type header ([#887](https://github.com/fallenbagel/jellyseerr/issues/887)) ([2be9c7d](https://github.com/fallenbagel/jellyseerr/commit/2be9c7dcc1f418726a19e99cfdb3933257a03c6f))
|
||||
* add missing header when creating an issue ([#879](https://github.com/fallenbagel/jellyseerr/issues/879)) ([084e1b2](https://github.com/fallenbagel/jellyseerr/commit/084e1b224e109f0f8279741b9a5ead138396d7f8))
|
||||
* add missing parameter to delete requests from ExternalAPI ([#904](https://github.com/fallenbagel/jellyseerr/issues/904)) ([36d98a2](https://github.com/fallenbagel/jellyseerr/commit/36d98a2681921a8770027b78878688f2782e8b77)), closes [#903](https://github.com/fallenbagel/jellyseerr/issues/903)
|
||||
* **api:** fix nextjs error handler ([#882](https://github.com/fallenbagel/jellyseerr/issues/882)) ([0116c13](https://github.com/fallenbagel/jellyseerr/commit/0116c13e0632d1ccec43299fbb10cd71db45bc29))
|
||||
* **api:** handle non-existent ratings on IMDb ([#822](https://github.com/fallenbagel/jellyseerr/issues/822)) ([74a2d25](https://github.com/fallenbagel/jellyseerr/commit/74a2d25f153b07a0cae5b44adca5fa1fed5a3b9e))
|
||||
* **api:** save new password when reset password of local account ([#886](https://github.com/fallenbagel/jellyseerr/issues/886)) ([5cc4389](https://github.com/fallenbagel/jellyseerr/commit/5cc43898256b130c2576f34a3d4e7ce6a3940d3e))
|
||||
* **blacklist:** add blacklist to mobile menu ([#980](https://github.com/fallenbagel/jellyseerr/issues/980)) ([f390da4](https://github.com/fallenbagel/jellyseerr/commit/f390da486625a22951956ba96867de63f73bfc2b)), closes [#979](https://github.com/fallenbagel/jellyseerr/issues/979)
|
||||
* change SeriesSearch to MissingEpisodeSearch for season requests ([#711](https://github.com/fallenbagel/jellyseerr/issues/711)) ([ee7e91c](https://github.com/fallenbagel/jellyseerr/commit/ee7e91c7c948b17b556a625919eb1252a721bb6e))
|
||||
* **docker:** add postinstall script ([#839](https://github.com/fallenbagel/jellyseerr/issues/839)) ([f714132](https://github.com/fallenbagel/jellyseerr/commit/f7141329094d88eb0940b1db1f21376142cb8893))
|
||||
* enhance error messages when Fetch API fails ([#893](https://github.com/fallenbagel/jellyseerr/issues/893)) ([fccfca6](https://github.com/fallenbagel/jellyseerr/commit/fccfca6ed06c8dc599e1ea4b1b3dbac48eb3a7f6))
|
||||
* handle status badge for season packs ([#927](https://github.com/fallenbagel/jellyseerr/issues/927)) ([80f6301](https://github.com/fallenbagel/jellyseerr/commit/80f63017ac5e9b1720a19c761dbef4dd517f1c2c))
|
||||
* length of undefined on users warnings ([#875](https://github.com/fallenbagel/jellyseerr/issues/875)) ([c600566](https://github.com/fallenbagel/jellyseerr/commit/c600566ac0045c2314f9013b063007b087ee4327))
|
||||
* remove DNS caching ([#837](https://github.com/fallenbagel/jellyseerr/issues/837)) ([268c7df](https://github.com/fallenbagel/jellyseerr/commit/268c7df28eea8b911d6a53297f5ce296983067ce))
|
||||
* remove email requirement for the user, and use the username if no email provided ([#900](https://github.com/fallenbagel/jellyseerr/issues/900)) ([d5f817e](https://github.com/fallenbagel/jellyseerr/commit/d5f817e734131cdacc229361d9498a095af57950))
|
||||
* remove protocol-relative URLs from next/image ([#889](https://github.com/fallenbagel/jellyseerr/issues/889)) ([c80d9a8](https://github.com/fallenbagel/jellyseerr/commit/c80d9a853a2a3451293a5382ef183c18add0c040))
|
||||
* resize episode preview image ([#842](https://github.com/fallenbagel/jellyseerr/issues/842)) ([96ba53f](https://github.com/fallenbagel/jellyseerr/commit/96ba53fecc7b9d269f0d974051ab62836b0102bc))
|
||||
* resize header image in network and studio pages ([#902](https://github.com/fallenbagel/jellyseerr/issues/902)) ([4220855](https://github.com/fallenbagel/jellyseerr/commit/422085523e5dfc132f3c3ca19eaa87117828b7be))
|
||||
* rewrite request from axios to Fetch ([#920](https://github.com/fallenbagel/jellyseerr/issues/920)) ([9aee888](https://github.com/fallenbagel/jellyseerr/commit/9aee8887d3cca6e018f4be1c8400c22e86bf8dab))
|
||||
* rewrite the rate limit utility ([#896](https://github.com/fallenbagel/jellyseerr/issues/896)) ([3fc14c9](https://github.com/fallenbagel/jellyseerr/commit/3fc14c9e2262463afec666e7f54e38d0d36cff68))
|
||||
* **session:** set the correct TTL for the cookie store ([#992](https://github.com/fallenbagel/jellyseerr/issues/992)) ([96e1d40](https://github.com/fallenbagel/jellyseerr/commit/96e1d40304749ce00d2ff7359efc39a1d9724358)), closes [#991](https://github.com/fallenbagel/jellyseerr/issues/991)
|
||||
* set correct user type when importing from emby ([#949](https://github.com/fallenbagel/jellyseerr/issues/949)) ([e57d265](https://github.com/fallenbagel/jellyseerr/commit/e57d2654d1c634a91649722d3a2bf4d73c4a02ca)), closes [#948](https://github.com/fallenbagel/jellyseerr/issues/948)
|
||||
* **setup:** page display when homepage is loading ([#940](https://github.com/fallenbagel/jellyseerr/issues/940)) ([7423bbb](https://github.com/fallenbagel/jellyseerr/commit/7423bbbffc5bee2e52e3348254f035dc8527d973))
|
||||
* **tmdb:** fallback movie/show overview to English when none is available in requested locale ([#928](https://github.com/fallenbagel/jellyseerr/issues/928)) ([12f908d](https://github.com/fallenbagel/jellyseerr/commit/12f908de7f5fbd717a5f151858b6edee3be13ed9)), closes [#925](https://github.com/fallenbagel/jellyseerr/issues/925)
|
||||
* update the filter removing existing users from Jellyfin import modal ([#924](https://github.com/fallenbagel/jellyseerr/issues/924)) ([61dcd8e](https://github.com/fallenbagel/jellyseerr/commit/61dcd8e487d7886773ccb12501623c17838476e5))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **jellyfin:** abstract jellyfin hostname, updated ui to reflect it, better validation ([#773](https://github.com/fallenbagel/jellyseerr/issues/773)) ([38ad875](https://github.com/fallenbagel/jellyseerr/commit/38ad875dd7848b4e92ac3ccdd16dbf785f6a5c4d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add environment variable for API key ([#831](https://github.com/fallenbagel/jellyseerr/issues/831)) ([45ef150](https://github.com/fallenbagel/jellyseerr/commit/45ef150e36944d456cc9440574b5ac75f2e4bbc1))
|
||||
* adds status filter for tv shows ([#796](https://github.com/fallenbagel/jellyseerr/issues/796)) ([cfd1bc2](https://github.com/fallenbagel/jellyseerr/commit/cfd1bc253557d6e19725743b8aa9a2fa33bbe760)), closes [#605](https://github.com/fallenbagel/jellyseerr/issues/605)
|
||||
* allow request managers to delete data from sonarr/radarr ([#644](https://github.com/fallenbagel/jellyseerr/issues/644)) ([a5d22ba](https://github.com/fallenbagel/jellyseerr/commit/a5d22ba5b83dd0e812b16f06476d993b5d59cb2a))
|
||||
* blacklist items from Discover page ([#632](https://github.com/fallenbagel/jellyseerr/issues/632)) ([818aa60](https://github.com/fallenbagel/jellyseerr/commit/818aa60aac185da07bfb71b08e0448939b63a736)), closes [#490](https://github.com/fallenbagel/jellyseerr/issues/490)
|
||||
* Jellyfin/Emby server type setup ([#685](https://github.com/fallenbagel/jellyseerr/issues/685)) ([15cb949](https://github.com/fallenbagel/jellyseerr/commit/15cb949f1f2e617853f90ae7bb8ae5d6622f610e))
|
||||
* **jellyfinapi:** switch to API tokens instead of auth tokens ([#868](https://github.com/fallenbagel/jellyseerr/issues/868)) ([bd4da6d](https://github.com/fallenbagel/jellyseerr/commit/bd4da6d5fc8cb55c2bc3d9a8336787cbd30814d0))
|
||||
* Option on item's page to add/remove from watchlist ([#781](https://github.com/fallenbagel/jellyseerr/issues/781)) ([2348f23](https://github.com/fallenbagel/jellyseerr/commit/2348f23f433195d64dee3e6eeede296fca5fdbc9)), closes [#730](https://github.com/fallenbagel/jellyseerr/issues/730)
|
||||
* refresh monitored downloads before getting queue items ([#994](https://github.com/fallenbagel/jellyseerr/issues/994)) ([92ba262](https://github.com/fallenbagel/jellyseerr/commit/92ba26207dcb1ddd696e0f01931d2609c521ae45)), closes [#866](https://github.com/fallenbagel/jellyseerr/issues/866)
|
||||
* show quality profile on request ([#847](https://github.com/fallenbagel/jellyseerr/issues/847)) ([6445332](https://github.com/fallenbagel/jellyseerr/commit/64453320d36595e75dcb710dfd43997bf2d2acd5))
|
||||
* **translation:** added full Hebrew translation ([#871](https://github.com/fallenbagel/jellyseerr/issues/871)) ([c96ca67](https://github.com/fallenbagel/jellyseerr/commit/c96ca6742e0a6d5685319c52f995fe06e439a450))
|
||||
* update Plex logo ([#884](https://github.com/fallenbagel/jellyseerr/issues/884)) ([3a363ae](https://github.com/fallenbagel/jellyseerr/commit/3a363ae1ffa7f384be6f7d25f8558b1e55a73fb3))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* fix(api): fix nextjs error handler ([#882](https://github.com/fallenbagel/jellyseerr/issues/882)) ([#892](https://github.com/fallenbagel/jellyseerr/issues/892)) ([62dbde4](https://github.com/fallenbagel/jellyseerr/commit/62dbde448c7f7d530de8534bb8538452d0f91276))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* This commit deprecates the JELLYFIN_TYPE variable to identify Emby media server and
|
||||
instead rely on the mediaServerType that is set in the `settings.json`. Existing environment
|
||||
variable users can log out and log back in to set the mediaServerType to `3` (Emby).
|
||||
|
||||
* feat(api): add severType to the api
|
||||
* This adds a serverType to the `/auth/jellyfin` which requires a serverType to be
|
||||
set (`jellyfin`/`emby`)
|
||||
|
||||
* refactor: use enums for serverType and rename selectedservice to serverType
|
||||
|
||||
* refactor(auth): jellyfin/emby authentication to set MediaServerType
|
||||
|
||||
* fix: issue page formatMessage for 4k media
|
||||
|
||||
* refactor: cleaner way of handling serverType change using MediaServerType instead of strings
|
||||
|
||||
instead of using strings now it will use MediaServerType enums for serverType
|
||||
|
||||
* revert: removed conditional render of the auto-request permission
|
||||
|
||||
reverts the conditional render toshow the auto-request permission if the mediaServerType was set to
|
||||
Plex as this should be handled in a different PR and Cypress tests should be modified
|
||||
accordingly(currently cypress test would fail if this conditional check is there)
|
||||
|
||||
* feat: add server type step to setup
|
||||
|
||||
* feat: migrate existing emby setups to use emby mediaServerType
|
||||
|
||||
* fix: scan jobs not running when media server type is emby
|
||||
|
||||
* fix: emby media server type migration
|
||||
|
||||
* refactor: change emby logo to full logo
|
||||
|
||||
* style: decrease emby logo size in setup screen
|
||||
|
||||
* refactor: use title case for servertype i18n message
|
||||
|
||||
* refactor(i18n): fix a typo
|
||||
|
||||
* refactor: use enums instead of numbers
|
||||
|
||||
* fix: remove old references to JELLYFIN_TYPE environment variable
|
||||
|
||||
* fix: go back to the last step when refresh the setup page
|
||||
|
||||
* fix: move "scanning in background" tip next to the scanning section
|
||||
|
||||
* fix: redirect the setup page when Jellyseerr is already setup
|
||||
* **jellyfin:** Jellyfin settings now does not include a hostname. Instead it abstracted it to ip,
|
||||
port, useSsl, and urlBase. However, migration of old settings to new settings should work
|
||||
automatically.
|
||||
|
||||
* refactor: remove console logs and use getHostname and ApiErrorCodes
|
||||
|
||||
* fix: store req.body jellyfin settings temporarily and store only if valid
|
||||
|
||||
This should fix the issue where settings are saved even if the url
|
||||
was invalid. Now the settings will only be saved if the url is
|
||||
valid. Sort of like a test connection.
|
||||
|
||||
* refactor: clean up commented out code
|
||||
|
||||
* refactor(i18n): extract translation keys
|
||||
|
||||
* fix(auth): auth failing with jellyfin login is disabled
|
||||
|
||||
* fix(settings): jellyfin migrations replacing the rest of the settings
|
||||
|
||||
* fix(settings): jellyfin hostname should be carried out if hostname exists
|
||||
|
||||
* fix(settings): merging the wrong settings source
|
||||
|
||||
* refactor(settings): use migrator for dynamic settings migrations
|
||||
|
||||
* refactor(settingsmigrator): settings migration handler and the migrations
|
||||
|
||||
* test(cypress): fix cypress tests failing
|
||||
|
||||
cypress settings were lacking some of the jobs so when the startJobs() is called when the app
|
||||
starts, it was failing to schedule the jobs where their cron timings were not specified in the
|
||||
cypress settings. Therefore, this commit adds those jobs back. In addition, other setting options
|
||||
were added to keep cypress settings consistent with a normal user.
|
||||
|
||||
* chore(prettierignore): ignore cypress/config/settings.cypress.json as it does not need prettier
|
||||
|
||||
* chore(prettier): ran formatter on cypress config to fix format check error
|
||||
|
||||
format check locally passes on this file. However, it fails during the github actions format check.
|
||||
Therefore, json language features formatter was run instead of prettier to see if that fixes the
|
||||
issue.
|
||||
|
||||
* test(cypress): add only missing jobs to the cypress settings
|
||||
|
||||
* ci: attempt at trying to get formatter to pass on cypress config json file
|
||||
|
||||
* refactor: revert the changes brought to try and fix formatter
|
||||
|
||||
added back the rest of the cypress settings and removed cypress settings from .prettierignore
|
||||
|
||||
* refactor(settings): better erorr logging when jellyfin connection test fails in settings page
|
||||
|
||||
## [1.9.2](https://github.com/fallenbagel/jellyseerr/compare/v1.9.1...v1.9.2) (2024-06-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **auth:** improve login resilience with headerless fallback authentication ([#814](https://github.com/fallenbagel/jellyseerr/issues/814)) ([a9741fa](https://github.com/fallenbagel/jellyseerr/commit/a9741fa36d06710aa00d28db3dd2c29f2b0973d3))
|
||||
* **auth:** validation of ipv6/ipv4 ([#812](https://github.com/fallenbagel/jellyseerr/issues/812)) ([9aeb360](https://github.com/fallenbagel/jellyseerr/commit/9aeb3604e6498c388df1d30dd0b613ba84160fc0)), closes [#795](https://github.com/fallenbagel/jellyseerr/issues/795)
|
||||
* bypass cache-able lookups when resolving localhost ([#813](https://github.com/fallenbagel/jellyseerr/issues/813)) ([b5a0699](https://github.com/fallenbagel/jellyseerr/commit/b5a069901a9545772deaa9c491f2075261da0189))
|
||||
|
||||
## [1.9.1](https://github.com/fallenbagel/jellyseerr/compare/v1.9.0...v1.9.1) (2024-06-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** add DNS caching ([#810](https://github.com/fallenbagel/jellyseerr/issues/810)) ([46ee8a4](https://github.com/fallenbagel/jellyseerr/commit/46ee8a4ca13b026bd929b4027eb001cc74064bb8)), closes [#387](https://github.com/fallenbagel/jellyseerr/issues/387) [#657](https://github.com/fallenbagel/jellyseerr/issues/657) [#728](https://github.com/fallenbagel/jellyseerr/issues/728)
|
||||
* empty email in user settings ([#807](https://github.com/fallenbagel/jellyseerr/issues/807)) ([20863d4](https://github.com/fallenbagel/jellyseerr/commit/20863d4a8dabe78fb5c52995b5bcb2da557a804e)), closes [#803](https://github.com/fallenbagel/jellyseerr/issues/803)
|
||||
* **jellyfinscanner:** assign only 4k available badge for a 4k request instead of both badges ([#805](https://github.com/fallenbagel/jellyseerr/issues/805)) ([d31a2c3](https://github.com/fallenbagel/jellyseerr/commit/d31a2c37e639c1126b446277fa5d666d8102fef5))
|
||||
* remove the settings button of media when useless ([#809](https://github.com/fallenbagel/jellyseerr/issues/809)) ([f52939e](https://github.com/fallenbagel/jellyseerr/commit/f52939e4cdcbee94fc35165f613f6b3e21599e3c))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "ci: update format check command to ignore .prettierignore files (#787)" (#788) ([4757f1c](https://github.com/fallenbagel/jellyseerr/commit/4757f1c3e599304410a737c11f97db92a2bfcefd)), closes [#787](https://github.com/fallenbagel/jellyseerr/issues/787) [#788](https://github.com/fallenbagel/jellyseerr/issues/788)
|
||||
|
||||
# [1.9.0](https://github.com/fallenbagel/jellyseerr/compare/v1.8.1...v1.9.0) (2024-05-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** save user email on the first try ([#760](https://github.com/fallenbagel/jellyseerr/issues/760)) ([0bbcfdc](https://github.com/fallenbagel/jellyseerr/commit/0bbcfdc4f9ff9735f45232a2412ac8444f525de9)), closes [#227](https://github.com/fallenbagel/jellyseerr/issues/227) [#748](https://github.com/fallenbagel/jellyseerr/issues/748)
|
||||
* **api:** small errors on overseerr-api.yaml ([#721](https://github.com/fallenbagel/jellyseerr/issues/721)) ([0eea109](https://github.com/fallenbagel/jellyseerr/commit/0eea1090dfdba4333646280c84b09b0197fefa74))
|
||||
* **auth:** case-sensitive logins not updating authtokens ([#778](https://github.com/fallenbagel/jellyseerr/issues/778)) ([2bd125d](https://github.com/fallenbagel/jellyseerr/commit/2bd125d9a55d15a398ceb5f2996105a5e861b6e0))
|
||||
* **jellyfinapi:** use external api class for jellyfin api requests ([#762](https://github.com/fallenbagel/jellyseerr/issues/762)) ([650c339](https://github.com/fallenbagel/jellyseerr/commit/650c339d74d4fe85ef7f76184901e86f4eeada85)), closes [#728](https://github.com/fallenbagel/jellyseerr/issues/728) [#387](https://github.com/fallenbagel/jellyseerr/issues/387)
|
||||
* **logging:** handle media server connection refused error/toast ([#748](https://github.com/fallenbagel/jellyseerr/issues/748)) ([f486fb5](https://github.com/fallenbagel/jellyseerr/commit/f486fb5e75f9ea21456952b6a52cb841e30f3556))
|
||||
* use UTF8 encoding for webhook JSON ([#714](https://github.com/fallenbagel/jellyseerr/issues/714)) ([c0a0b9c](https://github.com/fallenbagel/jellyseerr/commit/c0a0b9c8a8b0c2eeaf3fa9159f10742baa9f6c1f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Latin American Spanish translation ([#725](https://github.com/fallenbagel/jellyseerr/issues/725)) ([783fda9](https://github.com/fallenbagel/jellyseerr/commit/783fda9621aef8ffd46e5f036136de82ed502ccc)), closes [#677](https://github.com/fallenbagel/jellyseerr/issues/677)
|
||||
* add merge conflict labeler workflow ([#719](https://github.com/fallenbagel/jellyseerr/issues/719)) ([d9d07c7](https://github.com/fallenbagel/jellyseerr/commit/d9d07c705a24d5c49905066aac45a3c6a2e36a53))
|
||||
* **auth:** send real information on login ([#470](https://github.com/fallenbagel/jellyseerr/issues/470)) ([d765055](https://github.com/fallenbagel/jellyseerr/commit/d765055da83ee94546399f6348aee14d8427d462))
|
||||
* **settings:** stores jellyfin/emby server name in the settings ([#763](https://github.com/fallenbagel/jellyseerr/issues/763)) ([7a5e8d6](https://github.com/fallenbagel/jellyseerr/commit/7a5e8d69bf620c8e7bf5f284840b1a5fe757ae5f))
|
||||
|
||||
## [1.8.1](https://github.com/fallenbagel/jellyseerr/compare/v1.8.0...v1.8.1) (2024-04-17)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix: disable seasonfolder option in sonarr for jellyfin/Emby users" (#718) ([cd0fa3e](https://github.com/fallenbagel/jellyseerr/commit/cd0fa3e2232dcb522673143f113fc382fb2ff0a3)), closes [#718](https://github.com/fallenbagel/jellyseerr/issues/718)
|
||||
|
||||
# [1.8.0](https://github.com/fallenbagel/jellyseerr/compare/v1.7.0...v1.8.0) (2024-04-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct width issue in datepicker of filterSliderOver ([f564cdd](https://github.com/fallenbagel/jellyseerr/commit/f564cddff4525ccebffbf304672d49c57aefe635)), closes [#415](https://github.com/fallenbagel/jellyseerr/issues/415)
|
||||
* disable seasonfolder option in sonarr for jellyfin/Emby users ([8ec8f2a](https://github.com/fallenbagel/jellyseerr/commit/8ec8f2ac5730aad3b12dcd8ed95bb553b46b399c)), closes [#126](https://github.com/fallenbagel/jellyseerr/issues/126) [#575](https://github.com/fallenbagel/jellyseerr/issues/575)
|
||||
* **embyauth:** remove the accidentally added mediaServerType change code from another PR ([#684](https://github.com/fallenbagel/jellyseerr/issues/684)) ([c2e8771](https://github.com/fallenbagel/jellyseerr/commit/c2e87714b4c4aa11bf68dcd82b76979f82990f3c))
|
||||
* ensure watchlist updates are immediately reflected ([b85d7f3](https://github.com/fallenbagel/jellyseerr/commit/b85d7f37b931735ca2ad955dccb6599bf445fc73))
|
||||
* fix german translation for "components.Discover.FilterSlideover.tmdbuservotecount" ([e032c02](https://github.com/fallenbagel/jellyseerr/commit/e032c02f5f84dc4b6b470eecb18ba2c376c55f37))
|
||||
* fix the translations for watchlist permissions and userSettings page ([8c82a61](https://github.com/fallenbagel/jellyseerr/commit/8c82a61450a7525c0e2f1b64e6939da47a7c715d))
|
||||
* **i18n:** fixed jellyfin jobs ([7eed236](https://github.com/fallenbagel/jellyseerr/commit/7eed23637ddfb10bdcb19698e7ae171f07299502))
|
||||
* **jellyfin.ts:** process virtual seasons if they have non virtual episodes ([#639](https://github.com/fallenbagel/jellyseerr/issues/639)) ([db84f65](https://github.com/fallenbagel/jellyseerr/commit/db84f6529ab285be26c96daaab065dfabf347417))
|
||||
* **jellyfinapi:** refactors jellyfin library sync to support automatic grouping and collections ([#700](https://github.com/fallenbagel/jellyseerr/issues/700)) ([3856061](https://github.com/fallenbagel/jellyseerr/commit/3856061fe1ee4d3457996586b4979ad9dd60765a)), closes [#450](https://github.com/fallenbagel/jellyseerr/issues/450) [#524](https://github.com/fallenbagel/jellyseerr/issues/524) [#256](https://github.com/fallenbagel/jellyseerr/issues/256) [#489](https://github.com/fallenbagel/jellyseerr/issues/489) [#450](https://github.com/fallenbagel/jellyseerr/issues/450) [#524](https://github.com/fallenbagel/jellyseerr/issues/524) [#515](https://github.com/fallenbagel/jellyseerr/issues/515) [#474](https://github.com/fallenbagel/jellyseerr/issues/474) [#473](https://github.com/fallenbagel/jellyseerr/issues/473)
|
||||
* **jellyfinlogin:** use externalHostname if set for forgetpassword link ([405f6bb](https://github.com/fallenbagel/jellyseerr/commit/405f6bbb7ffc390327c99dcef2cbbf9b3bc75f01)), closes [#199](https://github.com/fallenbagel/jellyseerr/issues/199) [#424](https://github.com/fallenbagel/jellyseerr/issues/424) [#212](https://github.com/fallenbagel/jellyseerr/issues/212)
|
||||
* **jellyfinscanner:** conditionally assign the jellyfinMediaId and jellyfinMediaId4k ([#686](https://github.com/fallenbagel/jellyseerr/issues/686)) ([530be42](https://github.com/fallenbagel/jellyseerr/commit/530be4272cce1b0d74d7f4156b8d794cda6ea03f)), closes [#681](https://github.com/fallenbagel/jellyseerr/issues/681)
|
||||
* **langcode:** fixes the ukranian language code ([dc67aaa](https://github.com/fallenbagel/jellyseerr/commit/dc67aaaf53eae86ba20c6c2798c92ec40962d85f)), closes [#504](https://github.com/fallenbagel/jellyseerr/issues/504)
|
||||
* nullable type for jellyfinMediaId(4k) ([#702](https://github.com/fallenbagel/jellyseerr/issues/702)) ([0900a95](https://github.com/fallenbagel/jellyseerr/commit/0900a95532501b6f4d9698de7530a771512924fc)), closes [#668](https://github.com/fallenbagel/jellyseerr/issues/668)
|
||||
* request watchlist items sequentially to prevent bypassing quota ([#3667](https://github.com/fallenbagel/jellyseerr/issues/3667)) ([b40ba07](https://github.com/fallenbagel/jellyseerr/commit/b40ba07a4de5857b8392f667038eeb0b22aa5d9a))
|
||||
* resolved issue with region selector and all regions value ([#3652](https://github.com/fallenbagel/jellyseerr/issues/3652)) ([28a2c50](https://github.com/fallenbagel/jellyseerr/commit/28a2c50495d0ce531da7f8c442bd488a54b1e84c))
|
||||
* typos on readme ([#655](https://github.com/fallenbagel/jellyseerr/issues/655)) ([eee9a02](https://github.com/fallenbagel/jellyseerr/commit/eee9a025d246c72bcd3aca753d9e49c1f8f064ea))
|
||||
* **watchlist:** added missing prop for watchlist item removal button in watchlist page ([a0ec992](https://github.com/fallenbagel/jellyseerr/commit/a0ec992028093257e9fa043622e236014f02dea3))
|
||||
* **watchlist:** discover local watchlist item display and profile local watchlist slider visibility ([3cb9494](https://github.com/fallenbagel/jellyseerr/commit/3cb9494e6210151716587d8c4b22e0a21692cf88))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ko language ([#3619](https://github.com/fallenbagel/jellyseerr/issues/3619)) ([9250735](https://github.com/fallenbagel/jellyseerr/commit/92507359b48db08b0066047d6505660b8c8b0b12))
|
||||
* add Peacock to Network Slider ([#3545](https://github.com/fallenbagel/jellyseerr/issues/3545)) ([0c39057](https://github.com/fallenbagel/jellyseerr/commit/0c39057ca58743697e9dcc3b678440ac3688c65a))
|
||||
* add tooltips to tautulli avatars ([#3601](https://github.com/fallenbagel/jellyseerr/issues/3601)) ([c484810](https://github.com/fallenbagel/jellyseerr/commit/c484810f965f8d04643c25c6d283dd83f4bd4a23))
|
||||
* added Letterboxd links for the external link blocks for movies ([981f5e6](https://github.com/fallenbagel/jellyseerr/commit/981f5e679c4c707e119741240a58de8bb07f9d6c))
|
||||
* check if first jellyfin user is admin ([#635](https://github.com/fallenbagel/jellyseerr/issues/635)) ([010df62](https://github.com/fallenbagel/jellyseerr/commit/010df62776191fe4c195e590df338f8d8523f55b)), closes [#610](https://github.com/fallenbagel/jellyseerr/issues/610)
|
||||
* jellyseerr makeover ([#715](https://github.com/fallenbagel/jellyseerr/issues/715)) ([0c27132](https://github.com/fallenbagel/jellyseerr/commit/0c2713213c56de342f76300d12ce01fd543d2ce3))
|
||||
* **job:** media availability support for jellyfin/emby ([#522](https://github.com/fallenbagel/jellyseerr/issues/522)) ([3eb1bb3](https://github.com/fallenbagel/jellyseerr/commit/3eb1bb3d8ff22391acb2e629bbec7b6e4b65ca95)), closes [#406](https://github.com/fallenbagel/jellyseerr/issues/406) [#193](https://github.com/fallenbagel/jellyseerr/issues/193) [#516](https://github.com/fallenbagel/jellyseerr/issues/516) [#362](https://github.com/fallenbagel/jellyseerr/issues/362) [#84](https://github.com/fallenbagel/jellyseerr/issues/84)
|
||||
* **notif:** add Pushover sound options ([#2403](https://github.com/fallenbagel/jellyseerr/issues/2403)) ([3ea5076](https://github.com/fallenbagel/jellyseerr/commit/3ea5076053359b518b1b4d537e7b61580d9275a3))
|
||||
* select default seriesType for anime ([#3627](https://github.com/fallenbagel/jellyseerr/issues/3627)) ([f628635](https://github.com/fallenbagel/jellyseerr/commit/f6286359cfd2ed93fc692aa2efda37310e02c11c)), closes [#3626](https://github.com/fallenbagel/jellyseerr/issues/3626)
|
||||
* standard series type selector ([#3628](https://github.com/fallenbagel/jellyseerr/issues/3628)) ([7bdd25e](https://github.com/fallenbagel/jellyseerr/commit/7bdd25e5a45843a3e530d3fa2b0887664b53eec8))
|
||||
* translations update from Hosted Weblate ([#3258](https://github.com/fallenbagel/jellyseerr/issues/3258)) ([e62a078](https://github.com/fallenbagel/jellyseerr/commit/e62a078298ced7dec627fb3ff9fc8f99a39d5e1b))
|
||||
* update SameSite policy of session cookie to Lax ([#3650](https://github.com/fallenbagel/jellyseerr/issues/3650)) ([c84ca43](https://github.com/fallenbagel/jellyseerr/commit/c84ca4307465af4278f3dad5cf9c2b8cbae3fada))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* **jellyfinapi:** reverts [#450](https://github.com/fallenbagel/jellyseerr/issues/450) as it broke library sync support for local accounts using LDAP ([b5acc09](https://github.com/fallenbagel/jellyseerr/commit/b5acc09ba98e2dd9b61e6b78721e4dd9f42a996c)), closes [#489](https://github.com/fallenbagel/jellyseerr/issues/489)
|
||||
|
||||
# [1.7.0](https://github.com/fallenbagel/jellyseerr/compare/v1.6.0...v1.7.0) (2023-09-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* adjust the plex watchlist sync schedule to have fuzziness ([#3502](https://github.com/fallenbagel/jellyseerr/issues/3502)) ([2c3f533](https://github.com/fallenbagel/jellyseerr/commit/2c3f5330764492e1323afd2d1f25e28ad78a2f2f))
|
||||
* handle issue causing incorrect media to change to unknown ([#3516](https://github.com/fallenbagel/jellyseerr/issues/3516)) ([83b008c](https://github.com/fallenbagel/jellyseerr/commit/83b008c8391459bd02dc74bcdb0d8caf27207bdf))
|
||||
* improved handling of edge case that could cause availability sync to fail ([#3497](https://github.com/fallenbagel/jellyseerr/issues/3497)) ([d0836ce](https://github.com/fallenbagel/jellyseerr/commit/d0836ce0efd55fccf2546087a0c4f94f7cb2e82a))
|
||||
* Include all defaults in payload ([#3538](https://github.com/fallenbagel/jellyseerr/issues/3538)) ([cb63bf2](https://github.com/fallenbagel/jellyseerr/commit/cb63bf217b9e8810a5210b4bf475b2a96583cc84))
|
||||
* multiple notifications for available media ([048fa96](https://github.com/fallenbagel/jellyseerr/commit/048fa967f2e5b23831ac9917c703934c50ef75f0))
|
||||
* repeat notifications for available 4k media ([30361f2](https://github.com/fallenbagel/jellyseerr/commit/30361f2ab751d9a882a9120e0f3df28dc42cc2cd))
|
||||
* resolved issue with create slider causing incorrect form submission ([#3514](https://github.com/fallenbagel/jellyseerr/issues/3514)) ([a761b7d](https://github.com/fallenbagel/jellyseerr/commit/a761b7dd35a5bd61bb4eb0275b75d1e0977e6a2d))
|
||||
* resolved user access check issue ([#3551](https://github.com/fallenbagel/jellyseerr/issues/3551)) ([2816c66](https://github.com/fallenbagel/jellyseerr/commit/2816c66300bf870d493c0665b0e984d60f707dfd))
|
||||
* **server/api/jellyfin.ts:** use /Library/VirtualFolders Jellyfin API call to fetch Jellyfin libs ([8685f57](https://github.com/fallenbagel/jellyseerr/commit/8685f5796a99d9700146bae9892319db10508d68)), closes [#256](https://github.com/fallenbagel/jellyseerr/issues/256)
|
||||
* **statusbadge:** handle missing season/episode number ([#3526](https://github.com/fallenbagel/jellyseerr/issues/3526)) ([01de972](https://github.com/fallenbagel/jellyseerr/commit/01de972a8fe2ea3c18d5b2f426d01b5b14d142d4))
|
||||
* **tautulli:** only test connection if hostname is defined ([#3573](https://github.com/fallenbagel/jellyseerr/issues/3573)) ([f7b4dfc](https://github.com/fallenbagel/jellyseerr/commit/f7b4dfcac472d08c54779a14fc1ad3c90927df26))
|
||||
* **ui:** corrected issues icon color ([#3498](https://github.com/fallenbagel/jellyseerr/issues/3498)) ([c1a47bd](https://github.com/fallenbagel/jellyseerr/commit/c1a47bd9de332cb4925974690f5a33448b5cc2e6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **rating:** added IMDB Radarr proxy ([#3496](https://github.com/fallenbagel/jellyseerr/issues/3496)) ([b4191f9](https://github.com/fallenbagel/jellyseerr/commit/b4191f9c65b7ff08764e61d18e7a75bc8d4b3325))
|
||||
|
||||
# [1.6.0](https://github.com/fallenbagel/jellyseerr/compare/v1.5.0...v1.6.0) (2023-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* availability sync file detection ([#3371](https://github.com/fallenbagel/jellyseerr/issues/3371)) ([7522aa3](https://github.com/fallenbagel/jellyseerr/commit/7522aa31743b169c903ebdf9d4d698645d27514c))
|
||||
* corrected initial fallback data load on details page ([#3395](https://github.com/fallenbagel/jellyseerr/issues/3395)) ([4bd8764](https://github.com/fallenbagel/jellyseerr/commit/4bd87647d0551c20e13589a62690a6f3e5ad8ff7))
|
||||
* correctly load series fallback modal with sonarr v4 ([#3451](https://github.com/fallenbagel/jellyseerr/issues/3451)) ([e051b1d](https://github.com/fallenbagel/jellyseerr/commit/e051b1dfea9c9320cc9dd420c475ae74cff0d901))
|
||||
* **deps:** update all non-major dependencies ([#3223](https://github.com/fallenbagel/jellyseerr/issues/3223)) ([f5191ad](https://github.com/fallenbagel/jellyseerr/commit/f5191aded680357522a65bbdcc40d162b8fbf594))
|
||||
* error deleting users with over 1000 requests ([#3376](https://github.com/fallenbagel/jellyseerr/issues/3376)) ([ac77b03](https://github.com/fallenbagel/jellyseerr/commit/ac77b037d5fb0c54f5edf4b29d04adb57aef388f))
|
||||
* external url regex is now consistent with internal url ([33ec443](https://github.com/fallenbagel/jellyseerr/commit/33ec4436fb82e1eb1bc97dd650088c27785e9d94))
|
||||
* externalLinkBlock ([46cd4d0](https://github.com/fallenbagel/jellyseerr/commit/46cd4d01d9a3cf17d79350c5e678202820272299))
|
||||
* fix regex for internal url to use a more effecient one ([e848386](https://github.com/fallenbagel/jellyseerr/commit/e848386d10f05f157e7a6dde8847ecab50c169ac))
|
||||
* fixes RT ratings for tv shows ([#3492](https://github.com/fallenbagel/jellyseerr/issues/3492)) ([04fbd00](https://github.com/fallenbagel/jellyseerr/commit/04fbd00d4ac29045592588ef8b664d1916991e37)), closes [#3491](https://github.com/fallenbagel/jellyseerr/issues/3491)
|
||||
* **genreselector:** fix searching in Genre filter ([#3468](https://github.com/fallenbagel/jellyseerr/issues/3468)) ([d7fa35e](https://github.com/fallenbagel/jellyseerr/commit/d7fa35e066cf371797aaa46ca464aa531ba8fb35))
|
||||
* handle search results with collections ([#3393](https://github.com/fallenbagel/jellyseerr/issues/3393)) ([70b1540](https://github.com/fallenbagel/jellyseerr/commit/70b1540ae23e83e01013856a9e06ad39e600922d))
|
||||
* lock body scroll when using webkit ([#3399](https://github.com/fallenbagel/jellyseerr/issues/3399)) ([c27f960](https://github.com/fallenbagel/jellyseerr/commit/c27f96096ac8cc6c387f9d1dde5b263576ac2132))
|
||||
* **logs:** jellyfin auth error now has the severity warn consistent with local login ([cc041b5](https://github.com/fallenbagel/jellyseerr/commit/cc041b5e0aa2b67573edba5919772b77a5111162)), closes [#224](https://github.com/fallenbagel/jellyseerr/issues/224)
|
||||
* make a (shallow) copy of radarr/sonarr tags into a request before adding user tags ([#3485](https://github.com/fallenbagel/jellyseerr/issues/3485)) ([48f7666](https://github.com/fallenbagel/jellyseerr/commit/48f76662d5c08156f1da3f47e216c5f02668f64b))
|
||||
* **ui:** corrected default badge hover opacity ([#3369](https://github.com/fallenbagel/jellyseerr/issues/3369)) ([a4d07f5](https://github.com/fallenbagel/jellyseerr/commit/a4d07f5afab613317d96c9c6e9b47157a5a28986))
|
||||
* **ui:** corrected mobile menu spacing in collection details ([#3432](https://github.com/fallenbagel/jellyseerr/issues/3432)) ([77a33cb](https://github.com/fallenbagel/jellyseerr/commit/77a33cb74d744bb747b791785799b632af8c7862))
|
||||
* **ui:** Make play symbol white ([1fe4bb8](https://github.com/fallenbagel/jellyseerr/commit/1fe4bb8a0415a72791ced75a2fba1027287398d5))
|
||||
* **ui:** Resize Emby icon and add margins ([ad69d67](https://github.com/fallenbagel/jellyseerr/commit/ad69d6715e976630092bfbbb1843886523551014))
|
||||
* **watchlist:** add validation for creation request ([03316c6](https://github.com/fallenbagel/jellyseerr/commit/03316c642d1ecf89753789af08caf6e3aac80113))
|
||||
* **watchlist:** fix github code scanning ([c08897b](https://github.com/fallenbagel/jellyseerr/commit/c08897bdc1cff65862c62347572bbbd01b6c36ac))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **add watchlist:** adding midding functionality from overserr ([5f1c10d](https://github.com/fallenbagel/jellyseerr/commit/5f1c10d50aaa430bcda96218ef2cc12a0eb926f3))
|
||||
* adds streaming services custom slider ([#3361](https://github.com/fallenbagel/jellyseerr/issues/3361)) ([2520d8f](https://github.com/fallenbagel/jellyseerr/commit/2520d8f739abfde608f3ef66a9fbe6b7b5c6647a))
|
||||
* auto tagging requested media with username ([#3338](https://github.com/fallenbagel/jellyseerr/issues/3338)) ([24f268b](https://github.com/fallenbagel/jellyseerr/commit/24f268b6cb67d9a8d8675cd6e09dd83a7f499add))
|
||||
* **discover:** support filtering by tmdb user vote count on discover page ([#3407](https://github.com/fallenbagel/jellyseerr/issues/3407)) ([aa84977](https://github.com/fallenbagel/jellyseerr/commit/aa849776809dfe891e67ff4db6861ef44df1a774))
|
||||
* **settings:** add internal url to jellyfin settings form ([0a30cd3](https://github.com/fallenbagel/jellyseerr/commit/0a30cd356d217a39546c016cc8bfa6ff6ad75e3e)), closes [#194](https://github.com/fallenbagel/jellyseerr/issues/194)
|
||||
* **src/components/externallinkblock/index.tsx:** support Emby icon ([672061c](https://github.com/fallenbagel/jellyseerr/commit/672061cd646c97c9954790c8e50eac88ea2666e9))
|
||||
* **tooltip:** email tooltip now appears when hovered over info icon ([cd7930e](https://github.com/fallenbagel/jellyseerr/commit/cd7930eef98451a781e5c9dc5ec223600a379f42))
|
||||
* translations update ([47287c3](https://github.com/fallenbagel/jellyseerr/commit/47287c368885d14bd1a56e3e8318ce22dd0f6ddf)), closes [#381](https://github.com/fallenbagel/jellyseerr/issues/381)
|
||||
* **watchlist:** add translation for en ([b7e3d28](https://github.com/fallenbagel/jellyseerr/commit/b7e3d285ed35b623062eceb0d99035cafbf075a6))
|
||||
|
||||
# [1.5.0](https://github.com/fallenbagel/jellyseerr/compare/v1.4.1...v1.5.0) (2023-04-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add better checks on 4k detection of series ([bc9017f](https://github.com/fallenbagel/jellyseerr/commit/bc9017f54d84ec24c4d74d38e1b4e24219425d41))
|
||||
* added a refresh interval if download status is in progress ([#3275](https://github.com/fallenbagel/jellyseerr/issues/3275)) ([1e2c6f4](https://github.com/fallenbagel/jellyseerr/commit/1e2c6f46ab66c836f321b5d8e34f1e8124c0b542))
|
||||
* **build:** increase threshold for amount of data to be fetched when SSR'ing ([#3320](https://github.com/fallenbagel/jellyseerr/issues/3320)) ([d7b83d2](https://github.com/fallenbagel/jellyseerr/commit/d7b83d22cee3d20db564cc0564d42802b02327e3))
|
||||
* disable availability sync temporarily ([2e5cf22](https://github.com/fallenbagel/jellyseerr/commit/2e5cf226265686012329248e7f729fec324c3deb))
|
||||
* hide remove button when default service is not configured ([7d4455b](https://github.com/fallenbagel/jellyseerr/commit/7d4455ba6bfd12e2730f7085cbb87df246f01d22))
|
||||
* **jellyfin scan:** temporary workaround fix for jellyfin scan when display specials within season ([38fb66d](https://github.com/fallenbagel/jellyseerr/commit/38fb66d31e41232c01898d0d362af8338eb7b960)), closes [#215](https://github.com/fallenbagel/jellyseerr/issues/215) [#176](https://github.com/fallenbagel/jellyseerr/issues/176) [#246](https://github.com/fallenbagel/jellyseerr/issues/246)
|
||||
* lint issues ([bcd2bb7](https://github.com/fallenbagel/jellyseerr/commit/bcd2bb7c96810f5a6932f42468a628d2db1bc771))
|
||||
* logger was set to info for the wrong logs ([#3354](https://github.com/fallenbagel/jellyseerr/issues/3354)) ([c36a4ba](https://github.com/fallenbagel/jellyseerr/commit/c36a4ba2b8df05873f5dfd0946a9bc3dc4ecfd1d))
|
||||
* remove unnecessary parenthesis from api key generation ([#3336](https://github.com/fallenbagel/jellyseerr/issues/3336)) ([6bd3f01](https://github.com/fallenbagel/jellyseerr/commit/6bd3f015d65507efca60279007bd2b86ee860643))
|
||||
* **snapcraft:** use the correct config folder for image cache ([#3302](https://github.com/fallenbagel/jellyseerr/issues/3302)) ([c93467b](https://github.com/fallenbagel/jellyseerr/commit/c93467b3acf2c256324297e7e8f21e9944005dd4))
|
||||
* **ui:** hide mini status badge if non-4K media status is unknown ([#3346](https://github.com/fallenbagel/jellyseerr/issues/3346)) ([50f06da](https://github.com/fallenbagel/jellyseerr/commit/50f06dabbffc693f0843584a64d1d96e77982820))
|
||||
* **ui:** hide search bar behind slideover when opened ([#3348](https://github.com/fallenbagel/jellyseerr/issues/3348)) ([b3882de](https://github.com/fallenbagel/jellyseerr/commit/b3882de8930a70adb2f93a27be6370bfa1826587))
|
||||
* **ui:** prevent title cards from flickering when quickly hovering across them ([#3349](https://github.com/fallenbagel/jellyseerr/issues/3349)) ([eb5502a](https://github.com/fallenbagel/jellyseerr/commit/eb5502a16f86e37a933f6beca0678c2d228e77d5))
|
||||
* **watchlist:** correctly load more than 20 watchlist items ([#3351](https://github.com/fallenbagel/jellyseerr/issues/3351)) ([af880a6](https://github.com/fallenbagel/jellyseerr/commit/af880a6c839794b34bddcd7e0fe56353aa48ba36))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a button in ManageSlideOver to remove the movie and the file from Radarr/Sonarr ([2e74584](https://github.com/fallenbagel/jellyseerr/commit/2e7458457e995dd3ec6dd96035fe997646cdd446))
|
||||
* availability sync rework ([#3219](https://github.com/fallenbagel/jellyseerr/issues/3219)) ([ae38183](https://github.com/fallenbagel/jellyseerr/commit/ae3818304b2f75222d1bd223ece94f829a3b42d0)), closes [#377](https://github.com/fallenbagel/jellyseerr/issues/377)
|
||||
* full title of download item on hover with tooltip ([#3296](https://github.com/fallenbagel/jellyseerr/issues/3296)) ([33e7691](https://github.com/fallenbagel/jellyseerr/commit/33e7691b94d7d369a0a1410e434850bc51e5572e))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **imageproxy:** do not set cookies to image proxy so CDNs can cache images ([#3332](https://github.com/fallenbagel/jellyseerr/issues/3332)) ([966639d](https://github.com/fallenbagel/jellyseerr/commit/966639df430d32f6bfebdb16314dc4590d21caf8))
|
||||
|
||||
## [1.4.1](https://github.com/fallenbagel/jellyseerr/compare/v1.4.0...v1.4.1) (2023-01-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass in library type when scanning recently added items ([#3287](https://github.com/fallenbagel/jellyseerr/issues/3287)) ([8942eb8](https://github.com/fallenbagel/jellyseerr/commit/8942eb8b7c4fa1d16aa2e72e8ba7120a653c9aa2))
|
||||
* **ui:** air date will use UTC for timezone ([#3297](https://github.com/fallenbagel/jellyseerr/issues/3297)) ([3e43586](https://github.com/fallenbagel/jellyseerr/commit/3e43586acc0804c3fff524509caa890a104e132b))
|
||||
* **ui:** correct range slider styling in chrome ([#3299](https://github.com/fallenbagel/jellyseerr/issues/3299)) ([d954328](https://github.com/fallenbagel/jellyseerr/commit/d9543289111d72245564d25d300a71b0ea3954ba))
|
||||
* **ui:** show 5 icons when possible on mobile menu ([#3298](https://github.com/fallenbagel/jellyseerr/issues/3298)) ([7040da1](https://github.com/fallenbagel/jellyseerr/commit/7040da1334f6d18e19a494c73caa17f7df552dfe))
|
||||
* **ui:** style range thumbs correctly for firefox ([#3294](https://github.com/fallenbagel/jellyseerr/issues/3294)) ([9d10e6a](https://github.com/fallenbagel/jellyseerr/commit/9d10e6a88c0996671f1d9d20792e1930dbc82329))
|
||||
|
||||
# [1.4.0](https://github.com/fallenbagel/jellyseerr/compare/v1.3.0...v1.4.0) (2023-01-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add bg-opacity to in-progress status badges ([#3190](https://github.com/fallenbagel/jellyseerr/issues/3190)) ([68223f4](https://github.com/fallenbagel/jellyseerr/commit/68223f4b1e98b01825516dcba39cbb2d3df31a70))
|
||||
* added download status and title to request card/item error components ([#3186](https://github.com/fallenbagel/jellyseerr/issues/3186)) ([3309f77](https://github.com/fallenbagel/jellyseerr/commit/3309f77aa4be1d70b27693531c119a8e26822518))
|
||||
* arrow icons were misplaced on mobile in slider edit ([#3260](https://github.com/fallenbagel/jellyseerr/issues/3260)) ([d328485](https://github.com/fallenbagel/jellyseerr/commit/d328485161b9cae6a70ef0713b4878207bc6015e))
|
||||
* **build:** update usage of publish snap action ([#3272](https://github.com/fallenbagel/jellyseerr/issues/3272)) ([51b05cd](https://github.com/fallenbagel/jellyseerr/commit/51b05cd8fbb5d332807d8c00b2ffb7b10c3d0179))
|
||||
* changed overflow scroll to only if necessary ([#3184](https://github.com/fallenbagel/jellyseerr/issues/3184)) ([27feeea](https://github.com/fallenbagel/jellyseerr/commit/27feeea69121336557deda1f32b65a5daa146f82))
|
||||
* convert genre/studio to string in create slider ([#3201](https://github.com/fallenbagel/jellyseerr/issues/3201)) ([93afead](https://github.com/fallenbagel/jellyseerr/commit/93afead92e497f2e5bce67a34fffdaa08d20c7f2))
|
||||
* correct checkbox position (again) for slider edits ([#3227](https://github.com/fallenbagel/jellyseerr/issues/3227)) ([3ba6df1](https://github.com/fallenbagel/jellyseerr/commit/3ba6df1a41c084c4a6a90354338047623abef521))
|
||||
* correct grid sizing for webkit on streaming services ([#3248](https://github.com/fallenbagel/jellyseerr/issues/3248)) ([6fd11cf](https://github.com/fallenbagel/jellyseerr/commit/6fd11cf4254e1a19310592bec78a6de52bc073a8))
|
||||
* correct issue detail bottom padding on mobile displays ([#3268](https://github.com/fallenbagel/jellyseerr/issues/3268)) ([3db010b](https://github.com/fallenbagel/jellyseerr/commit/3db010b9eaec62aa08d973a61caf1801471bbf3e))
|
||||
* correct link to correct keyword results for series ([#3208](https://github.com/fallenbagel/jellyseerr/issues/3208)) ([4e9be7a](https://github.com/fallenbagel/jellyseerr/commit/4e9be7a3f7304ee7be5ee6fd34b1ea8f6c0cf399))
|
||||
* correct spacing between sliders ([#3225](https://github.com/fallenbagel/jellyseerr/issues/3225)) ([62e2de7](https://github.com/fallenbagel/jellyseerr/commit/62e2de70bf37b72d5f63370b662d4103a642775b))
|
||||
* correctly check mobile menu permissions ([#3271](https://github.com/fallenbagel/jellyseerr/issues/3271)) ([f4a22dc](https://github.com/fallenbagel/jellyseerr/commit/f4a22dc437404558f301ccfc195cf0a300dd1ff2))
|
||||
* correctly restore selected streaming service filters ([#3249](https://github.com/fallenbagel/jellyseerr/issues/3249)) ([154f3e7](https://github.com/fallenbagel/jellyseerr/commit/154f3e72efbf0b663358b3029156f54516f01a2f))
|
||||
* create shared class to add bottom spacing ([#3269](https://github.com/fallenbagel/jellyseerr/issues/3269)) ([5d1c6f7](https://github.com/fallenbagel/jellyseerr/commit/5d1c6f706555613d97ed9e61d8b665543c2f239b))
|
||||
* **deps:** pin dependency @headlessui/react to 1.7.7 ([#3194](https://github.com/fallenbagel/jellyseerr/issues/3194)) [skip ci] ([c4b16ab](https://github.com/fallenbagel/jellyseerr/commit/c4b16abc62647c74215155942a4230a31a238677))
|
||||
* **deps:** update dependency @heroicons/react to v2 ([#2970](https://github.com/fallenbagel/jellyseerr/issues/2970)) ([dd48d59](https://github.com/fallenbagel/jellyseerr/commit/dd48d59b20e2d1800ea30912116f4a4f1bb7928f))
|
||||
* **deps:** update dependency axios to v1 ([#3202](https://github.com/fallenbagel/jellyseerr/issues/3202)) ([421029e](https://github.com/fallenbagel/jellyseerr/commit/421029ebab66c9a6622ba47e56d7f6473524cce4))
|
||||
* **deps:** update dependency swr to v2 ([#3212](https://github.com/fallenbagel/jellyseerr/issues/3212)) ([7b6db50](https://github.com/fallenbagel/jellyseerr/commit/7b6db50ae55b1fc60d19a5cff62dd46bb989fa51))
|
||||
* **experimental:** use new RT API (sorta) ([#3179](https://github.com/fallenbagel/jellyseerr/issues/3179)) ([357cab8](https://github.com/fallenbagel/jellyseerr/commit/357cab87ac7752b8e119b51c938b343c661d83c2))
|
||||
* improve small screen layout for discover editing ([#3221](https://github.com/fallenbagel/jellyseerr/issues/3221)) ([d23b213](https://github.com/fallenbagel/jellyseerr/commit/d23b2132de05f072f7f9daad83d81421d747cf99))
|
||||
* include new package calendar css in build ([#3235](https://github.com/fallenbagel/jellyseerr/issues/3235)) ([c2a1a20](https://github.com/fallenbagel/jellyseerr/commit/c2a1a20a3bb20039a1936c7fe0ecb9e8311a0aea))
|
||||
* issues with issues ([#3267](https://github.com/fallenbagel/jellyseerr/issues/3267)) ([fd21971](https://github.com/fallenbagel/jellyseerr/commit/fd219717c01c558814d7a80de6304272b5a7944e))
|
||||
* multiple genre filtering now works ([#3282](https://github.com/fallenbagel/jellyseerr/issues/3282)) ([5076938](https://github.com/fallenbagel/jellyseerr/commit/507693881b939819413f0959df5ef6b7a357eb5c))
|
||||
* prevent double encode if we are on /search endpoint ([#3238](https://github.com/fallenbagel/jellyseerr/issues/3238)) ([a343f8a](https://github.com/fallenbagel/jellyseerr/commit/a343f8ad915491a9c81512c7e541a1dac8906025))
|
||||
* **request:** approve request when retrying request ([#3234](https://github.com/fallenbagel/jellyseerr/issues/3234)) ([b515701](https://github.com/fallenbagel/jellyseerr/commit/b5157010c46cd9083993d5ee0172007b83d631da))
|
||||
* **request:** mark request as approved if media is already available when retrying failed request ([#3244](https://github.com/fallenbagel/jellyseerr/issues/3244)) ([cb65074](https://github.com/fallenbagel/jellyseerr/commit/cb650745f6a33e69391a633e6d272831f314e098))
|
||||
* restore border to ghost button and fix discover slider visibility toggle position ([#3226](https://github.com/fallenbagel/jellyseerr/issues/3226)) ([2eebb7f](https://github.com/fallenbagel/jellyseerr/commit/2eebb7fd3941b34fe9472aaf9d28265df8cce311))
|
||||
* restore status badges on titles on actors page when hide available media enabled ([#3206](https://github.com/fallenbagel/jellyseerr/issues/3206)) ([9d3446d](https://github.com/fallenbagel/jellyseerr/commit/9d3446d370499c3251159393e5c791b01225e05c))
|
||||
* screen would zoom on mobile if date picker input was selected ([#3241](https://github.com/fallenbagel/jellyseerr/issues/3241)) ([3aefddd](https://github.com/fallenbagel/jellyseerr/commit/3aefddd48834d86150d5f5cceb2d08af3a78847b))
|
||||
* series displayed an empty season with series list/request modal ([#3147](https://github.com/fallenbagel/jellyseerr/issues/3147)) ([2179637](https://github.com/fallenbagel/jellyseerr/commit/2179637d437999290eaa4152f6f37c71fc3d8ba3))
|
||||
* tooltip shows properly if not in progress ([#3185](https://github.com/fallenbagel/jellyseerr/issues/3185)) ([6face8c](https://github.com/fallenbagel/jellyseerr/commit/6face8cc4564b978fb98af32659b326d8c5cede8))
|
||||
* **ui:** series first air date sorting ([#3283](https://github.com/fallenbagel/jellyseerr/issues/3283)) ([374c78c](https://github.com/fallenbagel/jellyseerr/commit/374c78c989cc86bb144a954a91d5d183c4b591c0))
|
||||
* update StatusBadgeMini to shrink on title cards (and remove ring) ([#3210](https://github.com/fallenbagel/jellyseerr/issues/3210)) ([042a1a9](https://github.com/fallenbagel/jellyseerr/commit/042a1a950fdd4d4a61edf4bc19657f9b7a526da8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add discover customization ([#3182](https://github.com/fallenbagel/jellyseerr/issues/3182)) ([cd35748](https://github.com/fallenbagel/jellyseerr/commit/cd3574851a12517cbfadc109e6412a7a9e44c114))
|
||||
* add keywords to movie/series detail pages ([#3204](https://github.com/fallenbagel/jellyseerr/issues/3204)) ([e084649](https://github.com/fallenbagel/jellyseerr/commit/e084649878a58c296786141d12dd69a69a27ee85))
|
||||
* add streaming services filter ([#3247](https://github.com/fallenbagel/jellyseerr/issues/3247)) ([1154156](https://github.com/fallenbagel/jellyseerr/commit/1154156459403494e8daf0c89a3ba356aeea1d97))
|
||||
* discover inline customization ([#3220](https://github.com/fallenbagel/jellyseerr/issues/3220)) ([8bd10b5](https://github.com/fallenbagel/jellyseerr/commit/8bd10b5bf3d1b8069872b616c7c8596caeb4937e))
|
||||
* discover overhaul (filters!) ([#3232](https://github.com/fallenbagel/jellyseerr/issues/3232)) ([dd00e48](https://github.com/fallenbagel/jellyseerr/commit/dd00e48f59054b44bef6b32a2c169e59f6175051))
|
||||
* discover slider edit arrow buttons for reordering ([#3259](https://github.com/fallenbagel/jellyseerr/issues/3259)) ([da00d45](https://github.com/fallenbagel/jellyseerr/commit/da00d454e17e8b00d04f6e26f6dd5153ed6ced81))
|
||||
* **lang:** translations update from Hosted Weblate ([#3030](https://github.com/fallenbagel/jellyseerr/issues/3030)) ([0d8b390](https://github.com/fallenbagel/jellyseerr/commit/0d8b390b678731e76bd1f0f8a0a4952c11e77f4d))
|
||||
* new mobile menu ([#3251](https://github.com/fallenbagel/jellyseerr/issues/3251)) ([fcbca17](https://github.com/fallenbagel/jellyseerr/commit/fcbca1722f31f32633a57bc5048f46c9da057d87))
|
||||
* translations update from Hosted Weblate ([#3218](https://github.com/fallenbagel/jellyseerr/issues/3218)) ([5940ff7](https://github.com/fallenbagel/jellyseerr/commit/5940ff7f5f62eed9ac5aa6f02803418aaa09813a))
|
||||
* **ui:** add episode number to front of episode name in season details ([#3086](https://github.com/fallenbagel/jellyseerr/issues/3086)) ([a672b32](https://github.com/fallenbagel/jellyseerr/commit/a672b324ec391a20f6f3a1daed82a8d276a52c2c))
|
||||
* **ui:** request card progress bar ([#3123](https://github.com/fallenbagel/jellyseerr/issues/3123)) ([03853a1](https://github.com/fallenbagel/jellyseerr/commit/03853a1b9155c8a2153c8885022a74619af1bc15))
|
||||
|
||||
# [1.3.0](https://github.com/fallenbagel/jellyseerr/compare/v1.2.1...v1.3.0) (2023-01-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<a href="http://translate.jellyseerr.dev/engage/jellyseerr/"><img src="http://translate.jellyseerr.dev/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-47-orange.svg"/></a>
|
||||
<a href="#contributors-"><img alt="All Contributors" src="https://img.shields.io/badge/all_contributors-48-orange.svg"/></a>
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
**Jellyseerr** is a free and open source software application for managing requests for your media library.
|
||||
@@ -146,6 +146,7 @@ Thanks goes to these wonderful people from Overseerr ([emoji key](https://allcon
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/XDark187"><img src="https://avatars.githubusercontent.com/u/39034192?v=4?s=100" width="100px;" alt="Baraa"/><br /><sub><b>Baraa</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=XDark187" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/franciscofsales"><img src="https://avatars.githubusercontent.com/u/7977645?v=4?s=100" width="100px;" alt="Francisco Sales"/><br /><sub><b>Francisco Sales</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=franciscofsales" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/myselfolli"><img src="https://avatars.githubusercontent.com/u/37535998?v=4?s=100" width="100px;" alt="Oliver Laing"/><br /><sub><b>Oliver Laing</b></sub></a><br /><a href="https://github.com/Fallenbagel/jellyseerr/commits?author=myselfolli" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/M0NsTeRRR"><img src="https://avatars.githubusercontent.com/u/37785089?v=4?s=100" width="100px;" alt="Ludovic Ortega"/><br /><sub><b>Ludovic Ortega</b></sub></a><br /><a href="#security-M0NsTeRRR" title="Security">🛡️</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -6,6 +6,10 @@ sidebar_position: 4
|
||||
|
||||
# AUR (Arch User Repository)
|
||||
|
||||
:::note Disclaimer
|
||||
This AUR package is not maintained by us but by a third party. Please refer to the maintainer for any issues.
|
||||
:::
|
||||
|
||||
:::info
|
||||
This method is not recommended for most users. It is intended for advanced users who are using Arch Linux or an Arch-based distribution.
|
||||
:::
|
||||
|
||||
@@ -12,49 +12,12 @@ import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
### Prerequisites
|
||||
<Tabs groupId="versions" queryString>
|
||||
<TabItem value="latest" label="Latest">
|
||||
- [Node.js 18.x](https://nodejs.org/en/download/)
|
||||
- [Yarn 1.x](https://classic.yarnpkg.com/lang/en/docs/install)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="develop" label="Develop">
|
||||
- [Node.js 20.x](https://nodejs.org/en/download/)
|
||||
- [Pnpm 9.x](https://pnpm.io/installation)
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
|
||||
## Unix (Linux, macOS)
|
||||
### Installation
|
||||
<Tabs groupId="versions" queryString>
|
||||
<TabItem value="latest" label="latest">
|
||||
1. Assuming you want the working directory to be `/opt/jellyseerr`, create the directory and navigate to it:
|
||||
```bash
|
||||
sudo mkdir -p /opt/jellyseerr && cd /opt/jellyseerr
|
||||
```
|
||||
2. Clone the Jellyseerr repository and checkout the latest release:
|
||||
```bash
|
||||
git clone https://github.com/Fallenbagel/jellyseerr.git
|
||||
cd jellyseerr
|
||||
git checkout main
|
||||
```
|
||||
3. Install the dependencies:
|
||||
```bash
|
||||
CYPRESS_INSTALL_BINARY=0 yarn install --frozen-lockfile --network-timeout 1000000
|
||||
```
|
||||
4. Build the project:
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
5. Start Jellyseerr:
|
||||
```bash
|
||||
yarn start
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="develop" label="develop">
|
||||
1. Assuming you want the working directory to be `/opt/jellyseerr`, create the directory and navigate to it:
|
||||
```bash
|
||||
sudo mkdir -p /opt/jellyseerr && cd /opt/jellyseerr
|
||||
@@ -77,8 +40,6 @@ pnpm build
|
||||
```bash
|
||||
pnpm start
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::info
|
||||
You can now access Jellyseerr by visiting `http://localhost:5055` in your web browser.
|
||||
@@ -234,33 +195,6 @@ pm2 status jellyseerr
|
||||
|
||||
## Windows
|
||||
### Installation
|
||||
<Tabs groupId="versions" queryString>
|
||||
<TabItem value="latest" label="latest">
|
||||
1. Assuming you want the working directory to be `C:\jellyseerr`, create the directory and navigate to it:
|
||||
```powershell
|
||||
mkdir C:\jellyseerr
|
||||
cd C:\jellyseerr
|
||||
```
|
||||
2. Clone the Jellyseerr repository and checkout the latest release:
|
||||
```powershell
|
||||
git clone https://github.com/Fallenbagel/jellyseerr.git .
|
||||
git checkout main
|
||||
```
|
||||
3. Install the dependencies:
|
||||
```powershell
|
||||
npm install -g win-node-env
|
||||
set CYPRESS_INSTALL_BINARY=0 && yarn install --frozen-lockfile --network-timeout 1000000
|
||||
```
|
||||
4. Build the project:
|
||||
```powershell
|
||||
yarn build
|
||||
```
|
||||
5. Start Jellyseerr:
|
||||
```powershell
|
||||
yarn start
|
||||
```
|
||||
</TabItem>
|
||||
<TabItem value="develop" label="develop">
|
||||
1. Assuming you want the working directory to be `C:\jellyseerr`, create the directory and navigate to it:
|
||||
```powershell
|
||||
mkdir C:\jellyseerr
|
||||
@@ -284,8 +218,6 @@ pnpm build
|
||||
```powershell
|
||||
pnpm start
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
You can add the environment variables to a `.env` file in the Jellyseerr directory.
|
||||
@@ -313,6 +245,7 @@ node dist/index.js
|
||||
- Set the trigger to "When the computer starts"
|
||||
- Set the action to "Start a program"
|
||||
- Set the program/script to the path of the `start-jellyseerr.bat` file
|
||||
- Set the "Start in" to the jellyseerr directory.
|
||||
- Click "Finish"
|
||||
|
||||
Now, Jellyseerr will start when the computer boots up in the background.
|
||||
|
||||
@@ -1988,6 +1988,9 @@ paths:
|
||||
appDataPath:
|
||||
type: string
|
||||
example: /app/config
|
||||
appDataPermissions:
|
||||
type: boolean
|
||||
example: true
|
||||
/settings/main:
|
||||
get:
|
||||
summary: Get main settings
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jellyseerr",
|
||||
"version": "2.0.0",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
@@ -93,7 +93,8 @@
|
||||
"sqlite3": "5.1.4",
|
||||
"swagger-ui-express": "4.6.2",
|
||||
"swr": "2.2.5",
|
||||
"typeorm": "0.3.12",
|
||||
"typeorm": "0.3.11",
|
||||
"undici": "^6.20.1",
|
||||
"web-push": "3.5.0",
|
||||
"winston": "3.8.2",
|
||||
"winston-daily-rotate-file": "4.7.1",
|
||||
|
||||
65
pnpm-lock.yaml
generated
65
pnpm-lock.yaml
generated
@@ -49,7 +49,7 @@ importers:
|
||||
version: 2.11.0
|
||||
connect-typeorm:
|
||||
specifier: 1.1.4
|
||||
version: 1.1.4(typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)))
|
||||
version: 1.1.4(typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)))
|
||||
cookie-parser:
|
||||
specifier: 1.4.6
|
||||
version: 1.4.6
|
||||
@@ -192,8 +192,11 @@ importers:
|
||||
specifier: 2.2.5
|
||||
version: 2.2.5(react@18.3.1)
|
||||
typeorm:
|
||||
specifier: 0.3.12
|
||||
version: 0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))
|
||||
specifier: 0.3.11
|
||||
version: 0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))
|
||||
undici:
|
||||
specifier: ^6.20.1
|
||||
version: 6.20.1
|
||||
web-push:
|
||||
specifier: 3.5.0
|
||||
version: 3.5.0
|
||||
@@ -4264,10 +4267,6 @@ packages:
|
||||
resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
|
||||
engines: {node: '>=0.11'}
|
||||
|
||||
date-fns@2.30.0:
|
||||
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
|
||||
engines: {node: '>=0.11'}
|
||||
|
||||
dateformat@3.0.3:
|
||||
resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==}
|
||||
|
||||
@@ -5389,8 +5388,8 @@ packages:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
https-proxy-agent@7.0.4:
|
||||
resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
|
||||
https-proxy-agent@7.0.5:
|
||||
resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
human-signals@1.1.1:
|
||||
@@ -6554,11 +6553,6 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
mkdirp@2.1.6:
|
||||
resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==}
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
modify-values@1.0.1:
|
||||
resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -7730,9 +7724,6 @@ packages:
|
||||
reflect-metadata@0.1.13:
|
||||
resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==}
|
||||
|
||||
reflect-metadata@0.1.14:
|
||||
resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==}
|
||||
|
||||
reflect.getprototypeof@1.0.6:
|
||||
resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -8670,8 +8661,8 @@ packages:
|
||||
typedarray@0.0.6:
|
||||
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
|
||||
|
||||
typeorm@0.3.12:
|
||||
resolution: {integrity: sha512-sYSxBmCf1nJLLTcYtwqZ+lQIRtLPyUoO93rHTOKk9vJCyT4UfRtU7oRsJvfvKP3nnZTD1hzz2SEy2zwPEN6OyA==}
|
||||
typeorm@0.3.11:
|
||||
resolution: {integrity: sha512-pzdOyWbVuz/z8Ww6gqvBW4nylsM0KLdUCDExr2gR20/x1khGSVxQkjNV/3YqliG90jrWzrknYbYscpk8yxFJVg==}
|
||||
engines: {node: '>= 12.9.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -8682,7 +8673,7 @@ packages:
|
||||
ioredis: ^5.0.4
|
||||
mongodb: ^3.6.0
|
||||
mssql: ^7.3.0
|
||||
mysql2: ^2.2.5 || ^3.0.1
|
||||
mysql2: ^2.2.5
|
||||
oracledb: ^5.1.0
|
||||
pg: ^8.5.1
|
||||
pg-native: ^3.0.0
|
||||
@@ -8768,6 +8759,10 @@ packages:
|
||||
undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
undici@6.20.1:
|
||||
resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==}
|
||||
engines: {node: '>=18.17'}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.0:
|
||||
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -12310,7 +12305,7 @@ snapshots:
|
||||
fs-extra: 11.2.0
|
||||
globby: 11.1.0
|
||||
http-proxy-agent: 7.0.2
|
||||
https-proxy-agent: 7.0.4
|
||||
https-proxy-agent: 7.0.5
|
||||
issue-parser: 6.0.0
|
||||
lodash: 4.17.21
|
||||
mime: 3.0.0
|
||||
@@ -13824,13 +13819,13 @@ snapshots:
|
||||
ini: 1.3.8
|
||||
proto-list: 1.2.4
|
||||
|
||||
connect-typeorm@1.1.4(typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))):
|
||||
connect-typeorm@1.1.4(typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))):
|
||||
dependencies:
|
||||
'@types/debug': 0.0.31
|
||||
'@types/express-session': 1.17.6
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
express-session: 1.18.0
|
||||
typeorm: 0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))
|
||||
typeorm: 0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -14181,10 +14176,6 @@ snapshots:
|
||||
|
||||
date-fns@2.29.3: {}
|
||||
|
||||
date-fns@2.30.0:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.24.7
|
||||
|
||||
dateformat@3.0.3: {}
|
||||
|
||||
dayjs@1.11.11: {}
|
||||
@@ -15739,7 +15730,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
https-proxy-agent@7.0.4:
|
||||
https-proxy-agent@7.0.5:
|
||||
dependencies:
|
||||
agent-base: 7.1.1
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
@@ -17149,8 +17140,6 @@ snapshots:
|
||||
|
||||
mkdirp@1.0.4: {}
|
||||
|
||||
mkdirp@2.1.6: {}
|
||||
|
||||
modify-values@1.0.1: {}
|
||||
|
||||
moment@2.30.1: {}
|
||||
@@ -18372,8 +18361,6 @@ snapshots:
|
||||
|
||||
reflect-metadata@0.1.13: {}
|
||||
|
||||
reflect-metadata@0.1.14: {}
|
||||
|
||||
reflect.getprototypeof@1.0.6:
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
@@ -19431,23 +19418,23 @@ snapshots:
|
||||
|
||||
typedarray@0.0.6: {}
|
||||
|
||||
typeorm@0.3.12(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)):
|
||||
typeorm@0.3.11(sqlite3@5.1.4(encoding@0.1.13))(ts-node@10.9.1(@swc/core@1.6.5(@swc/helpers@0.5.11))(@types/node@20.14.8)(typescript@4.9.5)):
|
||||
dependencies:
|
||||
'@sqltools/formatter': 1.2.5
|
||||
app-root-path: 3.1.0
|
||||
buffer: 6.0.3
|
||||
chalk: 4.1.2
|
||||
cli-highlight: 2.1.11
|
||||
date-fns: 2.30.0
|
||||
date-fns: 2.29.3
|
||||
debug: 4.3.5(supports-color@8.1.1)
|
||||
dotenv: 16.4.5
|
||||
glob: 8.1.0
|
||||
glob: 7.2.3
|
||||
js-yaml: 4.1.0
|
||||
mkdirp: 2.1.6
|
||||
reflect-metadata: 0.1.14
|
||||
mkdirp: 1.0.4
|
||||
reflect-metadata: 0.1.13
|
||||
sha.js: 2.4.11
|
||||
tslib: 2.6.3
|
||||
uuid: 9.0.1
|
||||
uuid: 8.3.2
|
||||
xml2js: 0.4.23
|
||||
yargs: 17.7.2
|
||||
optionalDependencies:
|
||||
@@ -19486,6 +19473,8 @@ snapshots:
|
||||
|
||||
undici-types@5.26.5: {}
|
||||
|
||||
undici@6.20.1: {}
|
||||
|
||||
unicode-canonical-property-names-ecmascript@2.0.0: {}
|
||||
|
||||
unicode-emoji-utils@1.2.0:
|
||||
|
||||
@@ -180,7 +180,7 @@ class PlexAPI {
|
||||
settings.plex.libraries = [];
|
||||
}
|
||||
|
||||
settings.save();
|
||||
await settings.save();
|
||||
}
|
||||
|
||||
public async getLibraryContents(
|
||||
|
||||
@@ -182,7 +182,7 @@ class RottenTomatoes extends ExternalAPI {
|
||||
);
|
||||
}
|
||||
|
||||
if (!tvshow) {
|
||||
if (!tvshow || !tvshow.rottenTomatoes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ class Media {
|
||||
this.mediaUrl = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId}&context=home&serverId=${serverId}`;
|
||||
}
|
||||
if (this.jellyfinMediaId4k) {
|
||||
this.mediaUrl4k = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId}&context=home&serverId=${serverId}`;
|
||||
this.mediaUrl4k = `${jellyfinHost}/web/index.html#!/${pageName}?id=${this.jellyfinMediaId4k}&context=home&serverId=${serverId}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ import clearCookies from '@server/middleware/clearcookies';
|
||||
import routes from '@server/routes';
|
||||
import avatarproxy from '@server/routes/avatarproxy';
|
||||
import imageproxy from '@server/routes/imageproxy';
|
||||
import { appDataPermissions } from '@server/utils/appDataVolume';
|
||||
import { getAppVersion } from '@server/utils/appVersion';
|
||||
import createCustomProxyAgent from '@server/utils/customProxyAgent';
|
||||
import restartFlag from '@server/utils/restartFlag';
|
||||
import { getClientIp } from '@supercharge/request-ip';
|
||||
import { TypeormStore } from 'connect-typeorm/out';
|
||||
@@ -51,6 +53,12 @@ const dev = process.env.NODE_ENV !== 'production';
|
||||
const app = next({ dev });
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
if (!appDataPermissions()) {
|
||||
logger.error(
|
||||
'Something went wrong while checking config folder! Please ensure the config folder is set up properly.\nhttps://docs.jellyseerr.dev/getting-started'
|
||||
);
|
||||
}
|
||||
|
||||
app
|
||||
.prepare()
|
||||
.then(async () => {
|
||||
@@ -67,6 +75,11 @@ app
|
||||
const settings = await getSettings().load();
|
||||
restartFlag.initializeSettings(settings.main);
|
||||
|
||||
// Register HTTP proxy
|
||||
if (settings.main.proxy.enabled) {
|
||||
await createCustomProxyAgent(settings.main.proxy);
|
||||
}
|
||||
|
||||
// Migrate library types
|
||||
if (
|
||||
settings.plex.libraries.length > 1 &&
|
||||
|
||||
@@ -135,6 +135,7 @@ class ImageProxy {
|
||||
private cacheVersion;
|
||||
private key;
|
||||
private baseUrl;
|
||||
private headers: HeadersInit | null = null;
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
@@ -142,6 +143,7 @@ class ImageProxy {
|
||||
options: {
|
||||
cacheVersion?: number;
|
||||
rateLimitOptions?: RateLimitOptions;
|
||||
headers?: HeadersInit;
|
||||
} = {}
|
||||
) {
|
||||
this.cacheVersion = options.cacheVersion ?? 1;
|
||||
@@ -155,9 +157,13 @@ class ImageProxy {
|
||||
} else {
|
||||
this.fetch = fetch;
|
||||
}
|
||||
this.headers = options.headers || null;
|
||||
}
|
||||
|
||||
public async getImage(path: string): Promise<ImageResponse> {
|
||||
public async getImage(
|
||||
path: string,
|
||||
fallbackPath?: string
|
||||
): Promise<ImageResponse> {
|
||||
const cacheKey = this.getCacheKey(path);
|
||||
|
||||
const imageResponse = await this.get(cacheKey);
|
||||
@@ -166,7 +172,11 @@ class ImageProxy {
|
||||
const newImage = await this.set(path, cacheKey);
|
||||
|
||||
if (!newImage) {
|
||||
throw new Error('Failed to load image');
|
||||
if (fallbackPath) {
|
||||
return await this.getImage(fallbackPath);
|
||||
} else {
|
||||
throw new Error('Failed to load image');
|
||||
}
|
||||
}
|
||||
|
||||
return newImage;
|
||||
@@ -247,7 +257,12 @@ class ImageProxy {
|
||||
: '/'
|
||||
: '') +
|
||||
(path.startsWith('/') ? path.slice(1) : path);
|
||||
const response = await this.fetch(href);
|
||||
const response = await this.fetch(href, {
|
||||
headers: this.headers || undefined,
|
||||
});
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class PlexScanner
|
||||
});
|
||||
|
||||
settings.plex.libraries = newLibraries;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
}
|
||||
} else {
|
||||
for (const library of this.libraries) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { MediaServerType } from '@server/constants/server';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import { runMigrations } from '@server/lib/settings/migrator';
|
||||
import { randomUUID } from 'crypto';
|
||||
import fs from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import { merge } from 'lodash';
|
||||
import path from 'path';
|
||||
import webpush from 'web-push';
|
||||
@@ -99,6 +99,17 @@ interface Quota {
|
||||
quotaDays?: number;
|
||||
}
|
||||
|
||||
export interface ProxySettings {
|
||||
enabled: boolean;
|
||||
hostname: string;
|
||||
port: number;
|
||||
useSsl: boolean;
|
||||
user: string;
|
||||
password: string;
|
||||
bypassFilter: string;
|
||||
bypassLocalAddresses: boolean;
|
||||
}
|
||||
|
||||
export interface MainSettings {
|
||||
apiKey: string;
|
||||
applicationTitle: string;
|
||||
@@ -119,6 +130,7 @@ export interface MainSettings {
|
||||
mediaServerType: number;
|
||||
partialRequestsEnabled: boolean;
|
||||
locale: string;
|
||||
proxy: ProxySettings;
|
||||
}
|
||||
|
||||
interface PublicSettings {
|
||||
@@ -325,6 +337,16 @@ class Settings {
|
||||
mediaServerType: MediaServerType.NOT_CONFIGURED,
|
||||
partialRequestsEnabled: true,
|
||||
locale: 'en',
|
||||
proxy: {
|
||||
enabled: false,
|
||||
hostname: '',
|
||||
port: 8080,
|
||||
useSsl: false,
|
||||
user: '',
|
||||
password: '',
|
||||
bypassFilter: '',
|
||||
bypassLocalAddresses: true,
|
||||
},
|
||||
},
|
||||
plex: {
|
||||
name: '',
|
||||
@@ -479,10 +501,6 @@ class Settings {
|
||||
}
|
||||
|
||||
get main(): MainSettings {
|
||||
if (!this.data.main.apiKey) {
|
||||
this.data.main.apiKey = this.generateApiKey();
|
||||
this.save();
|
||||
}
|
||||
return this.data.main;
|
||||
}
|
||||
|
||||
@@ -584,29 +602,20 @@ class Settings {
|
||||
}
|
||||
|
||||
get clientId(): string {
|
||||
if (!this.data.clientId) {
|
||||
this.data.clientId = randomUUID();
|
||||
this.save();
|
||||
}
|
||||
|
||||
return this.data.clientId;
|
||||
}
|
||||
|
||||
get vapidPublic(): string {
|
||||
this.generateVapidKeys();
|
||||
|
||||
return this.data.vapidPublic;
|
||||
}
|
||||
|
||||
get vapidPrivate(): string {
|
||||
this.generateVapidKeys();
|
||||
|
||||
return this.data.vapidPrivate;
|
||||
}
|
||||
|
||||
public regenerateApiKey(): MainSettings {
|
||||
public async regenerateApiKey(): Promise<MainSettings> {
|
||||
this.main.apiKey = this.generateApiKey();
|
||||
this.save();
|
||||
await this.save();
|
||||
return this.main;
|
||||
}
|
||||
|
||||
@@ -618,15 +627,6 @@ class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
private generateVapidKeys(force = false): void {
|
||||
if (!this.data.vapidPublic || !this.data.vapidPrivate || force) {
|
||||
const vapidKeys = webpush.generateVAPIDKeys();
|
||||
this.data.vapidPrivate = vapidKeys.privateKey;
|
||||
this.data.vapidPublic = vapidKeys.publicKey;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings Load
|
||||
*
|
||||
@@ -641,30 +641,51 @@ class Settings {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(SETTINGS_PATH)) {
|
||||
this.save();
|
||||
let data;
|
||||
try {
|
||||
data = await fs.readFile(SETTINGS_PATH, 'utf-8');
|
||||
} catch {
|
||||
await this.save();
|
||||
}
|
||||
const data = fs.readFileSync(SETTINGS_PATH, 'utf-8');
|
||||
|
||||
if (data) {
|
||||
const parsedJson = JSON.parse(data);
|
||||
this.data = await runMigrations(parsedJson);
|
||||
|
||||
this.data = merge(this.data, parsedJson);
|
||||
|
||||
if (process.env.API_KEY) {
|
||||
if (this.main.apiKey != process.env.API_KEY) {
|
||||
this.main.apiKey = process.env.API_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
this.save();
|
||||
const migratedData = await runMigrations(parsedJson, SETTINGS_PATH);
|
||||
this.data = merge(this.data, migratedData);
|
||||
}
|
||||
|
||||
// generate keys and ids if it's missing
|
||||
let change = false;
|
||||
if (!this.data.main.apiKey) {
|
||||
this.data.main.apiKey = this.generateApiKey();
|
||||
change = true;
|
||||
} else if (process.env.API_KEY) {
|
||||
if (this.main.apiKey != process.env.API_KEY) {
|
||||
this.main.apiKey = process.env.API_KEY;
|
||||
}
|
||||
}
|
||||
if (!this.data.clientId) {
|
||||
this.data.clientId = randomUUID();
|
||||
change = true;
|
||||
}
|
||||
if (!this.data.vapidPublic || !this.data.vapidPrivate) {
|
||||
const vapidKeys = webpush.generateVAPIDKeys();
|
||||
this.data.vapidPrivate = vapidKeys.privateKey;
|
||||
this.data.vapidPublic = vapidKeys.publicKey;
|
||||
change = true;
|
||||
}
|
||||
if (change) {
|
||||
await this.save();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(this.data, undefined, ' '));
|
||||
public async save(): Promise<void> {
|
||||
await fs.writeFile(
|
||||
SETTINGS_PATH,
|
||||
JSON.stringify(this.data, undefined, ' ')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import type { AllSettings } from '@server/lib/settings';
|
||||
|
||||
const migrateHostname = (settings: any): AllSettings => {
|
||||
const oldJellyfinSettings = settings.jellyfin;
|
||||
if (oldJellyfinSettings && oldJellyfinSettings.hostname) {
|
||||
const { hostname } = oldJellyfinSettings;
|
||||
if (settings.jellyfin?.hostname) {
|
||||
const { hostname } = settings.jellyfin;
|
||||
const protocolMatch = hostname.match(/^(https?):\/\//i);
|
||||
const useSsl = protocolMatch && protocolMatch[1].toLowerCase() === 'https';
|
||||
const remainingUrl = hostname.replace(/^(https?):\/\//i, '');
|
||||
const urlMatch = remainingUrl.match(/^([^:]+)(:([0-9]+))?(\/.*)?$/);
|
||||
|
||||
delete oldJellyfinSettings.hostname;
|
||||
delete settings.jellyfin.hostname;
|
||||
if (urlMatch) {
|
||||
const [, ip, , port, urlBase] = urlMatch;
|
||||
settings.jellyfin = {
|
||||
@@ -21,9 +20,7 @@ const migrateHostname = (settings: any): AllSettings => {
|
||||
};
|
||||
}
|
||||
}
|
||||
if (settings.jellyfin && settings.jellyfin.hostname) {
|
||||
delete settings.jellyfin.hostname;
|
||||
}
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,30 +1,95 @@
|
||||
/* eslint-disable no-console */
|
||||
import type { AllSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import fs from 'fs';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
const migrationsDir = path.join(__dirname, 'migrations');
|
||||
|
||||
export const runMigrations = async (
|
||||
settings: AllSettings
|
||||
settings: AllSettings,
|
||||
SETTINGS_PATH: string
|
||||
): Promise<AllSettings> => {
|
||||
const migrations = fs
|
||||
.readdirSync(migrationsDir)
|
||||
.filter((file) => file.endsWith('.js') || file.endsWith('.ts'))
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
.map((file) => require(path.join(migrationsDir, file)).default);
|
||||
|
||||
let migrated = settings;
|
||||
|
||||
try {
|
||||
// we read old backup and create a backup of currents settings
|
||||
const BACKUP_PATH = SETTINGS_PATH.replace('.json', '.old.json');
|
||||
let oldBackup: string | null = null;
|
||||
try {
|
||||
oldBackup = await fs.readFile(BACKUP_PATH, 'utf-8');
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
await fs.writeFile(BACKUP_PATH, JSON.stringify(settings, undefined, ' '));
|
||||
|
||||
const migrations = (await fs.readdir(migrationsDir)).filter(
|
||||
(file) => file.endsWith('.js') || file.endsWith('.ts')
|
||||
);
|
||||
|
||||
const settingsBefore = JSON.stringify(migrated);
|
||||
|
||||
for (const migration of migrations) {
|
||||
migrated = await migration(migrated);
|
||||
try {
|
||||
logger.debug(`Checking migration '${migration}'...`, {
|
||||
label: 'Settings Migrator',
|
||||
});
|
||||
const { default: migrationFn } = await import(
|
||||
path.join(migrationsDir, migration)
|
||||
);
|
||||
const newSettings = await migrationFn(structuredClone(migrated));
|
||||
if (JSON.stringify(migrated) !== JSON.stringify(newSettings)) {
|
||||
logger.debug(`Migration '${migration}' has been applied.`, {
|
||||
label: 'Settings Migrator',
|
||||
});
|
||||
}
|
||||
migrated = newSettings;
|
||||
} catch (e) {
|
||||
logger.error(`Error while running migration '${migration}'`, {
|
||||
label: 'Settings Migrator',
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const settingsAfter = JSON.stringify(migrated);
|
||||
|
||||
if (settingsBefore !== settingsAfter) {
|
||||
// a migration occured
|
||||
// we check that the new config will be saved
|
||||
await fs.writeFile(
|
||||
SETTINGS_PATH,
|
||||
JSON.stringify(migrated, undefined, ' ')
|
||||
);
|
||||
const fileSaved = JSON.parse(await fs.readFile(SETTINGS_PATH, 'utf-8'));
|
||||
if (JSON.stringify(fileSaved) !== settingsAfter) {
|
||||
// something went wrong while saving file
|
||||
throw new Error('Unable to save settings after migration.');
|
||||
}
|
||||
} else if (oldBackup) {
|
||||
// no migration occured
|
||||
// we save the old backup (to avoid settings.json and settings.old.json being the same)
|
||||
await fs.writeFile(BACKUP_PATH, oldBackup.toString());
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Something went wrong while running settings migrations: ${e.message}`,
|
||||
{ label: 'Settings Migrator' }
|
||||
);
|
||||
// we stop jellyseerr if the migration failed
|
||||
console.log(
|
||||
'===================================================================='
|
||||
);
|
||||
console.log(
|
||||
' SOMETHING WENT WRONG WHILE RUNNING SETTINGS MIGRATIONS '
|
||||
);
|
||||
console.log(
|
||||
' Please check that your configuration folder is properly set up '
|
||||
);
|
||||
console.log(
|
||||
'===================================================================='
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
return migrated;
|
||||
|
||||
@@ -6,7 +6,6 @@ import { UserType } from '@server/constants/user';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import { User } from '@server/entity/User';
|
||||
import { startJobs } from '@server/job/schedule';
|
||||
import ImageProxy from '@server/lib/imageproxy';
|
||||
import { Permission } from '@server/lib/permissions';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
@@ -15,7 +14,6 @@ import { ApiError } from '@server/types/error';
|
||||
import { getHostname } from '@server/utils/getHostname';
|
||||
import * as EmailValidator from 'email-validator';
|
||||
import { Router } from 'express';
|
||||
import gravatarUrl from 'gravatar-url';
|
||||
import net from 'net';
|
||||
|
||||
const authRoutes = Router();
|
||||
@@ -89,7 +87,7 @@ authRoutes.post('/plex', async (req, res, next) => {
|
||||
});
|
||||
|
||||
settings.main.mediaServerType = MediaServerType.PLEX;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
startJobs();
|
||||
|
||||
await userRepository.save(user);
|
||||
@@ -262,8 +260,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
urlBase: body.urlBase,
|
||||
});
|
||||
|
||||
const { externalHostname } = getSettings().jellyfin;
|
||||
|
||||
// Try to find deviceId that corresponds to jellyfin user, else generate a new one
|
||||
let user = await userRepository.findOne({
|
||||
where: { jellyfinUsername: body.username },
|
||||
@@ -281,11 +277,6 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
// First we need to attempt to log the user in to jellyfin
|
||||
const jellyfinserver = new JellyfinAPI(hostname ?? '', undefined, deviceId);
|
||||
|
||||
const jellyfinHost =
|
||||
externalHostname && externalHostname.length > 0
|
||||
? externalHostname
|
||||
: hostname;
|
||||
|
||||
const ip = req.ip;
|
||||
let clientIp;
|
||||
|
||||
@@ -335,12 +326,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
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 || account.User.Name, {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
avatar: `/avatarproxy/${account.User.Id}`,
|
||||
userType: UserType.EMBY,
|
||||
});
|
||||
|
||||
@@ -354,12 +340,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
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 || account.User.Name, {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
avatar: `/avatarproxy/${account.User.Id}`,
|
||||
userType: UserType.JELLYFIN,
|
||||
});
|
||||
|
||||
@@ -385,7 +366,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
settings.jellyfin.urlBase = body.urlBase ?? '';
|
||||
settings.jellyfin.useSsl = body.useSsl ?? false;
|
||||
settings.jellyfin.apiKey = apiKey;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
startJobs();
|
||||
|
||||
await userRepository.save(user);
|
||||
@@ -408,27 +389,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
jellyfinUsername: account.User.Name,
|
||||
}
|
||||
);
|
||||
// Update the users avatar with their jellyfin profile pic (incase it changed)
|
||||
if (account.User.PrimaryImageTag) {
|
||||
const avatar = `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`;
|
||||
if (avatar !== user.avatar) {
|
||||
const avatarProxy = new ImageProxy('avatar', '');
|
||||
avatarProxy.clearCachedImage(user.avatar);
|
||||
}
|
||||
user.avatar = avatar;
|
||||
} else {
|
||||
const avatar = gravatarUrl(user.email || account.User.Name, {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
});
|
||||
|
||||
if (avatar !== user.avatar) {
|
||||
const avatarProxy = new ImageProxy('avatar', '');
|
||||
avatarProxy.clearCachedImage(user.avatar);
|
||||
}
|
||||
|
||||
user.avatar = avatar;
|
||||
}
|
||||
user.avatar = `/avatarproxy/${account.User.Id}`;
|
||||
user.jellyfinUsername = account.User.Name;
|
||||
|
||||
if (user.username === account.User.Name) {
|
||||
@@ -466,12 +427,7 @@ authRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
jellyfinUserId: account.User.Id,
|
||||
jellyfinDeviceId: deviceId,
|
||||
permissions: settings.main.defaultPermissions,
|
||||
avatar: account.User.PrimaryImageTag
|
||||
? `${jellyfinHost}/Users/${account.User.Id}/Images/Primary/?tag=${account.User.PrimaryImageTag}&quality=90`
|
||||
: gravatarUrl(body.email || account.User.Name, {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
avatar: `/avatarproxy/${account.User.Id}`,
|
||||
userType:
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? UserType.JELLYFIN
|
||||
|
||||
@@ -1,16 +1,71 @@
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import { getRepository } from '@server/datasource';
|
||||
import { User } from '@server/entity/User';
|
||||
import ImageProxy from '@server/lib/imageproxy';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { getAppVersion } from '@server/utils/appVersion';
|
||||
import { getHostname } from '@server/utils/getHostname';
|
||||
import { Router } from 'express';
|
||||
import gravatarUrl from 'gravatar-url';
|
||||
|
||||
const router = Router();
|
||||
|
||||
const avatarImageProxy = new ImageProxy('avatar', '');
|
||||
// Proxy avatar images
|
||||
router.get('/*', async (req, res) => {
|
||||
const imagePath = req.url.startsWith('/') ? req.url.slice(1) : req.url;
|
||||
let _avatarImageProxy: ImageProxy | null = null;
|
||||
async function initAvatarImageProxy() {
|
||||
if (!_avatarImageProxy) {
|
||||
const userRepository = getRepository(User);
|
||||
const admin = await userRepository.findOne({
|
||||
where: { id: 1 },
|
||||
select: ['id', 'jellyfinUserId', 'jellyfinDeviceId'],
|
||||
order: { id: 'ASC' },
|
||||
});
|
||||
const deviceId = admin?.jellyfinDeviceId;
|
||||
const authToken = getSettings().jellyfin.apiKey;
|
||||
_avatarImageProxy = new ImageProxy('avatar', '', {
|
||||
headers: {
|
||||
'X-Emby-Authorization': `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="${getAppVersion()}", Token="${authToken}"`,
|
||||
},
|
||||
});
|
||||
}
|
||||
return _avatarImageProxy;
|
||||
}
|
||||
|
||||
router.get('/:jellyfinUserId', async (req, res) => {
|
||||
try {
|
||||
const imageData = await avatarImageProxy.getImage(imagePath);
|
||||
if (!req.params.jellyfinUserId.match(/^[a-f0-9]{32}$/)) {
|
||||
const mediaServerType = getSettings().main.mediaServerType;
|
||||
throw new Error(
|
||||
`Provided URL is not ${
|
||||
mediaServerType === MediaServerType.JELLYFIN
|
||||
? 'a Jellyfin'
|
||||
: 'an Emby'
|
||||
} avatar.`
|
||||
);
|
||||
}
|
||||
|
||||
const avatarImageCache = await initAvatarImageProxy();
|
||||
|
||||
const user = await getRepository(User).findOne({
|
||||
where: { jellyfinUserId: req.params.jellyfinUserId },
|
||||
});
|
||||
|
||||
const fallbackUrl = gravatarUrl(user?.email || 'none', {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
});
|
||||
const jellyfinAvatarUrl = `${getHostname()}/UserImage?UserId=${
|
||||
req.params.jellyfinUserId
|
||||
}`;
|
||||
let imageData = await avatarImageCache.getImage(
|
||||
jellyfinAvatarUrl,
|
||||
fallbackUrl
|
||||
);
|
||||
|
||||
if (imageData.meta.extension === 'json') {
|
||||
// this is a 404
|
||||
imageData = await avatarImageCache.getImage(fallbackUrl);
|
||||
}
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': `image/${imageData.meta.extension}`,
|
||||
@@ -23,7 +78,6 @@ router.get('/*', async (req, res) => {
|
||||
res.end(imageData.imageBuffer);
|
||||
} catch (e) {
|
||||
logger.error('Failed to proxy avatar image', {
|
||||
imagePath,
|
||||
errorMessage: e.message,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ import { mapProductionCompany } from '@server/models/Movie';
|
||||
import { mapNetwork } from '@server/models/Tv';
|
||||
import settingsRoutes from '@server/routes/settings';
|
||||
import watchlistRoutes from '@server/routes/watchlist';
|
||||
import { appDataPath, appDataStatus } from '@server/utils/appDataVolume';
|
||||
import {
|
||||
appDataPath,
|
||||
appDataPermissions,
|
||||
appDataStatus,
|
||||
} from '@server/utils/appDataVolume';
|
||||
import { getAppVersion, getCommitTag } from '@server/utils/appVersion';
|
||||
import restartFlag from '@server/utils/restartFlag';
|
||||
import { isPerson } from '@server/utils/typeHelpers';
|
||||
@@ -93,6 +97,7 @@ router.get('/status/appdata', (_req, res) => {
|
||||
return res.status(200).json({
|
||||
appData: appDataStatus(),
|
||||
appDataPath: appDataPath(),
|
||||
appDataPermissions: appDataPermissions(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -123,9 +123,13 @@ serviceRoutes.get<{ sonarrId: string }>(
|
||||
});
|
||||
|
||||
try {
|
||||
const systemStatus = await sonarr.getSystemStatus();
|
||||
const sonarrMajorVersion = Number(systemStatus.version.split('.')[0]);
|
||||
|
||||
const profiles = await sonarr.getProfiles();
|
||||
const rootFolders = await sonarr.getRootFolders();
|
||||
const languageProfiles = await sonarr.getLanguageProfiles();
|
||||
const languageProfiles =
|
||||
sonarrMajorVersion <= 3 ? await sonarr.getLanguageProfiles() : null;
|
||||
const tags = await sonarr.getTags();
|
||||
|
||||
return res.status(200).json({
|
||||
|
||||
@@ -32,7 +32,6 @@ import { getHostname } from '@server/utils/getHostname';
|
||||
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';
|
||||
@@ -70,19 +69,19 @@ settingsRoutes.get('/main', (req, res, next) => {
|
||||
res.status(200).json(filteredMainSettings(req.user, settings.main));
|
||||
});
|
||||
|
||||
settingsRoutes.post('/main', (req, res) => {
|
||||
settingsRoutes.post('/main', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.main = merge(settings.main, req.body);
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(settings.main);
|
||||
});
|
||||
|
||||
settingsRoutes.post('/main/regenerate', (req, res, next) => {
|
||||
settingsRoutes.post('/main/regenerate', async (req, res, next) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const main = settings.regenerateApiKey();
|
||||
const main = await settings.regenerateApiKey();
|
||||
|
||||
if (!req.user) {
|
||||
return next({ status: 500, message: 'User missing from request.' });
|
||||
@@ -119,7 +118,7 @@ settingsRoutes.post('/plex', async (req, res, next) => {
|
||||
settings.plex.machineId = result.MediaContainer.machineIdentifier;
|
||||
settings.plex.name = result.MediaContainer.friendlyName;
|
||||
|
||||
settings.save();
|
||||
await settings.save();
|
||||
} catch (e) {
|
||||
logger.error('Something went wrong testing Plex connection', {
|
||||
label: 'API',
|
||||
@@ -232,7 +231,7 @@ settingsRoutes.get('/plex/library', async (req, res) => {
|
||||
...library,
|
||||
enabled: enabledLibraries.includes(library.id),
|
||||
}));
|
||||
settings.save();
|
||||
await settings.save();
|
||||
return res.status(200).json(settings.plex.libraries);
|
||||
});
|
||||
|
||||
@@ -283,7 +282,7 @@ settingsRoutes.post('/jellyfin', async (req, res, next) => {
|
||||
Object.assign(settings.jellyfin, req.body);
|
||||
settings.jellyfin.serverId = result.Id;
|
||||
settings.jellyfin.name = result.ServerName;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
} catch (e) {
|
||||
if (e instanceof ApiError) {
|
||||
logger.error('Something went wrong testing Jellyfin connection', {
|
||||
@@ -371,17 +370,12 @@ settingsRoutes.get('/jellyfin/library', async (req, res, next) => {
|
||||
...library,
|
||||
enabled: enabledLibraries.includes(library.id),
|
||||
}));
|
||||
settings.save();
|
||||
await settings.save();
|
||||
return res.status(200).json(settings.jellyfin.libraries);
|
||||
});
|
||||
|
||||
settingsRoutes.get('/jellyfin/users', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
const { externalHostname } = settings.jellyfin;
|
||||
const jellyfinHost =
|
||||
externalHostname && externalHostname.length > 0
|
||||
? externalHostname
|
||||
: getHostname();
|
||||
|
||||
const userRepository = getRepository(User);
|
||||
const admin = await userRepository.findOneOrFail({
|
||||
@@ -400,9 +394,7 @@ settingsRoutes.get('/jellyfin/users', async (req, res) => {
|
||||
const users = resp.users.map((user) => ({
|
||||
username: user.Name,
|
||||
id: user.Id,
|
||||
thumb: user.PrimaryImageTag
|
||||
? `${jellyfinHost}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}&quality=90`
|
||||
: gravatarUrl(user.Name, { default: 'mm', size: 200 }),
|
||||
thumb: `/avatarproxy/${user.Id}`,
|
||||
email: user.Name,
|
||||
}));
|
||||
|
||||
@@ -442,7 +434,7 @@ settingsRoutes.post('/tautulli', async (req, res, next) => {
|
||||
throw new Error('Tautulli version not supported');
|
||||
}
|
||||
|
||||
settings.save();
|
||||
await settings.save();
|
||||
} catch (e) {
|
||||
logger.error('Something went wrong testing Tautulli connection', {
|
||||
label: 'API',
|
||||
@@ -703,7 +695,7 @@ settingsRoutes.post<{ jobId: JobId }>(
|
||||
|
||||
settingsRoutes.post<{ jobId: JobId }>(
|
||||
'/jobs/:jobId/schedule',
|
||||
(req, res, next) => {
|
||||
async (req, res, next) => {
|
||||
const scheduledJob = scheduledJobs.find(
|
||||
(job) => job.id === req.params.jobId
|
||||
);
|
||||
@@ -717,7 +709,7 @@ settingsRoutes.post<{ jobId: JobId }>(
|
||||
|
||||
if (result) {
|
||||
settings.jobs[scheduledJob.id].schedule = req.body.schedule;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
scheduledJob.cronSchedule = req.body.schedule;
|
||||
|
||||
@@ -774,11 +766,11 @@ settingsRoutes.post<{ cacheId: AvailableCacheIds }>(
|
||||
settingsRoutes.post(
|
||||
'/initialize',
|
||||
isAuthenticated(Permission.ADMIN),
|
||||
(_req, res) => {
|
||||
async (_req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.public.initialized = true;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(settings.public);
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ notificationRoutes.get('/discord', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.discord);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/discord', (req, res) => {
|
||||
notificationRoutes.post('/discord', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.discord = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.discord);
|
||||
});
|
||||
@@ -65,11 +65,11 @@ notificationRoutes.get('/slack', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.slack);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/slack', (req, res) => {
|
||||
notificationRoutes.post('/slack', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.slack = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.slack);
|
||||
});
|
||||
@@ -99,11 +99,11 @@ notificationRoutes.get('/telegram', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.telegram);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/telegram', (req, res) => {
|
||||
notificationRoutes.post('/telegram', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.telegram = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.telegram);
|
||||
});
|
||||
@@ -133,11 +133,11 @@ notificationRoutes.get('/pushbullet', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.pushbullet);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/pushbullet', (req, res) => {
|
||||
notificationRoutes.post('/pushbullet', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.pushbullet = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.pushbullet);
|
||||
});
|
||||
@@ -167,11 +167,11 @@ notificationRoutes.get('/pushover', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.pushover);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/pushover', (req, res) => {
|
||||
notificationRoutes.post('/pushover', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.pushover = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.pushover);
|
||||
});
|
||||
@@ -201,11 +201,11 @@ notificationRoutes.get('/email', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.email);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/email', (req, res) => {
|
||||
notificationRoutes.post('/email', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.email = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.email);
|
||||
});
|
||||
@@ -235,11 +235,11 @@ notificationRoutes.get('/webpush', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.webpush);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/webpush', (req, res) => {
|
||||
notificationRoutes.post('/webpush', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.webpush = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.webpush);
|
||||
});
|
||||
@@ -284,7 +284,7 @@ notificationRoutes.get('/webhook', (_req, res) => {
|
||||
res.status(200).json(response);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/webhook', (req, res, next) => {
|
||||
notificationRoutes.post('/webhook', async (req, res, next) => {
|
||||
const settings = getSettings();
|
||||
try {
|
||||
JSON.parse(req.body.options.jsonPayload);
|
||||
@@ -300,7 +300,7 @@ notificationRoutes.post('/webhook', (req, res, next) => {
|
||||
authHeader: req.body.options.authHeader,
|
||||
},
|
||||
};
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.webhook);
|
||||
} catch (e) {
|
||||
@@ -351,11 +351,11 @@ notificationRoutes.get('/lunasea', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.lunasea);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/lunasea', (req, res) => {
|
||||
notificationRoutes.post('/lunasea', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.lunasea = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.lunasea);
|
||||
});
|
||||
@@ -385,11 +385,11 @@ notificationRoutes.get('/gotify', (_req, res) => {
|
||||
res.status(200).json(settings.notifications.agents.gotify);
|
||||
});
|
||||
|
||||
notificationRoutes.post('/gotify', (req, res) => {
|
||||
notificationRoutes.post('/gotify', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
settings.notifications.agents.gotify = req.body;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json(settings.notifications.agents.gotify);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ radarrRoutes.get('/', (_req, res) => {
|
||||
res.status(200).json(settings.radarr);
|
||||
});
|
||||
|
||||
radarrRoutes.post('/', (req, res) => {
|
||||
radarrRoutes.post('/', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const newRadarr = req.body as RadarrSettings;
|
||||
@@ -31,7 +31,7 @@ radarrRoutes.post('/', (req, res) => {
|
||||
}
|
||||
|
||||
settings.radarr = [...settings.radarr, newRadarr];
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
return res.status(201).json(newRadarr);
|
||||
});
|
||||
@@ -76,7 +76,7 @@ radarrRoutes.post<
|
||||
|
||||
radarrRoutes.put<{ id: string }, RadarrSettings, RadarrSettings>(
|
||||
'/:id',
|
||||
(req, res, next) => {
|
||||
async (req, res, next) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const radarrIndex = settings.radarr.findIndex(
|
||||
@@ -102,7 +102,7 @@ radarrRoutes.put<{ id: string }, RadarrSettings, RadarrSettings>(
|
||||
...req.body,
|
||||
id: Number(req.params.id),
|
||||
} as RadarrSettings;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(settings.radarr[radarrIndex]);
|
||||
}
|
||||
@@ -134,7 +134,7 @@ radarrRoutes.get<{ id: string }>('/:id/profiles', async (req, res, next) => {
|
||||
);
|
||||
});
|
||||
|
||||
radarrRoutes.delete<{ id: string }>('/:id', (req, res, next) => {
|
||||
radarrRoutes.delete<{ id: string }>('/:id', async (req, res, next) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const radarrIndex = settings.radarr.findIndex(
|
||||
@@ -146,7 +146,7 @@ radarrRoutes.delete<{ id: string }>('/:id', (req, res, next) => {
|
||||
}
|
||||
|
||||
const removed = settings.radarr.splice(radarrIndex, 1);
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(removed[0]);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@ sonarrRoutes.get('/', (_req, res) => {
|
||||
res.status(200).json(settings.sonarr);
|
||||
});
|
||||
|
||||
sonarrRoutes.post('/', (req, res) => {
|
||||
sonarrRoutes.post('/', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const newSonarr = req.body as SonarrSettings;
|
||||
@@ -31,7 +31,7 @@ sonarrRoutes.post('/', (req, res) => {
|
||||
}
|
||||
|
||||
settings.sonarr = [...settings.sonarr, newSonarr];
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
return res.status(201).json(newSonarr);
|
||||
});
|
||||
@@ -43,13 +43,14 @@ sonarrRoutes.post('/test', async (req, res, next) => {
|
||||
url: SonarrAPI.buildUrl(req.body, '/api/v3'),
|
||||
});
|
||||
|
||||
const urlBase = await sonarr
|
||||
.getSystemStatus()
|
||||
.then((value) => value.urlBase)
|
||||
.catch(() => req.body.baseUrl);
|
||||
const systemStatus = await sonarr.getSystemStatus();
|
||||
const sonarrMajorVersion = Number(systemStatus.version.split('.')[0]);
|
||||
|
||||
const urlBase = systemStatus.urlBase;
|
||||
const profiles = await sonarr.getProfiles();
|
||||
const folders = await sonarr.getRootFolders();
|
||||
const languageProfiles = await sonarr.getLanguageProfiles();
|
||||
const languageProfiles =
|
||||
sonarrMajorVersion <= 3 ? await sonarr.getLanguageProfiles() : null;
|
||||
const tags = await sonarr.getTags();
|
||||
|
||||
return res.status(200).json({
|
||||
@@ -72,7 +73,7 @@ sonarrRoutes.post('/test', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
sonarrRoutes.put<{ id: string }>('/:id', (req, res) => {
|
||||
sonarrRoutes.put<{ id: string }>('/:id', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const sonarrIndex = settings.sonarr.findIndex(
|
||||
@@ -100,12 +101,12 @@ sonarrRoutes.put<{ id: string }>('/:id', (req, res) => {
|
||||
...req.body,
|
||||
id: Number(req.params.id),
|
||||
} as SonarrSettings;
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(settings.sonarr[sonarrIndex]);
|
||||
});
|
||||
|
||||
sonarrRoutes.delete<{ id: string }>('/:id', (req, res) => {
|
||||
sonarrRoutes.delete<{ id: string }>('/:id', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const sonarrIndex = settings.sonarr.findIndex(
|
||||
@@ -119,7 +120,7 @@ sonarrRoutes.delete<{ id: string }>('/:id', (req, res) => {
|
||||
}
|
||||
|
||||
const removed = settings.sonarr.splice(sonarrIndex, 1);
|
||||
settings.save();
|
||||
await settings.save();
|
||||
|
||||
return res.status(200).json(removed[0]);
|
||||
});
|
||||
|
||||
@@ -516,12 +516,6 @@ router.post(
|
||||
|
||||
//const jellyfinUsersResponse = await jellyfinClient.getUsers();
|
||||
const createdUsers: User[] = [];
|
||||
const { externalHostname } = getSettings().jellyfin;
|
||||
|
||||
const jellyfinHost =
|
||||
externalHostname && externalHostname.length > 0
|
||||
? externalHostname
|
||||
: hostname;
|
||||
|
||||
jellyfinClient.setUserId(admin.jellyfinUserId ?? '');
|
||||
const jellyfinUsers = await jellyfinClient.getUsers();
|
||||
@@ -545,12 +539,7 @@ router.post(
|
||||
).toString('base64'),
|
||||
email: jellyfinUser?.Name,
|
||||
permissions: settings.main.defaultPermissions,
|
||||
avatar: jellyfinUser?.PrimaryImageTag
|
||||
? `${jellyfinHost}/Users/${jellyfinUser.Id}/Images/Primary/?tag=${jellyfinUser.PrimaryImageTag}&quality=90`
|
||||
: gravatarUrl(jellyfinUser?.Name ?? '', {
|
||||
default: 'mm',
|
||||
size: 200,
|
||||
}),
|
||||
avatar: `/avatarproxy/${jellyfinUser?.Id}`,
|
||||
userType:
|
||||
settings.main.mediaServerType === MediaServerType.JELLYFIN
|
||||
? UserType.JELLYFIN
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync } from 'fs';
|
||||
import { accessSync, existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const CONFIG_PATH = process.env.CONFIG_DIRECTORY
|
||||
@@ -14,3 +14,12 @@ export const appDataStatus = (): boolean => {
|
||||
export const appDataPath = (): string => {
|
||||
return CONFIG_PATH;
|
||||
};
|
||||
|
||||
export const appDataPermissions = (): boolean => {
|
||||
try {
|
||||
accessSync(CONFIG_PATH);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
111
server/utils/customProxyAgent.ts
Normal file
111
server/utils/customProxyAgent.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { ProxySettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import type { Dispatcher } from 'undici';
|
||||
import { Agent, ProxyAgent, setGlobalDispatcher } from 'undici';
|
||||
|
||||
export default async function createCustomProxyAgent(
|
||||
proxySettings: ProxySettings
|
||||
) {
|
||||
const defaultAgent = new Agent();
|
||||
|
||||
const skipUrl = (url: string) => {
|
||||
const hostname = new URL(url).hostname;
|
||||
|
||||
if (proxySettings.bypassLocalAddresses && isLocalAddress(hostname)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const address of proxySettings.bypassFilter.split(',')) {
|
||||
const trimmedAddress = address.trim();
|
||||
if (!trimmedAddress) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmedAddress.startsWith('*')) {
|
||||
const domain = trimmedAddress.slice(1);
|
||||
if (hostname.endsWith(domain)) {
|
||||
return true;
|
||||
}
|
||||
} else if (hostname === trimmedAddress) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const noProxyInterceptor = (
|
||||
dispatch: Dispatcher['dispatch']
|
||||
): Dispatcher['dispatch'] => {
|
||||
return (opts, handler) => {
|
||||
const url = opts.origin?.toString();
|
||||
return url && skipUrl(url)
|
||||
? defaultAgent.dispatch(opts, handler)
|
||||
: dispatch(opts, handler);
|
||||
};
|
||||
};
|
||||
|
||||
const token =
|
||||
proxySettings.user && proxySettings.password
|
||||
? `Basic ${Buffer.from(
|
||||
`${proxySettings.user}:${proxySettings.password}`
|
||||
).toString('base64')}`
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const proxyAgent = new ProxyAgent({
|
||||
uri:
|
||||
(proxySettings.useSsl ? 'https://' : 'http://') +
|
||||
proxySettings.hostname +
|
||||
':' +
|
||||
proxySettings.port,
|
||||
token,
|
||||
interceptors: {
|
||||
Client: [noProxyInterceptor],
|
||||
},
|
||||
});
|
||||
|
||||
setGlobalDispatcher(proxyAgent);
|
||||
} catch (e) {
|
||||
logger.error('Failed to connect to the proxy: ' + e.message, {
|
||||
label: 'Proxy',
|
||||
});
|
||||
setGlobalDispatcher(defaultAgent);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('https://www.google.com', { method: 'HEAD' });
|
||||
if (res.ok) {
|
||||
logger.debug('HTTP(S) proxy connected successfully', { label: 'Proxy' });
|
||||
} else {
|
||||
logger.error('Proxy responded, but with a non-OK status: ' + res.status, {
|
||||
label: 'Proxy',
|
||||
});
|
||||
setGlobalDispatcher(defaultAgent);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Failed to connect to the proxy: ' + e.message + ': ' + e.cause,
|
||||
{ label: 'Proxy' }
|
||||
);
|
||||
setGlobalDispatcher(defaultAgent);
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalAddress(hostname: string) {
|
||||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const privateIpRanges = [
|
||||
/^10\./, // 10.x.x.x
|
||||
/^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.x.x - 172.31.x.x
|
||||
/^192\.168\./, // 192.168.x.x
|
||||
];
|
||||
if (privateIpRanges.some((regex) => regex.test(hostname))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -13,7 +13,8 @@ class RestartFlag {
|
||||
|
||||
return (
|
||||
this.settings.csrfProtection !== settings.csrfProtection ||
|
||||
this.settings.trustProxy !== settings.trustProxy
|
||||
this.settings.trustProxy !== settings.trustProxy ||
|
||||
this.settings.proxy.enabled !== settings.proxy.enabled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +268,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
|
||||
{title && title.backdropPath && (
|
||||
<div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
@@ -293,6 +294,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
|
||||
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
title?.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
@@ -355,6 +357,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => {
|
||||
<Link href={`/users/${item.user.id}`}>
|
||||
<span className="group flex items-center truncate">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={item.user.avatar}
|
||||
alt=""
|
||||
className="avatar-sm ml-1.5"
|
||||
|
||||
@@ -198,6 +198,7 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
{data.backdropPath && (
|
||||
<div className="media-page-bg-image">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
alt=""
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
@@ -228,6 +229,7 @@ const CollectionDetails = ({ collection }: CollectionDetailsProps) => {
|
||||
<div className="media-header">
|
||||
<div className="media-poster">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
data.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
|
||||
|
||||
@@ -4,24 +4,31 @@ import Image from 'next/image';
|
||||
|
||||
const imageLoader: ImageLoader = ({ src }) => src;
|
||||
|
||||
export type CachedImageProps = ImageProps & {
|
||||
src: string;
|
||||
type: 'tmdb' | 'avatar';
|
||||
};
|
||||
|
||||
/**
|
||||
* The CachedImage component should be used wherever
|
||||
* we want to offer the option to locally cache images.
|
||||
**/
|
||||
const CachedImage = ({ src, ...props }: ImageProps) => {
|
||||
const CachedImage = ({ src, type, ...props }: CachedImageProps) => {
|
||||
const { currentSettings } = useSettings();
|
||||
|
||||
let imageUrl = src;
|
||||
let imageUrl: string;
|
||||
|
||||
if (typeof imageUrl === 'string' && imageUrl.startsWith('http')) {
|
||||
const parsedUrl = new URL(imageUrl);
|
||||
|
||||
if (parsedUrl.host === 'image.tmdb.org') {
|
||||
if (currentSettings.cacheImages)
|
||||
imageUrl = imageUrl.replace('https://image.tmdb.org', '/imageproxy');
|
||||
} else if (parsedUrl.host !== 'gravatar.com') {
|
||||
imageUrl = '/avatarproxy/' + imageUrl;
|
||||
}
|
||||
if (type === 'tmdb') {
|
||||
// tmdb stuff
|
||||
imageUrl =
|
||||
currentSettings.cacheImages && !src.startsWith('/')
|
||||
? src.replace(/^https:\/\/image\.tmdb\.org\//, '/imageproxy/')
|
||||
: src;
|
||||
} else if (type === 'avatar') {
|
||||
// jellyfin avatar (if any)
|
||||
imageUrl = src;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Image unoptimized loader={imageLoader} src={imageUrl} {...props} />;
|
||||
|
||||
@@ -61,6 +61,7 @@ const ImageFader: ForwardRefRenderFunction<HTMLDivElement, ImageFaderProps> = (
|
||||
{...props}
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
className="absolute inset-0 h-full w-full"
|
||||
alt=""
|
||||
src={imageUrl}
|
||||
|
||||
@@ -123,6 +123,7 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
|
||||
{backdrop && (
|
||||
<div className="absolute top-0 left-0 right-0 z-0 h-64 max-h-full w-full">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
alt=""
|
||||
src={backdrop}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
|
||||
@@ -33,6 +33,7 @@ const CompanyCard = ({ image, url, name }: CompanyCardProps) => {
|
||||
>
|
||||
<div className="relative h-full w-full">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={image}
|
||||
alt={name}
|
||||
className="relative z-40 h-full w-full"
|
||||
|
||||
@@ -36,6 +36,7 @@ const GenreCard = ({ image, url, name, canExpand = false }: GenreCardProps) => {
|
||||
tabIndex={0}
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={image}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
|
||||
@@ -89,7 +89,8 @@ const IssueComment = ({
|
||||
</Transition>
|
||||
<Link href={isActiveUser ? '/profile' : `/users/${comment.user.id}`}>
|
||||
<CachedImage
|
||||
src={`${comment.user.avatar}`}
|
||||
type="avatar"
|
||||
src={comment.user.avatar}
|
||||
alt=""
|
||||
className="h-10 w-10 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
|
||||
width={40}
|
||||
|
||||
@@ -217,6 +217,7 @@ const IssueDetails = () => {
|
||||
{data.backdropPath && (
|
||||
<div className="media-page-bg-image">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
alt=""
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
@@ -235,6 +236,7 @@ const IssueDetails = () => {
|
||||
<div className="media-header">
|
||||
<div className="media-poster">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
data.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
|
||||
@@ -287,7 +289,8 @@ const IssueDetails = () => {
|
||||
className="group ml-1 inline-flex h-full items-center xl:ml-1.5"
|
||||
>
|
||||
<CachedImage
|
||||
src={`${issueData.createdBy.avatar}`}
|
||||
type="avatar"
|
||||
src={issueData.createdBy.avatar}
|
||||
alt=""
|
||||
className="mr-0.5 h-5 w-5 scale-100 transform-gpu rounded-full object-cover transition duration-300 group-hover:scale-105 xl:mr-1 xl:h-6 xl:w-6"
|
||||
width={20}
|
||||
|
||||
@@ -112,6 +112,7 @@ const IssueItem = ({ issue }: IssueItemProps) => {
|
||||
{title.backdropPath && (
|
||||
<div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
@@ -137,6 +138,7 @@ const IssueItem = ({ issue }: IssueItemProps) => {
|
||||
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
title.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
@@ -226,7 +228,8 @@ const IssueItem = ({ issue }: IssueItemProps) => {
|
||||
className="group flex items-center truncate"
|
||||
>
|
||||
<CachedImage
|
||||
src={'/avatarproxy/' + issue.createdBy.avatar}
|
||||
type="avatar"
|
||||
src={issue.createdBy.avatar}
|
||||
alt=""
|
||||
className="avatar-sm ml-1.5 object-cover"
|
||||
width={20}
|
||||
|
||||
@@ -57,6 +57,7 @@ const UserDropdown = () => {
|
||||
data-testid="user-menu"
|
||||
>
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
|
||||
src={user ? user.avatar : ''}
|
||||
alt=""
|
||||
@@ -80,6 +81,7 @@ const UserDropdown = () => {
|
||||
<div className="flex flex-col space-y-4 px-4 py-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
className="h-8 w-8 rounded-full object-cover sm:h-10 sm:w-10"
|
||||
src={user ? user.avatar : ''}
|
||||
alt=""
|
||||
|
||||
@@ -369,6 +369,7 @@ const ManageSlideOver = ({
|
||||
content={user.displayName}
|
||||
>
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={user.avatar}
|
||||
alt={user.displayName}
|
||||
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
|
||||
@@ -530,6 +531,7 @@ const ManageSlideOver = ({
|
||||
content={user.displayName}
|
||||
>
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={user.avatar}
|
||||
alt={user.displayName}
|
||||
className="h-8 w-8 scale-100 transform-gpu rounded-full object-cover ring-1 ring-gray-500 transition duration-300 hover:scale-105"
|
||||
|
||||
@@ -448,6 +448,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
{data.backdropPath && (
|
||||
<div className="media-page-bg-image">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
alt=""
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
@@ -494,6 +495,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
<div className="media-header">
|
||||
<div className="media-poster">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
data.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
|
||||
@@ -741,6 +743,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => {
|
||||
<div className="group relative z-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-lg bg-gray-800 bg-cover bg-center shadow-md ring-1 ring-gray-700 transition duration-300 hover:scale-105 hover:ring-gray-500">
|
||||
<div className="absolute inset-0 z-0">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={`https://image.tmdb.org/t/p/w1440_and_h320_multi_faces/${data.collection.backdropPath}`}
|
||||
alt=""
|
||||
style={{
|
||||
|
||||
@@ -51,6 +51,7 @@ const PersonCard = ({
|
||||
{profilePath ? (
|
||||
<div className="relative h-full w-3/4 overflow-hidden rounded-full ring-1 ring-gray-700">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${profilePath}`}
|
||||
alt=""
|
||||
style={{
|
||||
|
||||
@@ -227,6 +227,7 @@ const PersonDetails = () => {
|
||||
{data.profilePath && (
|
||||
<div className="relative mb-6 mr-0 h-36 w-36 flex-shrink-0 overflow-hidden rounded-full ring-1 ring-gray-700 lg:mb-0 lg:mr-6 lg:h-44 lg:w-44">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={`https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.profilePath}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
|
||||
@@ -116,6 +116,7 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
|
||||
>
|
||||
<span className="avatar-sm">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={requestData.requestedBy.avatar}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
@@ -345,6 +346,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
{title.backdropPath && (
|
||||
<div className="absolute inset-0 z-0">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
alt=""
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
@@ -390,6 +392,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
>
|
||||
<span className="avatar-sm">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={requestData.requestedBy.avatar}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
@@ -602,6 +605,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
className="w-20 flex-shrink-0 scale-100 transform-gpu cursor-pointer overflow-hidden rounded-md shadow-sm transition duration-300 hover:scale-105 hover:shadow-md sm:w-28"
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
title.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
|
||||
@@ -191,6 +191,7 @@ const RequestItemError = ({
|
||||
>
|
||||
<span className="avatar-sm ml-1.5">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={requestData.requestedBy.avatar}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
@@ -250,6 +251,7 @@ const RequestItemError = ({
|
||||
>
|
||||
<span className="avatar-sm ml-1.5">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={requestData.modifiedBy.avatar}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
@@ -418,6 +420,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
{title.backdropPath && (
|
||||
<div className="absolute inset-0 z-0 w-full bg-cover bg-center xl:w-2/3">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${title.backdropPath}`}
|
||||
alt=""
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
@@ -443,6 +446,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
className="relative h-auto w-12 flex-shrink-0 scale-100 transform-gpu overflow-hidden rounded-md transition duration-300 hover:scale-105"
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
title.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${title.posterPath}`
|
||||
@@ -570,6 +574,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
>
|
||||
<span className="avatar-sm ml-1.5">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={requestData.requestedBy.avatar}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
@@ -629,7 +634,8 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
>
|
||||
<span className="avatar-sm ml-1.5">
|
||||
<CachedImage
|
||||
src={requestData.requestedBy.avatar}
|
||||
type="avatar"
|
||||
src={requestData.modifiedBy.avatar}
|
||||
alt=""
|
||||
className="avatar-sm object-cover"
|
||||
width={20}
|
||||
|
||||
@@ -562,6 +562,7 @@ const AdvancedRequester = ({
|
||||
<Listbox.Button className="focus:shadow-outline-blue relative w-full cursor-default rounded-md border border-gray-700 bg-gray-800 py-2 pl-3 pr-10 text-left text-white transition duration-150 ease-in-out focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5">
|
||||
<span className="flex items-center">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={selectedUser.avatar}
|
||||
alt=""
|
||||
className="h-6 w-6 flex-shrink-0 rounded-full object-cover"
|
||||
@@ -614,6 +615,7 @@ const AdvancedRequester = ({
|
||||
} flex items-center`}
|
||||
>
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
src={user.avatar}
|
||||
alt=""
|
||||
className="h-6 w-6 flex-shrink-0 rounded-full object-cover"
|
||||
|
||||
@@ -437,6 +437,7 @@ const CollectionRequestModal = ({
|
||||
>
|
||||
<div className="relative h-auto w-10 flex-shrink-0 overflow-hidden rounded-md">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
part.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${part.posterPath}`
|
||||
|
||||
@@ -452,6 +452,7 @@ export const WatchProviderSelector = ({
|
||||
tabIndex={0}
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={`https://image.tmdb.org/t/p/original${provider.logoPath}`}
|
||||
alt=""
|
||||
style={{
|
||||
@@ -497,6 +498,7 @@ export const WatchProviderSelector = ({
|
||||
tabIndex={0}
|
||||
>
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={`https://image.tmdb.org/t/p/original${provider.logoPath}`}
|
||||
alt=""
|
||||
style={{
|
||||
|
||||
@@ -55,6 +55,17 @@ const messages = defineMessages('components.Settings.SettingsMain', {
|
||||
validationApplicationUrlTrailingSlash: 'URL must not end in a trailing slash',
|
||||
partialRequestsEnabled: 'Allow Partial Series Requests',
|
||||
locale: 'Display Language',
|
||||
proxyEnabled: 'HTTP(S) Proxy',
|
||||
proxyHostname: 'Proxy Hostname',
|
||||
proxyPort: 'Proxy Port',
|
||||
proxySsl: 'Use SSL For Proxy',
|
||||
proxyUser: 'Proxy Username',
|
||||
proxyPassword: 'Proxy Password',
|
||||
proxyBypassFilter: 'Proxy Ignored Addresses',
|
||||
proxyBypassFilterTip:
|
||||
"Use ',' as a separator, and '*.' as a wildcard for subdomains",
|
||||
proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses',
|
||||
validationProxyPort: 'You must provide a valid port',
|
||||
});
|
||||
|
||||
const SettingsMain = () => {
|
||||
@@ -82,6 +93,12 @@ const SettingsMain = () => {
|
||||
intl.formatMessage(messages.validationApplicationUrlTrailingSlash),
|
||||
(value) => !value || !value.endsWith('/')
|
||||
),
|
||||
proxyPort: Yup.number().when('proxyEnabled', {
|
||||
is: (proxyEnabled: boolean) => proxyEnabled,
|
||||
then: Yup.number().required(
|
||||
intl.formatMessage(messages.validationProxyPort)
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const regenerate = async () => {
|
||||
@@ -137,6 +154,14 @@ const SettingsMain = () => {
|
||||
partialRequestsEnabled: data?.partialRequestsEnabled,
|
||||
trustProxy: data?.trustProxy,
|
||||
cacheImages: data?.cacheImages,
|
||||
proxyEnabled: data?.proxy?.enabled,
|
||||
proxyHostname: data?.proxy?.hostname,
|
||||
proxyPort: data?.proxy?.port,
|
||||
proxySsl: data?.proxy?.useSsl,
|
||||
proxyUser: data?.proxy?.user,
|
||||
proxyPassword: data?.proxy?.password,
|
||||
proxyBypassFilter: data?.proxy?.bypassFilter,
|
||||
proxyBypassLocalAddresses: data?.proxy?.bypassLocalAddresses,
|
||||
}}
|
||||
enableReinitialize
|
||||
validationSchema={MainSettingsSchema}
|
||||
@@ -158,6 +183,16 @@ const SettingsMain = () => {
|
||||
partialRequestsEnabled: values.partialRequestsEnabled,
|
||||
trustProxy: values.trustProxy,
|
||||
cacheImages: values.cacheImages,
|
||||
proxy: {
|
||||
enabled: values.proxyEnabled,
|
||||
hostname: values.proxyHostname,
|
||||
port: values.proxyPort,
|
||||
useSsl: values.proxySsl,
|
||||
user: values.proxyUser,
|
||||
password: values.proxyPassword,
|
||||
bypassFilter: values.proxyBypassFilter,
|
||||
bypassLocalAddresses: values.proxyBypassLocalAddresses,
|
||||
},
|
||||
}),
|
||||
});
|
||||
if (!res.ok) throw new Error();
|
||||
@@ -437,6 +472,176 @@ const SettingsMain = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="proxyEnabled" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.proxyEnabled)}
|
||||
</span>
|
||||
<SettingsBadge badgeType="advanced" className="mr-2" />
|
||||
<SettingsBadge badgeType="restartRequired" />
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="proxyEnabled"
|
||||
name="proxyEnabled"
|
||||
onChange={() => {
|
||||
setFieldValue('proxyEnabled', !values.proxyEnabled);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{values.proxyEnabled && (
|
||||
<>
|
||||
<div className="form-row">
|
||||
<label htmlFor="proxyHostname" className="checkbox-label">
|
||||
<span className="mr-2 ml-4">
|
||||
{intl.formatMessage(messages.proxyHostname)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
id="proxyHostname"
|
||||
name="proxyHostname"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
{errors.proxyHostname &&
|
||||
touched.proxyHostname &&
|
||||
typeof errors.proxyHostname === 'string' && (
|
||||
<div className="error">{errors.proxyHostname}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="proxyPort" className="checkbox-label">
|
||||
<span className="mr-2 ml-4">
|
||||
{intl.formatMessage(messages.proxyPort)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field id="proxyPort" name="proxyPort" type="text" />
|
||||
</div>
|
||||
{errors.proxyPort &&
|
||||
touched.proxyPort &&
|
||||
typeof errors.proxyPort === 'string' && (
|
||||
<div className="error">{errors.proxyPort}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="proxySsl" className="checkbox-label">
|
||||
<span className="mr-2 ml-4">
|
||||
{intl.formatMessage(messages.proxySsl)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="proxySsl"
|
||||
name="proxySsl"
|
||||
onChange={() => {
|
||||
setFieldValue('proxySsl', !values.proxySsl);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="proxyUser" className="checkbox-label">
|
||||
<span className="mr-2 ml-4">
|
||||
{intl.formatMessage(messages.proxyUser)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field id="proxyUser" name="proxyUser" type="text" />
|
||||
</div>
|
||||
{errors.proxyUser &&
|
||||
touched.proxyUser &&
|
||||
typeof errors.proxyUser === 'string' && (
|
||||
<div className="error">{errors.proxyUser}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="proxyPassword" className="checkbox-label">
|
||||
<span className="mr-2 ml-4">
|
||||
{intl.formatMessage(messages.proxyPassword)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
id="proxyPassword"
|
||||
name="proxyPassword"
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
{errors.proxyPassword &&
|
||||
touched.proxyPassword &&
|
||||
typeof errors.proxyPassword === 'string' && (
|
||||
<div className="error">{errors.proxyPassword}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="proxyBypassFilter"
|
||||
className="checkbox-label"
|
||||
>
|
||||
<span className="mr-2 ml-4">
|
||||
{intl.formatMessage(messages.proxyBypassFilter)}
|
||||
</span>
|
||||
<span className="label-tip ml-4">
|
||||
{intl.formatMessage(messages.proxyBypassFilterTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
id="proxyBypassFilter"
|
||||
name="proxyBypassFilter"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
{errors.proxyBypassFilter &&
|
||||
touched.proxyBypassFilter &&
|
||||
typeof errors.proxyBypassFilter === 'string' && (
|
||||
<div className="error">
|
||||
{errors.proxyBypassFilter}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="proxyBypassLocalAddresses"
|
||||
className="checkbox-label"
|
||||
>
|
||||
<span className="mr-2 ml-4">
|
||||
{intl.formatMessage(
|
||||
messages.proxyBypassLocalAddresses
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
type="checkbox"
|
||||
id="proxyBypassLocalAddresses"
|
||||
name="proxyBypassLocalAddresses"
|
||||
onChange={() => {
|
||||
setFieldValue(
|
||||
'proxyBypassLocalAddresses',
|
||||
!values.proxyBypassLocalAddresses
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
|
||||
@@ -86,10 +86,12 @@ interface TestResponse {
|
||||
id: number;
|
||||
path: string;
|
||||
}[];
|
||||
languageProfiles: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
languageProfiles:
|
||||
| {
|
||||
id: number;
|
||||
name: string;
|
||||
}[]
|
||||
| null;
|
||||
tags: {
|
||||
id: number;
|
||||
label: string;
|
||||
@@ -112,7 +114,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
|
||||
const [testResponse, setTestResponse] = useState<TestResponse>({
|
||||
profiles: [],
|
||||
rootFolders: [],
|
||||
languageProfiles: [],
|
||||
languageProfiles: null,
|
||||
tags: [],
|
||||
});
|
||||
const SonarrSettingsSchema = Yup.object().shape({
|
||||
@@ -137,9 +139,11 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
|
||||
activeProfileId: Yup.string().required(
|
||||
intl.formatMessage(messages.validationProfileRequired)
|
||||
),
|
||||
activeLanguageProfileId: Yup.number().required(
|
||||
intl.formatMessage(messages.validationLanguageProfileRequired)
|
||||
),
|
||||
activeLanguageProfileId: testResponse.languageProfiles
|
||||
? Yup.number().required(
|
||||
intl.formatMessage(messages.validationLanguageProfileRequired)
|
||||
)
|
||||
: Yup.number(),
|
||||
externalUrl: Yup.string()
|
||||
.url(intl.formatMessage(messages.validationApplicationUrl))
|
||||
.test(
|
||||
@@ -658,54 +662,56 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="activeLanguageProfileId"
|
||||
className="text-label"
|
||||
>
|
||||
{intl.formatMessage(messages.languageprofile)}
|
||||
<span className="label-required">*</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
as="select"
|
||||
id="activeLanguageProfileId"
|
||||
name="activeLanguageProfileId"
|
||||
disabled={!isValidated || isTesting}
|
||||
>
|
||||
<option value="">
|
||||
{isTesting
|
||||
? intl.formatMessage(
|
||||
messages.loadinglanguageprofiles
|
||||
)
|
||||
: !isValidated
|
||||
? intl.formatMessage(
|
||||
messages.testFirstLanguageProfiles
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.selectLanguageProfile
|
||||
)}
|
||||
</option>
|
||||
{testResponse.languageProfiles.length > 0 &&
|
||||
testResponse.languageProfiles.map((language) => (
|
||||
<option
|
||||
key={`loaded-profile-${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
{testResponse.languageProfiles && (
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="activeLanguageProfileId"
|
||||
className="text-label"
|
||||
>
|
||||
{intl.formatMessage(messages.languageprofile)}
|
||||
<span className="label-required">*</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
as="select"
|
||||
id="activeLanguageProfileId"
|
||||
name="activeLanguageProfileId"
|
||||
disabled={!isValidated || isTesting}
|
||||
>
|
||||
<option value="">
|
||||
{isTesting
|
||||
? intl.formatMessage(
|
||||
messages.loadinglanguageprofiles
|
||||
)
|
||||
: !isValidated
|
||||
? intl.formatMessage(
|
||||
messages.testFirstLanguageProfiles
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.selectLanguageProfile
|
||||
)}
|
||||
</option>
|
||||
{testResponse.languageProfiles.length > 0 &&
|
||||
testResponse.languageProfiles.map((language) => (
|
||||
<option
|
||||
key={`loaded-profile-${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
{errors.activeLanguageProfileId &&
|
||||
touched.activeLanguageProfileId && (
|
||||
<div className="error">
|
||||
{errors.activeLanguageProfileId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{errors.activeLanguageProfileId &&
|
||||
touched.activeLanguageProfileId && (
|
||||
<div className="error">
|
||||
{errors.activeLanguageProfileId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="form-row">
|
||||
<label htmlFor="tags" className="text-label">
|
||||
{intl.formatMessage(messages.tags)}
|
||||
@@ -863,53 +869,55 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="activeAnimeLanguageProfileId"
|
||||
className="text-label"
|
||||
>
|
||||
{intl.formatMessage(messages.animelanguageprofile)}
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
as="select"
|
||||
id="activeAnimeLanguageProfileId"
|
||||
name="activeAnimeLanguageProfileId"
|
||||
disabled={!isValidated || isTesting}
|
||||
>
|
||||
<option value="">
|
||||
{isTesting
|
||||
? intl.formatMessage(
|
||||
messages.loadinglanguageprofiles
|
||||
)
|
||||
: !isValidated
|
||||
? intl.formatMessage(
|
||||
messages.testFirstLanguageProfiles
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.selectLanguageProfile
|
||||
)}
|
||||
</option>
|
||||
{testResponse.languageProfiles.length > 0 &&
|
||||
testResponse.languageProfiles.map((language) => (
|
||||
<option
|
||||
key={`loaded-profile-${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
{testResponse.languageProfiles && (
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="activeAnimeLanguageProfileId"
|
||||
className="text-label"
|
||||
>
|
||||
{intl.formatMessage(messages.animelanguageprofile)}
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
as="select"
|
||||
id="activeAnimeLanguageProfileId"
|
||||
name="activeAnimeLanguageProfileId"
|
||||
disabled={!isValidated || isTesting}
|
||||
>
|
||||
<option value="">
|
||||
{isTesting
|
||||
? intl.formatMessage(
|
||||
messages.loadinglanguageprofiles
|
||||
)
|
||||
: !isValidated
|
||||
? intl.formatMessage(
|
||||
messages.testFirstLanguageProfiles
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.selectLanguageProfile
|
||||
)}
|
||||
</option>
|
||||
{testResponse.languageProfiles.length > 0 &&
|
||||
testResponse.languageProfiles.map((language) => (
|
||||
<option
|
||||
key={`loaded-profile-${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
{errors.activeAnimeLanguageProfileId &&
|
||||
touched.activeAnimeLanguageProfileId && (
|
||||
<div className="error">
|
||||
{errors.activeAnimeLanguageProfileId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{errors.activeAnimeLanguageProfileId &&
|
||||
touched.activeAnimeLanguageProfileId && (
|
||||
<div className="error">
|
||||
{errors.activeAnimeLanguageProfileId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="form-row">
|
||||
<label htmlFor="tags" className="text-label">
|
||||
{intl.formatMessage(messages.animeTags)}
|
||||
|
||||
@@ -346,6 +346,7 @@ const TitleCard = ({
|
||||
>
|
||||
<div className="absolute inset-0 h-full w-full overflow-hidden">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
className="absolute inset-0 h-full w-full"
|
||||
alt=""
|
||||
src={
|
||||
|
||||
@@ -471,6 +471,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
{data.backdropPath && (
|
||||
<div className="media-page-bg-image">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
alt=""
|
||||
src={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath}`}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
@@ -527,6 +528,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => {
|
||||
<div className="media-header">
|
||||
<div className="media-poster">
|
||||
<CachedImage
|
||||
type="tmdb"
|
||||
src={
|
||||
data.posterPath
|
||||
? `https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`
|
||||
|
||||
@@ -250,6 +250,7 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
|
||||
<td className="whitespace-nowrap px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6">
|
||||
<div className="flex items-center">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
className="h-10 w-10 flex-shrink-0 rounded-full"
|
||||
src={user.thumb}
|
||||
alt=""
|
||||
|
||||
@@ -634,6 +634,7 @@ const UserList = () => {
|
||||
className="h-10 w-10 flex-shrink-0"
|
||||
>
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
className="h-10 w-10 rounded-full object-cover"
|
||||
src={user.avatar}
|
||||
alt=""
|
||||
|
||||
@@ -43,6 +43,7 @@ const ProfileHeader = ({ user, isSettingsPage }: ProfileHeaderProps) => {
|
||||
<div className="flex-shrink-0">
|
||||
<div className="relative">
|
||||
<CachedImage
|
||||
type="avatar"
|
||||
className="h-24 w-24 rounded-full bg-gray-600 object-cover ring-1 ring-gray-700"
|
||||
src={user.avatar}
|
||||
alt=""
|
||||
|
||||
@@ -12,6 +12,7 @@ import { SettingsProvider } from '@app/context/SettingsContext';
|
||||
import { UserContext } from '@app/context/UserContext';
|
||||
import type { User } from '@app/hooks/useUser';
|
||||
import '@app/styles/globals.css';
|
||||
import '@app/utils/fetchOverride';
|
||||
import { polyfillIntl } from '@app/utils/polyfillIntl';
|
||||
import { MediaServerType } from '@server/constants/server';
|
||||
import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces';
|
||||
|
||||
46
src/utils/fetchOverride.ts
Normal file
46
src/utils/fetchOverride.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
const getCsrfToken = (): string | null => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const match = document.cookie.match(/XSRF-TOKEN=([^;]+)/);
|
||||
return match ? decodeURIComponent(match[1]) : null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const isSameOrigin = (url: RequestInfo | URL): boolean => {
|
||||
const parsedUrl = new URL(
|
||||
url instanceof Request ? url.url : url.toString(),
|
||||
window.location.origin
|
||||
);
|
||||
return parsedUrl.origin === window.location.origin;
|
||||
};
|
||||
|
||||
// We are using a custom fetch implementation to add the X-XSRF-TOKEN heade
|
||||
// to all requests. This is required when CSRF protection is enabled.
|
||||
if (typeof window !== 'undefined') {
|
||||
const originalFetch: typeof fetch = window.fetch;
|
||||
|
||||
(window as typeof globalThis).fetch = async (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit
|
||||
): Promise<Response> => {
|
||||
if (!isSameOrigin(input)) {
|
||||
return originalFetch(input, init);
|
||||
}
|
||||
|
||||
const csrfToken = getCsrfToken();
|
||||
|
||||
const headers = {
|
||||
...(init?.headers || {}),
|
||||
...(csrfToken ? { 'XSRF-TOKEN': csrfToken } : {}),
|
||||
};
|
||||
|
||||
const newInit: RequestInit = {
|
||||
...init,
|
||||
headers,
|
||||
};
|
||||
|
||||
return originalFetch(input, newInit);
|
||||
};
|
||||
}
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user