From 17b5a0bf9c39a5436a62e096800bb7ee8274163f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 6 Dec 2020 01:58:28 +0000 Subject: [PATCH 001/238] chore(release): 1.0.0 --- CHANGELOG.md | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..f5e4a1ae3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,120 @@ +# 1.0.0 (2020-12-06) + + +### Bug Fixes + +* **api:** fix scheduling for plex full sync (maybe) ([7287a6a](https://github.com/sct/overseerr/commit/7287a6a95703b23acc0c4f6eb3beb9ec2295e33f)) +* **frontend:** always show request modal option for tv ([2b46268](https://github.com/sct/overseerr/commit/2b462688243531b4be620a942f59defd4e0534d0)) +* **frontend:** canceled movie request should set parent movie status back to unknown ([#198](https://github.com/sct/overseerr/issues/198)) ([139871f](https://github.com/sct/overseerr/commit/139871f218812a15f742aa66408db12704e0b9b5)) +* **frontend:** close request modals when complete ([85ae499](https://github.com/sct/overseerr/commit/85ae4998f0ba8d4869b9b244f2c440b9df1310d2)) +* **frontend:** dont show runtime if there is no runtime data ([e0c39ae](https://github.com/sct/overseerr/commit/e0c39aeca119b822f2a54ff05a97f91780ddd052)) +* **frontend:** fix missing data for request modal title i18n ([a56fd16](https://github.com/sct/overseerr/commit/a56fd16ab6638d4649fe9f8b9d75e7cae7742f73)) +* **frontend:** fix missing import for ReactNode type in Slider ([b26a234](https://github.com/sct/overseerr/commit/b26a2347e7b0f7ff8720a204d9faefd501ba886c)) +* **frontend:** fix modal design and rename some text for adding servers ([46d99b0](https://github.com/sct/overseerr/commit/46d99b02b1c992c7b8dde2150217ed9ce326b7a5)) +* **frontend:** fix opening popups on safari ([364d9d1](https://github.com/sct/overseerr/commit/364d9d105ca3690fcd5f635485d7c025353bb9f1)) +* **frontend:** fix request card placeholder sizes for mobile ([ef62c67](https://github.com/sct/overseerr/commit/ef62c67480ed52d753ea6db8205f035b2e9da272)) +* **frontend:** show a badge on requestcard for partially available status ([59056c4](https://github.com/sct/overseerr/commit/59056c44f942a37df536ff947b5faccc27f32246)) +* dont cross import SyncStatus type ([e032e38](https://github.com/sct/overseerr/commit/e032e385a5253d215490255c676f42ee48f39428)) +* fix type import from server side crashing build process ([89be56d](https://github.com/sct/overseerr/commit/89be56d8403ebc60c411e7cb357593edd9c79bb2)) +* **frontend:** fix title detail background image to be centered ([b92f64f](https://github.com/sct/overseerr/commit/b92f64fa6e167bc89168d8f5c0f2eb12efa0b6f0)) +* **frontend:** fixed similar/recommendations showing when empty ([#180](https://github.com/sct/overseerr/issues/180)) ([a3ca9b4](https://github.com/sct/overseerr/commit/a3ca9b40c552e6cc5effc2f57f7562ff6f723e42)) +* **frontend:** have tvDetail use the new RequestModal ([6aca826](https://github.com/sct/overseerr/commit/6aca82607b97d4a4ad74e2ea843d52fba4689e6a)) +* **frontend:** reinitalize plex form after data loads ([97e3036](https://github.com/sct/overseerr/commit/97e30367fb5d2d27efc42c1d76b0d051b6f1da76)) +* **frontend:** remove requestId from tilecard request modal component ([61b6152](https://github.com/sct/overseerr/commit/61b6152e8915c99585b944756a61d33b8c8a0307)) +* **frontend:** run initial props for children components after getting the user ([fdf9f38](https://github.com/sct/overseerr/commit/fdf9f38776b6d4c08b3505c03b354639cebb011f)) +* **frontend:** when there were no results in the list view, it would call fetch more infinitely ([c0ce87b](https://github.com/sct/overseerr/commit/c0ce87b6f65bf0ab1301c7ca61090d779709529f)) +* fixed an issue with eslint-prettier on windows ([#32](https://github.com/sct/overseerr/issues/32)) ([b673ea1](https://github.com/sct/overseerr/commit/b673ea1b18ca0f432996bb9e4e5d148af0247170)) +* fixes next.js build to not include server files ([de8ee9b](https://github.com/sct/overseerr/commit/de8ee9ba85e0160b0b472cab44f92c01796efec8)) + + +### Features + +* add migration for delete cascades on season requests/seasons ([c688cf6](https://github.com/sct/overseerr/commit/c688cf60c710f0cf0b2da5ba6b0c18a2d137e7f9)) +* **api:** email notification agent ([0962392](https://github.com/sct/overseerr/commit/0962392e3930c7fdcb3164b9143cc8faca38bdfa)) +* **frontend:** add french language file ([cd6d8a8](https://github.com/sct/overseerr/commit/cd6d8a8216e7ae183b046d26cd22f3c1dc1d2b35)) +* **frontend:** add translatable strings for request card ([0d2f360](https://github.com/sct/overseerr/commit/0d2f360c22cd9bb50ae04f00a25e5fcc6c21bcdd)) +* **frontend:** added more localized strings ([659a601](https://github.com/sct/overseerr/commit/659a6018777718f7a90141307678d8dadcfd77f8)) +* actually include email templates in built server files ([a28a8b3](https://github.com/sct/overseerr/commit/a28a8b37b0afc79583e4a7191a91f73ff6d3adad)) +* add application url config to main settings ui ([a359672](https://github.com/sct/overseerr/commit/a359672ebafffef742858814f0faa918e0341aa3)) +* add filtering for requests api ([cb9ae25](https://github.com/sct/overseerr/commit/cb9ae25d94f21e97113dfea3ca45c7002089e344)) +* add trending to discover page ([ff8b9d8](https://github.com/sct/overseerr/commit/ff8b9d8e7ed228a153c2da4d237f7a4f99a79321)) +* force setup if app is not initialized ([a99705f](https://github.com/sct/overseerr/commit/a99705f6a5674b436ae28cbc558f4ee6e99ac910)) +* initial user list (no edit/delete yet) and job schedules ([24a0423](https://github.com/sct/overseerr/commit/24a0423f3b14303cfb0e83aef6e9e3bb273c5ba9)) +* manage series slideover added (and approve/decline/delete hooked up) ([236c4e5](https://github.com/sct/overseerr/commit/236c4e5e6126d2424a4badc08b7f7e6d1d70f401)) +* media delete option in manage media slideover ([250f484](https://github.com/sct/overseerr/commit/250f48492c95d74e40d95d3f026d2952157bc6e1)) +* other email notifications for approved/available ([0d73d88](https://github.com/sct/overseerr/commit/0d73d88f35b03e993f305873dc72672003c7d9e5)) +* radarr edit/create modal/backend functionality ([c4ac357](https://github.com/sct/overseerr/commit/c4ac357ef4cdd7a2c610260db46a4f0c325cd785)) +* season creation migration ([978f92a](https://github.com/sct/overseerr/commit/978f92a1c589ac404a3cb1103a68a8a5ffb0dd7d)) +* sonarr edit/delete modal ([3204326](https://github.com/sct/overseerr/commit/320432657e6ccf4d255238098e03590f28267bdb)) +* throw 404 when movie/tv show doesnt exist ([0601b44](https://github.com/sct/overseerr/commit/0601b446873e2eaf042044dd6a995b713586b0cc)) +* **api:** sonarr api wrapper / send to sonarr ([9385592](https://github.com/sct/overseerr/commit/9385592362eeba1dba05c5aa8fc7a2de1d054d74)) +* **frontend:** add header styling to movie/tv recommendation and similar list views ([f5f2545](https://github.com/sct/overseerr/commit/f5f2545520a43daa23e1276d24ff60d794ebbc6e)) +* **frontend:** add links to detail pages from new request card ([6ad3384](https://github.com/sct/overseerr/commit/6ad3384a78f7bcb03f409cce8b35cc61d634d6b2)) +* **frontend:** new design for request card ([93738e1](https://github.com/sct/overseerr/commit/93738e154c41fd11d5c6cf3d35573daf54ead471)) +* **frontend:** update favicon ([886389a](https://github.com/sct/overseerr/commit/886389a361da54c616da3bdfeee9a85e9d12bcf3)) +* notification framework ([d8e542e](https://github.com/sct/overseerr/commit/d8e542e5fe2ed76dcb20fb6dfc5f59430cd4245d)) +* notifications for media_available and media_approved ([a6c5e65](https://github.com/sct/overseerr/commit/a6c5e65bbfc196545471e99fe2e5b7194f9dd387)) +* rotten tomatoes scores on movie/tv details pages ([1694f60](https://github.com/sct/overseerr/commit/1694f60e8aa475ceeb7f170a783ec0ba70bd4bce)) +* upcoming movies on discover ([67290dd](https://github.com/sct/overseerr/commit/67290dd502571a22dcf8559ac07f42e855275bd0)) +* upcoming/trending list views and larger title cards ([94eaaf9](https://github.com/sct/overseerr/commit/94eaaf96b4302a832c52ccb72009b3593452c779)) +* upgrade tailwindcss to 2.0.1 ([fb5c791](https://github.com/sct/overseerr/commit/fb5c791b0b6b7593a472bf01713999a001f92dc7)) +* user edit functionality (managing permissions) ([185ac26](https://github.com/sct/overseerr/commit/185ac2648fd21c4bf9692ac5ac055e9c740065ca)) +* **api:** plex tv sync and recently added sync ([1390cc1](https://github.com/sct/overseerr/commit/1390cc1f130bb3975996e84b12ac833f55f2f753)) +* **frontend:** allow permission check for showing nav items ([0b239f0](https://github.com/sct/overseerr/commit/0b239f0bdfb1394897bce5c50b0d112abfbb4ad7)) +* **frontend:** alpha notice ([33da7e9](https://github.com/sct/overseerr/commit/33da7e9df3a2546b0f208bd3b1d1f268e343cead)) +* **frontend:** buttonWithDropdown component added (no hookups yet) ([4975841](https://github.com/sct/overseerr/commit/4975841b5d4ba4ed1ba8cacaa5a063eeb3b8c311)) +* **frontend:** cancel movie request modal ([1f9cbbf](https://github.com/sct/overseerr/commit/1f9cbbfdf1ac98e54de5b8777c52c7bfc69c7e20)) +* **frontend:** improved settings menu design for mobile ([16221a4](https://github.com/sct/overseerr/commit/16221a46a7d57c77f53aa0186263aa27267d9863)) +* **frontend:** initial Settings design ([8742da0](https://github.com/sct/overseerr/commit/8742da0ebb92d2f78309a998de0f67e788e14376)) +* **frontend:** plex library scan ([1bc3f7b](https://github.com/sct/overseerr/commit/1bc3f7be4b07211563a1e254c28ce51e1bc337a2)) +* **frontend:** plex settings page ([47714b6](https://github.com/sct/overseerr/commit/47714b698cf4351c1ee38bdf0b672d9f0baed03a)) +* **frontend:** radarr delete modal ([877a518](https://github.com/sct/overseerr/commit/877a5184158fb4aa371fa2ea2107032543c9aa37)) +* **frontend:** recently added on discover ([06dc606](https://github.com/sct/overseerr/commit/06dc606bcfeb50b7be1c35ac180c10738bade458)) +* **frontend:** slideover initial work ([14b9cb6](https://github.com/sct/overseerr/commit/14b9cb610c0dcfef939ebec328f371e1cdfb689d)) +* tv request modal status hookup ([5f8114f](https://github.com/sct/overseerr/commit/5f8114f730b067eb710704952824057e7b5b8fbf)) +* **.editorconfig:** add .editorconfig ([b982066](https://github.com/sct/overseerr/commit/b982066327525156f8dd0d32818d3fe7cb28f9c8)) +* **api:** add external ids to movie/tv response ([4aa7431](https://github.com/sct/overseerr/commit/4aa74319e0adcc19041239e57a00bc40fb127826)) +* **api:** add movie details endpoint ([b176148](https://github.com/sct/overseerr/commit/b1761484cb2861329763d51a868f37dd3098760d)) +* **api:** add tmdb discover api wrapper ([#67](https://github.com/sct/overseerr/issues/67)) ([839448f](https://github.com/sct/overseerr/commit/839448fcc8cc14ea83092af82e2ba3d0d92c9b73)) +* **api:** allow plex logins from users who have access to the server ([5147140](https://github.com/sct/overseerr/commit/514714071dfe4be04e607fe6412f5b3f0ef74dd4)) +* **api:** decouple media requests from media info ([8577db1](https://github.com/sct/overseerr/commit/8577db1be16f099d92c6649bbfb15f15e09a2f73)) +* **api:** discover endpoint for movie/tv ([#73](https://github.com/sct/overseerr/issues/73)) ([258bb93](https://github.com/sct/overseerr/commit/258bb93be2acc2ca32eaaefb617a5c326c5943ba)) +* **api:** initial implementation of the auth system ([#30](https://github.com/sct/overseerr/issues/30)) ([5343f35](https://github.com/sct/overseerr/commit/5343f35e5b572fe366a8712b24bd735de30e6170)) +* **api:** plex Sync (Movies) ([1be8b18](https://github.com/sct/overseerr/commit/1be8b183617c3a44ab8d4454a64b43dfe1d877fe)) +* **api:** public settings route ([#57](https://github.com/sct/overseerr/issues/57)) ([c0166e7](https://github.com/sct/overseerr/commit/c0166e7ecb5df110a4167f33338ed6406bf47f41)) +* **api:** radarr api wrapper / send to radarr when requests approved ([#93](https://github.com/sct/overseerr/issues/93)) ([48d62c3](https://github.com/sct/overseerr/commit/48d62c3178488d0d51831155ddd35cc31867db2b)) +* **api:** request api ([#80](https://github.com/sct/overseerr/issues/80)) ([f4c2c47](https://github.com/sct/overseerr/commit/f4c2c47e569e7faea7f99664966cb98b321ce952)) +* **api:** tmdb api wrapper / multi search route ([#62](https://github.com/sct/overseerr/issues/62)) ([c702c17](https://github.com/sct/overseerr/commit/c702c17cee00a52b23f685206e2d5d0c2eddf5a2)) +* **api:** tmdb trending api wrapper ([#68](https://github.com/sct/overseerr/issues/68)) ([ba34e54](https://github.com/sct/overseerr/commit/ba34e54d77d142d211df58d6ce9f53b6e673e004)) +* **api:** tv details endpoint ([a3beeed](https://github.com/sct/overseerr/commit/a3beeede7e72e99c7595673a27e38611ca4bb0cd)) +* **api:** validate plex when settings are saved ([8f6247d](https://github.com/sct/overseerr/commit/8f6247d82160704a3cfb76262696957b27641e87)) +* **api-user:** add basic User Entity and basic routing to fetch all users ([d902ef7](https://github.com/sct/overseerr/commit/d902ef72770712f2f71f33c09bca9ba99a30fc64)) +* **components/plexloginbutton:** added PlexLoginButton ([0abf743](https://github.com/sct/overseerr/commit/0abf743b17c664b58da18bdbf176f4a55ddc4179)) +* **extensions.json:** added recommended extensions for VSCode ([5dc9b51](https://github.com/sct/overseerr/commit/5dc9b510b8049516ad889c9d76a2f84daa0d2718)) +* **frontend:** add cancel request modal for titlecards ([f22f8c5](https://github.com/sct/overseerr/commit/f22f8c5d734be5cc0b1dcca869458a7321cd43a2)) +* **frontend:** approve/decline request well added to movie detail ([8f21358](https://github.com/sct/overseerr/commit/8f21358f797ed55923d90ba43acf1126856e9dfd)) +* **frontend:** basic discover page (only movies) ([#74](https://github.com/sct/overseerr/issues/74)) ([bbfe349](https://github.com/sct/overseerr/commit/bbfe349b52d308620796b37aaf986a0ed1ff0006)) +* **frontend:** design updates for responsive titlecards ([31809d9](https://github.com/sct/overseerr/commit/31809d952c8bafde3f63e2c1d952cc013149940e)) +* **frontend:** discover tv/movies full page ([be0003a](https://github.com/sct/overseerr/commit/be0003a85dc4e91799e85019aeb1110bd524a026)) +* **frontend:** initial search functionality ([#78](https://github.com/sct/overseerr/issues/78)) ([342d1a3](https://github.com/sct/overseerr/commit/342d1a3c75b32b172a51ca7d82fdfde8510abedf)) +* **frontend:** loading spinner ([de84658](https://github.com/sct/overseerr/commit/de84658b48985e24b0f92a1690387f6d59d0bc16)) +* **frontend:** logo updates ([5a43ec5](https://github.com/sct/overseerr/commit/5a43ec5405855deb244e8085484a9d2b743caba6)) +* **frontend:** modal component and basic request hookup ([#91](https://github.com/sct/overseerr/issues/91)) ([626099a](https://github.com/sct/overseerr/commit/626099a2c98fb30d0cb53d8ccf79a6bf75a00059)) +* **frontend:** new dashboard concept ([#82](https://github.com/sct/overseerr/issues/82)) ([eae38bb](https://github.com/sct/overseerr/commit/eae38bb9ec8588856f319387d2f262d7ee3f7e9c)) +* **frontend:** refresh indicator for titlecards / toasts ([4638fae](https://github.com/sct/overseerr/commit/4638fae336edc62a539796b3f55277a238683603)) +* **frontend:** request card / recent requests ([371e433](https://github.com/sct/overseerr/commit/371e43356d2c057e52368c32ffe2af1744311d91)) +* **frontend:** title detail (movie) initial version ([73ce24a](https://github.com/sct/overseerr/commit/73ce24a37bda3713e8cedc44e1ed065bdbc4ee4f)) +* **frontend/api:** beginning of new request modal ([2bf7e10](https://github.com/sct/overseerr/commit/2bf7e10e32718b36799be2feb0a7f9ff54d85744)) +* **frontend/api:** cast included with movie request and cast list on detail page ([04252f8](https://github.com/sct/overseerr/commit/04252f88bbdf51949923586feda582f86ac668ce)) +* **frontend/api:** i18n support ([9131254](https://github.com/sct/overseerr/commit/9131254f3371f12a17de44b6fa8f9bfb0e5c002e)) +* **frontend/api:** movie recommendations/similar request and frontend detail page update ([6398e36](https://github.com/sct/overseerr/commit/6398e3645a1e4ddbb9de9f4fda0a0659b4cac4d0)) +* **frontend/api:** tv details page ([02cbb5b](https://github.com/sct/overseerr/commit/02cbb5b030a3af5d62ab6c4cafdd4d800b4f61f4)) +* **frontend/api:** tv request modal (no status. only request) ([608b966](https://github.com/sct/overseerr/commit/608b96600a926adf16331b36e77789afa5d67069)) +* logout route/sign out button ([#54](https://github.com/sct/overseerr/issues/54)) ([cb9098f](https://github.com/sct/overseerr/commit/cb9098f457f79b71734959fd924b6c72ca77d61d)) +* user avatars from plex ([#53](https://github.com/sct/overseerr/issues/53)) ([e6349c1](https://github.com/sct/overseerr/commit/e6349c13a0eb0489289aa7663fcc64fa7d2906e6)) +* **layout:** created Layout component ([1f497e8](https://github.com/sct/overseerr/commit/1f497e8913146ceb9748d667e638141b2ca4612a)) +* **login component/route:** add: Login Component and Route ([6e47be2](https://github.com/sct/overseerr/commit/6e47be2fa865bcd51582ce30ebee6fd820c5f9dd)) +* **login route conditional:** on login route, do not display layout ([7d179ae](https://github.com/sct/overseerr/commit/7d179ae3b42d8ffae5e1b6e266038793260f1bbe)) +* **pass pageprops to loginpage:** pass page props to loginPage ([1597188](https://github.com/sct/overseerr/commit/159718891fb363001c650ac8b7e1446a1520ce4a)) +* **plex/utils:** added Plex OAuth class ([72f9624](https://github.com/sct/overseerr/commit/72f9624f1db721fe0324b7be9f0f811d2ae02389)) +* bootstrap the basic app structure ([89a6017](https://github.com/sct/overseerr/commit/89a6017c7f6f7637fe249ac0d667a652f44e02bb)) diff --git a/package.json b/package.json index 74a61cbae..156616daf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "0.1.0", + "version": "1.0.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 5fc02d0d9499b0b00aa7442a5fd7e7b53ba57dbe Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 8 Dec 2020 08:21:58 +0000 Subject: [PATCH 002/238] chore(release): 1.1.0 --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5e4a1ae3..4bf59f4a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# [1.1.0](https://github.com/sct/overseerr/compare/v1.0.0...v1.1.0) (2020-12-08) + + +### Bug Fixes + +* fix a few misc unused imports and useless assignments/conditionals ([8e6daf7](https://github.com/sct/overseerr/commit/8e6daf7bd271ce5bebf4a00f5bb1144bd6b60aa5)) +* **frontend:** dont show delete button in request list for users without correct permission ([83fde46](https://github.com/sct/overseerr/commit/83fde46a59c6f1910806a6106b5526b8adbc386c)) +* **frontend:** push updated i18n locale files ([b4002d7](https://github.com/sct/overseerr/commit/b4002d71323a04e7991198cedc263660e872df8d)) + + +### Features + +* generate real api key ([a839370](https://github.com/sct/overseerr/commit/a8393707fec85a9262af5ba8c03d205190b2235b)) +* **frontend:** add i18n strings for request list and request item ([6c4022f](https://github.com/sct/overseerr/commit/6c4022fb236583ad20d4c4c6693c1339e165b4af)) +* **frontend:** initial version of the requests page (no filtering/sorting) ([1ba027b](https://github.com/sct/overseerr/commit/1ba027b4357e078c3f177d9d07208049f0c1ce65)) +* **frontend:** only load request/tmdb cards when in the browser view ([2d51efd](https://github.com/sct/overseerr/commit/2d51efd71612ec969b83c62d6aa0dac6df9391a3)) + # 1.0.0 (2020-12-06) diff --git a/package.json b/package.json index dbee0f2fc..7046d216e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.0.0", + "version": "1.1.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 45ebfb937e6a27a11712427bde5a5227316af8ae Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 11 Dec 2020 22:55:12 +0000 Subject: [PATCH 003/238] chore(release): 1.2.0 --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf59f4a7..237b09a47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +# [1.2.0](https://github.com/sct/overseerr/compare/v1.1.0...v1.2.0) (2020-12-11) + + +### Bug Fixes + +* **frontend:** person cards now show correctly in ListView's ([ccb9855](https://github.com/sct/overseerr/commit/ccb98553f104c1aebd33796b7090cc9bbe964bd7)) +* **frontend:** properly remove site overlay when closing modals ([3fa7ff9](https://github.com/sct/overseerr/commit/3fa7ff9858d14d132151f3329164d55d74638f53)) +* **frontend:** switch to using Transition component for modals ([b16fbaf](https://github.com/sct/overseerr/commit/b16fbafa1f3d5e105c0a4ba6f1d66aa064019636)), closes [#220](https://github.com/sct/overseerr/issues/220) +* fix missing personid in Discover ([d8060af](https://github.com/sct/overseerr/commit/d8060afe02574337f51b88cab0a0f824976ac721)) +* missing personId in ListView component ([6502feb](https://github.com/sct/overseerr/commit/6502feb1a5be3c6daab33230814fe74632c87f7e)) +* **frontend:** update overflow issues with seasons + email ([#217](https://github.com/sct/overseerr/issues/217)) ([2d0afb2](https://github.com/sct/overseerr/commit/2d0afb29d37798a626e3f182571ccce43d80063c)), closes [#216](https://github.com/sct/overseerr/issues/216) +* **lang:** fix missing i18n string for agent enabled in email notification page ([42788ad](https://github.com/sct/overseerr/commit/42788adb75f7d23e68327688b1c542dd047e9609)) + + +### Features + +* **lang:** update language files ([8cd067b](https://github.com/sct/overseerr/commit/8cd067b6e9df1a3c8f4056789436a31177703986)) +* person details page ([d6eb3ae](https://github.com/sct/overseerr/commit/d6eb3ae64ef46bd62145010d3029e272676487c3)) +* **lang:** add nb-NO and de language support to app ([d38b28d](https://github.com/sct/overseerr/commit/d38b28d2061b38366989ff412957a5dee5766c6f)) +* **lang:** add support for dutch language ([df94db0](https://github.com/sct/overseerr/commit/df94db050bf68a925118e0ce865d27178b702f9e)) +* **lang:** add support for russian languge ([8d8e750](https://github.com/sct/overseerr/commit/8d8e7509826514eebc859374d2e1ab212cc442d1)) +* **lang:** added translation using Weblate (Russian) ([887f5dd](https://github.com/sct/overseerr/commit/887f5dd487b61676029652d99cbc5b40213aa22e)) +* **lang:** translated using Weblate (French) ([30a8934](https://github.com/sct/overseerr/commit/30a8934626fa2d47e95b5925d7e4227a0d0aa728)) +* **lang:** translated using Weblate (German) ([44dbb74](https://github.com/sct/overseerr/commit/44dbb745b6216ce19fab4740520785c6414cf367)) +* **lang:** translated using Weblate (Japanese) ([a494507](https://github.com/sct/overseerr/commit/a494507dfeafb0cfd2bd66fb01138522e0e80737)) +* **lang:** translated using Weblate (Russian) ([86cadb8](https://github.com/sct/overseerr/commit/86cadb8283fcab8745b4c09f8429fd9e46708813)) +* **lang:** translations update from Weblate ([#201](https://github.com/sct/overseerr/issues/201)) ([b0c663b](https://github.com/sct/overseerr/commit/b0c663baccd994e234b4d41d86486c3af4906344)) + # [1.1.0](https://github.com/sct/overseerr/compare/v1.0.0...v1.1.0) (2020-12-08) diff --git a/package.json b/package.json index cf38f2864..6d8636d62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.1.0", + "version": "1.2.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 157ab80be139ea09a96fdfc5c5832e26b16130f5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 14 Dec 2020 05:32:31 +0000 Subject: [PATCH 004/238] chore(release): 1.3.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 237b09a47..85757d6a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# [1.3.0](https://github.com/sct/overseerr/compare/v1.2.0...v1.3.0) (2020-12-14) + + +### Bug Fixes + +* **api:** correctly generate clientId on first startup ([5f09e83](https://github.com/sct/overseerr/commit/5f09e83ed870336638d3e9d94fcf55ead928e737)) + + +### Features + +* **frontend:** add full cast page for movies and series ([051f1b3](https://github.com/sct/overseerr/commit/051f1b3e899bf749e632743e5c8d45a02b621998)) +* **lang:** translated using Weblate (Dutch) ([1ab3a4b](https://github.com/sct/overseerr/commit/1ab3a4b80a081d7e4a201f1290cd270ed5b38ac7)) +* **lang:** translated using Weblate (English) ([0949c9b](https://github.com/sct/overseerr/commit/0949c9b334b3a4b6c342517a157a9e2b7596f2f0)) +* **lang:** translated using Weblate (French) ([f943701](https://github.com/sct/overseerr/commit/f943701e13c7f0de5a711302597858cc898b16e2)) +* **lang:** translated using Weblate (French) ([30d04ce](https://github.com/sct/overseerr/commit/30d04ce35adc21070cce37ab10384154afda191b)) +* **lang:** translated using Weblate (German) ([7bf9add](https://github.com/sct/overseerr/commit/7bf9addd13a707aac23b64ef3f1733e491d40a4e)) +* **lang:** translated using Weblate (German) ([b6e60a4](https://github.com/sct/overseerr/commit/b6e60a412b30907aea751a4cf1ce0cc8230f9814)) +* **lang:** translated using Weblate (Japanese) ([08e968f](https://github.com/sct/overseerr/commit/08e968fd0097ec7b2a65de064ed5b07e7c49ef39)) +* **lang:** translated using Weblate (Norwegian Bokmål) ([83efb0e](https://github.com/sct/overseerr/commit/83efb0e3d4d96b6a2d2ebdd85d36c9d78c1717b2)) +* **lang:** translated using Weblate (Russian) ([0d8e0d0](https://github.com/sct/overseerr/commit/0d8e0d0352f72fdb65ee8f054371eae08c39fe33)) + # [1.2.0](https://github.com/sct/overseerr/compare/v1.1.0...v1.2.0) (2020-12-11) diff --git a/package.json b/package.json index b624575b0..601ff756b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.2.0", + "version": "1.3.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From f1df9eeb2fd1fed28a234d263892fcdfe881f14e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 14 Dec 2020 08:03:51 +0000 Subject: [PATCH 005/238] chore(release): 1.3.1 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85757d6a0..1cab8681e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [1.3.1](https://github.com/sct/overseerr/compare/v1.3.0...v1.3.1) (2020-12-14) + + +### Bug Fixes + +* **frontend:** also convert activeProfileId to a number for radarr/sonarr submissions ([7bf924f](https://github.com/sct/overseerr/commit/7bf924f7e94a0e0834f41b4ec067ed277c652766)) +* **frontend:** also convert ports to numbers when saving radarr/sonarr servers ([c53dc3b](https://github.com/sct/overseerr/commit/c53dc3b15da522c6e6ab76bbc9d15008a8a9fb9d)) +* **frontend:** new radarr/sonarr ports will be converted to a number before posting ([92c9001](https://github.com/sct/overseerr/commit/92c9001c9d1f2cbd272a5897ea1157d2cadbce2d)) + # [1.3.0](https://github.com/sct/overseerr/compare/v1.2.0...v1.3.0) (2020-12-14) diff --git a/package.json b/package.json index 601ff756b..7b2f2324c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.3.0", + "version": "1.3.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 16bd3e9fd58f91759fb8b28087631cc6b87e267d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 14 Dec 2020 13:11:15 +0000 Subject: [PATCH 006/238] chore(release): 1.3.2 --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cab8681e..30305e3a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [1.3.2](https://github.com/sct/overseerr/compare/v1.3.1...v1.3.2) (2020-12-14) + + +### Bug Fixes + +* **frontend:** convert plex port to a number before posting to the api ([8cb05c4](https://github.com/sct/overseerr/commit/8cb05c413a15a4b74e37ece5e24367d115995b32)) +* **frontend:** converts email smtp port to a number before posting to the api ([2098a2d](https://github.com/sct/overseerr/commit/2098a2d3d2981fd2ae54392aec3ef81327f2858e)), closes [#251](https://github.com/sct/overseerr/issues/251) +* **frontend:** encode special characters in search input to prevent crashing router ([15013d6](https://github.com/sct/overseerr/commit/15013d6c5dbff15704c7c30d261d68a265e7f2d7)), closes [#252](https://github.com/sct/overseerr/issues/252) +* **plex sync:** catch errors that occur during processMovie ([edbbccf](https://github.com/sct/overseerr/commit/edbbccf3ae623430294f1a5c3fd2728dbd42e555)), closes [#244](https://github.com/sct/overseerr/issues/244) [#246](https://github.com/sct/overseerr/issues/246) [#250](https://github.com/sct/overseerr/issues/250) +* **services:** improve logging for adding movies to Radarr ([6c1ee83](https://github.com/sct/overseerr/commit/6c1ee830a183f89bb1fe96a181a7d61684e23b22)) +* **services:** radarr/sonarr will use the correct default server ([0658b79](https://github.com/sct/overseerr/commit/0658b7943e1ab25816db9da34d4c9ea808d9203d)) + ## [1.3.1](https://github.com/sct/overseerr/compare/v1.3.0...v1.3.1) (2020-12-14) diff --git a/package.json b/package.json index f137fa5e5..a14f5c87a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.3.1", + "version": "1.3.2", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 17df5dd0720ae4312b33f5e77c672aebb538fd23 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 15 Dec 2020 00:53:05 +0000 Subject: [PATCH 007/238] chore(release): 1.4.0 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30305e3a7..2790d2f9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [1.4.0](https://github.com/sct/overseerr/compare/v1.3.2...v1.4.0) (2020-12-15) + + +### Bug Fixes + +* changing parameter name to use correct 'port' [#276](https://github.com/sct/overseerr/issues/276) ([#277](https://github.com/sct/overseerr/issues/277)) ([6d08b10](https://github.com/sct/overseerr/commit/6d08b108200177ca3068c852e60a0df75ce2232a)) +* **services:** include radarr/sonarr baseUrl when adding media ([78af1a3](https://github.com/sct/overseerr/commit/78af1a3e6d00a5645a05e7bf3cf56a59439b6cc9)) + + +### Features + +* **lang:** Translations update from Weblate ([#240](https://github.com/sct/overseerr/issues/240)) ([e17c637](https://github.com/sct/overseerr/commit/e17c63748362b6a480693e003ef5eec614dcec43)) + ## [1.3.2](https://github.com/sct/overseerr/compare/v1.3.1...v1.3.2) (2020-12-14) diff --git a/package.json b/package.json index a14f5c87a..a99ebe184 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.3.2", + "version": "1.4.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 54b96cc4516e7a3f246d0d911272222bf9f02731 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 15 Dec 2020 09:05:37 +0000 Subject: [PATCH 008/238] chore(release): 1.5.0 --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2790d2f9e..8a1fd9827 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +# [1.5.0](https://github.com/sct/overseerr/compare/v1.4.0...v1.5.0) (2020-12-15) + + +### Bug Fixes + +* **api:** require package.json directly so typescript doesnt compile it into dist folder ([b9faa64](https://github.com/sct/overseerr/commit/b9faa6486b35aa865019aa8af9d307531054bc1d)) +* **frontend:** add validation for Radarr/Sonarr server name ([b5988f9](https://github.com/sct/overseerr/commit/b5988f9a5ff274e97f208c2726abe76c22c858ee)) +* **frontend:** only show alpha notice to admins ([ff61895](https://github.com/sct/overseerr/commit/ff618956b5d9cf933d867ea979b612c3d8a6f30b)) +* add support for ssl when connecting to plex ([3ba09d0](https://github.com/sct/overseerr/commit/3ba09d07eb0367c41603cd55e7ff41c66fb641c4)), closes [#275](https://github.com/sct/overseerr/issues/275) +* **services:** improve logging for when Radarr movie already exists ([#285](https://github.com/sct/overseerr/issues/285)) ([f998873](https://github.com/sct/overseerr/commit/f998873fc5669a547901f2733c9c785d744d27ca)), closes [#260](https://github.com/sct/overseerr/issues/260) + + +### Features + +* **lang:** add i18n strings for new about page ([900827b](https://github.com/sct/overseerr/commit/900827be97845688e4bea72a8c5d9611a3e9d069)) +* about page initial version ([3f2a04c](https://github.com/sct/overseerr/commit/3f2a04c881bf06b73a952181fa463af84454b0dd)) + # [1.4.0](https://github.com/sct/overseerr/compare/v1.3.2...v1.4.0) (2020-12-15) diff --git a/package.json b/package.json index a99ebe184..6d01ccc08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.4.0", + "version": "1.5.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 0bd1bee1facb6a7c3bd3f970d3c3432b9bc17f68 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 16 Dec 2020 05:56:57 +0000 Subject: [PATCH 009/238] chore(release): 1.6.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ package.json | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1fd9827..0d68d628b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +# [1.6.0](https://github.com/sct/overseerr/compare/v1.5.0...v1.6.0) (2020-12-16) + + +### Bug Fixes + +* **api:** accept the api key to perform actions on the api with X-API-Key header ([33f8831](https://github.com/sct/overseerr/commit/33f8831e880dc7fd3f69d951246cada5c6c0ffe7)) +* **api:** filter out libraries that do not have any metadata agent or are not movie/show ([01c179f](https://github.com/sct/overseerr/commit/01c179f762e686a1e5a3d4dab3a5bea53425b575)) +* **api:** only run recently added sync on enabled libraries ([e08fa35](https://github.com/sct/overseerr/commit/e08fa35548bb8644afa8df3124e6f9cc3a2c8f4a)), closes [#259](https://github.com/sct/overseerr/issues/259) +* **api:** set plex libraries to disabled if the name changes ([675060b](https://github.com/sct/overseerr/commit/675060bcdf23acbfd4de2900a65f95e74f4966a5)), closes [#324](https://github.com/sct/overseerr/issues/324) +* **frontend:** adds a tip to plex setup to clarify that syncing runs in the background ([df4ac83](https://github.com/sct/overseerr/commit/df4ac8361f82971ee845f3be217408a9123a0bf3)), closes [#325](https://github.com/sct/overseerr/issues/325) +* **frontend:** aligned movie and tv details ([#331](https://github.com/sct/overseerr/issues/331)) ([db0a5c4](https://github.com/sct/overseerr/commit/db0a5c44f678e76eee7f5582381016306d1f46a2)) +* **frontend:** close sidebar when clicking outside ([#333](https://github.com/sct/overseerr/issues/333)) ([6d7907e](https://github.com/sct/overseerr/commit/6d7907e844a909993d185759d660632f55aeaa35)) +* spelling mistake on the word 'requested' fixed ([#319](https://github.com/sct/overseerr/issues/319)) ([961d110](https://github.com/sct/overseerr/commit/961d1107208069a6fc820a1ba97ffda7336677cb)) + + +### Features + +* add version to startup logs ([2948f93](https://github.com/sct/overseerr/commit/2948f9360eb484d1d6c0740a840135ca97e7240a)) +* **frontend:** temporary logs page to clear up confusion about it 404ing ([d9788c4](https://github.com/sct/overseerr/commit/d9788c4aa9f87e2eda3f7e3f1adc985f16039552)), closes [#272](https://github.com/sct/overseerr/issues/272) +* **lang:** add support for Spanish language ([6cd2049](https://github.com/sct/overseerr/commit/6cd20491d2a0ceb995c4744eeb92a6e2f57a4893)) +* **lang:** Translations update from Weblate ([#291](https://github.com/sct/overseerr/issues/291)) ([fddbb3c](https://github.com/sct/overseerr/commit/fddbb3cdfe3d50b2835c248556139c769dc2b805)) + # [1.5.0](https://github.com/sct/overseerr/compare/v1.4.0...v1.5.0) (2020-12-15) diff --git a/package.json b/package.json index 6d01ccc08..4c98b350e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.5.0", + "version": "1.6.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From a9144a21670ec598d10f1bf91dfeff11129b2c12 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 17 Dec 2020 14:09:58 +0000 Subject: [PATCH 010/238] chore(release): 1.7.0 --- CHANGELOG.md | 19 +++++++++++++++++++ package.json | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d68d628b..33ce0b28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# [1.7.0](https://github.com/sct/overseerr/compare/v1.6.0...v1.7.0) (2020-12-17) + + +### Bug Fixes + +* **email:** do not pass auth object to transport if no auth data present ([d5eb4d8](https://github.com/sct/overseerr/commit/d5eb4d8d438a159266b2de66b6bcdd9440a0c8ef)), closes [#312](https://github.com/sct/overseerr/issues/312) +* **frontend:** add http/https prefix to hostname fields for plex/radarr/sonarr ([ce0266f](https://github.com/sct/overseerr/commit/ce0266f74ea3979b291ff962271a928682892788)), closes [#357](https://github.com/sct/overseerr/issues/357) +* **frontend:** clarify that radarr/sonnarr servers must be tested before profiles/folders appear ([fc12ab8](https://github.com/sct/overseerr/commit/fc12ab84d9482eb3a11f117f8cab6fd48a9401cd)), closes [#326](https://github.com/sct/overseerr/issues/326) [#328](https://github.com/sct/overseerr/issues/328) +* **frontend:** correctly show an unauthorized error when a user fails to login ([18925de](https://github.com/sct/overseerr/commit/18925decafdac518f52a354c594cc378d2529022)), closes [#322](https://github.com/sct/overseerr/issues/322) +* **frontend:** fix tv shows failing to open when firstAirDate is undefined ([c21fa5b](https://github.com/sct/overseerr/commit/c21fa5b5350abdd8e03c077fde7246fa398e176e)), closes [#347](https://github.com/sct/overseerr/issues/347) +* **frontend:** make minimum availability required for Radarr servers ([2fe53ec](https://github.com/sct/overseerr/commit/2fe53ec5a8534e75c7d0cef31a8b46065111e0a7)), closes [#345](https://github.com/sct/overseerr/issues/345) +* **plex-sync:** bundle duplicate ratingKeys to speed up recently added sync ([67146c3](https://github.com/sct/overseerr/commit/67146c33ef7f28d520ba2c50b32673d43f4525c8)), closes [#360](https://github.com/sct/overseerr/issues/360) +* **sonarr.ts, mediarequest.ts:** add missing seasonFolder option ([#358](https://github.com/sct/overseerr/issues/358)) ([e9c899c](https://github.com/sct/overseerr/commit/e9c899ce419d149dde2ad9a0f7d5a2f2545b3ebf)) + + +### Features + +* **frontend:** show alert when there are no default radarr/sonarr servers ([0d088e0](https://github.com/sct/overseerr/commit/0d088e085e68d39455fda21d1fd08ebcaef2c06b)), closes [#344](https://github.com/sct/overseerr/issues/344) + # [1.6.0](https://github.com/sct/overseerr/compare/v1.5.0...v1.6.0) (2020-12-16) diff --git a/package.json b/package.json index 4c98b350e..c30732e0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.6.0", + "version": "1.7.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From bee6eb288fa80d20a3f0c0ff867148357c7bd060 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 17 Dec 2020 15:54:40 +0000 Subject: [PATCH 011/238] chore(release): 1.8.0 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33ce0b28e..472a588cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [1.8.0](https://github.com/sct/overseerr/compare/v1.7.0...v1.8.0) (2020-12-17) + + +### Features + +* **lang:** translations update from Weblate ([#336](https://github.com/sct/overseerr/issues/336)) ([ee84f74](https://github.com/sct/overseerr/commit/ee84f74f8a3558875b41daa539f42d00b949898a)) + # [1.7.0](https://github.com/sct/overseerr/compare/v1.6.0...v1.7.0) (2020-12-17) diff --git a/package.json b/package.json index c30732e0d..c27830053 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.7.0", + "version": "1.8.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From abb0b2d763ab61da759bdaecdd37fd1951b2c1cf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 18 Dec 2020 12:05:35 +0000 Subject: [PATCH 012/238] chore(release): 1.9.0 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 472a588cc..64c6635fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [1.9.0](https://github.com/sct/overseerr/compare/v1.8.0...v1.9.0) (2020-12-18) + + +### Features + +* api key regeneration ([6beac73](https://github.com/sct/overseerr/commit/6beac736efcf7b9102e02e43b75d91a9a158cd22)) +* **api:** add movie keyword search ([f88c4a6](https://github.com/sct/overseerr/commit/f88c4a6d4a49f8f3451ba6c85153677f33b7f5f6)) +* **frontend:** add studio/networks to movie/tv details ([4b6ad8a](https://github.com/sct/overseerr/commit/4b6ad8a3871957db4192b603abf38404250cea5d)), closes [#370](https://github.com/sct/overseerr/issues/370) +* **frontend:** added user deletion to the user list ([727fa06](https://github.com/sct/overseerr/commit/727fa06c18febb2a97ca219cc6bf0277ff462acd)), closes [#348](https://github.com/sct/overseerr/issues/348) +* **holiday:** special seasonal slider added to discover :) ([908f635](https://github.com/sct/overseerr/commit/908f63557ca03a1da8b16809ffa2c3acd782d94e)) +* allow to listen server on specific host interface ([#381](https://github.com/sct/overseerr/issues/381)) ([086183b](https://github.com/sct/overseerr/commit/086183b5636aa8d075d01fe59492c3eab0d1345b)), closes [#273](https://github.com/sct/overseerr/issues/273) +* anime profile support ([#384](https://github.com/sct/overseerr/issues/384)) ([0972f40](https://github.com/sct/overseerr/commit/0972f40a4e1fb3b5f02b07ae46b997d71aab9bfb)), closes [#266](https://github.com/sct/overseerr/issues/266) + # [1.8.0](https://github.com/sct/overseerr/compare/v1.7.0...v1.8.0) (2020-12-17) diff --git a/package.json b/package.json index 64dd419c0..2db4cf153 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.8.0", + "version": "1.9.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 048b6c1d9036b9e441b3f1220909bcc906cbb0be Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 18 Dec 2020 16:08:58 +0000 Subject: [PATCH 013/238] chore(release): 1.9.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64c6635fa..8ce50056b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.9.1](https://github.com/sct/overseerr/compare/v1.9.0...v1.9.1) (2020-12-18) + + +### Bug Fixes + +* change default internal port to 5055 ([#389](https://github.com/sct/overseerr/issues/389)) ([5e5ba40](https://github.com/sct/overseerr/commit/5e5ba4050563f07bff367d2fb31ed7e7fca4291e)) + # [1.9.0](https://github.com/sct/overseerr/compare/v1.8.0...v1.9.0) (2020-12-18) diff --git a/package.json b/package.json index 2db4cf153..585c0b9b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.9.0", + "version": "1.9.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 077513151e8aade3429299370b9b2780fca20fc4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 19 Dec 2020 04:54:22 +0000 Subject: [PATCH 014/238] chore(release): 1.10.0 --- CHANGELOG.md | 19 +++++++++++++++++++ package.json | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ce50056b..5d565787b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# [1.10.0](https://github.com/sct/overseerr/compare/v1.9.1...v1.10.0) (2020-12-19) + + +### Bug Fixes + +* **email:** fix link to Overseerr in email templates ([816fec1](https://github.com/sct/overseerr/commit/816fec1a83a53edb3b65c3e5e7d0e6e1bd49726d)), closes [#392](https://github.com/sct/overseerr/issues/392) +* **frontend:** adjust padding of search box so placeholder text fits on mobile ([3601d44](https://github.com/sct/overseerr/commit/3601d442db32d3f98f7b050365c11ea8ef9bc4ae)), closes [#393](https://github.com/sct/overseerr/issues/393) +* **frontend:** changed request block for slideover on mobile UI ([#387](https://github.com/sct/overseerr/issues/387)) ([549567a](https://github.com/sct/overseerr/commit/549567a7e9db01933546d9970fc06f17218dfab1)) +* **frontend:** hide Request More button if all current seasons are available ([2a4dd52](https://github.com/sct/overseerr/commit/2a4dd52275007e48f946c3b9e29f1d78da57bdaa)), closes [#343](https://github.com/sct/overseerr/issues/343) +* **frontend:** try not to render broken rottentomatoes data ([a0c5608](https://github.com/sct/overseerr/commit/a0c5608aa0b6c7a4294300589efa9a662163ce48)) + + +### Features + +* **lang:** translations update from Weblate ([#391](https://github.com/sct/overseerr/issues/391)) ([5f71fb7](https://github.com/sct/overseerr/commit/5f71fb7ee280714275d2ac045c472fcdddd5a2ea)) +* add missing tzdata package to image ([53bede6](https://github.com/sct/overseerr/commit/53bede692d4f0e940dededa63015fe1908129914)), closes [#394](https://github.com/sct/overseerr/issues/394) +* **frontend:** add external links to movie and tv detail pages ([a0024a0](https://github.com/sct/overseerr/commit/a0024a0cbe717d78f53413bb78644c829f143c4d)) +* **lang:** translations update from Weblate ([#380](https://github.com/sct/overseerr/issues/380)) ([8408e19](https://github.com/sct/overseerr/commit/8408e19568b2f239c57e11e2946c75f193d1c22e)) + ## [1.9.1](https://github.com/sct/overseerr/compare/v1.9.0...v1.9.1) (2020-12-18) diff --git a/package.json b/package.json index e3b9add29..2e77c7587 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.9.1", + "version": "1.10.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 0a3d7c5915251ca5dace907a44372a08039afd98 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 20 Dec 2020 01:18:39 +0000 Subject: [PATCH 015/238] chore(release): 1.11.0 --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d565787b..c5d584fd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [1.11.0](https://github.com/sct/overseerr/compare/v1.10.0...v1.11.0) (2020-12-20) + + +### Features + +* **frontend:** add language picker to setup/login ([ff2ab29](https://github.com/sct/overseerr/commit/ff2ab29491a80c421525b9a394d6fbbf54914dc2)) +* **frontend:** add support overseerr block to about page ([c128898](https://github.com/sct/overseerr/commit/c128898206d6cbb482de4d8dca53f70b87e4911a)) +* **frontend:** releases added to about page ([b7f5739](https://github.com/sct/overseerr/commit/b7f573903500cc8a62e39afd787bc1da8c09d88b)), closes [#303](https://github.com/sct/overseerr/issues/303) +* **lang:** add support for Italian, Portuguese (Brazil) and Serbian ([108dfc4](https://github.com/sct/overseerr/commit/108dfc4afd31388cb6c9e07deccd168ade8b1574)) +* **lang:** add support for swedish language ([c9fe6cb](https://github.com/sct/overseerr/commit/c9fe6cb0b7ea984d8e4e1cb3f284935c9da7cc2b)) +* **lang:** translations update from Weblate ([#400](https://github.com/sct/overseerr/issues/400)) ([1bd0e64](https://github.com/sct/overseerr/commit/1bd0e646e313ddf77ef331e818e03401fbf64a72)) +* **lang:** translations update from Weblate ([#403](https://github.com/sct/overseerr/issues/403)) ([3778ad8](https://github.com/sct/overseerr/commit/3778ad829c0897de178212b3bde4c0d3b5089161)) + # [1.10.0](https://github.com/sct/overseerr/compare/v1.9.1...v1.10.0) (2020-12-19) diff --git a/package.json b/package.json index eb68615b2..e346acbb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.10.0", + "version": "1.11.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 96328ab5c63d4b7295ec2d21616ee561266108c7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 22 Dec 2020 02:26:31 +0000 Subject: [PATCH 016/238] chore(release): 1.12.0 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5d584fd9..b51f04cee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +# [1.12.0](https://github.com/sct/overseerr/compare/v1.11.0...v1.12.0) (2020-12-22) + + +### Bug Fixes + +* **api:** fix cross-imported type crashing build ([f35dae5](https://github.com/sct/overseerr/commit/f35dae56a583a5545375318fa5be994ae1f2557f)) +* **api:** prevent checking first admin account for plex server access ([22006e9](https://github.com/sct/overseerr/commit/22006e9dbde82609440f89bde9a40887b4742682)) +* **frontend:** add name, short_name and start_url to manifest ([#424](https://github.com/sct/overseerr/issues/424)) ([c6836e0](https://github.com/sct/overseerr/commit/c6836e02c810e8adb12c3a4b110f9604cf5b7b81)) +* **frontend:** adjust person card layout to deal with overflowing content ([4891298](https://github.com/sct/overseerr/commit/48912988915ae40606a900a6f1dd23fc25ed567f)), closes [#416](https://github.com/sct/overseerr/issues/416) +* **frontend:** allow more special characters in search input ([5deb64a](https://github.com/sct/overseerr/commit/5deb64a87fd70e97da27a025ad11fb8ace0e0b57)), closes [#430](https://github.com/sct/overseerr/issues/430) +* **logs:** improve logging when adding to sonarr/radarr ([4b50522](https://github.com/sct/overseerr/commit/4b505223b881a750007e3fbc7d4bcb9677d4d412)) +* only run migrations in production ([ab9cef3](https://github.com/sct/overseerr/commit/ab9cef3624b5db1ec03507553a69d33b87857e29)) +* **notifications:** always update the media table when seasons become available ([0916b58](https://github.com/sct/overseerr/commit/0916b58594a00db98c6701fdcaee4f3c3e08904e)) +* **plex-sync:** fixes processing movies using TMDB agent ([764db94](https://github.com/sct/overseerr/commit/764db94f1bd7866309684d5bd56033b21cbc2e0c)), closes [#363](https://github.com/sct/overseerr/issues/363) + + +### Features + +* **frontend:** add crew related movies/shows to person details page ([12127a7](https://github.com/sct/overseerr/commit/12127a77633f0e92ae88cbafd49581296f559c33)) +* **frontend:** add full crew page for movies/shows ([604ba2a](https://github.com/sct/overseerr/commit/604ba2a92f1d59489e7fc6dfc011347f8595c123)) +* default user permissions added to settings ([e7ee85c](https://github.com/sct/overseerr/commit/e7ee85c29b5d25c6bff58717eae5e62de4dcef0c)), closes [#388](https://github.com/sct/overseerr/issues/388) +* import users from plex ([#428](https://github.com/sct/overseerr/issues/428)) ([7e8f361](https://github.com/sct/overseerr/commit/7e8f361af711001cfc4dcc06a384b76f9846f90f)), closes [#281](https://github.com/sct/overseerr/issues/281) +* **frontend:** add prioritized crew under overview ([6753d9d](https://github.com/sct/overseerr/commit/6753d9daaafb18672f14fd86f2c1675dcec39b13)), closes [#406](https://github.com/sct/overseerr/issues/406) +* **notifications:** added ability to send test notifications ([44a3054](https://github.com/sct/overseerr/commit/44a305426f3e9829c167a4a73095d0d248641f47)), closes [#309](https://github.com/sct/overseerr/issues/309) + + +### Reverts + +* **deps:** revert react-use-clipboard to 1.0.2 ([7083ddf](https://github.com/sct/overseerr/commit/7083ddf18121716e3442acab3506c395fdc351ac)) + # [1.11.0](https://github.com/sct/overseerr/compare/v1.10.0...v1.11.0) (2020-12-20) diff --git a/package.json b/package.json index 5de8bf866..00e25630a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.11.0", + "version": "1.12.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 741b2c4e9f5065f5a86a91db50e3941673618748 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 22 Dec 2020 03:17:58 +0000 Subject: [PATCH 017/238] chore(release): 1.12.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b51f04cee..e6b587975 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.12.1](https://github.com/sct/overseerr/compare/v1.12.0...v1.12.1) (2020-12-22) + + +### Bug Fixes + +* **migration:** fixes issue migrating away from the unique imdbId constraint ([69fd7a5](https://github.com/sct/overseerr/commit/69fd7a5511215674a5c22ba48627f221da900229)) + # [1.12.0](https://github.com/sct/overseerr/compare/v1.11.0...v1.12.0) (2020-12-22) diff --git a/package.json b/package.json index 00e25630a..28d742a94 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.12.0", + "version": "1.12.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 7e4175ff212cb07ebbfb27499101bfff75cbe070 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 23 Dec 2020 12:44:01 +0000 Subject: [PATCH 018/238] chore(release): 1.13.0 --- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6b587975..0965b5707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# [1.13.0](https://github.com/sct/overseerr/compare/v1.12.1...v1.13.0) (2020-12-23) + + +### Bug Fixes + +* **api:** correctly return firstAirDate for series in search endpoints ([32b4c99](https://github.com/sct/overseerr/commit/32b4c99950659d9e1da2ffa93c22383c54d0d904)), closes [#462](https://github.com/sct/overseerr/issues/462) +* **email:** correctly log errors when emails fail to send ([0980fa5](https://github.com/sct/overseerr/commit/0980fa54f9fc3bdfae6c57fa5a20ce3b2a88a677)) +* **frontend:** added new Radarr v3 logo ([#471](https://github.com/sct/overseerr/issues/471)) ([3bbc716](https://github.com/sct/overseerr/commit/3bbc716434dc04bfe6b55de9898eb2c0ecb03baa)) +* **frontend:** approve and decline button (in manage panel) will now fit on mobile ([#441](https://github.com/sct/overseerr/issues/441)) ([66ef72d](https://github.com/sct/overseerr/commit/66ef72dd42912d83ea8f86aabb75fbee547f8de9)) +* **frontend:** filter out undefined backdrop paths for person details page ([2e0e4d5](https://github.com/sct/overseerr/commit/2e0e4d5129ed4912415f61eb8d1da41e88ddcaff)) +* **frontend:** show backdrops instead of posters for new person detail design ([9f5f920](https://github.com/sct/overseerr/commit/9f5f920c23007363aa7f53ebef0b61236d4f53ea)) +* clarify full sync runs every 24 hours ([0c8a180](https://github.com/sct/overseerr/commit/0c8a180189b2610bab2fa977d458743d8a60343e)) +* **plex-sync:** match correct tmdb format for movies ([4205e32](https://github.com/sct/overseerr/commit/4205e32ae71bc18c07209f1c82e6af1cb5f01335)) + + +### Features + +* **email:** option to allow self signed certificates ([6898357](https://github.com/sct/overseerr/commit/6898357b13a6aa53a55709ea95819c2b3df6784c)) +* **frontend:** adjust person details design and add improved truncate ([1fb7ea7](https://github.com/sct/overseerr/commit/1fb7ea72589d2908ae80a2a688881d4eb3c050e5)) +* **frontend:** first air date added to TV details page ([#470](https://github.com/sct/overseerr/issues/470)) ([a7db01f](https://github.com/sct/overseerr/commit/a7db01fba483ca633a6eb9d39eb085ab9939d4d2)) +* **lang:** translations update from Weblate ([#410](https://github.com/sct/overseerr/issues/410)) ([941fe19](https://github.com/sct/overseerr/commit/941fe1990454439cf05b48ef92bd3493432f8ed8)) +* **logs:** rotate log files if they reach 20MB in size ([22002ab](https://github.com/sct/overseerr/commit/22002ab4c76aace2bb202ac58da605b7a6f75d6d)), closes [#438](https://github.com/sct/overseerr/issues/438) +* **notifications:** include direct links to media in notifications ([659fa50](https://github.com/sct/overseerr/commit/659fa505f0db32262ad0041cddb4daea893e6d65)), closes [#437](https://github.com/sct/overseerr/issues/437) +* **plex-sync:** add support for hama guid's ([ffe9e19](https://github.com/sct/overseerr/commit/ffe9e19c3b99de6af1185900e292da641ff44320)), closes [#453](https://github.com/sct/overseerr/issues/453) + ## [1.12.1](https://github.com/sct/overseerr/compare/v1.12.0...v1.12.1) (2020-12-22) diff --git a/package.json b/package.json index 6d0c11c9d..f8f14eeda 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.12.1", + "version": "1.13.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 2cdf755c204024d1d71d76011edb3331061754b7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 25 Dec 2020 11:52:44 +0000 Subject: [PATCH 019/238] chore(release): 1.14.0 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0965b5707..36c205da8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +# [1.14.0](https://github.com/sct/overseerr/compare/v1.13.0...v1.14.0) (2020-12-25) + + +### Bug Fixes + +* **frontend:** add margin to ButtonWithDropdown component on movie/tv details page ([06fc98b](https://github.com/sct/overseerr/commit/06fc98b6b958221fa180f57f702c348f15b31f1c)) +* **frontend:** correctly position title card hover section ([#486](https://github.com/sct/overseerr/issues/486)) ([4b7af86](https://github.com/sct/overseerr/commit/4b7af86111a0300e1a137f23fa4ad1639fa55feb)) +* **frontend:** fix missing styles for alert component ([de3d288](https://github.com/sct/overseerr/commit/de3d288949b60d3a3af889d69a62bea2bc799ed7)) +* **frontend:** fix mobile dropdown in notifications settings ([6353cda](https://github.com/sct/overseerr/commit/6353cda5825f442dd539886c7b9ba437edf27ac4)) +* **frontend:** fix scaling titlecard content position ([bd94740](https://github.com/sct/overseerr/commit/bd947409e6e8ff313011b77adc76ccd5f9112c78)) +* **frontend:** improve flex header on movie/tv details page ([d7b1c28](https://github.com/sct/overseerr/commit/d7b1c2840690c144ebf29a360defcbd6fdb21354)) +* **frontend:** invalid dom-nesting title card fix ([#482](https://github.com/sct/overseerr/issues/482)) ([f2ebba7](https://github.com/sct/overseerr/commit/f2ebba7b1df775d33d2af6abc3ee2c9de5f2e57a)), closes [#476](https://github.com/sct/overseerr/issues/476) +* **frontend:** remove vote permission for now ([5d06a34](https://github.com/sct/overseerr/commit/5d06a347311bd10c05d8f58068ca7104e265dcca)) +* **frontend:** sort person detail credits by tmdb votes ([17518db](https://github.com/sct/overseerr/commit/17518dbe7f545100770a892d03d1f8508adc3650)) +* **frontend:** status badge Unavailable renamed to Requested ([ed94a0f](https://github.com/sct/overseerr/commit/ed94a0f335c59de526dd812aea7616313fe002fd)), closes [#374](https://github.com/sct/overseerr/issues/374) +* **frontend:** update titlecard status badge to new requested colors ([8f292d5](https://github.com/sct/overseerr/commit/8f292d538b937ea133175089979ef02599f6fef4)) +* **logs:** rotate logs on a daily basis instead of incrementing log filename ([395cbb2](https://github.com/sct/overseerr/commit/395cbb2be6c62f1d7573593e49a93615eaf22853)) +* improve apple-touch-icon and android app icons ([329a814](https://github.com/sct/overseerr/commit/329a814a8fb791122266c0b04b05848c71d68ba1)) + + +### Features + +* **lang:** translations update from Weblate ([#479](https://github.com/sct/overseerr/issues/479)) ([c8c74b0](https://github.com/sct/overseerr/commit/c8c74b0ae54fcc524aa8b2edf5a5c5e5db6c1638)) +* **notifications:** add slack notification agent ([1163e81](https://github.com/sct/overseerr/commit/1163e81adc7da1e8334155ebee5b4672a22143db)), closes [#365](https://github.com/sct/overseerr/issues/365) +* add collections ([#484](https://github.com/sct/overseerr/issues/484)) ([a333a09](https://github.com/sct/overseerr/commit/a333a095820ce3f10857026ba4770a2fffeed7cb)), closes [#418](https://github.com/sct/overseerr/issues/418) +* add separate auto approve permissions for Movies/Series ([4809257](https://github.com/sct/overseerr/commit/480925781691de456abc427fbbba161be11a3a8a)), closes [#268](https://github.com/sct/overseerr/issues/268) +* simple failed request handling ([#474](https://github.com/sct/overseerr/issues/474)) ([02969d5](https://github.com/sct/overseerr/commit/02969d5426245062a2f53475d83c4a8639632c9d)) +* YouTube Movie/TV Trailers ([#454](https://github.com/sct/overseerr/issues/454)) ([e88dc83](https://github.com/sct/overseerr/commit/e88dc83aeba0475e3ad421d5ab130cea4fc9a806)) + # [1.13.0](https://github.com/sct/overseerr/compare/v1.12.1...v1.13.0) (2020-12-23) diff --git a/package.json b/package.json index 69e4e4168..2b1f335e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.13.0", + "version": "1.14.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 22f2037ea6c5a0ba2ffa4d69f2b7cf42bdcf8575 Mon Sep 17 00:00:00 2001 From: sct Date: Sat, 2 Jan 2021 08:33:24 +0000 Subject: [PATCH 020/238] fix(holiday): remove special holiday slider --- src/components/Discover/Holiday/index.tsx | 70 -------------------- src/components/Discover/index.tsx | 81 ++++------------------- src/pages/discover/holiday.tsx | 9 --- 3 files changed, 12 insertions(+), 148 deletions(-) delete mode 100644 src/components/Discover/Holiday/index.tsx delete mode 100644 src/pages/discover/holiday.tsx diff --git a/src/components/Discover/Holiday/index.tsx b/src/components/Discover/Holiday/index.tsx deleted file mode 100644 index 319101946..000000000 --- a/src/components/Discover/Holiday/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useContext } from 'react'; -import { useSWRInfinite } from 'swr'; -import type { MovieResult } from '../../../../server/models/Search'; -import ListView from '../../Common/ListView'; -import { LanguageContext } from '../../../context/LanguageContext'; -import Header from '../../Common/Header'; - -interface SearchResult { - page: number; - totalResults: number; - totalPages: number; - results: MovieResult[]; -} - -const Holiday: React.FC = () => { - const { locale } = useContext(LanguageContext); - const { data, error, size, setSize } = useSWRInfinite( - (pageIndex: number, previousPageData: SearchResult | null) => { - if (previousPageData && pageIndex + 1 > previousPageData.totalPages) { - return null; - } - - return `/api/v1/discover/keyword/207317/movies?page=${ - pageIndex + 1 - }&language=${locale}`; - }, - { - initialSize: 3, - } - ); - - const isLoadingInitialData = !data && !error; - const isLoadingMore = - isLoadingInitialData || - (size > 0 && data && typeof data[size - 1] === 'undefined'); - - const fetchMore = () => { - setSize(size + 1); - }; - - if (error) { - return
{error}
; - } - - const titles = data?.reduce( - (a, v) => [...a, ...v.results], - [] as MovieResult[] - ); - - const isEmpty = !isLoadingInitialData && titles?.length === 0; - const isReachingEnd = - isEmpty || (data && data[data.length - 1]?.results.length < 20); - - return ( - <> -
Happy Holidays!
- 0) - } - isReachingEnd={isReachingEnd} - onScrollBottom={fetchMore} - /> - - ); -}; - -export default Holiday; diff --git a/src/components/Discover/index.tsx b/src/components/Discover/index.tsx index 7790150f9..5377970d0 100644 --- a/src/components/Discover/index.tsx +++ b/src/components/Discover/index.tsx @@ -63,12 +63,6 @@ const Discover: React.FC = () => { } = useSWR( `/api/v1/discover/movies/upcoming?language=${locale}` ); - const { - data: holUpcomingData, - error: holUpcomingError, - } = useSWR( - `/api/v1/discover/keyword/207317/movies?language=${locale}` - ); const { data: trendingData, error: trendingError } = useSWR( `/api/v1/discover/trending?language=${locale}` @@ -87,9 +81,9 @@ const Discover: React.FC = () => { return ( <> -
+
-
+
@@ -108,10 +102,10 @@ const Discover: React.FC = () => { /> ))} /> -
+
- + @@ -146,61 +140,10 @@ const Discover: React.FC = () => { placeholder={} emptyMessage={intl.formatMessage(messages.nopending)} /> - {/* Special Temporary Slider */} - - ( - - ))} - /> - {/* End Special Temporary Slider */} -
+
- + @@ -240,10 +183,10 @@ const Discover: React.FC = () => { /> ))} /> -
+
- + @@ -308,10 +251,10 @@ const Discover: React.FC = () => { } })} /> -
+
- + @@ -351,10 +294,10 @@ const Discover: React.FC = () => { /> ))} /> -
+
- + diff --git a/src/pages/discover/holiday.tsx b/src/pages/discover/holiday.tsx deleted file mode 100644 index 3123acb80..000000000 --- a/src/pages/discover/holiday.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { NextPage } from 'next'; -import Holiday from '../../components/Discover/Holiday'; - -const HolidayPage: NextPage = () => { - return ; -}; - -export default HolidayPage; From d77f9eb44eb05e9961d08904af101d9460c848a2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 2 Jan 2021 08:57:24 +0000 Subject: [PATCH 021/238] chore(release): 1.14.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36c205da8..28ceb3e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.14.1](https://github.com/sct/overseerr/compare/v1.14.0...v1.14.1) (2021-01-02) + + +### Bug Fixes + +* **holiday:** remove special holiday slider ([22f2037](https://github.com/sct/overseerr/commit/22f2037ea6c5a0ba2ffa4d69f2b7cf42bdcf8575)) + # [1.14.0](https://github.com/sct/overseerr/compare/v1.13.0...v1.14.0) (2020-12-25) diff --git a/package.json b/package.json index 2b1f335e6..6a577b155 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.14.0", + "version": "1.14.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From fd90adba4d3b2abd7bc6b647c18c9ef4c0ee58ed Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 4 Jan 2021 12:01:25 +0000 Subject: [PATCH 022/238] chore(release): 1.15.0 --- CHANGELOG.md | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28ceb3e21..e4924cf32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,41 @@ +# [1.15.0](https://github.com/sct/overseerr/compare/v1.14.1...v1.15.0) (2021-01-04) + + +### Bug Fixes + +* **api:** return 202 when same seasons are requested again ([5c84702](https://github.com/sct/overseerr/commit/5c847026aad79fcac4d020786ded9f867696c226)) +* **build:** fixes build to include commit tag for app build step ([289864a](https://github.com/sct/overseerr/commit/289864af1a995ce04834bf8a220cc238e1954d19)) +* **docs:** fix typo in build instructions ([#503](https://github.com/sct/overseerr/issues/503)) ([2b27a71](https://github.com/sct/overseerr/commit/2b27a715b07c27200ba1e5e9623629a34389276d)) +* **frontend:** add i18n for request text on titlecard ([a524b9c](https://github.com/sct/overseerr/commit/a524b9c4c8968f6823d33eb270dc26069fe4a725)) +* **frontend:** add localized strings for status checker ([2dcda39](https://github.com/sct/overseerr/commit/2dcda39d40d820419e098bd6f1101eb820e5b42d)) +* **frontend:** center text in movie auto-approve modal on small screens ([#510](https://github.com/sct/overseerr/issues/510)) ([1438b08](https://github.com/sct/overseerr/commit/1438b08cf0b358d79c6688c64be99f1718ec2d23)), closes [#507](https://github.com/sct/overseerr/issues/507) +* **frontend:** change titlecard to only have a request button ([b5a3a7a](https://github.com/sct/overseerr/commit/b5a3a7a89fcaf86dd794dc419711677b53646577)) +* **frontend:** combine duplicate credits on a persons detail page ([d188f6f](https://github.com/sct/overseerr/commit/d188f6ffadff1564c47d5f33138e35498bed29fd)), closes [#504](https://github.com/sct/overseerr/issues/504) +* **frontend:** disable pointer-events on titlecard badges ([ce06879](https://github.com/sct/overseerr/commit/ce0687922a94588b3492e8ddf2e84f54dd1a0d4e)) +* **frontend:** fix count of requests in request list ([f124d73](https://github.com/sct/overseerr/commit/f124d732a2911abdccb5abc11471efe61cc20f7a)) +* **frontend:** fix sliders overflowing on firefox ([67ac9e0](https://github.com/sct/overseerr/commit/67ac9e075f0ca1cfe7e4766d9168815d7ab600fa)), closes [#566](https://github.com/sct/overseerr/issues/566) +* **frontend:** full season request modal fits on a smaller mobile UI ([#535](https://github.com/sct/overseerr/issues/535)) ([12db7a0](https://github.com/sct/overseerr/commit/12db7a065ad566b47d46de4b949343290894f153)) +* **frontend:** handle currentLibrary possibly being null on first manual sync ([93b57a7](https://github.com/sct/overseerr/commit/93b57a76f10a823615ca11ff59f523b67aa30fad)) +* **frontend:** increase titlecard status badge size on larger screens ([ba106c4](https://github.com/sct/overseerr/commit/ba106c447d76db2f9ac70a60c5b38cc60ab554fe)) +* **frontend:** search clear button now correctly triggers routing ([343f466](https://github.com/sct/overseerr/commit/343f466788abc308b91a414ef61bba816ac8875c)) +* **frontend:** set locale cookie expiration to be much longer ([fae4818](https://github.com/sct/overseerr/commit/fae481895736eab81d52eb93788beb00669fb355)) +* **frontend:** show movie/series badges always ([8cbf39a](https://github.com/sct/overseerr/commit/8cbf39a9d12eaee7720fa4721c350c1ef9dee856)) +* **frontend:** update login/setup images ([058fb65](https://github.com/sct/overseerr/commit/058fb65495baa08a0bd4c9e0aef320c6fc7d017b)) +* **holiday:** remove special holiday slider ([8c09033](https://github.com/sct/overseerr/commit/8c0903393cf2cb2a929ba70a8ab6ddcc4cba0574)) +* correctly deal with tmdb id duplicates between movies/series ([721ed9a](https://github.com/sct/overseerr/commit/721ed9a93087a57ae749388bddcacf26022e3df6)), closes [#526](https://github.com/sct/overseerr/issues/526) +* use new commit tag file for app version as well ([d00e470](https://github.com/sct/overseerr/commit/d00e470b55327489b49d770144b7cfdb24045be6)) + + +### Features + +* **email:** add sendername to email notification ([#506](https://github.com/sct/overseerr/issues/506)) ([0185bb1](https://github.com/sct/overseerr/commit/0185bb1a7084c1faeb61fb1c63e34e26732711c8)) +* **frontend:** add clear-field-icon to search field ([#498](https://github.com/sct/overseerr/issues/498)) ([7434a26](https://github.com/sct/overseerr/commit/7434a26f76b5e9f74918f3e1a34443d20ecfcbe4)) +* **frontend:** add documentation link to about page ([c034496](https://github.com/sct/overseerr/commit/c034496f557a031aed35cd28dc7221d8cdf36643)) +* **frontend:** add telegram integration ([#491](https://github.com/sct/overseerr/issues/491)) ([c8d4d67](https://github.com/sct/overseerr/commit/c8d4d674f412082ad9e9da09abd79660365cf728)) +* **frontend:** filter/sorting for request list ([5add44c](https://github.com/sct/overseerr/commit/5add44cfb0379aa6fed7c3b867230292feacc684)), closes [#431](https://github.com/sct/overseerr/issues/431) +* **notifications:** control notifcation types per agent ([8af6a1f](https://github.com/sct/overseerr/commit/8af6a1f566769c583af7dd9e18d162717835b7cc)), closes [#513](https://github.com/sct/overseerr/issues/513) +* status checker to prompt users to reload their frontend when app version changes ([75a4264](https://github.com/sct/overseerr/commit/75a426437a4182e21da13684066966dd5bf8fc5e)) + ## [1.14.1](https://github.com/sct/overseerr/compare/v1.14.0...v1.14.1) (2021-01-02) diff --git a/package.json b/package.json index 5cd62c423..69dafaa68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.14.1", + "version": "1.15.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From c6486c364322867f8a71f153b3454ab674dfcafb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 7 Jan 2021 09:58:09 +0000 Subject: [PATCH 023/238] chore(release): 1.16.0 --- CHANGELOG.md | 18 ++++++++++++++++++ package.json | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4924cf32..c871e179e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# [1.16.0](https://github.com/sct/overseerr/compare/v1.15.0...v1.16.0) (2021-01-07) + + +### Bug Fixes + +* **frontend:** adjust titlecard badge styling ([effc809](https://github.com/sct/overseerr/commit/effc80977a4ed732092254248f82363e52233171)) +* **frontend:** apply same titlecard hover effect to personcard ([67f2b57](https://github.com/sct/overseerr/commit/67f2b57f00216ded3b34965629d6fdd2f16bc25f)) +* **frontend:** only animate titlecard when showDetail is true ([0ab4c3c](https://github.com/sct/overseerr/commit/0ab4c3c36fe2c1ded142b6931111516f7f990a41)) +* **frontend:** use hardware acceleration for titlecard scale ([88810bf](https://github.com/sct/overseerr/commit/88810bf0a4ef74299f6541b60fa91cea3610f99c)) +* **plex-sync:** do not run plex sync if no admin exists ([493d82b](https://github.com/sct/overseerr/commit/493d82b6b066d77609cf66e005fd1f1472b8e011)) + + +### Features + +* **lang:** translations update from Weblate ([#495](https://github.com/sct/overseerr/issues/495)) ([b04eda6](https://github.com/sct/overseerr/commit/b04eda6c8a3bfcaa2a14b8a29612fdf690c9fba0)) +* **lang:** Translations update from Weblate ([#580](https://github.com/sct/overseerr/issues/580)) ([2bfe0f2](https://github.com/sct/overseerr/commit/2bfe0f2bf66956763ab26d5c54f26e6c456f59f7)) +* **notifications:** add pushover integration ([#574](https://github.com/sct/overseerr/issues/574)) ([ee5d018](https://github.com/sct/overseerr/commit/ee5d0181fc9a673b27aefd1d09b0a78c3d2e4f55)) + # [1.15.0](https://github.com/sct/overseerr/compare/v1.14.1...v1.15.0) (2021-01-04) diff --git a/package.json b/package.json index 2eabdaab3..a3ea375d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.15.0", + "version": "1.16.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From caa45415b154e769fda3f8951ee7a1ebd62835cd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 19 Jan 2021 07:58:37 +0000 Subject: [PATCH 024/238] chore(release): 1.17.0 --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c871e179e..96271c4f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +# [1.17.0](https://github.com/sct/overseerr/compare/v1.16.0...v1.17.0) (2021-01-19) + + +### Bug Fixes + +* **api:** improve rottentomatoes rating matching for movies ([7db62ab](https://github.com/sct/overseerr/commit/7db62ab824eefc42e6db16e42d52f4266b136f82)), closes [#494](https://github.com/sct/overseerr/issues/494) +* **build:** remove cross import from client to server for UserType ([23624bd](https://github.com/sct/overseerr/commit/23624bd144af5df4c31995b68ce48105b95b20f6)) +* **frontend:** clarify which fields are required in radarr/sonarr modals ([860d71e](https://github.com/sct/overseerr/commit/860d71ed69a69a1a3f74b79290ef471e04f57a6b)), closes [#575](https://github.com/sct/overseerr/issues/575) +* **frontend:** do not show failed media status on request list for declined requests ([00944b1](https://github.com/sct/overseerr/commit/00944b1ec2db8ddc5742448f6448f7364c473a98)), closes [#664](https://github.com/sct/overseerr/issues/664) +* **frontend:** fix button styling on details page on small screen sizes ([d9e0c90](https://github.com/sct/overseerr/commit/d9e0c90e76d80aef0c67318e00e997804805f46e)) +* **frontend:** fix request button height ([a262727](https://github.com/sct/overseerr/commit/a2627270784bdef8644875fa5c5a7349a0b7fd81)) +* **frontend:** request dropdown menu now properly shows up over collection button ([b491be1](https://github.com/sct/overseerr/commit/b491be1b1e7f6aa588274230e695e4c5302b961e)) +* **frontend:** show correct request status on request cards for 4k requests ([1aa0005](https://github.com/sct/overseerr/commit/1aa0005b4298fc1af9c1d0bf1f357738c0fa2673)) +* **lang:** add missing see more i18n string for SeeMoreCard ([d9919ab](https://github.com/sct/overseerr/commit/d9919abb8998d28558ddec35b8e60ab2af75d5b7)) +* **lang:** change email auth user/pass strings to SMTP Username/Password ([a77a2aa](https://github.com/sct/overseerr/commit/a77a2aa3ebb1be353d534db5b07647ac26c60e15)) +* **notifications:** correctly compare seasons before sending series notifications ([f17fa2a](https://github.com/sct/overseerr/commit/f17fa2a2db8144bac89936f588627e8dd37bf54a)) +* **notifications:** only send one available notification for standard media ([fc6f7cc](https://github.com/sct/overseerr/commit/fc6f7ccea586165a30022b6d5554911c66ece6df)) +* **notifications:** send media declined email ([eb6fc8a](https://github.com/sct/overseerr/commit/eb6fc8a19099469794d471db0b48a258c2866633)), closes [#679](https://github.com/sct/overseerr/issues/679) +* **plex-sync:** improve plex sync error handling. add session id to fix stuck runs ([a740b07](https://github.com/sct/overseerr/commit/a740b07f06f892b72a651b928af28ce71cb495ee)) +* **plex-sync:** store plex added date and sort recently added by it ([d688a96](https://github.com/sct/overseerr/commit/d688a967596afcba9799b8133089bebb5add27cf)) +* **requests:** select the correct radarr/sonarr server when sending request to service ([e0d9f89](https://github.com/sct/overseerr/commit/e0d9f891e797c3839f976b75a871903b6f2e55f1)) +* **server:** support absolute paths for CONFIG_DIRECTORY ([51d8fba](https://github.com/sct/overseerr/commit/51d8fba9162b9e148a35ced69e7e035438c8b0f1)) +* **user edit:** fix user edit not being able to be saved ([#651](https://github.com/sct/overseerr/issues/651)) ([b04d00e](https://github.com/sct/overseerr/commit/b04d00ef509d6f13c1f9677b3f318331782c0086)) + + +### Features + +* **api:** /request/count endpoint ([#682](https://github.com/sct/overseerr/issues/682)) ([192cfd8](https://github.com/sct/overseerr/commit/192cfd8a8ea9ab942d5bb265d42050917a2f5a04)) +* **frontend:** add see more card to media sliders ([587e8db](https://github.com/sct/overseerr/commit/587e8db15e9c19b4c58406e3e4215d8bf87d8762)) +* **frontend:** add template variable help button to custom webhook settings page ([29c5bc4](https://github.com/sct/overseerr/commit/29c5bc40975e7ab0a2e08bb77294f164f0c60769)) +* **lang:** add support for Chinese (Traditional) language ([686c4f7](https://github.com/sct/overseerr/commit/686c4f71bf930625af082ac5e14dc5f79f5c42eb)) +* **lang:** Translations update from Weblate ([#604](https://github.com/sct/overseerr/issues/604)) ([801e765](https://github.com/sct/overseerr/commit/801e76524d6ea0887249f1630402e9c3a3430b44)) +* **login:** add local users functionality ([#591](https://github.com/sct/overseerr/issues/591)) ([492e19d](https://github.com/sct/overseerr/commit/492e19df4014e67dc6a2de5903a33c25e13fcf45)) +* **notifications:** add notification for declined requests ([2f97f61](https://github.com/sct/overseerr/commit/2f97f61a6e8846975774aa16950a39ada2b1a016)), closes [#663](https://github.com/sct/overseerr/issues/663) +* **notifications:** Webhook Notifications ([#632](https://github.com/sct/overseerr/issues/632)) ([a7cc7c5](https://github.com/sct/overseerr/commit/a7cc7c59753dd9649b2ec37eb9d46fe4fa8e1e1c)) +* **requests:** Request Overrides & Request Editing ([#653](https://github.com/sct/overseerr/issues/653)) ([bdb3372](https://github.com/sct/overseerr/commit/bdb33722e6df09dd6d8caa36b104b61c6b8dc00d)) +* **server:** add CONFIG_DIRECTORY env var to control config directory location ([fa8f112](https://github.com/sct/overseerr/commit/fa8f112c31ccb5ee6244f776bc97e76d81958539)) +* 4K Requests ([#559](https://github.com/sct/overseerr/issues/559)) ([6b2df24](https://github.com/sct/overseerr/commit/6b2df24a2e8f96dd2277a814d7e02015d1f80cdc)) +* map AniDB IDs from Hama agent to tvdb/tmdb/imdb IDs ([#538](https://github.com/sct/overseerr/issues/538)) ([0600ac7](https://github.com/sct/overseerr/commit/0600ac7c3a1bc0cdd906634d5f77ea3e99b10e94)), closes [#453](https://github.com/sct/overseerr/issues/453) + + +### Reverts + +* **deps:** revert back to next@10.0.3 until sharp optional dependency bug is fixed ([7962964](https://github.com/sct/overseerr/commit/79629645aacc1a042919834da79bff0c1f69c9d6)) + # [1.16.0](https://github.com/sct/overseerr/compare/v1.15.0...v1.16.0) (2021-01-07) diff --git a/package.json b/package.json index e8db7fa70..26a0ab8ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.16.0", + "version": "1.17.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 519dd17d37a19c275a55cd50ffc5581ad8c692d9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 19 Jan 2021 10:24:57 +0000 Subject: [PATCH 025/238] chore(release): 1.17.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96271c4f9..52fb6e64e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.17.1](https://github.com/sct/overseerr/compare/v1.17.0...v1.17.1) (2021-01-19) + + +### Bug Fixes + +* **frontend:** show auto approval on series request modal only with correct permissions ([8927c6d](https://github.com/sct/overseerr/commit/8927c6d2e39dbda2b1121095a7273f5cab1c9b74)), closes [#687](https://github.com/sct/overseerr/issues/687) + # [1.17.0](https://github.com/sct/overseerr/compare/v1.16.0...v1.17.0) (2021-01-19) diff --git a/package.json b/package.json index 26a0ab8ca..fef773e4f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.17.0", + "version": "1.17.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 6167011ee5e1f2a72dc533d039a343a618b9c744 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 20 Jan 2021 07:27:33 +0000 Subject: [PATCH 026/238] chore(release): 1.17.2 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52fb6e64e..e4c2110a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [1.17.2](https://github.com/sct/overseerr/compare/v1.17.1...v1.17.2) (2021-01-20) + + +### Bug Fixes + +* **requests:** allow declined season requests to be re-requested ([e1032ff](https://github.com/sct/overseerr/commit/e1032ff5dfac4a8c9d4da9cf2788c19822343ad9)), closes [#690](https://github.com/sct/overseerr/issues/690) +* **requests:** update requests to approved when parent media is set as available ([78444a9](https://github.com/sct/overseerr/commit/78444a9e643829823162389dee60cca70da56bff)), closes [#688](https://github.com/sct/overseerr/issues/688) + ## [1.17.1](https://github.com/sct/overseerr/compare/v1.17.0...v1.17.1) (2021-01-19) diff --git a/package.json b/package.json index fef773e4f..8e091f2cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.17.1", + "version": "1.17.2", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 686fe6fdf88d103fed356d1be52291365f63ff7f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 30 Jan 2021 06:13:16 +0000 Subject: [PATCH 027/238] chore(release): 1.18.0 --- CHANGELOG.md | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4c2110a8..a3397b1d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,56 @@ +# [1.18.0](https://github.com/sct/overseerr/compare/v1.17.2...v1.18.0) (2021-01-30) + + +### Bug Fixes + +* **api:** prevent duplicate movie requests ([421f4c1](https://github.com/sct/overseerr/commit/421f4c17f0f206bbe7bfcbf2819014b8c7f55b6a)), closes [#705](https://github.com/sct/overseerr/issues/705) +* **build:** fix sqlite3 build error ([#691](https://github.com/sct/overseerr/issues/691)) ([3a1f6d5](https://github.com/sct/overseerr/commit/3a1f6d5706c8fc100e88425f3d89a26a0325af79)) +* **frontend:** add poster not found image to request card and request list item ([ae9a1b3](https://github.com/sct/overseerr/commit/ae9a1b3e940ac2abf6e842d91f458daab3dd0f0d)) +* **frontend:** add poster not found image to tv details page ([0b05545](https://github.com/sct/overseerr/commit/0b055458d0ddbfd4c87ebf9b0562f161fa3445a3)) +* **frontend:** dont show external links unless slug is set ([946bd2d](https://github.com/sct/overseerr/commit/946bd2db5ecde0748b2e9bc5edfe7ca6000ec3d5)) +* **frontend:** fix server name position on plex settings page ([86efcd8](https://github.com/sct/overseerr/commit/86efcd82c34ad6490f2899ebf6f84cdd1bffc498)) +* **frontend:** fixed mismatched rounded sizing on new login ([5e352c2](https://github.com/sct/overseerr/commit/5e352c201fc2f731ca5f713ecb6901527ef354da)), closes [#721](https://github.com/sct/overseerr/issues/721) +* **ip logging:** add env var for proxy to fix ip logging on failed logins ([#756](https://github.com/sct/overseerr/issues/756)) ([9342a40](https://github.com/sct/overseerr/commit/9342a40bbc03f7fdda23e3876b3a4a81ea8532c0)) +* **lang:** add missing i18n strings for notification settings ([2f75c4c](https://github.com/sct/overseerr/commit/2f75c4c6aed42a15bb47d3652272de8f852ec79f)) +* **notifications:** only send a single notification when standard media becomes available ([b5fd1d5](https://github.com/sct/overseerr/commit/b5fd1d520cd2a7be6e6356a25129e93af1caf542)), closes [#770](https://github.com/sct/overseerr/issues/770) +* **permissions:** use default user permissions when creating a local user ([#713](https://github.com/sct/overseerr/issues/713)) ([660ada0](https://github.com/sct/overseerr/commit/660ada0b2025eb2c06d9054fd0a7b5a632af6af2)) +* **radarr:** fix request bug which made it unable to be added to radarr ([#760](https://github.com/sct/overseerr/issues/760)) ([45a2779](https://github.com/sct/overseerr/commit/45a277964b0c39346d7216873812e0ebe505cb79)) +* **radarr:** return the updated data when updating radarr request ([#765](https://github.com/sct/overseerr/issues/765)) ([0c6d478](https://github.com/sct/overseerr/commit/0c6d4780c355ffe1a951268fb6949491d435bbf1)) +* **requests:** handle when tvdbid is null ([#657](https://github.com/sct/overseerr/issues/657)) ([2da0da8](https://github.com/sct/overseerr/commit/2da0da826ae1d73467bc8a671fda7cc5ca1f14c9)) +* **sonarr-sync:** correctly set series with no seasons to requested status ([3812989](https://github.com/sct/overseerr/commit/3812989a1ce1e07d4af09149008043a6e2e94060)), closes [#762](https://github.com/sct/overseerr/issues/762) +* **sync:** do not update series status if already available and no new seasons ([136d874](https://github.com/sct/overseerr/commit/136d874cba37babf9c0670844b002871710e6d99)), closes [#777](https://github.com/sct/overseerr/issues/777) +* **ui:** Capitalization, punctuation, and grammar inconsistences & errors ([#731](https://github.com/sct/overseerr/issues/731)) ([f05d4a0](https://github.com/sct/overseerr/commit/f05d4a0d0b42905fcaee49b2471bb1f4ee77fffe)) +* lookup movie by imdbid if tmdbid does not exits for plex movie agent ([#711](https://github.com/sct/overseerr/issues/711)) ([e972288](https://github.com/sct/overseerr/commit/e97228899a5936b2525c8060abfa14b5ce31658d)) +* show recently added series even if they are not complete ([d0c830e](https://github.com/sct/overseerr/commit/d0c830e80d389f9e0f48a9b83659331f54630d03)) + + +### Features + +* **lang:** translated using Weblate (Dutch) ([059995e](https://github.com/sct/overseerr/commit/059995e0ef3370a3192bd386fa6875ca0f58690a)) +* **lang:** translated using Weblate (French) ([4789583](https://github.com/sct/overseerr/commit/4789583d66305ac7b3d393659b2f3604c0acc576)) +* **lang:** translations update from Weblate ([#727](https://github.com/sct/overseerr/issues/727)) ([71875ef](https://github.com/sct/overseerr/commit/71875efb48246dbb0139ad15a4261a5661fcfe17)) +* **lang:** update languages and fix merge conflict ([083a74a](https://github.com/sct/overseerr/commit/083a74a686d202cce5775bf9752caaa9a626cf45)) +* **ui:** Move PROXY setting to UI ([#782](https://github.com/sct/overseerr/issues/782)) ([f1dd5e7](https://github.com/sct/overseerr/commit/f1dd5e7e12c1f602449c4769173dbce71e3569d0)) +* add manual availability buttons to manage slideover ([67f8aef](https://github.com/sct/overseerr/commit/67f8aef00d98c834b60cb6152ccd5cb7b5709d12)), closes [#672](https://github.com/sct/overseerr/issues/672) +* **media:** add link to the item on plex ([#735](https://github.com/sct/overseerr/issues/735)) ([1d7150c](https://github.com/sct/overseerr/commit/1d7150c24ec5ad347093889bfceab61b664900d5)) +* Radarr & Sonarr Sync ([#734](https://github.com/sct/overseerr/issues/734)) ([ec5fb83](https://github.com/sct/overseerr/commit/ec5fb836785855eb4846fd33b49faeb94c40506a)) +* **frontend:** add option to hide all available items from discovery ([#699](https://github.com/sct/overseerr/issues/699)) ([6c1742e](https://github.com/sct/overseerr/commit/6c1742e94ccfc6c13cf1d25fd9e893ee1f431aae)) +* **lang:** add support for Portuguese (Portugal) language ([e044146](https://github.com/sct/overseerr/commit/e044146aa55109a1eccfde9650b26beb0d5ec9a6)) +* **lang:** translated using Weblate (Dutch) ([6d0f7d4](https://github.com/sct/overseerr/commit/6d0f7d4b50370c420c1017f32d48313074543743)) +* **lang:** translated using Weblate (Italian) ([9aa5c12](https://github.com/sct/overseerr/commit/9aa5c121644518c1fbb308a487c26d8998bb5a36)) +* **lang:** translated using Weblate (Portuguese (Portugal)) ([f001fb3](https://github.com/sct/overseerr/commit/f001fb3b33d4fb749acb70c45b8a55a5bbef570c)) +* **lang:** translated using Weblate (Spanish) ([4f94d22](https://github.com/sct/overseerr/commit/4f94d227fc3096bcb8a1e5cf12fe9222d6c6b711)) +* **login:** add request ip to the failed request log ([#714](https://github.com/sct/overseerr/issues/714)) ([2d31ea9](https://github.com/sct/overseerr/commit/2d31ea940ac0a1a84d2150743798b41ff6490317)) +* **users:** add editable usernames ([#715](https://github.com/sct/overseerr/issues/715)) ([20ca3f2](https://github.com/sct/overseerr/commit/20ca3f2f5fcf4a9eb0d6a8be671bb4fb1f5e6178)) +* pre-populate server info from plex.tv API ([#563](https://github.com/sct/overseerr/issues/563)) ([82ac76b](https://github.com/sct/overseerr/commit/82ac76b0540ba1133cb5384744d2499c2488a4e8)) +* **auth:** Add optional CSRF protection ([#697](https://github.com/sct/overseerr/issues/697)) ([6e25891](https://github.com/sct/overseerr/commit/6e2589178b99f8f32f0ded9a7cfd9921c33e9b60)) +* ability to edit user settings in bulk ([#597](https://github.com/sct/overseerr/issues/597)) ([4b0241c](https://github.com/sct/overseerr/commit/4b0241c3b34d4229f928c21defb10a1c051264d1)) +* **lang:** translated using Weblate (English) ([9bb11af](https://github.com/sct/overseerr/commit/9bb11afc6b4a109ae1e14d41c9fe2b71f19c470a)) +* **lang:** translated using Weblate (German) ([c2a3e8e](https://github.com/sct/overseerr/commit/c2a3e8ed5243925dce991ec7995ae831702dbc7b)) +* **lang:** translated using Weblate (Portuguese (Brazil)) ([32f4916](https://github.com/sct/overseerr/commit/32f4916c4a926097f31ed472aee031536b847bb7)) +* **lang:** translated using Weblate (Portuguese (Brazil)) ([98570c9](https://github.com/sct/overseerr/commit/98570c920e4904a594bb7464161b985094958f84)) +* **notifications:** add option to send notifications for auto-approved requests ([21db367](https://github.com/sct/overseerr/commit/21db3676d1464b63384b04c0c2926cb2a6252e9b)), closes [#267](https://github.com/sct/overseerr/issues/267) + ## [1.17.2](https://github.com/sct/overseerr/compare/v1.17.1...v1.17.2) (2021-01-20) diff --git a/package.json b/package.json index 49a3581c0..533dc1767 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.17.2", + "version": "1.18.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 06261c1118aaa556c5d4f6d3d4107675bb31857b Mon Sep 17 00:00:00 2001 From: sct Date: Sat, 30 Jan 2021 06:31:34 +0000 Subject: [PATCH 028/238] ci(snapcraft): remove --unshallow argument from fetch in Prepare step --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3bd568842..92223b51d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -64,7 +64,7 @@ jobs: - name: Prepare id: prepare run: | - git fetch --prune --unshallow --tags + git fetch --prune --tags if [[ $GITHUB_REF == refs/tags/* || $GITHUB_REF == refs/heads/master ]]; then echo ::set-output name=RELEASE::stable else From a397893217b42ea42478e12f15fc8feb1adccf1c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 5 Feb 2021 10:32:05 +0000 Subject: [PATCH 029/238] chore(release): 1.19.0 --- CHANGELOG.md | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3397b1d0..2d3043d29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,59 @@ +# [1.19.0](https://github.com/sct/overseerr/compare/v1.18.0...v1.19.0) (2021-02-05) + + +### Bug Fixes + +* **api:** filter out adult content from combined credits ([3052f12](https://github.com/sct/overseerr/commit/3052f12c91b3ce86128324e3698fff61bbce3f2a)) +* **cache:** use formatted numbers for displaying cache counts ([6c437c5](https://github.com/sct/overseerr/commit/6c437c515fc01b9fe4461968875e23542bae7542)) +* **email:** make image a link to the action url in request template ([ee0a7bd](https://github.com/sct/overseerr/commit/ee0a7bd8c0b3a79c292b0abceb2f780f3889e49f)), closes [#834](https://github.com/sct/overseerr/issues/834) +* **frontend:** add github sponsor link to about page ([7c192d5](https://github.com/sct/overseerr/commit/7c192d54f422a5f2b55750535d2382e313f1d011)) +* **frontend:** correctly show 4k download tracker activity ([a7314f8](https://github.com/sct/overseerr/commit/a7314f876ea528fdec0fb0a2adaa36a01afcdf38)) +* **frontend:** fix possible division by zero in download status ([#839](https://github.com/sct/overseerr/issues/839)) ([c97c96a](https://github.com/sct/overseerr/commit/c97c96a30c50db7735f06c6d2d2f6193fb7da55e)) +* **frontend:** match request button color on titlecards to other request buttons ([5b39911](https://github.com/sct/overseerr/commit/5b39911e024513fab7a62948e653cee08fd166c7)) +* **frontend:** set 4k status on RequestItem when request is for 4k ([a3b00c3](https://github.com/sct/overseerr/commit/a3b00c3458b868506d4158fb24f0369fa5daefc5)) +* **frontend:** use consistent spinner style on TitleCard/Plex Presets ([cf7ebc4](https://github.com/sct/overseerr/commit/cf7ebc488db33725444c428b4244d780ab9d123b)) +* **html:** th elements should be nested under tr, not directly under thead ([#801](https://github.com/sct/overseerr/issues/801)) ([6e9ac27](https://github.com/sct/overseerr/commit/6e9ac275e19d56de8c7a366db970c7321f26fc8a)) +* **lang:** Add missing source strings & remove local user sign-in setting tip ([#828](https://github.com/sct/overseerr/issues/828)) ([c0769d4](https://github.com/sct/overseerr/commit/c0769d4f8f2bad88e4638d8c3cbcc0414b3ef6fb)) +* **lang:** Edit English language strings ([#820](https://github.com/sct/overseerr/issues/820)) ([f54df21](https://github.com/sct/overseerr/commit/f54df214af86d90ea8d7cfcd4e39022215c3568c)) +* **lang:** translate language names & change zh-Hant language code to zh-TW ([#793](https://github.com/sct/overseerr/issues/793)) ([3c5ae36](https://github.com/sct/overseerr/commit/3c5ae360fd179d794a78cc918fe97a09216ca6b2)) +* **notif/ui:** Use custom application title in notifications & sign-in page ([#849](https://github.com/sct/overseerr/issues/849)) ([38c76b5](https://github.com/sct/overseerr/commit/38c76b55e0039c489cb6a4a0a298aa6385406db4)) +* **radarr:** correctly set requested status after sending to radarr (with auto approve) ([ec44841](https://github.com/sct/overseerr/commit/ec448413569ddc2f24bb856d29084169979f9f05)) +* **sonarr-sync:** sonarr sync will no longer set shows with no episodes to partially available ([d20bd53](https://github.com/sct/overseerr/commit/d20bd530edaadc5887b0361358da80153e36505c)), closes [#796](https://github.com/sct/overseerr/issues/796) +* **ui:** Add additional URL & email input validation ([#843](https://github.com/sct/overseerr/issues/843)) ([3f9bfeb](https://github.com/sct/overseerr/commit/3f9bfeb01a67b2b587c7548b02ee826722e65c0f)) +* **ui:** Don't display empty dropdown when no trailer available ([#804](https://github.com/sct/overseerr/issues/804)) ([95c2a21](https://github.com/sct/overseerr/commit/95c2a2169799d96413b47ab24506b330435643eb)) +* **ui:** dont show bulk edit options on user list if there is only one user ([b658ddf](https://github.com/sct/overseerr/commit/b658ddf5cf61b2bb9b93cb1a4ca716cd75e18bb4)) +* **ui:** Dynamically generate path to config in warning message ([#851](https://github.com/sct/overseerr/issues/851)) ([b531a64](https://github.com/sct/overseerr/commit/b531a642f601f4ef9bf39c2f5915402157e55372)) +* **ui:** fix tables extending outside viewport in mobile formats ([e270999](https://github.com/sct/overseerr/commit/e270999745f97c2860f6a5b84e897dc6da8d6001)) +* **ui:** Hide 'Mark 4k as Available' button if 4k not enabled ([#833](https://github.com/sct/overseerr/issues/833)) ([e4a50c3](https://github.com/sct/overseerr/commit/e4a50c33f105b440243885d72a9e96595a525447)) +* **ui:** Limit max width of forms & lists ([#845](https://github.com/sct/overseerr/issues/845)) ([b9d14a9](https://github.com/sct/overseerr/commit/b9d14a9fd0f3c94d8267755147a87fe3b77fa2c3)) +* **ui:** prevent names from getting squished in AdvancedRequester user selector ([06e9411](https://github.com/sct/overseerr/commit/06e941171a1d019fbb178624167c026f6df5271c)) +* **ui:** remove yup validation from display name on user edit page ([63d7e2b](https://github.com/sct/overseerr/commit/63d7e2b39858fcb1cc0819a680eebccded7f4451)) +* **ui:** Restore original port input size ([#814](https://github.com/sct/overseerr/issues/814)) ([1ccafc0](https://github.com/sct/overseerr/commit/1ccafc0ebd368d798f9571b83910336efa317e37)) +* **ui:** show request as option even if there are no radarr/sonarr servers ([b116281](https://github.com/sct/overseerr/commit/b116281196c264b4ec35b07f1b4ffa717e50ade5)) +* **ui:** uniform-size checkboxes, vertically-aligned form labels, and fixes for other UI imperfections/inconsistencies ([#737](https://github.com/sct/overseerr/issues/737)) ([e34fbf7](https://github.com/sct/overseerr/commit/e34fbf72fda34d69b9f25563fa81f88b3c20912a)) +* **ui:** Use minimum char validation message ([#850](https://github.com/sct/overseerr/issues/850)) ([7456bea](https://github.com/sct/overseerr/commit/7456bea2ae600a28cb933278ffb310b63a474d6a)) +* **ui:** validate application url and service external urls ([026795d](https://github.com/sct/overseerr/commit/026795d4c940cb4797d3e68089456a4c3defbb21)) +* **ui:** when PersonCard has no profilePath, correctly position name/role content ([3ffd5ab](https://github.com/sct/overseerr/commit/3ffd5ab0ee8ffa63199d1428e37206f9b59fb7a5)) + + +### Features + +* **cache:** add cache table and flush cache option to settings ([996bd9f](https://github.com/sct/overseerr/commit/996bd9f14ed0f56767892c169b071be4f0f628d0)) +* **cache:** external API cache ([#786](https://github.com/sct/overseerr/issues/786)) ([20289b5](https://github.com/sct/overseerr/commit/20289b5960a93545cdff9331a1a7b613f382e702)) +* **docker:** Check for /app/config volume mount during setup ([#826](https://github.com/sct/overseerr/issues/826)) ([1e5f88f](https://github.com/sct/overseerr/commit/1e5f88f462b0c69db5f6ab8e0249a5905bc6952a)) +* **frontend:** add TheTVDB external link ([#800](https://github.com/sct/overseerr/issues/800)) ([72cffd7](https://github.com/sct/overseerr/commit/72cffd74a75984ba98c456c0ec006ec378a8dcec)) +* **lang:** add support for Hungarian language ([cfacb15](https://github.com/sct/overseerr/commit/cfacb151b52d08e19d2fcd603fb4bbcd78707cdf)) +* **lang:** translations update from Weblate ([#791](https://github.com/sct/overseerr/issues/791)) ([42295e0](https://github.com/sct/overseerr/commit/42295e076a7579b226d57407a20cb0ba044e9ec1)) +* **lang:** translations update from Weblate ([#819](https://github.com/sct/overseerr/issues/819)) ([9e5e4c2](https://github.com/sct/overseerr/commit/9e5e4c22f5b25df96f47875d599ed8685791382a)) +* **lang:** translations update from Weblate ([#841](https://github.com/sct/overseerr/issues/841)) ([e4f9b8a](https://github.com/sct/overseerr/commit/e4f9b8a9848f3af00e86fc7108c823ed0584609f)) +* **lang:** translations update from Weblate ([#852](https://github.com/sct/overseerr/issues/852)) ([c5be00e](https://github.com/sct/overseerr/commit/c5be00eebfd2b0e65295edbe282cbba22fffa660)) +* **ui:** Add local login setting ([#817](https://github.com/sct/overseerr/issues/817)) ([9d0d5b8](https://github.com/sct/overseerr/commit/9d0d5b86aae025e4647bb664c6412d42192e2fe7)) +* **ui:** added next airing date to TV Shows ([#842](https://github.com/sct/overseerr/issues/842)) ([4eae02a](https://github.com/sct/overseerr/commit/4eae02a7e14e377fd69ddd4a43774cb7e3d1855b)) +* new permission to allow users to see other users requests ([033ba9d](https://github.com/sct/overseerr/commit/033ba9d41bddf6dc1c4512d8404f747e57923bca)), closes [#840](https://github.com/sct/overseerr/issues/840) +* request as another user ([59150f9](https://github.com/sct/overseerr/commit/59150f955f7003672ef19eb9d37156e93b79c97d)) +* **tv:** show cast for the entire show instead of only the last season ([#778](https://github.com/sct/overseerr/issues/778)) ([b239598](https://github.com/sct/overseerr/commit/b239598e64d33b78dc5d7972878840149aff360a)), closes [#775](https://github.com/sct/overseerr/issues/775) +* **ui:** Add custom title functionality ([#825](https://github.com/sct/overseerr/issues/825)) ([35c6bfc](https://github.com/sct/overseerr/commit/35c6bfc0216bf879353b3ee546b439a06c8e6121)) + # [1.18.0](https://github.com/sct/overseerr/compare/v1.17.2...v1.18.0) (2021-01-30) diff --git a/package.json b/package.json index e1f2e78e9..c2a52c810 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.18.0", + "version": "1.19.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 726f62b9b69b5078e718f129e26abdf358f5cb06 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sat, 6 Feb 2021 00:08:24 -0500 Subject: [PATCH 030/238] fix(ui): Fix webhook URL validation regex (#864) --- .../Settings/Notifications/NotificationsWebhook/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx index 927a76395..a0f89679c 100644 --- a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx +++ b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx @@ -63,7 +63,7 @@ const NotificationsWebhook: React.FC = () => { .required(intl.formatMessage(messages.validationWebhookUrl)) .matches( // eslint-disable-next-line - /^(https?:)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/, + /^(https?:)?\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*)?([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i, intl.formatMessage(messages.validationWebhookUrl) ), jsonPayload: Yup.string() From 3dfbb0e0a5cbc439f636ddf838a958b7cb6431b8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 6 Feb 2021 05:14:36 +0000 Subject: [PATCH 031/238] chore(release): 1.19.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d3043d29..d5c7b20b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.19.1](https://github.com/sct/overseerr/compare/v1.19.0...v1.19.1) (2021-02-06) + + +### Bug Fixes + +* **ui:** Fix webhook URL validation regex ([#864](https://github.com/sct/overseerr/issues/864)) ([726f62b](https://github.com/sct/overseerr/commit/726f62b9b69b5078e718f129e26abdf358f5cb06)) + # [1.19.0](https://github.com/sct/overseerr/compare/v1.18.0...v1.19.0) (2021-02-05) diff --git a/package.json b/package.json index c2a52c810..db2f5287d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.19.0", + "version": "1.19.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From ea2765f8c78631be10f543a2d3e5a7413ebbb9f8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Feb 2021 12:33:56 +0000 Subject: [PATCH 032/238] chore(release): 1.20.0 --- CHANGELOG.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5c7b20b4..4a466d363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,68 @@ +# [1.20.0](https://github.com/sct/overseerr/compare/v1.19.1...v1.20.0) (2021-02-23) + + +### Bug Fixes + +* **api:** add isAuthenticated middleware to base user route ([8a27c70](https://github.com/sct/overseerr/commit/8a27c7062599ea23dca115e6e6e95a594e1b219a)) +* **api:** sort users requests by most recent ([1798383](https://github.com/sct/overseerr/commit/17983837fc10661a59d29fc1531530fca0d77825)) +* **api:** Use POST instead of GET for API endpoints that mutate state ([#877](https://github.com/sct/overseerr/issues/877)) ([ff0b5ed](https://github.com/sct/overseerr/commit/ff0b5ed44132cc5a0cd178035796d042ba735a8d)) +* **auth:** handle sign-in attempts from emails with no password ([#933](https://github.com/sct/overseerr/issues/933)) ([5e37a96](https://github.com/sct/overseerr/commit/5e37a96bc017471f8dc4cbdd57f2e8c3568bd97f)) +* **frontend:** changed plex, request, and cog buttons to align properly on smaller mobile UIs ([#928](https://github.com/sct/overseerr/issues/928)) ([f1c3358](https://github.com/sct/overseerr/commit/f1c335815f2f17465cdd36ceb223e78a58149b3b)) +* **frontend:** check for id instead of email after logging in ([c4af4c4](https://github.com/sct/overseerr/commit/c4af4c42ab00f1a63a2f5326c9cd8b26c19f4f14)) +* **frontend:** Do not allow user w/ ID 1 to disable 'Admin' permission ([#965](https://github.com/sct/overseerr/issues/965)) ([77b2d9e](https://github.com/sct/overseerr/commit/77b2d9ea22a2f70cff58ac9421f3f6231bc93059)) +* **frontend:** handle empty array of media attributes ([#922](https://github.com/sct/overseerr/issues/922)) ([04fa9f7](https://github.com/sct/overseerr/commit/04fa9f79e2ec90082b3fa15590dd170f7d68ad52)) +* **frontend:** request and cog button would be misaligned without play on plex/watch trailer button ([#956](https://github.com/sct/overseerr/issues/956)) ([e28dfad](https://github.com/sct/overseerr/commit/e28dfadaf57d47887013c31dc5006332473156e3)) +* **frontend:** Update AdvancedRequester to reflect new /user API response ([#970](https://github.com/sct/overseerr/issues/970)) ([b4bac6a](https://github.com/sct/overseerr/commit/b4bac6a9157119a4f234933245944e133c127bd0)) +* **frontend:** use region settings instead of hardcoded 'US' value for movie/TV ratings ([#1006](https://github.com/sct/overseerr/issues/1006)) ([6ecd202](https://github.com/sct/overseerr/commit/6ecd202607cb48d559440da810ecc585e740542b)) +* **lang:** formatMessage should not use an object spread ([8a7fa00](https://github.com/sct/overseerr/commit/8a7fa00164fd5c5501da525baa29be97bac7e7c4)) +* **lang:** Remove unused strings and correct spelling of 'canceling'/'canceled' ([#981](https://github.com/sct/overseerr/issues/981)) ([5b64655](https://github.com/sct/overseerr/commit/5b646557765d1ad75e44e1c0e60e0291313c7746)) +* **login:** fix the gap when 'use your overseer account' was selected ([#870](https://github.com/sct/overseerr/issues/870)) ([d163e29](https://github.com/sct/overseerr/commit/d163e294599c4bd9bdc0a148db15c8e8541410d8)) +* **notif:** Do not HTML-escape email subjects ([#931](https://github.com/sct/overseerr/issues/931)) ([019622a](https://github.com/sct/overseerr/commit/019622aab1b94cc4d71cacbf0dc5cf64b62c8623)) +* **notif:** Remove extra newlines from Telegram notifications ([#973](https://github.com/sct/overseerr/issues/973)) ([bbea522](https://github.com/sct/overseerr/commit/bbea52249950eb98a8d3886f2bd7648a7d669bf4)) +* **plex:** Check Plex server access on user import ([#955](https://github.com/sct/overseerr/issues/955)) ([bdb3cb2](https://github.com/sct/overseerr/commit/bdb3cb202550e34d8951ac2b5015f97f6a5c1ebf)) +* **plex-sync:** get correct Plex metadata for Hama movie items ([#901](https://github.com/sct/overseerr/issues/901)) ([03cecb3](https://github.com/sct/overseerr/commit/03cecb33559e27199c5a174fc86de0c4550fe666)), closes [#898](https://github.com/sct/overseerr/issues/898) +* **requests:** correctly filter requests out for users without view requests permission ([e118501](https://github.com/sct/overseerr/commit/e118501bf1dfa8dada2c57090e62631de620f3dd)) +* **requests:** correctly handle when tvdbid is missing ([#891](https://github.com/sct/overseerr/issues/891)) ([e037ba4](https://github.com/sct/overseerr/commit/e037ba48f173c06b0c9c8b03085edf832d770c06)) +* **search:** Handle search errors and escape * ([#893](https://github.com/sct/overseerr/issues/893)) ([034968e](https://github.com/sct/overseerr/commit/034968e4370eaea726c94730274349c083856813)) +* **services:** update all radarr/sonarr endpoints to use v3 ([da5ca02](https://github.com/sct/overseerr/commit/da5ca02f81fe91070afbda3e1ebc8d869fe39a8f)) +* **sonarr:** use qualityProfileId instad of profileId when adding series ([552a7e3](https://github.com/sct/overseerr/commit/552a7e30da5fc2cc0bd43b5aef79a0225c75d233)) +* **sync:** fix sonarr/plex sync fighting over availability ([9b73423](https://github.com/sct/overseerr/commit/9b73423d49e1e799cd82764a9ade8c75d92a28a2)), closes [#872](https://github.com/sct/overseerr/issues/872) +* **ui:** add fallback for region display name ([f9c83e1](https://github.com/sct/overseerr/commit/f9c83e14e52a57d6865307b3324a61c04a77a541)) +* **ui:** add missing string for default Discover Language & edit string for default Discover Region ([#1004](https://github.com/sct/overseerr/issues/1004)) ([0acad8e](https://github.com/sct/overseerr/commit/0acad8e9fa65a9de6cecac9b6a4a5b2313ba8f06)) +* **ui:** Add tip & validation for Discord ID input ([#966](https://github.com/sct/overseerr/issues/966)) ([e70a4ec](https://github.com/sct/overseerr/commit/e70a4ecae613e045977e262fd7f9643f30985ab7)) +* **ui:** also allow 17 digit discord ids ([57c00c1](https://github.com/sct/overseerr/commit/57c00c1ea71c1229d5a59e1b8dadd84a646772b9)), closes [#971](https://github.com/sct/overseerr/issues/971) +* **ui:** Automatically disable and uncheck user permissions with unmet requirements ([#941](https://github.com/sct/overseerr/issues/941)) ([c9a150b](https://github.com/sct/overseerr/commit/c9a150b1db2adbb305cf1a448489d7a8c14cf1cb)) +* **ui:** change font size in request list/user list dropdowns to prevent zoom on mobile ([fb9c878](https://github.com/sct/overseerr/commit/fb9c878db49c01d13773e8d2f94c93f840be0b82)) +* **ui:** Display 4K download status on 4K status badge ([#988](https://github.com/sct/overseerr/issues/988)) ([40b07c3](https://github.com/sct/overseerr/commit/40b07c35d40c03039e4bfa5ed1e73af7e8aa6a7d)) +* **ui:** Fix card sizes on person detail pages ([#881](https://github.com/sct/overseerr/issues/881)) ([a3042f8](https://github.com/sct/overseerr/commit/a3042f8e1b05a91d98f48a4aecb08e831a48fc56)) +* **ui:** Fix settings navigation horizontal scroll issues ([#987](https://github.com/sct/overseerr/issues/987)) ([8701fb2](https://github.com/sct/overseerr/commit/8701fb20d07773f4cc32e857b68575a813cf7e21)) +* **ui:** fix webhook URL validation regex ([baad19a](https://github.com/sct/overseerr/commit/baad19a2c94728313ee996fe1a0ffc64fbd9aaa3)) +* **ui:** fixed anime language profile typo ([#879](https://github.com/sct/overseerr/issues/879)) ([ee50761](https://github.com/sct/overseerr/commit/ee5076146ef3c5e8baba197a5b397d3c3f575262)) +* **ui:** Handle missing movie/series data ([#862](https://github.com/sct/overseerr/issues/862)) ([7c0ddad](https://github.com/sct/overseerr/commit/7c0ddad653393327226a877692f046d8693ddc66)) +* **ui:** Notification-related string/UI edits and field validation ([#985](https://github.com/sct/overseerr/issues/985)) ([c88fcb2](https://github.com/sct/overseerr/commit/c88fcb2e2d1c4b84527844a80680c15337626e72)) +* **ui:** rename global group class to form-group ([8056187](https://github.com/sct/overseerr/commit/8056187c3c0ea464a8f751aa6347ea1d35c01aac)) +* **ui:** Size cards appropriately based on base font size ([#871](https://github.com/sct/overseerr/issues/871)) ([282f28f](https://github.com/sct/overseerr/commit/282f28f2b9d0cc8c9105d01b43d4e1f730320b8b)) +* **ui/notif:** Custom application title in password-related emails and UI messages ([#979](https://github.com/sct/overseerr/issues/979)) ([4e2706b](https://github.com/sct/overseerr/commit/4e2706b4211b06f364910c327d84c2ceb45b2fe3)) + + +### Features + +* **lang:** translated using Weblate (French) ([#1007](https://github.com/sct/overseerr/issues/1007)) ([970da66](https://github.com/sct/overseerr/commit/970da664b2700b8cd9ad8dce0cbca1d37820eceb)) +* **lang:** translations update from Weblate ([#853](https://github.com/sct/overseerr/issues/853)) ([e156acc](https://github.com/sct/overseerr/commit/e156acc1ae2fa86b4441faacc0b58e1e993e0edc)) +* **lang:** translations update from Weblate ([#986](https://github.com/sct/overseerr/issues/986)) ([4296765](https://github.com/sct/overseerr/commit/4296765ad61bac09c2317b71b763366d328733e4)) +* **notif:** Add Pushbullet notification agent ([#950](https://github.com/sct/overseerr/issues/950)) ([29b97ef](https://github.com/sct/overseerr/commit/29b97ef6d85bbea31dd59b7ad857b0d8ab30bff0)) +* **notif:** Notification improvements ([#914](https://github.com/sct/overseerr/issues/914)) ([2768155](https://github.com/sct/overseerr/commit/2768155bbabe121a4c51fc1472461cd5114c4300)) +* **regions:** add region/original language setting for filtering Discover ([#732](https://github.com/sct/overseerr/issues/732)) ([#942](https://github.com/sct/overseerr/issues/942)) ([b557c06](https://github.com/sct/overseerr/commit/b557c06b0a78f5df5f64a05dc1e4511dae72df4f)) +* **requests:** add language profile support ([#860](https://github.com/sct/overseerr/issues/860)) ([53f6f59](https://github.com/sct/overseerr/commit/53f6f59798fa7e3f95959990a3df555db3c1c51e)) +* **ui:** Add 'Available' filter to request list and remove unused MediaRequestStatus.AVAILABLE enum value ([#905](https://github.com/sct/overseerr/issues/905)) ([9757e3a](https://github.com/sct/overseerr/commit/9757e3ae0c572fb46177e25154b29e0ceced665f)) +* **ui:** Add 'Page Size' setting for request/user list pages ([#957](https://github.com/sct/overseerr/issues/957)) ([621db89](https://github.com/sct/overseerr/commit/621db893281f0280fe773ac7dbdc44434895242c)) +* **ui:** Add separate permissions for 4K auto approval ([#908](https://github.com/sct/overseerr/issues/908)) ([53b7425](https://github.com/sct/overseerr/commit/53b7425f6711e250935e7bb024c38ff6c62e07d9)) +* **ui:** Add sort options to user list ([#913](https://github.com/sct/overseerr/issues/913)) ([ef5d019](https://github.com/sct/overseerr/commit/ef5d019c18d7f6cdbbb1e1b7f8ff7816ed9b117b)) +* **ui:** Add support for requesting collections in 4K ([#968](https://github.com/sct/overseerr/issues/968)) ([139341b](https://github.com/sct/overseerr/commit/139341b0434b41e7c31af36baacd8d65566a6a0c)) +* user profile/settings pages ([#958](https://github.com/sct/overseerr/issues/958)) ([bbb683e](https://github.com/sct/overseerr/commit/bbb683e637386ad8bbeb44dca97aac9cdaf11349)) +* **ui:** added content ratings for tv shows and movie ratings ([#878](https://github.com/sct/overseerr/issues/878)) ([c8b2a57](https://github.com/sct/overseerr/commit/c8b2a57721a51adcc7f90ec1acb48b127991d467)) +* **users:** add reset password flow ([#772](https://github.com/sct/overseerr/issues/772)) ([e5966bd](https://github.com/sct/overseerr/commit/e5966bd3fbfe172f264f4e986ad2aecf29ae1510)) + ## [1.19.1](https://github.com/sct/overseerr/compare/v1.19.0...v1.19.1) (2021-02-06) diff --git a/package.json b/package.json index 0b078b804..b56c27c2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.19.1", + "version": "1.20.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 0e612f01f19e48cc01fac08b861135b3b6e6c06f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 28 Feb 2021 04:10:48 +0000 Subject: [PATCH 033/238] chore(release): 1.20.1 --- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a466d363..fea86abc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## [1.20.1](https://github.com/sct/overseerr/compare/v1.20.0...v1.20.1) (2021-02-28) + + +### Bug Fixes + +* **notif:** escape application title in Telegram notifications ([#1012](https://github.com/sct/overseerr/issues/1012)) ([5560abf](https://github.com/sct/overseerr/commit/5560abf459b0350ff30b5e71d4208418fc8f3b3e)) +* **notif:** fixed typo in pushover hint ([#1029](https://github.com/sct/overseerr/issues/1029)) ([e9f2fe9](https://github.com/sct/overseerr/commit/e9f2fe910d72fa41bc27673ed43291211c3cac65)) +* **notifications:** correctly send tv auto approval notifications ([537850f](https://github.com/sct/overseerr/commit/537850f414a88df24c78794a2fd68e1e24ff73d1)), closes [#1041](https://github.com/sct/overseerr/issues/1041) +* **plex-sync:** no longer incorrectly sets 4k availability when there isnt any ([3f9a116](https://github.com/sct/overseerr/commit/3f9a116b17d78eeb04f0f125a4f3af6f907c83dd)), closes [#990](https://github.com/sct/overseerr/issues/990) +* **ui:** for server default options, display "All" region/language option instead of empty string ([#1042](https://github.com/sct/overseerr/issues/1042)) ([3fed26c](https://github.com/sct/overseerr/commit/3fed26cfbe74cb662ca531fd37b69f159a051ac1)) +* **ui:** show translated string on sonarr sucesss/failure toast messages ([#1035](https://github.com/sct/overseerr/issues/1035)) ([eefcbcd](https://github.com/sct/overseerr/commit/eefcbcd3ddfa5258ee24dbbbd79de5bf50310f27)) +* **ui:** use country-flag-icons instead of country-flag-emoji for RegionSelector ([#1011](https://github.com/sct/overseerr/issues/1011)) ([abcd7c9](https://github.com/sct/overseerr/commit/abcd7c997584c1310bd8b313ac38f30e335af8d7)) +* add missing default value for settings context ([084917f](https://github.com/sct/overseerr/commit/084917f02d399e2d29bb9927e033c2e6533f586c)) +* added missing language default for ssr context defaults ([9ce88ab](https://github.com/sct/overseerr/commit/9ce88abcc85d744d77172cd2357fdb4ff60dc5e4)) +* allow users to override language/region settings ([69294a7](https://github.com/sct/overseerr/commit/69294a7c4c5bbe55c5cd276786cdfd48ddbff889)), closes [#1013](https://github.com/sct/overseerr/issues/1013) + # [1.20.0](https://github.com/sct/overseerr/compare/v1.19.1...v1.20.0) (2021-02-23) diff --git a/package.json b/package.json index fd2f85ba1..b3e6b5713 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.20.0", + "version": "1.20.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From afd46ebee8130a2abd902d97e12a31d2d2fc4ad0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 15 Mar 2021 02:00:59 +0000 Subject: [PATCH 034/238] chore(release): 1.21.0 --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fea86abc3..949fcf252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,60 @@ +# [1.21.0](https://github.com/sct/overseerr/compare/v1.20.1...v1.21.0) (2021-03-15) + + +### Bug Fixes + +* do not allow editing of user settings under certain conditions ([#1168](https://github.com/sct/overseerr/issues/1168)) ([001dcd3](https://github.com/sct/overseerr/commit/001dcd328c8d3b1c417fd7c7ee2aa20183b08eef)) +* **frontend:** check for ID instead of email after initial setup Plex login ([#1097](https://github.com/sct/overseerr/issues/1097)) ([778dda6](https://github.com/sct/overseerr/commit/778dda67d54df87347dd79577ef1bdc88d3c1d3f)) +* **frontend:** check if swr is validating to determine if we should fetch new data ([e5f5bdb](https://github.com/sct/overseerr/commit/e5f5bdb95c62eba31a3321a7457d354f0226bf85)), closes [#719](https://github.com/sct/overseerr/issues/719) +* **frontend:** never hide available content in search results ([d48edeb](https://github.com/sct/overseerr/commit/d48edeb5a9bd8e2edce8bca0fea50e300bb7a1ae)) +* **lang:** add missing i18n strings ([6072e8a](https://github.com/sct/overseerr/commit/6072e8aa9a0f84e50c44a92af303aad15b5f3021)) +* **lang:** edit new Telegram-related strings to conform to style guide ([#1093](https://github.com/sct/overseerr/issues/1093)) ([bdf67e7](https://github.com/sct/overseerr/commit/bdf67e732b6c77cbae768a25edfc9a663ef0108b)) +* **notif:** loosen input validation on Pushover settings ([#1166](https://github.com/sct/overseerr/issues/1166)) ([3148d31](https://github.com/sct/overseerr/commit/3148d312141248653c5d1e42cd2882a67a339163)) +* **notif:** set URL for Discord embeds rather than adding a field for the link ([#1167](https://github.com/sct/overseerr/issues/1167)) ([0bd0912](https://github.com/sct/overseerr/commit/0bd0912613f0db24bd0da4ec956b5119133e35d4)) +* correctly send auto-approval notifictions for series ([8634081](https://github.com/sct/overseerr/commit/8634081c869a2078793ecf06b1b7e249bba0a2f8)) +* **lang:** fix singular form of season count ([#1080](https://github.com/sct/overseerr/issues/1080)) ([b57645d](https://github.com/sct/overseerr/commit/b57645d382361c856281e7a74295afe16c5390f2)) +* **requests:** add plex url to request item ([#1088](https://github.com/sct/overseerr/issues/1088)) ([420038d](https://github.com/sct/overseerr/commit/420038d5ffdd4070df03e5c5cb6ef8d6208fddb5)) +* **sonarr:** correctly search when updating existing sonarr series ([ed0a7fb](https://github.com/sct/overseerr/commit/ed0a7fbdf5122a26fa936e83b76a97c55781782d)), closes [#588](https://github.com/sct/overseerr/issues/588) +* **ui:** add alt prop to studio/network logos & fix blinking text cursor ([#1095](https://github.com/sct/overseerr/issues/1095)) ([0c4637f](https://github.com/sct/overseerr/commit/0c4637f779d8904037b9cbd5fe9166cf05a891c5)) +* **ui:** add link to poster image on request items ([7289872](https://github.com/sct/overseerr/commit/7289872937d5bb94d027424760ee1ceb94095604)) +* **ui:** correct language usage re: "sync" vs. "scan" ([#1079](https://github.com/sct/overseerr/issues/1079)) ([e98f2b9](https://github.com/sct/overseerr/commit/e98f2b96058fb9c5af77be2e8a1bd07fb8fcca06)) +* **ui:** display "Season" vs. "Seasons" as appropriate, and fix request block "Seasons" formatting ([#1127](https://github.com/sct/overseerr/issues/1127)) ([45886cc](https://github.com/sct/overseerr/commit/45886ccef1bee57dc555060a491834567e45b59c)) +* **ui:** request list button sizes ([#1152](https://github.com/sct/overseerr/issues/1152)) ([fc73592](https://github.com/sct/overseerr/commit/fc73592b69c38191f91a68a020868b8e5ec2e2e2)) +* fix language filter link on movie detail pages ([#1142](https://github.com/sct/overseerr/issues/1142)) ([60d453b](https://github.com/sct/overseerr/commit/60d453b0bbba5e2060f72f40d1dde85ec6b05af4)) +* remove language/region filtering on studio/network results ([#1129](https://github.com/sct/overseerr/issues/1129)) ([109aca8](https://github.com/sct/overseerr/commit/109aca8229dc7b81cac314d84591f1c04c12ac2e)) +* **api:** check correct permissions for auto approve when requests are created ([3c1a72b](https://github.com/sct/overseerr/commit/3c1a72b038fd178b4be4dc082cd1496474148d7e)) +* **frontend:** status, requested by, and modified alignment fix ([#1109](https://github.com/sct/overseerr/issues/1109)) ([1a7dc1a](https://github.com/sct/overseerr/commit/1a7dc1acf57888d3d0285b58c1c97a824a232216)) +* **ui:** don't show "Password" user settings tab if current user lacks perms to modify the password ([#1063](https://github.com/sct/overseerr/issues/1063)) ([b146d11](https://github.com/sct/overseerr/commit/b146d11e2ffecedae76472b0491a4662ca4a4a4e)) +* **ui:** fix Radarr logo alignment ([#1068](https://github.com/sct/overseerr/issues/1068)) ([0fa005a](https://github.com/sct/overseerr/commit/0fa005a99cd868b5a235ae9ce65b4c64b05d0f47)) +* **ui:** fix request list UI behavior when season list is too long ([#1106](https://github.com/sct/overseerr/issues/1106)) ([8507691](https://github.com/sct/overseerr/commit/85076919c6ccbf052699b7d5f4ba8b6e5e5af74d)) +* **ui:** improve responsive design on new request list UI ([#1105](https://github.com/sct/overseerr/issues/1105)) ([1f8b03f](https://github.com/sct/overseerr/commit/1f8b03ff6f67ce76051667de05166da54ed3dc89)) +* **ui:** list all movie studios instead of just the first result ([#1110](https://github.com/sct/overseerr/issues/1110)) ([239202d](https://github.com/sct/overseerr/commit/239202d9c11f27410b0fa084bcc4c824b7136081)) +* add correct permission checks to modifying user password/permissions ([ddfc5e6](https://github.com/sct/overseerr/commit/ddfc5e6aa8fc636931f495d6f23d56367466e3b5)) + + +### Features + +* add tagline, episode runtime, genres list to media details & clean/refactor CSS into globals ([#1160](https://github.com/sct/overseerr/issues/1160)) ([2f2e002](https://github.com/sct/overseerr/commit/2f2e00237d43bdab85bfadc3c4f2fbcdde4c2e90)) +* **docker:** add tini to docker image ([#1017](https://github.com/sct/overseerr/issues/1017)) ([1629d02](https://github.com/sct/overseerr/commit/1629d02f3d8368bfd5f6fed05382974ae6fce51f)) +* **email:** add pgp support ([#1138](https://github.com/sct/overseerr/issues/1138)) ([9e5adeb](https://github.com/sct/overseerr/commit/9e5adeb610bdc4800ff536412d0ae8a11fb4338d)) +* **frontend:** add loading bar indicator ([#1170](https://github.com/sct/overseerr/issues/1170)) ([3d6b343](https://github.com/sct/overseerr/commit/3d6b3434138fec49c58f2bf74f781d5e2fc2911f)) +* **lang:** localize job names ([#1043](https://github.com/sct/overseerr/issues/1043)) ([594aad9](https://github.com/sct/overseerr/commit/594aad9d3ae9b323677f3af8c434d7664526593d)) +* **lang:** translations update from Weblate ([#1051](https://github.com/sct/overseerr/issues/1051)) ([69bf817](https://github.com/sct/overseerr/commit/69bf817f598babed99964f073259f827b60bd014)) +* **lang:** Translations update from Weblate ([#1131](https://github.com/sct/overseerr/issues/1131)) ([e4686d6](https://github.com/sct/overseerr/commit/e4686d664b52448e32488ff1c4236f72e01e9a29)) +* **notif:** add "Media Automatically Approved" notification type ([#1137](https://github.com/sct/overseerr/issues/1137)) ([f7d2723](https://github.com/sct/overseerr/commit/f7d2723fab2c30564fd23945709cd39b178a6eef)) +* **notif:** add settings for Discord bot username & avatar URL ([#1113](https://github.com/sct/overseerr/issues/1113)) ([3384eb1](https://github.com/sct/overseerr/commit/3384eb1c479114c0246cb22f9a933aa79fb95fcf)) +* **notif:** include poster image in Telegram notifications ([#1112](https://github.com/sct/overseerr/issues/1112)) ([48387e5](https://github.com/sct/overseerr/commit/48387e5b2f26c0c33acd436c6e1cf902d6c32101)) +* **scan:** add support for new plex tv agent ([#1144](https://github.com/sct/overseerr/issues/1144)) ([a51d2a2](https://github.com/sct/overseerr/commit/a51d2a24d51d092a0c6da608e3322f19a37c2d28)) +* **ui:** add user ID to profile header ([6e95c8b](https://github.com/sct/overseerr/commit/6e95c8b7a10e3467bfd2c3df84ccf886fe01ca5c)) +* add genre/studio/network view to Discover results ([#1067](https://github.com/sct/overseerr/issues/1067)) ([f28112f](https://github.com/sct/overseerr/commit/f28112f057df2589f31ae0d0b14e8b50e479fdb7)) +* add language-filtered Discover pages ([#1111](https://github.com/sct/overseerr/issues/1111)) ([7501161](https://github.com/sct/overseerr/commit/75011610e57f03098c8be9375d0c9ba1e3647e9b)) +* add studio/network sliders to discover ([1c6914f](https://github.com/sct/overseerr/commit/1c6914f5ce5c0d171c4609813915b50233a8e3ad)) +* **telegram:** add support for individual chat notifications ([#1027](https://github.com/sct/overseerr/issues/1027)) ([f6d00d8](https://github.com/sct/overseerr/commit/f6d00d8d1559879189f83739193c6e2acafde51d)) +* **ui:** display "Owner" role instead of "Admin" for user ID 1 ([#1050](https://github.com/sct/overseerr/issues/1050)) ([1b55d2d](https://github.com/sct/overseerr/commit/1b55d2dfbc06d900e7370a4ddfd81789a25bf00c)) +* **ui:** display season count on TV details page ([#1078](https://github.com/sct/overseerr/issues/1078)) ([4365231](https://github.com/sct/overseerr/commit/436523139e8f1594c352b17032734b4498d3994f)) +* **ui:** in Settings > Services, make Radarr/Sonarr server names and logos clickable links ([#1008](https://github.com/sct/overseerr/issues/1008)) ([6a1e389](https://github.com/sct/overseerr/commit/6a1e3891aa5f84b6adb1e475a6658a8cd4e34c22)) +* **ui:** request list redesign ([#1099](https://github.com/sct/overseerr/issues/1099)) ([cd21865](https://github.com/sct/overseerr/commit/cd21865c4d5be00c13c372e0b7a058f61ec855a2)) + ## [1.20.1](https://github.com/sct/overseerr/compare/v1.20.0...v1.20.1) (2021-02-28) diff --git a/package.json b/package.json index bad21c7d8..a54ddadbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.20.1", + "version": "1.21.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From b24c23e549ec537c011456bd592a7b7ce4c912e6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 15 Mar 2021 08:04:26 +0000 Subject: [PATCH 035/238] chore(release): 1.21.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 949fcf252..fba93553a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.21.1](https://github.com/sct/overseerr/compare/v1.21.0...v1.21.1) (2021-03-15) + + +### Bug Fixes + +* **lang:** translations update from Weblate ([#1155](https://github.com/sct/overseerr/issues/1155)) ([ebc285c](https://github.com/sct/overseerr/commit/ebc285c758f69846e4a5cb74bb42ca5924d166d4)) + # [1.21.0](https://github.com/sct/overseerr/compare/v1.20.1...v1.21.0) (2021-03-15) diff --git a/package.json b/package.json index a54ddadbd..41fb0e022 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.21.0", + "version": "1.21.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 31ac11f4ebc67f865bf5c69c4df3bd50289ec57e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 1 Apr 2021 06:33:50 +0000 Subject: [PATCH 036/238] chore(release): 1.22.0 --- CHANGELOG.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fba93553a..a242a96e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,91 @@ +# [1.22.0](https://github.com/sct/overseerr/compare/v1.21.1...v1.22.0) (2021-04-01) + + +### Bug Fixes + +* **android:** adaptive icons for Android devices ([#1274](https://github.com/sct/overseerr/issues/1274)) ([a65e3d5](https://github.com/sct/overseerr/commit/a65e3d5bb6924cbde30b26ff8acf535e5274efee)) +* **backend:** fix getShowByTvdbId() error message ([#1314](https://github.com/sct/overseerr/issues/1314)) [skip ci] ([fe8d346](https://github.com/sct/overseerr/commit/fe8d34607b07095dce51b29ef7aaae0485573f14)) +* **db:** enable WAL journal mode ([aa205ff](https://github.com/sct/overseerr/commit/aa205ffa975d02ef0be30626e7c946a42679a847)) +* **frontend:** 'Recent Requests' slider should link to request list w/ same filter ([#1235](https://github.com/sct/overseerr/issues/1235)) ([49782c0](https://github.com/sct/overseerr/commit/49782c0b730cce9f0bad14e9c83842b5b0bfe11e)) +* **frontend:** call mutate after changing public settings ([#1302](https://github.com/sct/overseerr/issues/1302)) ([c8f67cf](https://github.com/sct/overseerr/commit/c8f67cf866ada791e4129a0bbae16b9eac41f32e)) +* **frontend:** include language parameter in TMDb links ([#1344](https://github.com/sct/overseerr/issues/1344)) ([1d88be9](https://github.com/sct/overseerr/commit/1d88be9341a8ff9e1f39b02556b489cdbd06392b)) +* **frontend:** redirect from /setup if already initialized ([#1238](https://github.com/sct/overseerr/issues/1238)) ([8016503](https://github.com/sct/overseerr/commit/80165038fd214897e3520a420f971341e7b94865)) +* **frontend:** use correct path to user profile in request modal quota dropdown ([#1307](https://github.com/sct/overseerr/issues/1307)) ([f990585](https://github.com/sct/overseerr/commit/f9905859148088afec53549b81611b07bf19d3b9)) +* **frontend:** use HTTPS to fetch TMDb assets for network/studio sliders ([#1343](https://github.com/sct/overseerr/issues/1343)) ([c886ea6](https://github.com/sct/overseerr/commit/c886ea6c0578cb7532d6c09266a76bfad8598b9d)) +* **frontend:** use next/image to serve login page images ([cbf4519](https://github.com/sct/overseerr/commit/cbf45196b023f60c8e4cf7602c0295f886fe610c)), closes [#1207](https://github.com/sct/overseerr/issues/1207) +* **lang:** allow proper localization of comma-delimited lists ([#1264](https://github.com/sct/overseerr/issues/1264)) ([173408a](https://github.com/sct/overseerr/commit/173408a1f269f09c724843ba087ef3f85b2832ad)) +* **lang:** change 'Extra Data' string to 'Additional Data' ([#1226](https://github.com/sct/overseerr/issues/1226)) ([665e164](https://github.com/sct/overseerr/commit/665e16475f3fa2ea6118340d9ea2d30b98abb238)) +* **lang:** correct mismatched language strings ([#1246](https://github.com/sct/overseerr/issues/1246)) ([8ebc829](https://github.com/sct/overseerr/commit/8ebc8292504cdc57a148ab69bcb4e1514ef018c6)) +* **lang:** correct strings for library sync button & user import toast ([#1252](https://github.com/sct/overseerr/issues/1252)) ([cb5ca7a](https://github.com/sct/overseerr/commit/cb5ca7acf38dcc2e27ec31d88434a11757cdb469)) +* **lang:** edit setting label strings for verb tense consistency ([#1214](https://github.com/sct/overseerr/issues/1214)) ([6d7671d](https://github.com/sct/overseerr/commit/6d7671dd80fea632e5cef29fc0b4968bffe231b0)) +* **lang:** fix overwritten/shared string ([#1212](https://github.com/sct/overseerr/issues/1212)) ([dfd4ff9](https://github.com/sct/overseerr/commit/dfd4ff9229822b0ce79ba322376194cbb6fd233d)) +* **lang:** remove 'requires and' ([#1215](https://github.com/sct/overseerr/issues/1215)) ([cb852fd](https://github.com/sct/overseerr/commit/cb852fded18f53806c23ec6f215385072b2a867b)) +* **lang:** remove unused strings ([#1330](https://github.com/sct/overseerr/issues/1330)) ([13e1595](https://github.com/sct/overseerr/commit/13e1595c6ebff32ca905d9bd3dd781e241545e83)) +* **lang:** UI string edits, round 2 ([#1202](https://github.com/sct/overseerr/issues/1202)) ([ea1863a](https://github.com/sct/overseerr/commit/ea1863ac3a5d3051e07815d07df0d3f2abd9166f)) +* **log:** fix typo in base scanner logging ([#1329](https://github.com/sct/overseerr/issues/1329)) [skip ci] ([b0b04ca](https://github.com/sct/overseerr/commit/b0b04ca1c7218ad5b67d9ec8b3fac5af78a4c132)) +* **logs:** add i18n strings for new log page changes ([8c51c28](https://github.com/sct/overseerr/commit/8c51c28f546b9c2d38ff7f20d59bb08a599e8146)) +* **notifications:** correctly send notifications for users that do not have any user settings yet ([d3a25b9](https://github.com/sct/overseerr/commit/d3a25b935aae35dd97ef0f168ac7e2898126a9a5)), closes [#1324](https://github.com/sct/overseerr/issues/1324) +* **overseerr-api.yml:** fixed pushbullet & webhook API definition refs and descriptions ([#1288](https://github.com/sct/overseerr/issues/1288)) [skip ci] ([3b003b7](https://github.com/sct/overseerr/commit/3b003b770120f7d150c64ff098b626015c030794)) +* **plex:** always send Overseerr for the device name to the plex.tv api ([f7146e4](https://github.com/sct/overseerr/commit/f7146e41899a59f75b963e1cc9dac9eddf24aebe)), closes [#1244](https://github.com/sct/overseerr/issues/1244) +* **ui:** add validation to hostname/IP fields ([#1206](https://github.com/sct/overseerr/issues/1206)) ([f49a024](https://github.com/sct/overseerr/commit/f49a02449c4928aef56cecbf908cf585ea0d4fca)) +* **ui:** better regex matching when parsing logs ([#1225](https://github.com/sct/overseerr/issues/1225)) ([2d737f2](https://github.com/sct/overseerr/commit/2d737f276095a8ca9abea360ef29134e9f639a39)) +* **ui:** button w/ dropdown z-indices ([#1230](https://github.com/sct/overseerr/issues/1230)) ([015671f](https://github.com/sct/overseerr/commit/015671f5be7a9f0f5c38db5a11a4b3c788dfaade)) +* **ui:** center role under title cards on person detail pages ([#1205](https://github.com/sct/overseerr/issues/1205)) ([4a61518](https://github.com/sct/overseerr/commit/4a6151873a3a3c5e45f9817131774a2c52957138)) +* **ui:** correctly enable the request button when partial requests are disabled with no quota ([16a611b](https://github.com/sct/overseerr/commit/16a611b9dfc3c66483640f4f5364646f41d37159)) +* **ui:** correctly paginate request list ([67fbb40](https://github.com/sct/overseerr/commit/67fbb401ac6ba05e58b8dfefd5954b28316254f2)) +* **ui:** correctly show quota display on tv request modal when only series quota is set ([3f1f85a](https://github.com/sct/overseerr/commit/3f1f85a80edfd2a4e9627162ff29ca6bcf2d8583)) +* **ui:** display asterisk indicator on required field labels ([#1236](https://github.com/sct/overseerr/issues/1236)) ([380d361](https://github.com/sct/overseerr/commit/380d36119f19a20ad67f79b3fb5db4036a093cac)) +* **ui:** do not check isValid on Sonarr/Radarr modals for the test button ([0974a4c](https://github.com/sct/overseerr/commit/0974a4c971358b7a64668f9a63fc356234a656c9)) +* **ui:** do not require numeric value in FormattedRelativeTime ([#1234](https://github.com/sct/overseerr/issues/1234)) ([3642b1e](https://github.com/sct/overseerr/commit/3642b1e84a20fef72428b3e240c86d35be8be8a2)) +* **ui:** filter out server options that do not match request type (non-4K or 4K) ([#1183](https://github.com/sct/overseerr/issues/1183)) ([28a6a70](https://github.com/sct/overseerr/commit/28a6a70e1ecc125f4cf4900e599ad0d4d7b55e3b)) +* **ui:** fix label formatting in general user settings ([#1275](https://github.com/sct/overseerr/issues/1275)) ([8546b0e](https://github.com/sct/overseerr/commit/8546b0ef53d232256b62cf08466e692a6971c16b)) +* **ui:** fix regex matching when parsing label from logs ([#1231](https://github.com/sct/overseerr/issues/1231)) ([4a00617](https://github.com/sct/overseerr/commit/4a00617fe47064ea50f95a02f29832a419ab13a3)) +* **ui:** gracefully handle lengthy titles & long words in overviews ([#1338](https://github.com/sct/overseerr/issues/1338)) ([d8bcb99](https://github.com/sct/overseerr/commit/d8bcb99b2fd3b24a5119ba5ff213a640425ff553)) +* **ui:** hide 'show details' button if there are no additional details ([#1254](https://github.com/sct/overseerr/issues/1254)) ([6210f12](https://github.com/sct/overseerr/commit/6210f12e8e9f593d629d22278d78310482ca0cfa)) +* **ui:** increase page size dropdown width when necessary ([#1216](https://github.com/sct/overseerr/issues/1216)) ([75c72b9](https://github.com/sct/overseerr/commit/75c72b987eb52b907ffd8af33f15ecc58213fc12)) +* **ui:** restore saved states of quota override checkboxes ([#1282](https://github.com/sct/overseerr/issues/1282)) ([2059fc1](https://github.com/sct/overseerr/commit/2059fc1cd4d48c7d80e761b7d41b7ec122d82769)) +* **ui:** sort regions & languages by their localized names rather than their TMDb English names ([#1157](https://github.com/sct/overseerr/issues/1157)) ([d76bf32](https://github.com/sct/overseerr/commit/d76bf32c9dcc83ebd0bae979726b1456a9028d8b)) +* **ui:** tweak request list design ([#1201](https://github.com/sct/overseerr/issues/1201)) ([d226fc7](https://github.com/sct/overseerr/commit/d226fc79b8d5f1263d4b80a7a1772074020ec94f)) +* **ui:** use appropriate cursor type for disabled UI elements ([#1184](https://github.com/sct/overseerr/issues/1184)) ([b767a58](https://github.com/sct/overseerr/commit/b767a58b011cc317a889cb8c2889b3210bec5fae)) +* **ui:** use appropriate cursor type for readonly input fields ([#1208](https://github.com/sct/overseerr/issues/1208)) ([9ec2c46](https://github.com/sct/overseerr/commit/9ec2c468cbbcbd41b94bbf9f3cfeb43eed09f36e)) +* **ui:** use correct colspan for 'No results.' message in Settings > Logs ([#1325](https://github.com/sct/overseerr/issues/1325)) ([5c135c9](https://github.com/sct/overseerr/commit/5c135c9974ebfcbdb434dafd459d1035624df6ed)) +* fetch localized person details from TMDb ([#1243](https://github.com/sct/overseerr/issues/1243)) ([1d7a938](https://github.com/sct/overseerr/commit/1d7a938ef8b0b8c20fda5024121de2a217ef4127)) + + +### Features + +* **frontend:** add apple splash for pwa ([232def9](https://github.com/sct/overseerr/commit/232def972b9156afcbd83592708dbf8b5866ee24)) +* **frontend:** add apple tv+ to network slider ([3dc27ff](https://github.com/sct/overseerr/commit/3dc27ffd9bb054e6cda58872939dbc352877d184)), closes [#1219](https://github.com/sct/overseerr/issues/1219) +* **frontend:** allow selecting multiple original languages ([a908c07](https://github.com/sct/overseerr/commit/a908c07670532b0ca7f766065bb4653ce2376e6f)) +* **lang:** add Catalan to language picker ([#1309](https://github.com/sct/overseerr/issues/1309)) ([77911c0](https://github.com/sct/overseerr/commit/77911c03e98aa3c2c6c062a01c22b030704309c2)) +* **lang:** translations update from Weblate ([#1178](https://github.com/sct/overseerr/issues/1178)) ([3c89010](https://github.com/sct/overseerr/commit/3c89010629bc16f225f1d3936abe9f4e47a0d7c7)) +* **lang:** translations update from Weblate ([#1224](https://github.com/sct/overseerr/issues/1224)) ([c1975b3](https://github.com/sct/overseerr/commit/c1975b33f1115a95068be000b7f479a401f0f0ae)) +* **lang:** translations update from Weblate ([#1237](https://github.com/sct/overseerr/issues/1237)) ([dabd32a](https://github.com/sct/overseerr/commit/dabd32a18b42980059c7a7a7450514ca827a5d3b)) +* **lang:** translations update from Weblate ([#1256](https://github.com/sct/overseerr/issues/1256)) ([e9b1a9e](https://github.com/sct/overseerr/commit/e9b1a9e80e6b8285fa451a8551c5832a850c1746)) +* **lang:** translations update from Weblate ([#1281](https://github.com/sct/overseerr/issues/1281)) ([bec1d3d](https://github.com/sct/overseerr/commit/bec1d3dde834b9a50e24c5894c362e5982ff3bd5)) +* **lang:** translations update from Weblate ([#1305](https://github.com/sct/overseerr/issues/1305)) ([1b129c0](https://github.com/sct/overseerr/commit/1b129c0b3863ea3c5ad34c66b3ace5d09cd4e391)) +* **lang:** translations update from Weblate ([#1313](https://github.com/sct/overseerr/issues/1313)) ([18ce349](https://github.com/sct/overseerr/commit/18ce349faac6ee560b9c92374039954f2365a8d1)) +* **logs:** add copy to clipboard button to logs page ([e2b8745](https://github.com/sct/overseerr/commit/e2b8745fdc192f3d49872625652184005a760885)) +* **notif:** include requested season numbers in notifications ([#1211](https://github.com/sct/overseerr/issues/1211)) ([4ee78ab](https://github.com/sct/overseerr/commit/4ee78ab2fe0359df6baa58f0986687f05a8392a2)) +* **requests:** add request quotas ([#1277](https://github.com/sct/overseerr/issues/1277)) ([6c75c88](https://github.com/sct/overseerr/commit/6c75c8822842514ffd31864992e8d3ce686fea1b)) +* **settings:** logs viewer ([#997](https://github.com/sct/overseerr/issues/997)) ([54429bb](https://github.com/sct/overseerr/commit/54429bbc1d765d0e50486a42749f9bbd4e5b3386)) +* **ui:** add movie/series genre list pages ([#1194](https://github.com/sct/overseerr/issues/1194)) ([6f1a31d](https://github.com/sct/overseerr/commit/6f1a31de473d1a25bc77e0961a52b07050b64c51)) +* **ui:** add option to only allow complete series requests ([#1164](https://github.com/sct/overseerr/issues/1164)) ([36c00fd](https://github.com/sct/overseerr/commit/36c00fde273799a56ec42ce6177ff44fed0904c3)) +* **ui:** Add user requests page ([#936](https://github.com/sct/overseerr/issues/936)) ([a9461f7](https://github.com/sct/overseerr/commit/a9461f760d8112f2ae16183e796f706d3392f8ec)) +* **ui:** allow any value 1-100 for quota limit/days ([#1337](https://github.com/sct/overseerr/issues/1337)) ([f4bed9a](https://github.com/sct/overseerr/commit/f4bed9a63b6b856ebedca9eb7662cd00038d7f7c)) +* **ui:** display movie/series original title ([#1240](https://github.com/sct/overseerr/issues/1240)) ([7230915](https://github.com/sct/overseerr/commit/723091509414465e98d870b3dc943f41b9ac590d)) +* **ui:** experimental status bar style change for ios pwa app ([958cdf9](https://github.com/sct/overseerr/commit/958cdf98fd1cb7c1bdb33aebb6c061750e9ab331)) +* **ui:** store sort order and page size of userlist in localstorage ([#1262](https://github.com/sct/overseerr/issues/1262)) ([f5f8269](https://github.com/sct/overseerr/commit/f5f8269cd28ee792120060f4f38ef09d571fb8d5)) +* add option to cache images locally ([#1213](https://github.com/sct/overseerr/issues/1213)) ([0ca3d43](https://github.com/sct/overseerr/commit/0ca3d4374942b54b59a19d017ab4ae14ba7019c1)) +* genre sliders (experiment) ([#1182](https://github.com/sct/overseerr/issues/1182)) ([1c4515a](https://github.com/sct/overseerr/commit/1c4515a1ae6097f3948aaa0d0ed210831581fd98)) + + +### Reverts + +* **ui:** remove local image cache option from settings page ([911faef](https://github.com/sct/overseerr/commit/911faeff562b737a2d18a395fcd90bf354af0cc4)) +* remove experimental tailwind jit compiler until title card hover is fixed ([1df67ba](https://github.com/sct/overseerr/commit/1df67baf9e7cdabc4045a0c115735797e8081bca)) +* **deps:** revert react-intl to 5.13.5 ([e16277c](https://github.com/sct/overseerr/commit/e16277c07d58ddbb749f4a60bc05924f4a5af146)) + ## [1.21.1](https://github.com/sct/overseerr/compare/v1.21.0...v1.21.1) (2021-03-15) diff --git a/package.json b/package.json index 21cd2481d..4a79ed44f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.21.1", + "version": "1.22.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 67e794aca490aeb3bd92ffdac9dac987b143ca90 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 16 Apr 2021 12:38:05 +0000 Subject: [PATCH 037/238] chore(release): 1.23.0 --- CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a242a96e7..16a1f4413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +# [1.23.0](https://github.com/sct/overseerr/compare/v1.22.0...v1.23.0) (2021-04-16) + + +### Bug Fixes + +* **api:** allow server owner to delete other admin accounts ([2ac6fe7](https://github.com/sct/overseerr/commit/2ac6fe7f6d666d64228d11cde24865acc54c7ce7)) +* **backend:** do not log error when user has no server access ([#1419](https://github.com/sct/overseerr/issues/1419)) ([fc14037](https://github.com/sct/overseerr/commit/fc14037ec1c0b7450d892fa9be8176f5b9ff9d73)) +* **frontend:** add crossorigin attribute to webmanifest link ([#1376](https://github.com/sct/overseerr/issues/1376)) ([82ca2f5](https://github.com/sct/overseerr/commit/82ca2f59349407e3b1b5cd4f321e196f37044df0)) +* **frontend:** autofill with Plex server address ([#1381](https://github.com/sct/overseerr/issues/1381)) ([d9e314b](https://github.com/sct/overseerr/commit/d9e314bad295463d26d8ffe92728f3b5eee4ad05)) +* **frontend:** handle media items/requests no longer having a valid tmdb id ([b5ac2f5](https://github.com/sct/overseerr/commit/b5ac2f5a2c5dda808eca177359f125d6e03d1b0f)), closes [#517](https://github.com/sct/overseerr/issues/517) +* **lang:** remove unused strings & correct manageModalNoRequests strings ([#1413](https://github.com/sct/overseerr/issues/1413)) ([190a5c0](https://github.com/sct/overseerr/commit/190a5c0723d4aeafc4ad6103d52c2042a4eaed0e)) +* **plex:** do not use SSL for local servers ([#1418](https://github.com/sct/overseerr/issues/1418)) ([9233fc0](https://github.com/sct/overseerr/commit/9233fc078579df8a193344ba45bafb0d5c2cb9af)) +* **plex:** use server 'address' returned by Plex API ([#1379](https://github.com/sct/overseerr/issues/1379)) ([33542c9](https://github.com/sct/overseerr/commit/33542c9b2dc53b1e036a7d9571cf467c3d3dc8af)) +* **quotas:** Time value of a quota was being ignored ([d3c6bc1](https://github.com/sct/overseerr/commit/d3c6bc1619c39b1e6225d405efaad5df99a27406)) +* **ui:** allow canceling from request list & hide edit button for own requests ([#1401](https://github.com/sct/overseerr/issues/1401)) ([bed850d](https://github.com/sct/overseerr/commit/bed850dce9ad0d0b52c3c628225aea938164c38b)) +* **ui:** close sidebar on mobile when clicking version status ([ad67381](https://github.com/sct/overseerr/commit/ad673813976669797202c2cefc50274aca84989d)) +* **ui:** correctly set autocomplete attribute for password fields ([#1430](https://github.com/sct/overseerr/issues/1430)) ([4b5e355](https://github.com/sct/overseerr/commit/4b5e355df9e291a5cb550483c7dad6c43f03d3a7)) +* **ui:** dim password field when password generation option is selected ([#1427](https://github.com/sct/overseerr/issues/1427)) ([e8bbd44](https://github.com/sct/overseerr/commit/e8bbd4497a5eab6357fa7b37c9906285b3d1f64f)) +* **ui:** hide alert when email notifs are already configured ([#1335](https://github.com/sct/overseerr/issues/1335)) ([5117987](https://github.com/sct/overseerr/commit/5117987feaed21ccc19e64b04a15f2b77c22b880)) +* fall back to English genre names ([#1352](https://github.com/sct/overseerr/issues/1352)) ([e43106a](https://github.com/sct/overseerr/commit/e43106a434548840acecaf1276a5cebdc30e1345)) +* fix outofdate string & display version status badge in Settings > About ([#1417](https://github.com/sct/overseerr/issues/1417)) ([4eb9209](https://github.com/sct/overseerr/commit/4eb92098ba1f141bf74875ce76816a615763de5f)) +* various fixes for new tags feature ([#1369](https://github.com/sct/overseerr/issues/1369)) ([b4450a3](https://github.com/sct/overseerr/commit/b4450a308c56f767fbaa769d574a1b3f8e221d59)) +* **ui:** link request card status badge to Plex media URL ([#1361](https://github.com/sct/overseerr/issues/1361)) ([7a5c4a3](https://github.com/sct/overseerr/commit/7a5c4a30b5735fe6fbe821a8fcfdb4bcbeca68b3)) + + +### Features + +* **lang:** Translations update from Weblate ([#1429](https://github.com/sct/overseerr/issues/1429)) ([a54241c](https://github.com/sct/overseerr/commit/a54241c775705fadc7c044f5312307f28f9a854b)) +* change alpha warning to beta warning ([03fd21b](https://github.com/sct/overseerr/commit/03fd21bebc3ffa34ce983b524d09e74b8ab2d057)) +* **lang:** translated using Weblate (Catalan) ([#1351](https://github.com/sct/overseerr/issues/1351)) ([35c13a8](https://github.com/sct/overseerr/commit/35c13a87467b4deabab3cb2cd1cab1b24ab51875)) +* **lang:** translations update from Weblate ([#1360](https://github.com/sct/overseerr/issues/1360)) ([8ee7693](https://github.com/sct/overseerr/commit/8ee7693a1f00a2f735b2555c7f8180c8a2c6144f)) +* **lang:** translations update from Weblate ([#1416](https://github.com/sct/overseerr/issues/1416)) ([dceca4d](https://github.com/sct/overseerr/commit/dceca4dd97f78f2e3aef678edcd5755c781f5249)) +* add overseerr version and update availability status to sidebar ([ecf1312](https://github.com/sct/overseerr/commit/ecf13123d21d765d67bfa7f9b6509b0f2af62cee)) +* **lang:** translations update from Weblate ([#1388](https://github.com/sct/overseerr/issues/1388)) ([9b199b2](https://github.com/sct/overseerr/commit/9b199b27d806e290cf0551e2d2ede6add61770aa)) +* **lang:** translations update from Weblate ([#1396](https://github.com/sct/overseerr/issues/1396)) ([3daf57e](https://github.com/sct/overseerr/commit/3daf57e9a12e4973dbc56656379ab2dbcb3c2619)) +* **notif:** allow users to enable/disable specific agents ([#1172](https://github.com/sct/overseerr/issues/1172)) ([46c4ee1](https://github.com/sct/overseerr/commit/46c4ee1625cf3e74bd885ecfc254b1e46cf44f29)) +* **webhook:** include requestedBy user in payload ([#1385](https://github.com/sct/overseerr/issues/1385)) ([e605687](https://github.com/sct/overseerr/commit/e60568758097d07f9d4b201ffdf34f0c32ba9cf3)) +* radarr/sonarr tag support ([#1366](https://github.com/sct/overseerr/issues/1366)) ([a306ebc](https://github.com/sct/overseerr/commit/a306ebc2d18317d8dbe4ccd3f24c22f55ffcd6a6)) + # [1.22.0](https://github.com/sct/overseerr/compare/v1.21.1...v1.22.0) (2021-04-01) diff --git a/package.json b/package.json index e7876f0a3..6df971c62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.22.0", + "version": "1.23.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 8c782e0c934093eaba58be9fe841c6228b878b20 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 16 Apr 2021 14:19:53 +0000 Subject: [PATCH 038/238] chore(release): 1.23.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a1f4413..82bdc5746 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.23.1](https://github.com/sct/overseerr/compare/v1.23.0...v1.23.1) (2021-04-16) + + +### Bug Fixes + +* **api:** correctly check if update is available for release versions ([190cbd6](https://github.com/sct/overseerr/commit/190cbd6559c51a02ec09b267891f3033add6afc8)) + # [1.23.0](https://github.com/sct/overseerr/compare/v1.22.0...v1.23.0) (2021-04-16) diff --git a/package.json b/package.json index 6df971c62..08f8a3024 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.23.0", + "version": "1.23.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From f2c37babeb20069905bccda5a577c1c63373efbf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 21 Apr 2021 12:28:28 +0000 Subject: [PATCH 039/238] chore(release): 1.23.2 --- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82bdc5746..8e20230e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +## [1.23.2](https://github.com/sct/overseerr/compare/v1.23.1...v1.23.2) (2021-04-21) + + +### Bug Fixes + +* **lang:** add missing '4K' from singular case of approve/deny 4K request strings ([#1481](https://github.com/sct/overseerr/issues/1481)) ([a822b01](https://github.com/sct/overseerr/commit/a822b019220e86e362a2570e7024289450b4ed46)) +* **ui:** change 'Disable Auto-Search' checkbox to 'Enable Automatic Search' ([#1476](https://github.com/sct/overseerr/issues/1476)) ([1a311d2](https://github.com/sct/overseerr/commit/1a311d211d78731c9089e66ed5387c1b5afe33c0)) +* better error message when creating a user with an existing email ([f13f1c9](https://github.com/sct/overseerr/commit/f13f1c94515b5bd51382fa18ad96a2ccfd06e50d)), closes [#1441](https://github.com/sct/overseerr/issues/1441) +* set editRequest attribute as necessary, allow users to edit their own pending requests, and show 'View Request' button on series pages ([#1446](https://github.com/sct/overseerr/issues/1446)) ([89455ad](https://github.com/sct/overseerr/commit/89455ad9b783d04d993a0009c351b1096f2b222e)) +* **api:** add check for 4K request perms to request creation endpoint ([#1450](https://github.com/sct/overseerr/issues/1450)) ([4449241](https://github.com/sct/overseerr/commit/4449241a8f63fdaeaa4995aa7ec34127c322b9dd)) +* **notif:** include year in notifications ([#1439](https://github.com/sct/overseerr/issues/1439)) ([4e98f56](https://github.com/sct/overseerr/commit/4e98f567534a650e26b0244990b7ca549cecbe89)) +* **plex:** add support for plex.direct URLs ([#1437](https://github.com/sct/overseerr/issues/1437)) ([db07770](https://github.com/sct/overseerr/commit/db077700e42ab1d2c870213fd55bbdee74002775)) +* **radarr:** search in addition to monitoring existing movies ([#1449](https://github.com/sct/overseerr/issues/1449)) ([3ae7d00](https://github.com/sct/overseerr/commit/3ae7d0098b225562499d7c8a74b8b6c3e8893ad9)) +* **ui:** adjust user list buttons on mobile ([#1452](https://github.com/sct/overseerr/issues/1452)) ([5d1b741](https://github.com/sct/overseerr/commit/5d1b741f55665c528e299a09464dff6d66f72666)) +* **ui:** align icons in user dropdown ([eb5d152](https://github.com/sct/overseerr/commit/eb5d1528869959cdf642e6fefc1a8f4dcf51b84e)) + ## [1.23.1](https://github.com/sct/overseerr/compare/v1.23.0...v1.23.1) (2021-04-16) diff --git a/package.json b/package.json index 6092bdde8..0c7a91b34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.23.1", + "version": "1.23.2", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 800f4668ec8356dfb33ffe26b8f4389adfcedfcb Mon Sep 17 00:00:00 2001 From: sct Date: Sat, 24 Apr 2021 09:10:36 +0900 Subject: [PATCH 040/238] chore: fix gitattributes for images [skip ci] --- .gitattributes | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/.gitattributes b/.gitattributes index fcadb2cf9..2883a5d26 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,26 @@ * text eol=lf + +# +## These files are binary and should be left untouched +# + +# (binary is a macro for -text -diff) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary +*.eot binary +*.woff binary +*.pyc binary +*.pdf binary From 7e0e2a92e33fb06f3dbb5f4804a3377174c183bd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 5 May 2021 08:57:48 +0000 Subject: [PATCH 041/238] chore(release): 1.24.0 --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e20230e0..2d8b5e4e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +# [1.24.0](https://github.com/sct/overseerr/compare/v1.23.2...v1.24.0) (2021-05-05) + + +### Bug Fixes + +* **api:** do not try to transform empty values passed to user notificationTypes ([ef3f977](https://github.com/sct/overseerr/commit/ef3f9778aa81f8ed39dcd835d63d94f2248e0204)), closes [#1501](https://github.com/sct/overseerr/issues/1501) +* **backend:** properly set request media status ([#1541](https://github.com/sct/overseerr/issues/1541)) ([b7b55e2](https://github.com/sct/overseerr/commit/b7b55e275cb2f1f61c3057cb8ab4cb1027f6356d)) +* **css:** don't target button globally ([#1510](https://github.com/sct/overseerr/issues/1510)) ([f78b9c1](https://github.com/sct/overseerr/commit/f78b9c1ca9648eb10b010e526d9b9db09648b154)) +* **css:** fix cog icon size on media detail pages ([#1520](https://github.com/sct/overseerr/issues/1520)) ([26ddc03](https://github.com/sct/overseerr/commit/26ddc03b2c01b343c24f1c359b78c587310cc747)) +* **email:** parse sender hostname from application URL ([#1518](https://github.com/sct/overseerr/issues/1518)) ([3baa55c](https://github.com/sct/overseerr/commit/3baa55c690dd9ba39768b8b271595cb6b09fe6da)) +* **lang:** correct overwritten email toast strings ([11a5e8d](https://github.com/sct/overseerr/commit/11a5e8d95bc2a2f16adf1e48d2ef38b508a6ace5)) +* **locale:** default user locale should be the server setting ([#1574](https://github.com/sct/overseerr/issues/1574)) ([549103f](https://github.com/sct/overseerr/commit/549103f6f6d5624201e425df7d7814f0f67863b9)) +* **pwa:** add Discover shortcut and fix/optimize icons ([#1525](https://github.com/sct/overseerr/issues/1525)) ([e1dc62b](https://github.com/sct/overseerr/commit/e1dc62b0a5b64202701aff821837ed11dd3f12db)) +* **radarr:** only process Radarr movies which are either monitored or downloaded ([#1511](https://github.com/sct/overseerr/issues/1511)) ([85899ab](https://github.com/sct/overseerr/commit/85899ab49a27542390e91443531905737224338d)) +* **ui:** add missing margins on button SVGs on Plex Settings page ([#1546](https://github.com/sct/overseerr/issues/1546)) ([5e588be](https://github.com/sct/overseerr/commit/5e588be8127b50dd83477f7f3a65f18de774e8af)) +* **ui:** add user profile links to RequestBlock and change 'ETA' string in DownloadBlock ([#1551](https://github.com/sct/overseerr/issues/1551)) ([e4d0029](https://github.com/sct/overseerr/commit/e4d0029f7b4245b8606e2447c54629def40c7761)) +* **ui:** apply rounded-l-only to SensitiveInput textareas and increase visible text input area ([#1561](https://github.com/sct/overseerr/issues/1561)) ([1123fce](https://github.com/sct/overseerr/commit/1123fce089b86251dcafebf77743d60a6e396bee)) +* **ui:** correct RegionSelector z-index ([#1567](https://github.com/sct/overseerr/issues/1567)) ([e912a00](https://github.com/sct/overseerr/commit/e912a00880f856fa9621e8587ef1cc6513a3d49c)) +* **ui:** correct toasts being in the wrong position on smaller screens ([2ecd9d7](https://github.com/sct/overseerr/commit/2ecd9d7b1391b8fc83e9c12a18bab105e7148f0f)) +* **ui:** default to text input type for SensitiveInputs ([#1568](https://github.com/sct/overseerr/issues/1568)) ([e2acf88](https://github.com/sct/overseerr/commit/e2acf8887cb0456c80308bd1b7f3bbe1930e8cff)) +* **ui:** explicitly specify width/height of Listbox dropdown icon ([#1514](https://github.com/sct/overseerr/issues/1514)) ([802e40a](https://github.com/sct/overseerr/commit/802e40a5dfa00f897f9d5a741718a319f74ff030)) +* **ui:** improve form usability ([#1563](https://github.com/sct/overseerr/issues/1563)) ([26580ea](https://github.com/sct/overseerr/commit/26580eaa218702bc5841718310e340d049c50332)) +* **ui:** show warning if user has both a default non-4K server and a non-default 4K server ([#1478](https://github.com/sct/overseerr/issues/1478)) ([4faddf3](https://github.com/sct/overseerr/commit/4faddf3810e20851c7ae1251ff0187fa13d7b0f6)) +* **webpush:** only prompt user to allow notifications if enabled in user settings ([#1552](https://github.com/sct/overseerr/issues/1552)) ([b05b177](https://github.com/sct/overseerr/commit/b05b177776a5d22bf3b5e93bad4358f4007b879a)) +* correctly fall back to English name in LanguageSelector ([#1537](https://github.com/sct/overseerr/issues/1537)) ([189313e](https://github.com/sct/overseerr/commit/189313e94a16e694d192d157642d77f664fd709b)) +* do not set locale when modifying other users ([#1499](https://github.com/sct/overseerr/issues/1499)) ([4858771](https://github.com/sct/overseerr/commit/48587719e9474139c7bbc2970b1c7d1d17b78a81)) + + +### Features + +* **email:** replace 'Enable SSL' setting with more descriptive/clear 'Encryption Method' setting ([#1549](https://github.com/sct/overseerr/issues/1549)) ([69ab7cc](https://github.com/sct/overseerr/commit/69ab7cc660bea43b70bdb646eabd3866c1b5a90f)) +* **inputs:** add support for toggling security on input fields ([#1404](https://github.com/sct/overseerr/issues/1404)) ([4fd452d](https://github.com/sct/overseerr/commit/4fd452dd1880f597a0acda812d567e7cb6c16d83)) +* **lang:** translated using Weblate (Spanish) ([#1553](https://github.com/sct/overseerr/issues/1553)) ([e3d5e33](https://github.com/sct/overseerr/commit/e3d5e33ec3e43d36ec832d6ca47f330fc7675088)) +* **lang:** translations update from Weblate ([#1497](https://github.com/sct/overseerr/issues/1497)) ([9a95a07](https://github.com/sct/overseerr/commit/9a95a073916c9968b8ef348d0805d77400ea203a)) +* **lang:** translations update from Weblate ([#1527](https://github.com/sct/overseerr/issues/1527)) ([1a6d4bd](https://github.com/sct/overseerr/commit/1a6d4bddc016f4aaad83b945e103b19be4d0da31)) +* **lang:** translations update from Weblate ([#1558](https://github.com/sct/overseerr/issues/1558)) ([6c9991d](https://github.com/sct/overseerr/commit/6c9991d474a5cd95d9a0a10104bd79d8a9f3ada9)) +* **lang:** translations update from Weblate ([#1566](https://github.com/sct/overseerr/issues/1566)) ([93c441e](https://github.com/sct/overseerr/commit/93c441ef6665291ca3698368e4b093c843726036)) +* add server default locale setting ([#1536](https://github.com/sct/overseerr/issues/1536)) ([f256a44](https://github.com/sct/overseerr/commit/f256a444c57f2d92c1c4918d4ff6e223ef85ecd2)) +* **notif:** add LunaSea agent ([#1495](https://github.com/sct/overseerr/issues/1495)) ([4e6fb00](https://github.com/sct/overseerr/commit/4e6fb00a4a59545817add1544c0b1555078809a4)) +* **notif:** show success/failure toast for test notifications ([#1442](https://github.com/sct/overseerr/issues/1442)) ([079645c](https://github.com/sct/overseerr/commit/079645c2c74edfb7e4f583de2ac72bb9824f6524)) +* **perms:** add separate REQUEST_MOVIE and REQUEST_TV permissions ([#1474](https://github.com/sct/overseerr/issues/1474)) ([91b9e0f](https://github.com/sct/overseerr/commit/91b9e0f67996a442b5c0117fe09e2d69c163fafb)) +* **pwa:** add shortcuts to PWA ([#1509](https://github.com/sct/overseerr/issues/1509)) ([ed99e49](https://github.com/sct/overseerr/commit/ed99e4976dc2700fe84c70af4887c1a431bba92c)) +* add option to only allow Plex sign-in from existing users ([#1496](https://github.com/sct/overseerr/issues/1496)) ([db49b20](https://github.com/sct/overseerr/commit/db49b2024d399d90f2d1500b262374efc42f333c)) +* PWA Support ([#1488](https://github.com/sct/overseerr/issues/1488)) ([28830d4](https://github.com/sct/overseerr/commit/28830d4ef809efa92a5879a81cac11ff52ea3d1f)) + ## [1.23.2](https://github.com/sct/overseerr/compare/v1.23.1...v1.23.2) (2021-04-21) diff --git a/package.json b/package.json index 564a5e41c..4828b6039 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.23.2", + "version": "1.24.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From f858f025fe0359f7675771801e8c518892cb6ab0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 10 Jun 2021 10:25:49 +0000 Subject: [PATCH 042/238] chore(release): 1.25.0 --- CHANGELOG.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d8b5e4e5..0b5ec025e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,65 @@ +# [1.25.0](https://github.com/sct/overseerr/compare/v1.24.0...v1.25.0) (2021-06-10) + + +### Bug Fixes + +* **frontend:** add missing route guards to settings pages ([#1700](https://github.com/sct/overseerr/issues/1700)) ([78fc1f7](https://github.com/sct/overseerr/commit/78fc1f7b7d9ef912077066a3605fed6237fb4c8a)) +* **locale:** set locale based on user settings upon login ([#1584](https://github.com/sct/overseerr/issues/1584)) ([f48312e](https://github.com/sct/overseerr/commit/f48312e833ed5d48c41179d0eadbc66d45486d8a)) +* **notif:** include year in Media Available notifications ([#1672](https://github.com/sct/overseerr/issues/1672)) ([11aa712](https://github.com/sct/overseerr/commit/11aa712eb0e8796874c96fbcc9b51b523108e2d4)) +* **plex:** disable library sync if Plex not configured, and disable scan if no libraries ([#1764](https://github.com/sct/overseerr/issues/1764)) ([22238fe](https://github.com/sct/overseerr/commit/22238fe4f711267d001be95942b3151c536e0c18)) +* **plex:** do not fail to import Plex users when Plex Home has managed users ([#1699](https://github.com/sct/overseerr/issues/1699)) ([310cdb3](https://github.com/sct/overseerr/commit/310cdb36df1601bca5e57f0bc796c44111b8435f)) +* **plex:** sync libraries after saving settings ([#1592](https://github.com/sct/overseerr/issues/1592)) ([9749d72](https://github.com/sct/overseerr/commit/9749d723fc0a282b291c06ee68a6e174dcec1c5b)) +* **requests:** appropriately set modifiedBy user for new requests ([#1684](https://github.com/sct/overseerr/issues/1684)) ([a3f04b3](https://github.com/sct/overseerr/commit/a3f04b3f3522d46dc65178bddd1e986426e48050)) +* **requests:** do not prevent duplicate requests if other requests are declined ([de0759c](https://github.com/sct/overseerr/commit/de0759c26a9e857e2b8d7244673625fc79ee4660)) +* **requests:** prevent duplicate movie requests ([126d866](https://github.com/sct/overseerr/commit/126d8665ee2808fc0bc37df4ca61f3e63be096e2)) +* check that application URL and email agent are configured for password reset/generation ([#1724](https://github.com/sct/overseerr/issues/1724)) ([091d66a](https://github.com/sct/overseerr/commit/091d66a1928d3c69a11eab2a789b4639b5ba9817)) +* correctly display error messages ([#1653](https://github.com/sct/overseerr/issues/1653)) ([31cb717](https://github.com/sct/overseerr/commit/31cb7176d286e706575a2dc8003df13f3e737106)) +* handle null values in User email transform ([#1712](https://github.com/sct/overseerr/issues/1712)) ([4a042f1](https://github.com/sct/overseerr/commit/4a042f12be6510ee47de3a7e025497f8d132d6a1)) +* **lang:** only set locale once at page load and move subsequent updates back into Layout ([14756f4](https://github.com/sct/overseerr/commit/14756f4b208c5b201a6e632b43e7a21c5bec6f9c)), closes [#1662](https://github.com/sct/overseerr/issues/1662) +* **locale:** properly restore display language upon page refresh ([#1646](https://github.com/sct/overseerr/issues/1646)) ([e85d1ce](https://github.com/sct/overseerr/commit/e85d1ce94ec45d8f5d086722cfd88e0e2c5b4bb6)) +* **notifications:** default webpush notification agent to enabled for users for settings response ([7520e24](https://github.com/sct/overseerr/commit/7520e24e9287e214dd31224f1201e9b6385fd567)), closes [#1663](https://github.com/sct/overseerr/issues/1663) +* **quotas:** do not count already-requested seasons when editing TV request ([#1649](https://github.com/sct/overseerr/issues/1649)) ([808ccf1](https://github.com/sct/overseerr/commit/808ccf1c6975f853db6dc89f4d9f1f5488dbaae3)) +* **requests:** remove requestedBy user param from existing movie request check ([#1569](https://github.com/sct/overseerr/issues/1569)) ([788f3dc](https://github.com/sct/overseerr/commit/788f3dc435ae224fcc4d4cb2890b1b9b494c64e8)) +* **sensitiveinput:** do not capture enter key input ([#1650](https://github.com/sct/overseerr/issues/1650)) ([bb8d14b](https://github.com/sct/overseerr/commit/bb8d14b5ffd840eff0c2a00e1b5d318677a5ca5f)) +* **sonarr:** do not mark media as failed if there is no season data on TVDB ([#1691](https://github.com/sct/overseerr/issues/1691)) ([0cd7fa0](https://github.com/sct/overseerr/commit/0cd7fa0f1a00d129339be13550a4f694c820a0e9)) +* **tv:** don't show duplicate air date ([#1666](https://github.com/sct/overseerr/issues/1666)) ([e1f5feb](https://github.com/sct/overseerr/commit/e1f5febe7bbf27e77b6f5d057c2c3f7e22898734)) +* **ui:** add clarification to user settings ([#1644](https://github.com/sct/overseerr/issues/1644)) ([2ef57e9](https://github.com/sct/overseerr/commit/2ef57e9b1a5b4d0a1499921f4e26b0b0712d7ded)) +* **ui:** correct horizontal overflow behavior of settings tabs ([#1667](https://github.com/sct/overseerr/issues/1667)) ([e6d5f0a](https://github.com/sct/overseerr/commit/e6d5f0abfebdc24f25d08822b57a8eb7bc48e137)) +* **ui:** hide advanced request options when there is only one choice ([#1591](https://github.com/sct/overseerr/issues/1591)) ([6b26188](https://github.com/sct/overseerr/commit/6b26188d888a1f80bd36a1968e41333bab2af794)) +* **ui:** improve QuotaSelector display of unlimited and singular values ([#1704](https://github.com/sct/overseerr/issues/1704)) ([59b2ec1](https://github.com/sct/overseerr/commit/59b2ec11fa8868bf6873ffa80f4999ae10d65637)) +* perform case-insensitive match for local user email addresses ([#1633](https://github.com/sct/overseerr/issues/1633)) ([928b8a7](https://github.com/sct/overseerr/commit/928b8a71cf361b7bc2b8957c621f5b66c4657b1e)) +* **ui:** apply pointer cursor style for clickable status badges ([#1632](https://github.com/sct/overseerr/issues/1632)) ([6968caa](https://github.com/sct/overseerr/commit/6968caa35a70c172bdd57c984fde6cb6a04a1470)) +* **ui:** remove delete button from request cards ([#1635](https://github.com/sct/overseerr/issues/1635)) ([6b37242](https://github.com/sct/overseerr/commit/6b37242a3f5a3b332d259f4814d235d751ae2491)) +* switch PGP regex to span multiple lines ([#1598](https://github.com/sct/overseerr/issues/1598)) ([d0703aa](https://github.com/sct/overseerr/commit/d0703aa37772759e8e28b5da7187e97e7aadc495)) +* **ui:** hide Plex alert after setup and add local login warning to local user modal ([#1600](https://github.com/sct/overseerr/issues/1600)) ([694d0ff](https://github.com/sct/overseerr/commit/694d0ffcf6b3e3fa00175400fa4217a7d6eb787f)) + + +### Features + +* **lang:** add Greek display language ([#1605](https://github.com/sct/overseerr/issues/1605)) ([2241564](https://github.com/sct/overseerr/commit/22415642e8602809e3507e5b13dc2f8de3000003)) +* **lang:** translations update from Weblate ([#1585](https://github.com/sct/overseerr/issues/1585)) ([361ea77](https://github.com/sct/overseerr/commit/361ea77588db3dc04a51dd3a62c73ae1297cdce2)) +* **lang:** translations update from Weblate ([#1603](https://github.com/sct/overseerr/issues/1603)) ([2efa7fa](https://github.com/sct/overseerr/commit/2efa7faf20d05a5fc423e0151c6b46fe6212d096)) +* **lang:** translations update from Weblate ([#1639](https://github.com/sct/overseerr/issues/1639)) ([d22400d](https://github.com/sct/overseerr/commit/d22400dbc9320743498eeb8e6a4dcbccf1a4d52d)) +* **lang:** translations update from Weblate ([#1676](https://github.com/sct/overseerr/issues/1676)) ([8a80571](https://github.com/sct/overseerr/commit/8a805716e3e34ae8d081ad47f9d4cd68f88b0116)) +* **lang:** translations update from Weblate ([#1703](https://github.com/sct/overseerr/issues/1703)) ([6a3649f](https://github.com/sct/overseerr/commit/6a3649f620e518ff07a48c17ce1182aaedff398a)) +* **lang:** translations update from Weblate ([#1727](https://github.com/sct/overseerr/issues/1727)) ([60c3ced](https://github.com/sct/overseerr/commit/60c3ced9e2466568eecde93c88410c87ff0b796f)) +* **lang:** translations update from Weblate ([#1746](https://github.com/sct/overseerr/issues/1746)) ([37a4df6](https://github.com/sct/overseerr/commit/37a4df646cc3e3101360037f1b6f061a734eb5e2)) +* **lang:** translations update from Weblate ([#1768](https://github.com/sct/overseerr/issues/1768)) ([dedf95e](https://github.com/sct/overseerr/commit/dedf95e574a15a708866c381353e58ce3b3a1a61)) +* add display name to create local user modal ([#1631](https://github.com/sct/overseerr/issues/1631)) ([44c3edb](https://github.com/sct/overseerr/commit/44c3edb98568ba15eb525e665115429cfb15d28b)) +* allow users to select notification types ([#1512](https://github.com/sct/overseerr/issues/1512)) ([e605989](https://github.com/sct/overseerr/commit/e60598905b2d6eef7c1872d0c9e92e6d70508ae8)) +* **notif:** prevent manage-request users receiving auto-approve notif from their requests ([#1707](https://github.com/sct/overseerr/issues/1707)) ([#1709](https://github.com/sct/overseerr/issues/1709)) ([9ead8bb](https://github.com/sct/overseerr/commit/9ead8bb1f1680b522550f963502c83e2f99d1e96)) +* **plex:** add support for custom Plex Web App URLs ([#1581](https://github.com/sct/overseerr/issues/1581)) ([a640a91](https://github.com/sct/overseerr/commit/a640a91390f1411637ad379a8253002fdf60480f)) +* **pwa:** add notification badge icon ([#1695](https://github.com/sct/overseerr/issues/1695)) ([9b3b6a9](https://github.com/sct/overseerr/commit/9b3b6a9170b25209e54c74aa9e96659bc2d19edd)) +* **ui:** request list item & request card improvements ([#1532](https://github.com/sct/overseerr/issues/1532)) ([d7b9b1a](https://github.com/sct/overseerr/commit/d7b9b1a525ec6d1d81ad6fe4e55994dd8428988f)) +* **webpush:** add warning to web push settings re: HTTPS requirement ([#1599](https://github.com/sct/overseerr/issues/1599)) ([0c4fb64](https://github.com/sct/overseerr/commit/0c4fb6446be425905a120df5be9a28b052e884c0)) + + +### Reverts + +* **deps:** revert back to typeorm 0.2.32 ([4368c3a](https://github.com/sct/overseerr/commit/4368c3aa4f88425ec08f3b555419e572cfa320e3)) +* **deps:** use 10.1.3 until css import issue is resolved ([2254248](https://github.com/sct/overseerr/commit/2254248abc0f2051a9dd28d9663c7ab1d0b547b6)) +* **requests:** go back to old modifiedBy request values for now ([0918b25](https://github.com/sct/overseerr/commit/0918b254132b0541999486e1f0679d0c0cd65864)) + # [1.24.0](https://github.com/sct/overseerr/compare/v1.23.2...v1.24.0) (2021-05-05) diff --git a/package.json b/package.json index b23952925..fd9709f6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.24.0", + "version": "1.25.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 353efa39d0aa2c940daf00bf711db91e4988e68f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 19 Sep 2021 12:15:52 +0000 Subject: [PATCH 043/238] chore(release): 1.26.0 --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b5ec025e..977bc0fe4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,37 @@ +# [1.26.0](https://github.com/sct/overseerr/compare/v1.25.0...v1.26.0) (2021-09-19) + + +### Bug Fixes + +* **email:** omit links when application URL is not configured ([#1806](https://github.com/sct/overseerr/issues/1806)) ([1133a34](https://github.com/sct/overseerr/commit/1133a34ffdf95c4d036be0264fe7f94f64007e8f)) +* **lang:** minor changes to password reset strings ([#1798](https://github.com/sct/overseerr/issues/1798)) ([a41245c](https://github.com/sct/overseerr/commit/a41245c703688743ec24f9b4a53e70f3340daa0f)) +* **notif:** truncate media overviews ([#1800](https://github.com/sct/overseerr/issues/1800)) ([42e45f3](https://github.com/sct/overseerr/commit/42e45f38e5ede7df0fc4bdb20a970917b2361569)) +* **plex:** do not fail to scan empty libraries ([#1771](https://github.com/sct/overseerr/issues/1771)) ([6789b87](https://github.com/sct/overseerr/commit/6789b8701cb644d9a3f1384f30b3dff707201ef7)) +* **quota:** block multi-season requests that would exceed a user's quota ([#1874](https://github.com/sct/overseerr/issues/1874)) ([8a55f85](https://github.com/sct/overseerr/commit/8a55f85d3ef14ccb83b139acb35d0746431637be)) +* **rt-api:** use rotten-tomatoes 2.0 search api for movies ([a11bb49](https://github.com/sct/overseerr/commit/a11bb49663ec345332c4dd70ddbb49ce230b5c3c)) +* **ui:** center logo on password reset pages ([#1807](https://github.com/sct/overseerr/issues/1807)) ([b8e82b5](https://github.com/sct/overseerr/commit/b8e82b5b4d3cb49ec372e3dce3cd89dff440ffd0)) +* **ui:** change sidebar breakpoint to lg ([#1972](https://github.com/sct/overseerr/issues/1972)) ([70bd9e9](https://github.com/sct/overseerr/commit/70bd9e9308b607206b60a2a36a511de6e397a3db)) +* **ui:** do not allow submission of invalid form inputs ([#1799](https://github.com/sct/overseerr/issues/1799)) ([910d00c](https://github.com/sct/overseerr/commit/910d00c19522a70125bfb5e5081a7ef4000e7f54)) +* **ui:** do not display negative remaining quota ([#1859](https://github.com/sct/overseerr/issues/1859)) ([3841fb0](https://github.com/sct/overseerr/commit/3841fb06ebe1e09250362cc6cb401fdca12eef7f)) +* **ui:** fix notifications settings buttons overflowing ([#1911](https://github.com/sct/overseerr/issues/1911)) ([0ce18b2](https://github.com/sct/overseerr/commit/0ce18b21ca547af6c083c3f248e22b7daf92aef0)) +* **ui:** sort 'Request As' user dropdown by display name ([#2099](https://github.com/sct/overseerr/issues/2099)) ([bb09f8e](https://github.com/sct/overseerr/commit/bb09f8eaf70f6d0c981f31bd5f3c8afb2fe101ab)) +* **webpush:** load user in push sub query ([#1894](https://github.com/sct/overseerr/issues/1894)) ([6f2db6a](https://github.com/sct/overseerr/commit/6f2db6a6ccf299262cf86d91acf639b921f28286)) +* correct logo filename ([#1805](https://github.com/sct/overseerr/issues/1805)) ([f95be83](https://github.com/sct/overseerr/commit/f95be832f95a68b114ff24a65ffa0ebbd71b4121)) + + +### Features + +* list streaming providers on movie/TV detail pages ([#1778](https://github.com/sct/overseerr/issues/1778)) ([98ece67](https://github.com/sct/overseerr/commit/98ece67655a5dffe894974e337a3603afeed0236)) +* **lang:** add Simplified Chinese display language ([#2032](https://github.com/sct/overseerr/issues/2032)) ([590ea7e](https://github.com/sct/overseerr/commit/590ea7e40460e381377b212d00869f191908b41f)) +* **lang:** translated using Weblate (German) ([#1791](https://github.com/sct/overseerr/issues/1791)) ([15f7941](https://github.com/sct/overseerr/commit/15f7941269075b7e12de8bbc0f98418af70df380)) +* **lang:** translations update from Weblate ([#1772](https://github.com/sct/overseerr/issues/1772)) ([6a75a05](https://github.com/sct/overseerr/commit/6a75a05c2348455d5374132a2574d988879d543a)) +* **lang:** translations update from Weblate ([#1796](https://github.com/sct/overseerr/issues/1796)) ([57b52fc](https://github.com/sct/overseerr/commit/57b52fc9cccd3fac93cdb68e36cf652ddbcdf86c)) +* **lang:** translations update from Weblate ([#1910](https://github.com/sct/overseerr/issues/1910)) ([fe89fd5](https://github.com/sct/overseerr/commit/fe89fd5f12460cb1b3acb09fb16b62497ef50f5f)) +* **lang:** translations update from Weblate ([#2058](https://github.com/sct/overseerr/issues/2058)) ([db42c46](https://github.com/sct/overseerr/commit/db42c4678145d2a9676aa71b6773607b696f7cea)) +* **notif:** Restyle HTML email notifications Part 2 ([#1917](https://github.com/sct/overseerr/issues/1917)) ([376149d](https://github.com/sct/overseerr/commit/376149d6ebb4db28d949391115f475afdd4e7d48)) +* **ui:** add 'show more/less...' for studios on movie details page ([#1770](https://github.com/sct/overseerr/issues/1770)) ([680ea0c](https://github.com/sct/overseerr/commit/680ea0c87a9ae143413354680c421d62bccd869d)) +* new logo, who dis? ([#1802](https://github.com/sct/overseerr/issues/1802)) ([beb5637](https://github.com/sct/overseerr/commit/beb5637d9f5c01d773eaee93035b7c195c2ae5f2)) + # [1.25.0](https://github.com/sct/overseerr/compare/v1.24.0...v1.25.0) (2021-06-10) diff --git a/package.json b/package.json index 508623efe..a12258c87 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.25.0", + "version": "1.26.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From f1df1caea5e314f0c309f4e45febfb73e06c1654 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 20 Sep 2021 00:22:06 +0000 Subject: [PATCH 044/238] chore(release): 1.26.1 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 977bc0fe4..2c79bc6eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [1.26.1](https://github.com/sct/overseerr/compare/v1.26.0...v1.26.1) (2021-09-20) + + +### Bug Fixes + +* **rt-api:** correctly format movie urls ([4c6009b](https://github.com/sct/overseerr/commit/4c6009bc2c3ff5f657a806363e3bdf7cd83d4261)) + # [1.26.0](https://github.com/sct/overseerr/compare/v1.25.0...v1.26.0) (2021-09-19) diff --git a/package.json b/package.json index a12258c87..09d72f52f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.26.0", + "version": "1.26.1", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 2333a81cfcf7eb1f9db2f3bb8ff1760f6553db85 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Fri, 8 Oct 2021 08:46:18 -0400 Subject: [PATCH 045/238] chore(github): update CODEOWNERS to allow collaborators to review/merge all-contributors PRs (#2165) [skip ci] --- .github/CODEOWNERS | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7babdcc7f..f090d7966 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,12 +1,14 @@ # Global code ownership -* @sct +* @sct # Documentation -docs/ @TheCatLady @samwiseg0 +/.all-contributorsrc @TheCatLady @samwiseg0 @danshilm +/*.md @TheCatLady @samwiseg0 @danshilm +/docs/ @TheCatLady @samwiseg0 @danshilm # Snap-related files -.github/workflows/snap.yaml @samwiseg0 -snap/ @samwiseg0 +/.github/workflows/snap.yaml @samwiseg0 +/snap/ @samwiseg0 # i18n locale files -src/i18n/locale/ @sct @TheCatLady +/src/i18n/locale/ @sct @TheCatLady From cbfe9beb3125abc8ee64711050e5080b0e07c397 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 12:52:40 +0000 Subject: [PATCH 046/238] docs: add sootylunatic as a contributor for translation (#2102) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 0367e4ac8..6dc9392f0 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -539,6 +539,15 @@ "contributions": [ "code" ] + }, + { + "login": "sootylunatic", + "name": "sootylunatic", + "avatar_url": "https://avatars.githubusercontent.com/u/36486087?v=4", + "profile": "https://github.com/sootylunatic", + "contributions": [ + "translation" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index 02c4a81a8..f873f3edd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -149,6 +149,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
tangentThought

💻
Nicolás Espinoza

💻 +
sootylunatic

🌍 From a20f395c94c97dd7ddbc25590f15def2c9bf13c9 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Fri, 8 Oct 2021 09:14:20 -0400 Subject: [PATCH 047/238] fix(api): use query builder for user requests endpoint (#2119) --- server/routes/user/index.ts | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index 4bccc772d..5d847e232 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -194,14 +194,11 @@ router.use('/:id/settings', userSettingsRoutes); router.get<{ id: string }, UserRequestsResponse>( '/:id/requests', async (req, res, next) => { - const userRepository = getRepository(User); - const requestRepository = getRepository(MediaRequest); - const pageSize = req.query.take ? Number(req.query.take) : 20; const skip = req.query.skip ? Number(req.query.skip) : 0; try { - const user = await userRepository.findOne({ + const user = await getRepository(User).findOne({ where: { id: Number(req.params.id) }, }); @@ -209,12 +206,32 @@ router.get<{ id: string }, UserRequestsResponse>( return next({ status: 404, message: 'User not found.' }); } - const [requests, requestCount] = await requestRepository.findAndCount({ - where: { requestedBy: user }, - order: { id: 'DESC' }, - take: pageSize, - skip, - }); + if ( + user.id !== req.user?.id && + !req.user?.hasPermission( + [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], + { type: 'or' } + ) + ) { + return next({ + status: 403, + message: "You do not have permission to view this user's requests.", + }); + } + + const [requests, requestCount] = await getRepository(MediaRequest) + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media') + .leftJoinAndSelect('request.seasons', 'seasons') + .leftJoinAndSelect('request.modifiedBy', 'modifiedBy') + .leftJoinAndSelect('request.requestedBy', 'requestedBy') + .andWhere('requestedBy.id = :id', { + id: req.user?.id, + }) + .orderBy('request.id', 'DESC') + .take(pageSize) + .skip(skip) + .getManyAndCount(); return res.status(200).json({ pageInfo: { From 50ce198471b1a3777a183d68904bbfb39ebd4523 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Fri, 8 Oct 2021 09:19:47 -0400 Subject: [PATCH 048/238] fix: apply request overrides iff override & selected servers match (#2164) --- .../RequestModal/AdvancedRequester/index.tsx | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/RequestModal/AdvancedRequester/index.tsx b/src/components/RequestModal/AdvancedRequester/index.tsx index f532917d4..9fe8bdafd 100644 --- a/src/components/RequestModal/AdvancedRequester/index.tsx +++ b/src/components/RequestModal/AdvancedRequester/index.tsx @@ -150,21 +150,21 @@ const AdvancedRequester: React.FC = ({ const defaultProfile = serverData.profiles.find( (profile) => profile.id === - (isAnime + (isAnime && serverData.server.activeAnimeProfileId ? serverData.server.activeAnimeProfileId : serverData.server.activeProfileId) ); const defaultFolder = serverData.rootFolders.find( (folder) => folder.path === - (isAnime + (isAnime && serverData.server.activeAnimeDirectory ? serverData.server.activeAnimeDirectory : serverData.server.activeDirectory) ); const defaultLanguage = serverData.languageProfiles?.find( (language) => language.id === - (isAnime + (isAnime && serverData.server.activeAnimeLanguageProfileId ? serverData.server.activeAnimeLanguageProfileId : serverData.server.activeLanguageProfileId) ); @@ -172,10 +172,15 @@ const AdvancedRequester: React.FC = ({ ? serverData.server.activeAnimeTags : serverData.server.activeTags; + const applyOverrides = + defaultOverrides && + ((defaultOverrides.server === null && serverData.server.isDefault) || + defaultOverrides.server === serverData.server.id); + if ( defaultProfile && defaultProfile.id !== selectedProfile && - (!defaultOverrides || defaultOverrides.profile === null) + (!applyOverrides || defaultOverrides.profile === null) ) { setSelectedProfile(defaultProfile.id); } @@ -183,7 +188,7 @@ const AdvancedRequester: React.FC = ({ if ( defaultFolder && defaultFolder.path !== selectedFolder && - (!defaultOverrides || defaultOverrides.folder === null) + (!applyOverrides || !defaultOverrides.folder) ) { setSelectedFolder(defaultFolder.path ?? ''); } @@ -191,7 +196,7 @@ const AdvancedRequester: React.FC = ({ if ( defaultLanguage && defaultLanguage.id !== selectedLanguage && - (!defaultOverrides || defaultOverrides.language === null) + (!applyOverrides || defaultOverrides.language === null) ) { setSelectedLanguage(defaultLanguage.id); } @@ -199,7 +204,7 @@ const AdvancedRequester: React.FC = ({ if ( defaultTags && !isEqual(defaultTags, selectedTags) && - (!defaultOverrides || defaultOverrides.tags === null) + (!applyOverrides || defaultOverrides.tags === null) ) { setSelectedTags(defaultTags); } @@ -215,7 +220,7 @@ const AdvancedRequester: React.FC = ({ setSelectedProfile(defaultOverrides.profile); } - if (defaultOverrides && defaultOverrides.folder != null) { + if (defaultOverrides && defaultOverrides.folder) { setSelectedFolder(defaultOverrides.folder); } @@ -241,7 +246,7 @@ const AdvancedRequester: React.FC = ({ profile: selectedProfile !== -1 ? selectedProfile : undefined, server: selectedServer ?? undefined, user: selectedUser ?? undefined, - language: selectedLanguage ?? undefined, + language: selectedLanguage !== -1 ? selectedLanguage : undefined, tags: selectedTags, }); } From a4dca2356b7605026f7bc45b691496e765c3328c Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Fri, 8 Oct 2021 09:27:07 -0400 Subject: [PATCH 049/238] feat: display release dates for theatrical, digital, and physical release types (#1492) * feat: display release dates for theatrical, digital, and physical release types * fix(ui): use disc icon for physical release * style: reformat to make new version of Prettier happy --- src/components/MovieDetails/index.tsx | 100 +++++++++++++++++++++----- src/components/TvDetails/index.tsx | 15 ++-- src/i18n/locale/en.json | 2 +- 3 files changed, 87 insertions(+), 30 deletions(-) diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index c4c27ca46..3faf23636 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -1,8 +1,10 @@ import { ArrowCircleRightIcon, + CloudIcon, CogIcon, FilmIcon, PlayIcon, + TicketIcon, } from '@heroicons/react/outline'; import { CheckCircleIcon, @@ -12,6 +14,7 @@ import { ExternalLinkIcon, } from '@heroicons/react/solid'; import axios from 'axios'; +import { uniqBy } from 'lodash'; import Link from 'next/link'; import { useRouter } from 'next/router'; import React, { useMemo, useState } from 'react'; @@ -49,7 +52,8 @@ import StatusBadge from '../StatusBadge'; const messages = defineMessages({ originaltitle: 'Original Title', - releasedate: 'Release Date', + releasedate: + '{releaseCount, plural, one {Release Date} other {Release Dates}}', revenue: 'Revenue', budget: 'Budget', watchtrailer: 'Watch Trailer', @@ -179,20 +183,29 @@ const MovieDetails: React.FC = ({ movie }) => { : settings.currentSettings.region ? settings.currentSettings.region : 'US'; + + const releases = data.releases.results.find( + (r) => r.iso_3166_1 === region + )?.release_dates; + + // Release date types: + // 1. Premiere + // 2. Theatrical (limited) + // 3. Theatrical + // 4. Digital + // 5. Physical + // 6. TV + const filteredReleases = uniqBy( + releases?.filter((r) => r.type > 2 && r.type < 6), + 'type' + ); + const movieAttributes: React.ReactNode[] = []; - if ( - data.releases.results.length && - (data.releases.results.find((r) => r.iso_3166_1 === region) - ?.release_dates[0].certification || - data.releases.results[0].release_dates[0].certification) - ) { + const certification = releases?.find((r) => r.certification)?.certification; + if (certification) { movieAttributes.push( - - {data.releases.results.find((r) => r.iso_3166_1 === region) - ?.release_dates[0].certification || - data.releases.results[0].release_dates[0].certification} - + {certification} ); } @@ -577,17 +590,66 @@ const MovieDetails: React.FC = ({ movie }) => { {intl.formatMessage(globalMessages.status)} {data.status}
- {data.releaseDate && ( + {filteredReleases && filteredReleases.length > 0 ? (
- {intl.formatMessage(messages.releasedate)} - - {intl.formatDate(data.releaseDate, { - year: 'numeric', - month: 'long', - day: 'numeric', + + {intl.formatMessage(messages.releasedate, { + releaseCount: filteredReleases.length, })} + + {filteredReleases.map((r, i) => ( + + {r.type === 3 ? ( + // Theatrical + + ) : r.type === 4 ? ( + // Digital + + ) : ( + // Physical + + + + )} + + {intl.formatDate(r.release_date, { + year: 'numeric', + month: 'long', + day: 'numeric', + })} + + + ))} +
+ ) : ( + data.releaseDate && ( +
+ + {intl.formatMessage(messages.releasedate, { + releaseCount: 1, + })} + + + {intl.formatDate(data.releaseDate, { + year: 'numeric', + month: 'long', + day: 'numeric', + })} + +
+ ) )} {data.revenue > 0 && (
diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 73a82c692..44554ba56 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -177,17 +177,12 @@ const TvDetails: React.FC = ({ tv }) => { : 'US'; const seriesAttributes: React.ReactNode[] = []; - if ( - data.contentRatings.results.length && - data.contentRatings.results.find( - (r) => r.iso_3166_1 === region || data.contentRatings.results[0].rating - ) - ) { + const contentRating = data.contentRatings.results.find( + (r) => r.iso_3166_1 === region + )?.rating; + if (contentRating) { seriesAttributes.push( - - {data.contentRatings.results.find((r) => r.iso_3166_1 === region) - ?.rating || data.contentRatings.results[0].rating} - + {contentRating} ); } diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index fb03c1af5..0ea703449 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -80,7 +80,7 @@ "components.MovieDetails.play4konplex": "Play in 4K on Plex", "components.MovieDetails.playonplex": "Play on Plex", "components.MovieDetails.recommendations": "Recommendations", - "components.MovieDetails.releasedate": "Release Date", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Release Date} other {Release Dates}}", "components.MovieDetails.revenue": "Revenue", "components.MovieDetails.runtime": "{minutes} minutes", "components.MovieDetails.showless": "Show Less", From 63789918c57d8df761999c3a497c2e58362bf381 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 13:43:09 +0000 Subject: [PATCH 050/238] docs: add JoKerIsCraZy as a contributor for translation (#2171) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 6dc9392f0..89dbf824d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -548,6 +548,15 @@ "contributions": [ "translation" ] + }, + { + "login": "JoKerIsCraZy", + "name": "JoKerIsCraZy", + "avatar_url": "https://avatars.githubusercontent.com/u/47474211?v=4", + "profile": "https://github.com/JoKerIsCraZy", + "contributions": [ + "translation" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index f873f3edd..3786a12b8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -150,6 +150,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
tangentThought

💻
Nicolás Espinoza

💻
sootylunatic

🌍 +
JoKerIsCraZy

🌍 From 0edb1f452b6ff4a49ae2bde15f7273769788cf4f Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Fri, 8 Oct 2021 10:59:21 -0400 Subject: [PATCH 051/238] fix(api): return queried user's requests instead of own requests (#2174) --- server/routes/user/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index 5d847e232..bb58e68b6 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -226,7 +226,7 @@ router.get<{ id: string }, UserRequestsResponse>( .leftJoinAndSelect('request.modifiedBy', 'modifiedBy') .leftJoinAndSelect('request.requestedBy', 'requestedBy') .andWhere('requestedBy.id = :id', { - id: req.user?.id, + id: user.id, }) .orderBy('request.id', 'DESC') .take(pageSize) From 4f36ca718fa5dd9d392f36062d039f0f201b4ce6 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 15:06:45 +0000 Subject: [PATCH 052/238] docs: add GoByeBye as a contributor for translation (#2172) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 89dbf824d..f4c90938b 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -557,6 +557,15 @@ "contributions": [ "translation" ] + }, + { + "login": "GoByeBye", + "name": "Daddie0", + "avatar_url": "https://avatars.githubusercontent.com/u/33762262?v=4", + "profile": "https://daddie.dev", + "contributions": [ + "translation" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index 3786a12b8..8a15af81c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -151,6 +151,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Nicolás Espinoza

💻
sootylunatic

🌍
JoKerIsCraZy

🌍 +
Daddie0

🌍 From c73cf7b19cbc19e97a777c0facb9264fb0113093 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Fri, 8 Oct 2021 17:16:48 +0200 Subject: [PATCH 053/238] feat(lang): translations update from Weblate (#2101) * feat(lang): translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (883 of 883 strings) feat(lang): translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: Tijuco Co-authored-by: costaht Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_BR/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: Kobe Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Italian) Currently translated at 100.0% (883 of 883 strings) feat(lang): translated using Weblate (Italian) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: Simone Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (French) Currently translated at 98.4% (869 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: Mathieu Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Russian) Currently translated at 99.7% (881 of 883 strings) feat(lang): translated using Weblate (Russian) Currently translated at 99.7% (881 of 883 strings) feat(lang): translated using Weblate (Russian) Currently translated at 64.3% (568 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: Sergey Moiseev Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ru/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Czech) Currently translated at 43.9% (388 of 883 strings) feat(lang): translated using Weblate (Czech) Currently translated at 43.7% (386 of 883 strings) feat(lang): added translation using Weblate (Czech) Co-authored-by: Core Intel Co-authored-by: Hosted Weblate Co-authored-by: sct Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/cs/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Danish) Currently translated at 5.5% (49 of 883 strings) feat(lang): added translation using Weblate (Danish) Co-authored-by: Hosted Weblate Co-authored-by: Nicolai Skafte Co-authored-by: sct Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/da/ Translation: Overseerr/Overseerr Frontend * feat(lang): added translation using Weblate (Polish) Co-authored-by: Hosted Weblate Co-authored-by: sct * feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: Shjosan Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/ Translation: Overseerr/Overseerr Frontend Co-authored-by: Tijuco Co-authored-by: costaht Co-authored-by: Kobe Co-authored-by: Simone Co-authored-by: TheCatLady Co-authored-by: Mathieu Co-authored-by: Sergey Moiseev Co-authored-by: Core Intel Co-authored-by: sct Co-authored-by: Nicolai Skafte Co-authored-by: Shjosan --- src/i18n/locale/cs.json | 427 +++++++++++++++++++++++ src/i18n/locale/da.json | 51 +++ src/i18n/locale/fr.json | 4 +- src/i18n/locale/it.json | 4 +- src/i18n/locale/nl.json | 4 +- src/i18n/locale/pl.json | 1 + src/i18n/locale/pt_BR.json | 6 +- src/i18n/locale/ru.json | 635 +++++++++++++++++++++++++++-------- src/i18n/locale/sv.json | 4 +- src/i18n/locale/zh_Hant.json | 4 +- 10 files changed, 998 insertions(+), 142 deletions(-) create mode 100644 src/i18n/locale/cs.json create mode 100644 src/i18n/locale/da.json create mode 100644 src/i18n/locale/pl.json diff --git a/src/i18n/locale/cs.json b/src/i18n/locale/cs.json new file mode 100644 index 000000000..eef281d87 --- /dev/null +++ b/src/i18n/locale/cs.json @@ -0,0 +1,427 @@ +{ + "components.Settings.notificationsettings": "Nastavení oznámení", + "components.Settings.locale": "Jazyk zobrazení", + "components.Settings.generalsettings": "Obecná nastavení", + "components.Settings.enablessl": "Použít SSL", + "components.Settings.default4k": "Výchozí 4K", + "components.Settings.cancelscan": "Zrušit skenování", + "components.Settings.apikey": "API klíč", + "components.Settings.activeProfile": "Aktivní profil", + "components.Settings.SonarrModal.syncEnabled": "Povolit skenování", + "components.Settings.SonarrModal.ssl": "Použít SSL", + "components.Settings.SonarrModal.servername": "Název serveru", + "components.Settings.SonarrModal.server4k": "4K server", + "components.Settings.SonarrModal.selecttags": "Vyberte značky", + "components.Settings.SonarrModal.seasonfolders": "Složky pro série", + "components.Settings.SonarrModal.rootfolder": "Kořenový adresář", + "components.Settings.SonarrModal.qualityprofile": "Profil kvality", + "components.Settings.SonarrModal.notagoptions": "Žádné značky.", + "components.Settings.SonarrModal.loadingTags": "Načítání značek…", + "components.Settings.SonarrModal.languageprofile": "Jazykový profil", + "components.Settings.SonarrModal.externalUrl": "Externí URL", + "components.Settings.SonarrModal.defaultserver": "Výchozí server", + "components.Settings.SonarrModal.apiKey": "API klíč", + "components.Settings.SonarrModal.animeTags": "Anime značky", + "components.Settings.SonarrModal.add": "Přidat server", + "components.Settings.SettingsUsers.userSettings": "Uživatelské nastavení", + "components.Settings.SettingsUsers.defaultPermissions": "Výchozí oprávnění", + "components.Settings.SettingsJobsCache.unknownJob": "Neznámá úloha", + "components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr Sken", + "components.Settings.SettingsJobsCache.runnow": "Spustit nyní", + "components.Settings.SettingsJobsCache.radarr-scan": "Radarr Sken", + "components.Settings.SettingsJobsCache.jobstarted": "{jobname} zahájeno.", + "components.Settings.SettingsJobsCache.jobname": "Název úlohy", + "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} zrušeno.", + "components.Settings.SettingsJobsCache.flushcache": "Vyprázdnit mezipaměť", + "components.Settings.SettingsJobsCache.canceljob": "Zrušit úlohu", + "components.Settings.SettingsJobsCache.cachevsize": "Velikost hodnoty", + "components.Settings.SettingsJobsCache.cachename": "Název mezipaměti", + "components.Settings.SettingsAbout.totalrequests": "Celkový počet žádostí", + "components.Settings.SettingsAbout.totalmedia": "Celkový počet médií", + "components.Settings.SettingsAbout.timezone": "Časové pásmo", + "components.Settings.SettingsAbout.supportoverseerr": "Podpořte Overseerr", + "components.Settings.SettingsAbout.overseerrinformation": "Overseerr Informace", + "components.Settings.SettingsAbout.githubdiscussions": "Diskuze na GitHubu", + "components.Settings.SettingsAbout.Releases.viewchangelog": "Zobrazit seznam změn", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Seznam změn", + "components.Settings.SettingsAbout.Releases.currentversion": "Aktuální verze", + "components.Settings.RadarrModal.syncEnabled": "Povolit skenování", + "components.Settings.RadarrModal.ssl": "Použít SSL", + "components.Settings.RadarrModal.servername": "Název serveru", + "components.Settings.RadarrModal.server4k": "4K server", + "components.Settings.RadarrModal.selecttags": "Vyberte značky", + "components.Settings.RadarrModal.rootfolder": "Kořenový adresář", + "components.Settings.RadarrModal.qualityprofile": "Profil kvality", + "components.Settings.RadarrModal.notagoptions": "Žádné značky.", + "components.Settings.RadarrModal.minimumAvailability": "Minimální dostupnost", + "components.Settings.RadarrModal.loadingTags": "Načítání značek…", + "components.Settings.RadarrModal.externalUrl": "Externí URL", + "components.Settings.RadarrModal.defaultserver": "Výchozí server", + "components.Settings.RadarrModal.apiKey": "API klíč", + "components.Settings.RadarrModal.add": "Přidat server", + "components.Settings.Notifications.webhookUrl": "Webhook URL", + "components.Settings.Notifications.smtpPort": "SMTP Port", + "components.Settings.Notifications.smtpHost": "SMTP Host", + "components.Settings.Notifications.senderName": "Jméno odesílatele", + "components.Settings.Notifications.sendSilently": "Odeslat potichu", + "components.Settings.Notifications.pgpPassword": "PGP heslo", + "components.Settings.Notifications.encryption": "Metoda šifrování", + "components.Settings.Notifications.emailsender": "Adresa odesílatele", + "components.Settings.Notifications.chatId": "ID chatu", + "components.Settings.Notifications.botUsername": "Jméno bota", + "components.Settings.Notifications.authUser": "SMTP uživatelské jméno", + "components.Settings.Notifications.authPass": "SMTP Heslo", + "components.Settings.Notifications.agentenabled": "Povolit agenta", + "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URL", + "components.Settings.Notifications.NotificationsWebhook.customJson": "JSON Payload", + "components.Settings.Notifications.NotificationsWebhook.authheader": "Autorizační hlavička", + "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Povolit agenta", + "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Povolit agenta", + "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook URL", + "components.Settings.Notifications.NotificationsSlack.agentenabled": "Povolit agenta", + "components.Settings.Notifications.NotificationsPushover.agentenabled": "Povolit agenta", + "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Povolit agenta", + "components.Settings.Notifications.NotificationsPushbullet.accessToken": "Přístupový token", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "Webhook URL", + "components.Settings.Notifications.NotificationsLunaSea.profileName": "Jméno profilu", + "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Povolit agenta", + "components.Search.searchresults": "Výsledek vyhledávání", + "components.ResetPassword.passwordreset": "Obnovení hesla", + "components.ResetPassword.email": "E-mailová adresa", + "components.ResetPassword.confirmpassword": "Potvrďte heslo", + "components.RequestModal.selectseason": "Vyberte série", + "components.RequestModal.seasonnumber": "{number} Série", + "components.RequestModal.requesttitle": "Zažádat o {title}", + "components.RequestModal.edit": "Upravit žádost", + "components.RequestModal.cancel": "Zrušit žádost", + "components.RequestModal.autoapproval": "Automatické schválení", + "components.RequestModal.alreadyrequested": "Již vyžádáno", + "components.RequestModal.AdvancedRequester.selecttags": "Vybrat značky", + "components.RequestModal.AdvancedRequester.rootfolder": "Kořenový adresář", + "components.RequestModal.AdvancedRequester.requestas": "Zažádat jako", + "components.RequestModal.AdvancedRequester.qualityprofile": "Profil kvality", + "components.RequestModal.AdvancedRequester.notagoptions": "Žádné značky.", + "components.RequestModal.AdvancedRequester.languageprofile": "Jazykový profil", + "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", + "components.RequestModal.AdvancedRequester.destinationserver": "Cílový server", + "components.RequestModal.AdvancedRequester.default": "{name} (výchozí)", + "components.RequestList.sortModified": "Naposledy změněno", + "components.RequestList.sortAdded": "Datum žádosti", + "components.RequestList.RequestItem.editrequest": "Upravit žádost", + "components.RequestList.RequestItem.deleterequest": "Odstranit žádost", + "components.RequestList.RequestItem.cancelRequest": "Zrušit žádost", + "components.RequestCard.deleterequest": "Odstranit žádost", + "components.RequestButton.viewrequest": "Zobrazit žádost", + "components.RequestButton.requestmore": "Vyžádat více", + "components.RequestButton.declinerequest": "Odmítnout žádost", + "components.RequestButton.approverequest": "Schválit žádost", + "components.RequestBlock.server": "Cílový server", + "components.RequestBlock.rootfolder": "Kořenový adresář", + "components.RequestBlock.requestoverrides": "Přepsání požadavku", + "components.RequestBlock.profilechanged": "Profil kvality", + "components.RegionSelector.regionServerDefault": "Výchozí ({region})", + "components.RegionSelector.regionDefault": "Všechny regiony", + "components.PlexLoginButton.signinwithplex": "Přihlásit se", + "components.PlexLoginButton.signingin": "Přihlašování…", + "components.PersonDetails.birthdate": "Narozen {birthdate}", + "components.PersonDetails.ascharacter": "jako {character}", + "components.PermissionEdit.viewrequests": "Zobrazit žádosti", + "components.PermissionEdit.users": "Spravovat uživatele", + "components.PermissionEdit.settings": "Spravovat nastavení", + "components.PermissionEdit.requestTv": "Žádat seriály", + "components.PermissionEdit.requestMovies": "Žádat filmy", + "components.PermissionEdit.request4k": "Žádosti ve 4K", + "components.PermissionEdit.managerequests": "Spravovat žádosti", + "components.PermissionEdit.autoapproveSeries": "Automaticky schvalovat seriály", + "components.PermissionEdit.autoapproveMovies": "Automaticky schvalovat filmy", + "components.UserProfile.UserSettings.UserNotificationSettings.notifications": "Oznámení", + "components.UserProfile.UserSettings.UserNotificationSettings.email": "E-mail", + "components.UserProfile.UserSettings.UserGeneralSettings.user": "Uživatel", + "components.UserProfile.UserSettings.UserGeneralSettings.role": "Role", + "components.UserProfile.UserSettings.UserGeneralSettings.owner": "Vlastník", + "components.UserProfile.UserSettings.UserGeneralSettings.general": "Obecné", + "components.UserProfile.UserSettings.UserGeneralSettings.admin": "Admin", + "components.UserList.users": "Uživatelé", + "components.UserList.user": "Uživatel", + "components.UserList.totalrequests": "Žádosti", + "components.UserList.role": "Role", + "components.UserList.password": "Heslo", + "components.UserList.owner": "Vlastník", + "components.UserList.lastupdated": "Aktualizováno", + "components.UserList.creating": "Vytváření…", + "components.UserList.created": "Vytvořeno", + "components.UserList.create": "Vytvořit", + "components.UserList.admin": "Admin", + "components.UserList.accounttype": "Typ", + "components.TvDetails.recommendations": "Doporučení", + "components.TvDetails.overview": "Přehled", + "components.TvDetails.manageModalRequests": "Žádosti", + "components.TvDetails.cast": "Obsazení", + "components.TvDetails.anime": "Anime", + "components.StatusChacker.reloadOverseerr": "Znovu načíst", + "components.Setup.tip": "Tip", + "components.Setup.setup": "Konfigurace", + "components.Setup.finishing": "Dokončování…", + "components.Setup.continue": "Pokračovat", + "components.Settings.webhook": "Webhook", + "components.Settings.ssl": "SSL", + "components.Settings.services": "Služby", + "components.Settings.serverpreset": "Server", + "components.Settings.serverSecure": "zabezpečené", + "components.Settings.serverRemote": "vzdálený", + "components.Settings.serverLocal": "místní", + "components.Settings.scanning": "Synchronizace…", + "components.Settings.port": "Port", + "components.Settings.plex": "Plex", + "components.Settings.notifications": "Oznámení", + "components.Settings.menuUsers": "Uživatelé", + "components.Settings.menuServices": "Služby", + "components.Settings.menuPlexSettings": "Plex", + "components.Settings.menuNotifications": "Oznámení", + "components.Settings.menuLogs": "Záznamy", + "components.Settings.menuGeneralSettings": "Obecné", + "components.Settings.menuAbout": "O aplikaci", + "components.Settings.mediaTypeSeries": "seriál", + "components.Settings.mediaTypeMovie": "film", + "components.Settings.is4k": "4K", + "components.Settings.general": "Obecné", + "components.Settings.email": "E-mail", + "components.Settings.default": "Výchozí", + "components.Settings.address": "Adresy", + "components.Settings.SonarrModal.tags": "Značky", + "components.Settings.SonarrModal.port": "Port", + "components.Settings.SettingsUsers.users": "Uživatelé", + "components.Settings.SettingsLogs.time": "Časová značka", + "components.Settings.SettingsLogs.resumeLogs": "Pokračovat", + "components.Settings.SettingsLogs.pauseLogs": "Pauza", + "components.Settings.SettingsLogs.message": "Zpráva", + "components.Settings.SettingsLogs.logs": "Záznamy", + "components.Settings.SettingsLogs.level": "Závažnost", + "components.Settings.SettingsLogs.label": "Štítek", + "components.Settings.SettingsLogs.filterWarn": "Varování", + "components.Settings.SettingsLogs.filterInfo": "Informace", + "components.Settings.SettingsLogs.filterError": "Chyba", + "components.Settings.SettingsLogs.filterDebug": "Ladění", + "components.Settings.SettingsJobsCache.process": "Proces", + "components.Settings.SettingsJobsCache.jobtype": "Typ", + "components.Settings.SettingsJobsCache.jobs": "Úkoly", + "components.Settings.SettingsJobsCache.command": "Příkaz", + "components.Settings.SettingsJobsCache.cachehits": "Úspěchy", + "components.Settings.SettingsJobsCache.cache": "Mezipaměť", + "components.Settings.SettingsAbout.version": "Verze", + "components.Settings.SettingsAbout.preferredmethod": "Preferované", + "components.Settings.SettingsAbout.documentation": "Dokumentace", + "components.Settings.SettingsAbout.about": "O aplikaci", + "components.Settings.SettingsAbout.Releases.releases": "Verze", + "components.Settings.SettingsAbout.Releases.latestversion": "Nejnovější", + "components.Settings.RadarrModal.tags": "Značky", + "components.Settings.RadarrModal.port": "Port", + "components.Settings.Notifications.encryptionNone": "Žádné", + "components.Search.search": "Vyhledat", + "components.ResetPassword.password": "Heslo", + "components.RequestModal.season": "Série", + "components.RequestModal.extras": "Extra", + "components.RequestModal.QuotaDisplay.season": "série", + "components.RequestModal.QuotaDisplay.movie": "film", + "components.RequestModal.AdvancedRequester.tags": "Značky", + "components.RequestModal.AdvancedRequester.advancedoptions": "Pokročilé", + "components.RequestList.requests": "Žádosti", + "components.RequestList.RequestItem.requesteddate": "Zažádáno", + "components.RequestList.RequestItem.requested": "Zažádáno", + "components.RequestList.RequestItem.modified": "Upraveno", + "components.QuotaSelector.unlimited": "Neomezené", + "components.PersonDetails.crewmember": "Další profese", + "components.PersonDetails.appearsin": "Vystoupení", + "components.PermissionEdit.request": "Zažádat", + "components.PermissionEdit.autoapprove4kSeries": "Automatické schválení 4K seriálů", + "components.NotificationTypeSelector.usermediaapprovedDescription": "Získat upozornění na schválení vašich žádostí o média.", + "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Získat upozornění, když ostatní uživatelé zadají nové požadavky na média, která jsou automaticky schválena.", + "components.NotificationTypeSelector.mediarequestedDescription": "Odeslat oznámení, když uživatelé zažádají o média vyžadující schválení.", + "components.MovieDetails.streamingproviders": "Aktuálně streamovatelné zde", + "pages.serviceunavailable": "Služba není k dispozici", + "pages.returnHome": "Vrátit se domů", + "pages.pagenotfound": "Stránka nebyla nalezena", + "pages.oops": "Jejda", + "pages.internalservererror": "Interní chyba serveru", + "pages.errormessagewithcode": "{statusCode} - {error}", + "i18n.view": "Zobrazit", + "i18n.usersettings": "Uživatelské nastavení", + "i18n.unavailable": "Nedostupné", + "i18n.tvshows": "Seriály", + "i18n.tvshow": "Seriál", + "i18n.testing": "Testuji…", + "i18n.test": "Otestovat", + "i18n.status": "Stav", + "i18n.showingresults": "Zobrazuji {from} do {to} {total} výsledky", + "i18n.settings": "Nastavení", + "i18n.saving": "Ukládání…", + "i18n.save": "Uložit změny", + "i18n.retrying": "Opakování…", + "i18n.retry": "Opakovat", + "i18n.resultsperpage": "Zobrazit {pageSize} výsledků na stránku", + "i18n.requesting": "Zažádáno…", + "i18n.requested": "Zažádáno", + "i18n.request4k": "Zažádat ve 4K", + "i18n.request": "Zažádat", + "i18n.processing": "Zpracovává se", + "i18n.previous": "Předchozí", + "i18n.pending": "Čekající", + "i18n.partiallyavailable": "Částečně k dispozici", + "i18n.notrequested": "Nebylo zažádáno", + "i18n.noresults": "Žádné výsledky.", + "i18n.next": "Další", + "i18n.movies": "Filmy", + "i18n.movie": "Film", + "i18n.loading": "Načítání…", + "i18n.failed": "Selhalo", + "i18n.experimental": "Experimentální", + "i18n.edit": "Upravit", + "i18n.delimitedlist": "{a}, {b}", + "i18n.deleting": "Odstraňování…", + "i18n.delete": "Odstranit", + "i18n.declined": "Odmítnuto", + "i18n.decline": "Odmítnout", + "i18n.close": "Zavřít", + "i18n.canceling": "Rušení…", + "i18n.cancel": "Zrušit", + "i18n.back": "Zpět", + "i18n.available": "K dispozici", + "i18n.areyousure": "Jste si jistý?", + "i18n.approved": "Schváleno", + "i18n.approve": "Schválit", + "i18n.all": "Vše", + "i18n.advanced": "Pokročilé", + "components.UserProfile.unlimited": "Neomezené", + "components.UserProfile.totalrequests": "Celkový počet žádostí", + "components.UserProfile.seriesrequest": "Seriál zažádán", + "components.UserProfile.requestsperdays": "Zbývá {limit}", + "components.UserProfile.recentrequests": "Nedávné žádosti", + "components.UserProfile.norequests": "Žádné žádosti.", + "components.UserProfile.UserSettings.menuPermissions": "Oprávnění", + "components.UserProfile.UserSettings.menuNotifications": "Oznámení", + "components.UserProfile.UserSettings.menuGeneralSettings": "Obecné", + "components.UserProfile.UserSettings.menuChangePass": "Heslo", + "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "Vlastní oprávnění nelze upravovat.", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "Oprávnění byla úspěšně uložena!", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "Při ukládání nastavení se něco pokazilo.", + "components.UserProfile.UserSettings.UserPermissions.permissions": "Oprávnění", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPasswordLength": "Heslo je příliš krátké; mělo by mít minimálně 8 znaků", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPassword": "Musíte zadat nové heslo", + "components.UserProfile.UserSettings.UserPasswordChange.validationCurrentPassword": "Musíte zadat své aktuální heslo", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPasswordSame": "Hesla se musí shodovat", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "Musíte potvrdit nové heslo", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "Heslo úspěšně uloženo!", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "Při ukládání hesla se něco pokazilo. Bylo vaše aktuální heslo zadáno správně?", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Při ukládání hesla se něco pokazilo.", + "components.UserProfile.UserSettings.UserPasswordChange.password": "Heslo", + "pages.somethingwentwrong": "Něco se pokazilo", + "components.PermissionEdit.autoapprove4kMovies": "Automatické schvalování 4K filmů", + "components.PermissionEdit.autoapprove4k": "Automatické schválení 4K", + "components.PermissionEdit.autoapprove": "Automatické schválení", + "components.PermissionEdit.advancedrequest": "Pokročilé žádosti", + "components.PermissionEdit.admin": "Admin", + "components.NotificationTypeSelector.notificationTypes": "Typy oznámení", + "components.NotificationTypeSelector.mediarequested": "Médium zažádáno", + "components.NotificationTypeSelector.mediafailedDescription": "Odeslat oznámení, když se nepodaří přidat požadavky na média do Radarru nebo Sonarru.", + "components.NotificationTypeSelector.mediafailed": "Médium selhalo", + "components.NotificationTypeSelector.mediadeclinedDescription": "Odeslat oznámení, pokud jsou požadavky na média odmítnuty.", + "components.NotificationTypeSelector.mediadeclined": "Médium odmítnuto", + "components.NotificationTypeSelector.mediaavailableDescription": "Odeslat oznámení, jakmile budou k dispozici žádosti o média.", + "components.NotificationTypeSelector.mediaavailable": "Médium je k dispozici", + "components.NotificationTypeSelector.mediaapprovedDescription": "Odeslat oznámení, když jsou požadavky na média ručně schváleny.", + "components.NotificationTypeSelector.mediaapproved": "Médium schváleno", + "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Odeslat oznámení, když uživatelé zadají nové požadavky na média, která jsou automaticky schválena.", + "components.NotificationTypeSelector.mediaAutoApproved": "Médium automaticky schváleno", + "components.MovieDetails.watchtrailer": "Sledovat trailer", + "components.MovieDetails.viewfullcrew": "Zobrazit kompletní štáb", + "components.MovieDetails.similar": "Podobné tituly", + "components.MovieDetails.showmore": "Zobrazit více", + "components.MovieDetails.showless": "Zobrazit méně", + "components.MovieDetails.runtime": "{minutes} minut", + "components.MovieDetails.revenue": "Výnos", + "components.MovieDetails.releasedate": "Datum vydání", + "components.MovieDetails.recommendations": "Doporučení", + "components.MovieDetails.playonplex": "Přehrát v Plexu", + "components.MovieDetails.play4konplex": "Přehrát v Plexu ve 4K", + "components.MovieDetails.overviewunavailable": "Přehled není k dispozici.", + "components.MovieDetails.overview": "Přehled", + "components.MovieDetails.originaltitle": "Původní název", + "components.MovieDetails.originallanguage": "Původní jazyk", + "components.MovieDetails.openradarr4k": "Otevřít film ve 4K Radarru", + "components.MovieDetails.openradarr": "Otevřít film v Radarru", + "components.MovieDetails.markavailable": "Označit jako dostupné", + "components.MovieDetails.mark4kavailable": "Označit jako dostupné ve 4K", + "components.MovieDetails.manageModalTitle": "Spravovat film", + "components.MovieDetails.manageModalRequests": "Žádosti", + "components.MovieDetails.manageModalNoRequests": "Žádné žádosti.", + "components.MovieDetails.manageModalClearMediaWarning": "* Tímto nevratně odstraníte všechna data pro tento film, včetně všech požadavků. Pokud tato položka v knihovně Plex existuje, budou informace o médiu znovu vytvořeny při příštím skenování.", + "components.MovieDetails.manageModalClearMedia": "Vymazat data médií", + "components.MovieDetails.downloadstatus": "Stav stahování", + "components.MovieDetails.cast": "Obsazení", + "components.MovieDetails.budget": "Rozpočet", + "components.MovieDetails.MovieCast.fullcast": "Kompletní obsazení", + "components.MovieDetails.MovieCrew.fullcrew": "Kompletní štáb", + "components.MediaSlider.ShowMoreCard.seemore": "Zobrazit více", + "components.Login.validationpasswordrequired": "Musíte zadat heslo", + "components.Login.validationemailrequired": "Musíte zadat platnou e-mailovou adresu", + "components.Login.signinwithplex": "Použijte svůj Plex účet", + "components.Login.signinwithoverseerr": "Použijte svůj {applicationTitle} účet", + "components.Login.signinheader": "Pro pokračování se přihlaste", + "components.Login.signingin": "Přihlašování…", + "components.Login.signin": "Přihlásit se", + "components.Login.password": "Heslo", + "components.Login.loginerror": "Při pokusu o přihlášení se něco pokazilo.", + "components.Login.forgotpassword": "Zapomenuté heslo?", + "components.Login.email": "E-mailová adresa", + "components.Layout.VersionStatus.streamstable": "Overseerr Stabilní", + "components.Layout.VersionStatus.streamdevelop": "Overseerr Vývoj", + "components.Layout.VersionStatus.outofdate": "Zastaralý", + "components.Layout.UserDropdown.signout": "Odhlásit se", + "components.Layout.UserDropdown.settings": "Nastavení", + "components.Layout.UserDropdown.myprofile": "Profil", + "components.Layout.Sidebar.users": "Uživatelé", + "components.Layout.Sidebar.settings": "Nastavení", + "components.Layout.Sidebar.requests": "Žádosti", + "components.Layout.Sidebar.dashboard": "Objevit", + "components.Layout.SearchInput.searchPlaceholder": "Vyhledat Filmy a Seriály", + "components.Layout.LanguagePicker.displaylanguage": "Jazyk zobrazení", + "components.LanguageSelector.originalLanguageDefault": "Všechny jazyky", + "components.LanguageSelector.languageServerDefault": "Výchozí ({language})", + "components.DownloadBlock.estimatedtime": "Odhadovaný {time}", + "components.Discover.upcomingtv": "Nadcházející Seriály", + "components.Discover.upcomingmovies": "Nadcházející filmy", + "components.Discover.upcoming": "Nadcházející filmy", + "components.Discover.trending": "Populární", + "components.Discover.recentrequests": "Nedávné žádosti", + "components.Discover.recentlyAdded": "Nedávno přidané", + "components.Discover.populartv": "Populární Seriály", + "components.Discover.discovertv": "Populární Seriály", + "components.Discover.DiscoverTvLanguage.languageSeries": "{language} Seriály", + "components.Discover.DiscoverTvGenre.genreSeries": "{genre} Seriály", + "components.Discover.DiscoverNetwork.networkSeries": "{network} Seriály", + "components.Discover.popularmovies": "Populární filmy", + "components.Discover.noRequests": "Žádné požadavky.", + "components.Discover.discovermovies": "Populární filmy", + "components.Discover.discover": "Objevte", + "components.Discover.TvGenreSlider.tvgenres": "Žánry seriálů", + "components.Discover.TvGenreList.seriesgenres": "Žánry seriálů", + "components.Discover.StudioSlider.studios": "Studia", + "components.Discover.NetworkSlider.networks": "TV sítě", + "components.Discover.MovieGenreSlider.moviegenres": "Filmové žánry", + "components.Discover.MovieGenreList.moviegenres": "Filmové žánry", + "components.CollectionDetails.numberofmovies": "{count} Filmů", + "components.AppDataWarning.dockerVolumeMissingDescription": "Připojení svazku {appDataPath} nebylo správně nakonfigurováno. Všechna data budou vymazána při zastavení nebo opětovném spuštění kontejneru.", + "components.CollectionDetails.requestSuccess": " {title} úspěšně požádáno!", + "components.Discover.DiscoverStudio.studioMovies": "{studio} Filmy", + "components.Discover.DiscoverMovieLanguage.languageMovies": "{language} Filmy", + "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Filmy", + "components.CollectionDetails.requestswillbecreated4k": "Pro následující tituly budou vytvořeny požadavky ve 4K:", + "components.CollectionDetails.requestswillbecreated": "Pro následující tituly budou vytvořeny požadavky:", + "components.CollectionDetails.requestcollection4k": "Požádat o kolekci ve 4K", + "components.CollectionDetails.requestcollection": "Požádat o kolekci", + "components.CollectionDetails.overview": "Přehled", + "components.Settings.SettingsJobsCache.cachemisses": "Neúspěchy", + "components.NotificationTypeSelector.usermediadeclinedDescription": "Dostat oznámení o odmítnutí vašich požadavků na média.", + "components.NotificationTypeSelector.usermediaavailableDescription": "Dostat oznámení, jakmile budou k dispozici žádosti o média.", + "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} za" +} diff --git a/src/i18n/locale/da.json b/src/i18n/locale/da.json new file mode 100644 index 000000000..4cebcdf7b --- /dev/null +++ b/src/i18n/locale/da.json @@ -0,0 +1,51 @@ +{ + "components.CollectionDetails.requestSuccess": "Dit ønske er accepteret!", + "components.Discover.discovermovies": "Populære Film", + "components.MediaSlider.ShowMoreCard.seemore": "Se Mere", + "components.Login.validationpasswordrequired": "Angiv et kodeord", + "components.Login.validationemailrequired": "Angiv en gyldig email adresse", + "components.Login.signinwithplex": "Brug din Plex Konto", + "components.Login.signinheader": "Log ind for at forsætte", + "components.Login.signingin": "Logger ind…", + "components.Login.signin": "Log ind", + "components.Login.password": "Kodeord", + "components.Login.loginerror": "Noget gik galt, i dit forsøg på at logge ind.", + "components.Login.forgotpassword": "Glemt kodeord?", + "components.Login.email": "Email Adresse", + "components.Layout.VersionStatus.streamdevelop": "Overseerr Udvikler", + "components.Layout.VersionStatus.outofdate": "Forældet", + "components.Layout.UserDropdown.signout": "Log ud", + "components.Layout.UserDropdown.settings": "Indstillinger", + "components.Layout.UserDropdown.myprofile": "Profil", + "components.Layout.Sidebar.users": "Brugere", + "components.Layout.Sidebar.settings": "Indstillinger", + "components.Layout.Sidebar.requests": "Ønsker", + "components.Layout.Sidebar.dashboard": "Udforsk", + "components.Layout.SearchInput.searchPlaceholder": "Søg Film & Serier", + "components.Layout.LanguagePicker.displaylanguage": "Vis Sprog", + "components.LanguageSelector.originalLanguageDefault": "Alle Sprog", + "components.LanguageSelector.languageServerDefault": "Standard ({sprog})", + "components.Discover.upcomingtv": "Kommende Serier", + "components.Discover.upcomingmovies": "Kommende Film", + "components.Discover.upcoming": "Kommende Film", + "components.Discover.trending": "Aktuelle", + "components.Discover.recentrequests": "Seneste Ønsker", + "components.Discover.recentlyAdded": "Nyligt tilføjet", + "components.Discover.populartv": "Populære Serier", + "components.Discover.popularmovies": "Populære Film", + "components.Discover.noRequests": "Ingen ønsker", + "components.Discover.discovertv": "Populære Serier", + "components.Discover.discover": "Udforsk", + "components.Discover.TvGenreSlider.tvgenres": "Serie Genre", + "components.Discover.TvGenreList.seriesgenres": "Serie Genre", + "components.Discover.NetworkSlider.networks": "Netværk", + "components.Discover.MovieGenreSlider.moviegenres": "Film Genre", + "components.Discover.MovieGenreList.moviegenres": "Film Genre", + "components.Discover.DiscoverStudio.studioMovies": "{studio} Film", + "components.Discover.DiscoverNetwork.networkSeries": "{netværk} Serier", + "components.Discover.DiscoverMovieLanguage.languageMovies": "{sprog} Film", + "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Film", + "components.CollectionDetails.requestcollection4k": "Ønskesamling i 4k", + "components.CollectionDetails.requestcollection": "Ønskesamling", + "components.CollectionDetails.overview": "Overblik" +} diff --git a/src/i18n/locale/fr.json b/src/i18n/locale/fr.json index fd1212e40..426cde2ca 100644 --- a/src/i18n/locale/fr.json +++ b/src/i18n/locale/fr.json @@ -878,5 +878,7 @@ "components.MovieDetails.showmore": "Montrer plus", "components.MovieDetails.showless": "Montrer moins", "components.Layout.LanguagePicker.displaylanguage": "Langue d'affichage", - "components.UserList.localLoginDisabled": "Le paramètre Activer la connexion locale est actuellement désactivé." + "components.UserList.localLoginDisabled": "Le paramètre Activer la connexion locale est actuellement désactivé.", + "components.TvDetails.streamingproviders": "Disponible en streaming sur", + "components.MovieDetails.streamingproviders": "Disponible en streaming sur" } diff --git a/src/i18n/locale/it.json b/src/i18n/locale/it.json index 12fb18fa2..de8699116 100644 --- a/src/i18n/locale/it.json +++ b/src/i18n/locale/it.json @@ -879,5 +879,7 @@ "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Ricevi una notifica quando altri utenti inviano nuove richieste che vengono approvate automaticamente.", "components.Layout.LanguagePicker.displaylanguage": "Lingua Interfaccia", "components.MovieDetails.showmore": "Mostra di più", - "components.MovieDetails.showless": "Mostra meno" + "components.MovieDetails.showless": "Mostra meno", + "components.TvDetails.streamingproviders": "Ora in streaming su", + "components.MovieDetails.streamingproviders": "Ora in streaming su" } diff --git a/src/i18n/locale/nl.json b/src/i18n/locale/nl.json index 01998f6ca..41ea6fd76 100644 --- a/src/i18n/locale/nl.json +++ b/src/i18n/locale/nl.json @@ -879,5 +879,7 @@ "components.Settings.SettingsAbout.betawarning": "Dit is BETA software. Functies kunnen kapot en/of instabiel zijn. Meld eventuele problemen op GitHub!", "components.Layout.LanguagePicker.displaylanguage": "Weergavetaal", "components.MovieDetails.showmore": "Meer tonen", - "components.MovieDetails.showless": "Minder tonen" + "components.MovieDetails.showless": "Minder tonen", + "components.TvDetails.streamingproviders": "Momenteel te streamen op", + "components.MovieDetails.streamingproviders": "Momenteel te streamen op" } diff --git a/src/i18n/locale/pl.json b/src/i18n/locale/pl.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/src/i18n/locale/pl.json @@ -0,0 +1 @@ +{} diff --git a/src/i18n/locale/pt_BR.json b/src/i18n/locale/pt_BR.json index c10697aaf..d61c01d97 100644 --- a/src/i18n/locale/pt_BR.json +++ b/src/i18n/locale/pt_BR.json @@ -7,7 +7,7 @@ "components.MovieDetails.similar": "Títulos Semelhantes", "components.MovieDetails.runtime": "{minutes} minutos", "components.MovieDetails.revenue": "Receita", - "components.MovieDetails.releasedate": "Lançamento", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Lançamento} other {Lançamentos}}", "components.MovieDetails.recommendations": "Recomendações", "components.MovieDetails.overviewunavailable": "Sinopse indisponível.", "components.MovieDetails.overview": "Sinopse", @@ -879,5 +879,7 @@ "components.Settings.SettingsAbout.betawarning": "Essa é uma versão BETA. Algumas funcionalidades podem ser instáveis ou não funcionarem. Por favor reporte qualquer problema no GitHub!", "components.Layout.LanguagePicker.displaylanguage": "Idioma da Interface", "components.MovieDetails.showmore": "Mostrar Mais", - "components.MovieDetails.showless": "Mostrar Menos" + "components.MovieDetails.showless": "Mostrar Menos", + "components.TvDetails.streamingproviders": "Em Exibição na", + "components.MovieDetails.streamingproviders": "Em Exibição na" } diff --git a/src/i18n/locale/ru.json b/src/i18n/locale/ru.json index 55f15ccf7..45f29352c 100644 --- a/src/i18n/locale/ru.json +++ b/src/i18n/locale/ru.json @@ -4,12 +4,12 @@ "components.Discover.popularmovies": "Популярные фильмы", "components.Discover.populartv": "Популярные сериалы", "components.Discover.recentlyAdded": "Недавно добавленные", - "components.Discover.recentrequests": "Недавние запросы", + "components.Discover.recentrequests": "Последние запросы", "components.Discover.trending": "В трендах", "components.Discover.upcoming": "Предстоящие фильмы", "components.Discover.upcomingmovies": "Предстоящие фильмы", "components.Layout.SearchInput.searchPlaceholder": "Поиск фильмов и сериалов", - "components.Layout.Sidebar.dashboard": "Открыть что-то новое", + "components.Layout.Sidebar.dashboard": "Найти что-то новое", "components.Layout.Sidebar.requests": "Запросы", "components.Layout.Sidebar.settings": "Настройки", "components.Layout.Sidebar.users": "Пользователи", @@ -31,139 +31,139 @@ "components.MovieDetails.similar": "Похожие фильмы", "components.PersonDetails.appearsin": "Появления в фильмах и сериалах", "components.PersonDetails.ascharacter": "в роли {character}", - "components.RequestBlock.seasons": "{seasonCount, plural, one {сезон} other {сезонов}}", - "components.RequestCard.seasons": "{seasonCount, plural, one {сезон} other {сезонов}}", - "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {сезон} other {сезонов}}", + "components.RequestBlock.seasons": "{seasonCount, plural, one {сезон} other {сезона(ов)}}", + "components.RequestCard.seasons": "{seasonCount, plural, one {сезон} other {сезона(ов)}}", + "components.RequestList.RequestItem.seasons": "{seasonCount, plural, one {сезон} other {сезона(ов)}}", "components.RequestList.requests": "Запросы", "components.RequestModal.cancel": "Отменить запрос", "components.RequestModal.extras": "Дополнительно", - "components.RequestModal.numberofepisodes": "# из эпизодов", - "components.RequestModal.pendingrequest": "", - "components.RequestModal.requestCancel": "", - "components.RequestModal.requestSuccess": "", + "components.RequestModal.numberofepisodes": "# эпизодов", + "components.RequestModal.pendingrequest": "В ожидании запрос на {title}", + "components.RequestModal.requestCancel": "Запрос на {title} отменён.", + "components.RequestModal.requestSuccess": "{title} успешно запрошен!", "components.RequestModal.requestadmin": "Этот запрос будет одобрен автоматически.", - "components.RequestModal.requestfrom": "", - "components.RequestModal.requestseasons": "", - "components.RequestModal.requesttitle": "Запрос {title}", + "components.RequestModal.requestfrom": "Запрос пользователя {username} ожидает одобрения.", + "components.RequestModal.requestseasons": "Запросить {seasonCount} {seasonCount, plural, one {сезон} other {сезона(ов)}}", + "components.RequestModal.requesttitle": "Запросить {title}", "components.RequestModal.season": "Сезон", "components.RequestModal.seasonnumber": "Сезон {number}", "components.RequestModal.selectseason": "Выберите сезон(ы)", "components.Search.searchresults": "Результаты поиска", - "components.Settings.Notifications.agentenabled": "Включить агент", + "components.Settings.Notifications.agentenabled": "Активировать службу", "components.Settings.Notifications.authPass": "Пароль SMTP", "components.Settings.Notifications.authUser": "Имя пользователя SMTP", "components.Settings.Notifications.emailsender": "Адрес отправителя", - "components.Settings.Notifications.smtpHost": "", - "components.Settings.Notifications.smtpPort": "", - "components.Settings.Notifications.validationSmtpHostRequired": "", - "components.Settings.Notifications.validationSmtpPortRequired": "", - "components.Settings.Notifications.webhookUrl": "", + "components.Settings.Notifications.smtpHost": "SMTP-хост", + "components.Settings.Notifications.smtpPort": "SMTP порт", + "components.Settings.Notifications.validationSmtpHostRequired": "Вы должны указать действительное имя хоста или IP-адрес", + "components.Settings.Notifications.validationSmtpPortRequired": "Вы должны указать действительный номер порта", + "components.Settings.Notifications.webhookUrl": "URL веб-перехватчика", "components.Settings.RadarrModal.add": "Добавить сервер", "components.Settings.RadarrModal.apiKey": "Ключ API", - "components.Settings.RadarrModal.baseUrl": "", + "components.Settings.RadarrModal.baseUrl": "Базовый URL", "components.Settings.RadarrModal.createradarr": "Добавить новый сервер Radarr", "components.Settings.RadarrModal.defaultserver": "Сервер по умолчанию", "components.Settings.RadarrModal.editradarr": "Редактировать сервер Radarr", - "components.Settings.RadarrModal.hostname": "Имя хоста", + "components.Settings.RadarrModal.hostname": "Имя хоста или IP-адрес", "components.Settings.RadarrModal.minimumAvailability": "Минимальная доступность", "components.Settings.RadarrModal.port": "Порт", "components.Settings.RadarrModal.qualityprofile": "Профиль качества", "components.Settings.RadarrModal.rootfolder": "Корневой каталог", - "components.Settings.RadarrModal.selectMinimumAvailability": "", + "components.Settings.RadarrModal.selectMinimumAvailability": "Выберите минимальную доступность", "components.Settings.RadarrModal.selectQualityProfile": "Выберите профиль качества", "components.Settings.RadarrModal.selectRootFolder": "Выберите корневой каталог", - "components.Settings.RadarrModal.server4k": "4K Сервер", + "components.Settings.RadarrModal.server4k": "4К сервер", "components.Settings.RadarrModal.servername": "Название сервера", "components.Settings.RadarrModal.ssl": "Использовать SSL", - "components.Settings.RadarrModal.toastRadarrTestFailure": "", - "components.Settings.RadarrModal.toastRadarrTestSuccess": "", - "components.Settings.RadarrModal.validationApiKeyRequired": "", - "components.Settings.RadarrModal.validationHostnameRequired": "", - "components.Settings.RadarrModal.validationPortRequired": "", + "components.Settings.RadarrModal.toastRadarrTestFailure": "Не удалось подключиться к Radarr.", + "components.Settings.RadarrModal.toastRadarrTestSuccess": "Соединение с Radarr установлено успешно!", + "components.Settings.RadarrModal.validationApiKeyRequired": "Вы должны предоставить ключ API", + "components.Settings.RadarrModal.validationHostnameRequired": "Вы должны указать имя хоста или IP-адрес", + "components.Settings.RadarrModal.validationPortRequired": "Вы должны указать действительный номер порта", "components.Settings.RadarrModal.validationProfileRequired": "Вы должны выбрать профиль качества", "components.Settings.RadarrModal.validationRootFolderRequired": "Вы должны выбрать корневой каталог", "components.Settings.SonarrModal.add": "Добавить сервер", "components.Settings.SonarrModal.apiKey": "Ключ API", - "components.Settings.SonarrModal.baseUrl": "", - "components.Settings.SonarrModal.createsonarr": "", + "components.Settings.SonarrModal.baseUrl": "Базовый URL", + "components.Settings.SonarrModal.createsonarr": "Добавить новый сервер Sonarr", "components.Settings.SonarrModal.defaultserver": "Сервер по умолчанию", - "components.Settings.SonarrModal.editsonarr": "", - "components.Settings.SonarrModal.hostname": "Имя хоста", + "components.Settings.SonarrModal.editsonarr": "Редактировать сервер Sonarr", + "components.Settings.SonarrModal.hostname": "Имя хоста или IP-адрес", "components.Settings.SonarrModal.port": "Порт", "components.Settings.SonarrModal.qualityprofile": "Профиль качества", "components.Settings.SonarrModal.rootfolder": "Корневой каталог", - "components.Settings.SonarrModal.seasonfolders": "", + "components.Settings.SonarrModal.seasonfolders": "Папки для сезонов", "components.Settings.SonarrModal.selectQualityProfile": "Выберите профиль качества", "components.Settings.SonarrModal.selectRootFolder": "Выберите корневой каталог", - "components.Settings.SonarrModal.server4k": "4K Сервер", + "components.Settings.SonarrModal.server4k": "4К сервер", "components.Settings.SonarrModal.servername": "Название сервера", "components.Settings.SonarrModal.ssl": "Использовать SSL", - "components.Settings.SonarrModal.validationApiKeyRequired": "", - "components.Settings.SonarrModal.validationHostnameRequired": "", - "components.Settings.SonarrModal.validationPortRequired": "", - "components.Settings.SonarrModal.validationProfileRequired": "", + "components.Settings.SonarrModal.validationApiKeyRequired": "Вы должны предоставить ключ API", + "components.Settings.SonarrModal.validationHostnameRequired": "Вы должны указать имя хоста или IP-адрес", + "components.Settings.SonarrModal.validationPortRequired": "Вы должны указать действительный номер порта", + "components.Settings.SonarrModal.validationProfileRequired": "Вы должны выбрать профиль качества", "components.Settings.SonarrModal.validationRootFolderRequired": "Вы должны выбрать корневой каталог", "components.Settings.activeProfile": "Активный профиль", - "components.Settings.addradarr": "", + "components.Settings.addradarr": "Добавить сервер Radarr", "components.Settings.address": "Адрес", - "components.Settings.addsonarr": "", + "components.Settings.addsonarr": "Добавить сервер Sonarr", "components.Settings.apikey": "Ключ API", "components.Settings.applicationurl": "URL-адрес приложения", "components.Settings.cancelscan": "Отменить сканирование", - "components.Settings.copied": "", + "components.Settings.copied": "Ключ API скопирован в буфер обмена.", "components.Settings.currentlibrary": "Текущая библиотека: {name}", "components.Settings.default": "По умолчанию", - "components.Settings.default4k": "По умолчанию 4K", - "components.Settings.deleteserverconfirm": "", + "components.Settings.default4k": "4К по умолчанию", + "components.Settings.deleteserverconfirm": "Вы уверены, что хотите удалить этот сервер?", "components.Settings.generalsettings": "Общие настройки", - "components.Settings.generalsettingsDescription": "", - "components.Settings.hostname": "", - "components.Settings.librariesRemaining": "", - "components.Settings.manualscan": "Сканирование библиотеки вручную", - "components.Settings.manualscanDescription": "", - "components.Settings.menuAbout": "", - "components.Settings.menuGeneralSettings": "Общие настройки", - "components.Settings.menuJobs": "", - "components.Settings.menuLogs": "", + "components.Settings.generalsettingsDescription": "Настройте глобальные параметры и параметры по умолчанию для Overseerr.", + "components.Settings.hostname": "Имя хоста или IP-адрес", + "components.Settings.librariesRemaining": "Осталось библиотек: {count}", + "components.Settings.manualscan": "Сканировать библиотеки вручную", + "components.Settings.manualscanDescription": "Обычно выполняется раз в 24 часа. Overseerr выполнит более агрессивную проверку вашего сервера Plex на предмет недавно добавленных мультимедиа. Если вы впервые настраиваете Plex, рекомендуется выполнить однократное полное сканирование библиотек вручную!", + "components.Settings.menuAbout": "О проекте", + "components.Settings.menuGeneralSettings": "Общее", + "components.Settings.menuJobs": "Задания и кэш", + "components.Settings.menuLogs": "Логи", "components.Settings.menuNotifications": "Уведомления", "components.Settings.menuPlexSettings": "Plex", - "components.Settings.menuServices": "", + "components.Settings.menuServices": "Службы", "components.Settings.notificationsettings": "Настройки уведомлений", - "components.Settings.notrunning": "", + "components.Settings.notrunning": "Не работает", "components.Settings.plexlibraries": "Библиотеки Plex", - "components.Settings.plexlibrariesDescription": "", + "components.Settings.plexlibrariesDescription": "Библиотеки, которые Overseerr сканирует на предмет наличия мультимедиа. Настройте и сохраните параметры подключения Plex, затем нажмите кнопку ниже, если список библиотек пуст.", "components.Settings.plexsettings": "Настройки Plex", - "components.Settings.plexsettingsDescription": "", + "components.Settings.plexsettingsDescription": "Настройте параметры вашего сервера Plex. Overseerr сканирует ваши библиотеки Plex, чтобы определить доступность контента.", "components.Settings.port": "Порт", "components.Settings.radarrsettings": "Настройки Radarr", "components.Settings.sonarrsettings": "Настройки Sonarr", "components.Settings.ssl": "SSL", "components.Settings.startscan": "Начать сканирование", - "components.Setup.configureplex": "", - "components.Setup.configureservices": "", + "components.Setup.configureplex": "Настройте Plex", + "components.Setup.configureservices": "Настройте службы", "components.Setup.continue": "Продолжить", "components.Setup.finish": "Завершить настройку", "components.Setup.finishing": "Завершение…", "components.Setup.loginwithplex": "Войти с помощью Plex", - "components.Setup.signinMessage": "", + "components.Setup.signinMessage": "Начните с входа в систему с помощью учётной записи Plex", "components.Setup.welcome": "Добро пожаловать в Overseerr", "components.TvDetails.cast": "В ролях", - "components.TvDetails.manageModalClearMedia": "Очистить все медиаданные", - "components.TvDetails.manageModalClearMediaWarning": "", - "components.TvDetails.manageModalNoRequests": "Нет запросов.", + "components.TvDetails.manageModalClearMedia": "Очистить данные мультимедиа", + "components.TvDetails.manageModalClearMediaWarning": "* Это приведет к безвозвратному удалению всех данных для этого сериала, включая все запросы. Если сериал существует в вашей библиотеке Plex, мультимедийная информация о нём будет воссоздана при следующем сканировании.", + "components.TvDetails.manageModalNoRequests": "Запросов нет.", "components.TvDetails.manageModalRequests": "Запросы", - "components.TvDetails.manageModalTitle": "", - "components.TvDetails.originallanguage": "Оригинальный язык", + "components.TvDetails.manageModalTitle": "Управление сериалом", + "components.TvDetails.originallanguage": "Язык оригинала", "components.TvDetails.overview": "Обзор", "components.TvDetails.overviewunavailable": "Обзор недоступен.", "components.TvDetails.recommendations": "Рекомендации", - "components.TvDetails.similar": "", + "components.TvDetails.similar": "Похожие сериалы", "components.UserList.admin": "Администратор", - "components.UserList.created": "Созданно", - "components.UserList.lastupdated": "Последнее обновление", + "components.UserList.created": "Создан", + "components.UserList.lastupdated": "Обновлено", "components.UserList.plexuser": "Пользователь Plex", "components.UserList.role": "Роль", - "components.UserList.totalrequests": "Всего запросов", + "components.UserList.totalrequests": "Запросов", "components.UserList.user": "Пользователь", "components.UserList.userlist": "Список пользователей", "i18n.approve": "Одобрить", @@ -174,11 +174,11 @@ "i18n.declined": "Отклонено", "i18n.delete": "Удалить", "i18n.movies": "Фильмы", - "i18n.partiallyavailable": "Частично доступно", + "i18n.partiallyavailable": "Доступно частично", "i18n.pending": "В ожидании", "i18n.processing": "Обработка", - "i18n.tvshows": "", - "i18n.unavailable": "Недоступен", + "i18n.tvshows": "Сериалы", + "i18n.unavailable": "Недоступно", "pages.oops": "Упс", "pages.returnHome": "Вернуться домой", "components.CollectionDetails.overview": "Обзор", @@ -188,7 +188,7 @@ "components.Login.email": "Адрес электронной почты", "components.UserList.users": "Пользователи", "components.UserList.userdeleted": "Пользователь успешно удален!", - "components.UserList.usercreatedsuccess": "Пользователь создан успешно!", + "components.UserList.usercreatedsuccess": "Пользователь успешно создан!", "components.Settings.SettingsAbout.totalrequests": "Всего запросов", "components.UserList.sortRequests": "Количество запросов", "components.UserList.sortCreated": "Дата создания", @@ -201,15 +201,15 @@ "components.UserList.creating": "Создание…", "components.UserList.createlocaluser": "Создать локального пользователя", "components.UserList.create": "Создать", - "components.TvDetails.network": "Сеть", + "components.TvDetails.network": "{networkCount, plural, one {Телеканал} other {Телеканалы}}", "components.TvDetails.anime": "Аниме", - "components.StatusChacker.newversionavailable": "Доступна новая версия", - "components.Settings.toastSettingsSuccess": "Настройки сохранены!", + "components.StatusChacker.newversionavailable": "Обновить приложение", + "components.Settings.toastSettingsSuccess": "Настройки успешно сохранены!", "components.Settings.serverpresetManualMessage": "Ручная настройка", "components.Settings.serverpreset": "Сервер", "i18n.deleting": "Удаление…", "components.Settings.applicationTitle": "Название приложения", - "components.Settings.SettingsAbout.Releases.latestversion": "Самый последний", + "components.Settings.SettingsAbout.Releases.latestversion": "Последняя", "components.Settings.SettingsAbout.Releases.currentversion": "Текущая версия", "components.Settings.SonarrModal.syncEnabled": "Включить сканирование", "components.Settings.RadarrModal.syncEnabled": "Включить сканирование", @@ -217,46 +217,46 @@ "components.Settings.Notifications.telegramsettingssaved": "Настройки уведомлений Telegram успешно сохранены!", "components.Settings.Notifications.senderName": "Имя отправителя", "components.Settings.Notifications.botAPI": "Токен авторизации бота", - "components.Settings.Notifications.NotificationsPushover.agentenabled": "Агент включен", - "components.Settings.Notifications.NotificationsSlack.agentenabled": "Агент включен", - "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Агент включен", + "components.Settings.Notifications.NotificationsPushover.agentenabled": "Активировать службу", + "components.Settings.Notifications.NotificationsSlack.agentenabled": "Активировать службу", + "components.Settings.Notifications.NotificationsWebhook.agentenabled": "Активировать службу", "components.Search.search": "Поиск", - "components.ResetPassword.resetpassword": "Сбросить пароль", + "components.ResetPassword.resetpassword": "Сброс пароля", "components.ResetPassword.password": "Пароль", "components.ResetPassword.confirmpassword": "Подтвердить пароль", "components.RequestModal.requesterror": "Что-то пошло не так при отправке запроса.", "components.RequestModal.requestedited": "Запрос на {title} успешно отредактирован!", - "components.RequestModal.requestcancelled": "Запрос на {title} отменен.", + "components.RequestModal.requestcancelled": "Запрос на {title} отменён.", "components.RequestModal.errorediting": "Что-то пошло не так при редактировании запроса.", "components.RequestModal.AdvancedRequester.rootfolder": "Корневой каталог", "components.RequestModal.AdvancedRequester.requestas": "Запросить как", "components.RequestModal.AdvancedRequester.qualityprofile": "Профиль качества", "components.RequestModal.AdvancedRequester.default": "{name} (по умолчанию)", - "components.RequestModal.AdvancedRequester.advancedoptions": "Дополнительно", + "components.RequestModal.AdvancedRequester.advancedoptions": "Расширенные настройки", "components.RequestList.sortModified": "Последнее изменение", "components.RequestList.sortAdded": "Дата запроса", "components.RequestList.showallrequests": "Показать все запросы", "components.RequestButton.viewrequest": "Посмотреть запрос", "i18n.retry": "Повторить", - "i18n.requested": "Запросы", + "i18n.requested": "Запрошено", "components.PermissionEdit.request4k": "Запрос 4K", "components.PermissionEdit.request": "Запрос", - "i18n.request": "Запрос", + "i18n.request": "Запросить", "i18n.failed": "Ошибка", - "i18n.experimental": "Экспериментально", + "i18n.experimental": "Экспериментальный параметр", "i18n.close": "Закрыть", - "i18n.advanced": "Дополнительно", + "i18n.advanced": "Для продвинутых пользователей", "components.Settings.SonarrModal.externalUrl": "Внешний URL-адрес", "components.Settings.RadarrModal.externalUrl": "Внешний URL-адрес", - "components.Settings.Notifications.sendSilently": "Отправить без звука", - "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Восстановить значения по умолчанию", + "components.Settings.Notifications.sendSilently": "Отправлять без звука", + "components.Settings.Notifications.NotificationsWebhook.resetPayload": "Сбросить к настройкам по умолчанию", "components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "Вы должны указать действительный URL-адрес", "components.Settings.validationApplicationUrl": "Вы должны указать действительный URL-адрес", "components.Settings.Notifications.validationUrl": "Вы должны указать действительный URL-адрес", "components.Settings.RadarrModal.validationApplicationUrl": "Вы должны указать действительный URL-адрес", "components.Settings.SonarrModal.validationApplicationUrl": "Вы должны указать действительный URL-адрес", "components.Settings.Notifications.NotificationsSlack.validationWebhookUrl": "Вы должны указать действительный URL-адрес", - "components.Settings.Notifications.NotificationsPushover.userToken": "Ключ пользователя", + "components.Settings.Notifications.NotificationsPushover.userToken": "Ключ пользователя или группы", "components.UserList.email": "Адрес электронной почты", "components.ResetPassword.email": "Адрес электронной почты", "components.Settings.SonarrModal.languageprofile": "Языковой профиль", @@ -277,21 +277,21 @@ "components.UserProfile.UserSettings.menuPermissions": "Разрешения", "components.UserProfile.UserSettings.UserPermissions.permissions": "Разрешения", "components.UserProfile.UserSettings.menuNotifications": "Уведомления", - "components.UserProfile.UserSettings.menuGeneralSettings": "Общие настройки", + "components.UserProfile.UserSettings.menuGeneralSettings": "Общее", "components.UserProfile.UserSettings.menuChangePass": "Пароль", "components.UserProfile.UserSettings.UserGeneralSettings.localuser": "Локальный пользователь", "components.UserProfile.UserSettings.UserGeneralSettings.generalsettings": "Общие настройки", "components.UserList.sortDisplayName": "Отображаемое имя", "components.UserProfile.UserSettings.UserGeneralSettings.displayName": "Отображаемое имя", "components.UserProfile.UserSettings.UserPasswordChange.validationCurrentPassword": "Вы должны указать свой текущий пароль", - "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "Вы должны подтвердить свой новый пароль", - "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "Пароль изменен!", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "Вы должны подтвердить новый пароль", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "Пароль успешно сохранён!", "components.UserProfile.UserSettings.UserPasswordChange.password": "Пароль", "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "Новый пароль", "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "Текущий пароль", - "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Подтвердить пароль", - "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Настройки сохранены!", - "components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "Настройки сохранены!", + "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "Подтвердите пароль", + "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsSuccess": "Настройки успешно сохранены!", + "components.UserProfile.UserSettings.UserPermissions.toastSettingsSuccess": "Разрешения успешно сохранены!", "components.Settings.toastSettingsFailure": "Что-то пошло не так при сохранении настроек.", "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "Что-то пошло не так при сохранении настроек.", "components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "Что-то пошло не так при сохранении настроек.", @@ -299,8 +299,8 @@ "components.UserProfile.UserSettings.UserGeneralSettings.plexuser": "Пользователь Plex", "components.UserList.owner": "Владелец", "components.UserProfile.UserSettings.UserGeneralSettings.owner": "Владелец", - "components.MovieDetails.markavailable": "Отметить как доступное", - "components.TvDetails.markavailable": "Отметить как доступное", + "components.MovieDetails.markavailable": "Пометить как доступный", + "components.TvDetails.markavailable": "Пометить как доступный", "components.MovieDetails.downloadstatus": "Статус загрузки", "components.TvDetails.downloadstatus": "Статус загрузки", "components.StatusChacker.reloadOverseerr": "Перезагрузить", @@ -310,7 +310,7 @@ "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "Вы должны указать действительный URL-адрес", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Название профиля", "components.ResetPassword.resetpasswordsuccessmessage": "Пароль сброшен успешно!", - "components.ResetPassword.passwordreset": "Сброс пароля", + "components.ResetPassword.passwordreset": "Сбросить пароль", "components.RequestModal.edit": "Редактировать запрос", "components.RequestModal.QuotaDisplay.movie": "фильм", "components.RequestModal.AdvancedRequester.tags": "Теги", @@ -339,23 +339,23 @@ "components.LanguageSelector.originalLanguageDefault": "Все языки", "components.LanguageSelector.languageServerDefault": "По умолчанию ({language})", "components.Discover.StudioSlider.studios": "Студии", - "components.Discover.NetworkSlider.networks": "TV-сети", + "components.Discover.NetworkSlider.networks": "Телеканалы", "components.Discover.DiscoverStudio.studioMovies": "Фильмы {studio}", "components.Discover.DiscoverMovieLanguage.languageMovies": "Фильмы на языке \"{language}\"", "components.Discover.DiscoverMovieGenre.genreMovies": "Фильмы в жанре \"{genre}\"", "components.Settings.SettingsAbout.overseerrinformation": "Информация о Overseerr", "components.Settings.SettingsAbout.githubdiscussions": "Обсуждения на GitHub", "components.Settings.enablessl": "Использовать SSL", - "components.Settings.is4k": "4K", + "components.Settings.is4k": "4К", "components.Settings.mediaTypeMovie": "фильм", "components.Settings.SonarrModal.tags": "Теги", "components.Settings.RadarrModal.tags": "Теги", "i18n.testing": "Тестирование…", - "i18n.test": "Тест", + "i18n.test": "Протестировать", "i18n.status": "Статус", "i18n.saving": "Сохранение…", - "i18n.previous": "Предыдущий", - "i18n.next": "Следующий", + "i18n.previous": "Предыдущая", + "i18n.next": "Следующая", "i18n.movie": "Фильм", "i18n.canceling": "Отмена…", "i18n.back": "Назад", @@ -365,20 +365,20 @@ "components.Settings.plex": "Plex", "components.Settings.notifications": "Уведомления", "components.Settings.SettingsUsers.users": "Пользователи", - "components.Settings.SettingsLogs.resumeLogs": "Продолжить", - "components.Settings.SettingsLogs.pauseLogs": "Пауза", + "components.Settings.SettingsLogs.resumeLogs": "Возобновить", + "components.Settings.SettingsLogs.pauseLogs": "Приостановить", "components.Settings.SettingsLogs.message": "Сообщение", "components.Settings.SettingsLogs.label": "Метка", - "components.Settings.SettingsLogs.filterWarn": "Предупреждение", - "components.Settings.SettingsLogs.filterInfo": "Информация", - "components.Settings.SettingsLogs.filterError": "Ошибка", + "components.Settings.SettingsLogs.filterWarn": "Предупреждения", + "components.Settings.SettingsLogs.filterInfo": "Информационные", + "components.Settings.SettingsLogs.filterError": "Ошибки", "components.Settings.menuUsers": "Пользователи", "components.Settings.scanning": "Синхронизация…", "i18n.loading": "Загрузка…", "components.UserProfile.UserSettings.UserGeneralSettings.user": "Пользователь", "components.UserProfile.UserSettings.UserGeneralSettings.role": "Роль", - "components.Settings.webhook": "Webhook", - "components.Setup.setup": "Настройка", + "components.Settings.webhook": "Веб-перехватчик", + "components.Setup.setup": "Настройки", "components.Settings.SettingsJobsCache.process": "Процесс", "components.Settings.SettingsJobsCache.command": "Команда", "components.Settings.SettingsJobsCache.jobtype": "Тип", @@ -389,46 +389,46 @@ "components.Settings.SettingsAbout.version": "Версия", "components.UserProfile.ProfileHeader.profile": "Посмотреть профиль", "components.Settings.SettingsJobsCache.cachename": "Название кэша", - "components.Settings.SettingsJobsCache.cacheksize": "Размер ключа", + "components.Settings.SettingsJobsCache.cacheksize": "Размер ключей", "components.Settings.SettingsJobsCache.cachekeys": "Всего ключей", "components.UserList.bulkedit": "Массовое редактирование", "components.MediaSlider.ShowMoreCard.seemore": "Больше информации", "components.TvDetails.watchtrailer": "Смотреть трейлер", "components.Settings.SettingsAbout.timezone": "Часовой пояс", - "components.Settings.SettingsAbout.supportoverseerr": "Поддержка Overseerr", + "components.Settings.SettingsAbout.supportoverseerr": "Поддержать Overseerr", "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Получать уведомления, когда другие пользователи отправляют новые медиа-запросы, которые одобряются автоматически.", "components.NotificationTypeSelector.mediarequestedDescription": "Отправлять уведомления, когда пользователи отправляют новые медиа-запросы, требующие одобрения.", - "components.NotificationTypeSelector.mediarequested": "Медиафайлы запрошены", + "components.NotificationTypeSelector.mediarequested": "Запросы медиафайлов", "components.NotificationTypeSelector.mediafailedDescription": "Отправлять уведомления, когда медиа-запросы не удаётся добавить в Radarr или Sonarr.", - "components.NotificationTypeSelector.mediafailed": "Не удалось добавить медиа-запрос", + "components.NotificationTypeSelector.mediafailed": "Ошибки при добавлении медиа-запросов", "components.NotificationTypeSelector.mediadeclinedDescription": "Отправлять уведомления, когда медиа-запросы отклоняются.", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Отправлять уведомления, когда пользователи отправляют новые медиа-запросы, которые одобряются автоматически.", - "components.NotificationTypeSelector.mediaAutoApproved": "Медиа-запрос одобрен автоматически", - "components.NotificationTypeSelector.mediaapproved": "Медиа-запрос одобрен", + "components.NotificationTypeSelector.mediaAutoApproved": "Автоматическое одобрение медиа-запросов", + "components.NotificationTypeSelector.mediaapproved": "Одобрение медиа-запросов", "components.NotificationTypeSelector.mediaapprovedDescription": "Отправлять уведомления, когда медиа-запросы одобряются вручную.", - "components.NotificationTypeSelector.mediadeclined": "Медиа-запрос отклонён", - "components.NotificationTypeSelector.mediaavailableDescription": "Отправлять уведомления, когда медиа-запросы становятся доступны.", - "components.NotificationTypeSelector.mediaavailable": "Медиафайлы доступны", - "components.MovieDetails.MovieCrew.fullcrew": "Вся съёмочная группа", - "components.MovieDetails.viewfullcrew": "Посмотреть всю cъёмочную группу", + "components.NotificationTypeSelector.mediadeclined": "Отклонение медиа-запросов", + "components.NotificationTypeSelector.mediaavailableDescription": "Отправлять уведомления, когда запрошенные медиафайлы становятся доступны.", + "components.NotificationTypeSelector.mediaavailable": "Доступны новые медиафайлы", + "components.MovieDetails.MovieCrew.fullcrew": "Полная съёмочная группа", + "components.MovieDetails.viewfullcrew": "Посмотреть полную cъёмочную группу", "components.MovieDetails.showmore": "Развернуть", "components.MovieDetails.showless": "Свернуть", "components.MovieDetails.playonplex": "Воспроизвести в Plex", "components.MovieDetails.play4konplex": "Воспроизвести в Plex в 4К", "components.MovieDetails.openradarr4k": "Открыть фильм в 4К Radarr", "components.MovieDetails.openradarr": "Открыть фильм в Radarr", - "components.MovieDetails.mark4kavailable": "Отметить как доступное в 4К", - "components.MovieDetails.MovieCast.fullcast": "Весь актёрский состав", - "components.Login.validationpasswordrequired": "Необходимо предоставить пароль", - "components.Login.validationemailrequired": "Необходимо предоставить корректный адрес электронной почты", - "components.Login.signinwithoverseerr": "Используйте ваш {applicationTitle} аккаунт", + "components.MovieDetails.mark4kavailable": "Пометить как доступный в 4К", + "components.MovieDetails.MovieCast.fullcast": "Полный актёрский состав", + "components.Login.validationpasswordrequired": "Вы должны предоставить пароль", + "components.Login.validationemailrequired": "Вы должны указать действительный адрес электронной почты", + "components.Login.signinwithoverseerr": "Используйте ваш аккаунт {applicationTitle}", "components.Login.signingin": "Выполняется вход…", "components.Login.loginerror": "Что-то пошло не так при попытке выполнить вход.", - "components.Layout.LanguagePicker.displaylanguage": "Язык отображения", + "components.Layout.LanguagePicker.displaylanguage": "Язык интерфейса", "components.DownloadBlock.estimatedtime": "Приблизительно {time}", "components.Discover.upcomingtv": "Предстоящие сериалы", "components.Discover.noRequests": "Запросов нет.", - "components.Discover.discover": "Открыть что-то новое", + "components.Discover.discover": "Найти что-то новое", "components.Discover.TvGenreSlider.tvgenres": "Сериалы по жанрам", "components.Discover.TvGenreList.seriesgenres": "Сериалы по жанрам", "components.Discover.MovieGenreSlider.moviegenres": "Фильмы по жанрам", @@ -436,8 +436,8 @@ "components.Discover.DiscoverTvLanguage.languageSeries": "Сериалы на языке \"{language}\"", "components.Discover.DiscoverTvGenre.genreSeries": "Сериалы в жанре \"{genre}\"", "components.Discover.DiscoverNetwork.networkSeries": "Сериалы {network}", - "components.CollectionDetails.requestswillbecreated4k": "Будут созданы 4К запросы для следующих заголовков:", - "components.CollectionDetails.requestswillbecreated": "Будут созданы запросы для следующих заголовков:", + "components.CollectionDetails.requestswillbecreated4k": "Будут созданы 4К запросы на следующие названия:", + "components.CollectionDetails.requestswillbecreated": "Будут созданы запросы на следующие названия:", "components.CollectionDetails.requestcollection4k": "Запросить Коллекцию в 4К", "components.QuotaSelector.movies": "{count, plural, one {фильм} other {фильма(ов)}}", "components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {фильм} other {фильма(ов)}}", @@ -446,12 +446,12 @@ "components.Settings.SonarrModal.testFirstRootFolders": "Протестировать соединение для загрузки корневых каталогов", "components.Settings.SonarrModal.loadingrootfolders": "Загрузка корневых каталогов…", "components.Settings.SonarrModal.animerootfolder": "Корневой каталог для аниме", - "components.Settings.RadarrModal.testFirstRootFolders": "Протестировать соединение для загрузки корневых каталогов", + "components.Settings.RadarrModal.testFirstRootFolders": "Протестировать подключение для загрузки корневых каталогов", "components.Settings.RadarrModal.loadingrootfolders": "Загрузка корневых каталогов…", "components.RequestModal.AdvancedRequester.destinationserver": "Сервер-получатель", - "components.RequestList.RequestItem.mediaerror": "Соответствующий заголовок для этого запроса больше недоступен.", + "components.RequestList.RequestItem.mediaerror": "Соответствующее название для этого запроса больше недоступно.", "components.RequestList.RequestItem.failedretry": "Что-то пошло не так при попытке повторить запрос.", - "components.RequestCard.mediaerror": "Соответствующий заголовок для этого запроса больше недоступен.", + "components.RequestCard.mediaerror": "Соответствующее название для этого запроса больше недоступно.", "components.RequestCard.failedretry": "Что-то пошло не так при попытке повторить запрос.", "components.RequestButton.viewrequest4k": "Посмотреть 4К запрос", "components.RequestButton.requestmore4k": "Запросить больше в 4К", @@ -466,7 +466,7 @@ "components.RequestButton.approverequest": "Одобрить запрос", "components.RequestBlock.server": "Сервер-получатель", "components.QuotaSelector.tvRequests": "{quotaLimit} {сезонов} за {quotaDays} {дней}", - "components.QuotaSelector.seasons": "{count, plural, one {сезон} other {сезонов}}", + "components.QuotaSelector.seasons": "{count, plural, one {сезон} other {сезона(ов)}}", "components.RequestBlock.requestoverrides": "Переопределение запроса", "components.QuotaSelector.unlimited": "Неограниченно", "components.QuotaSelector.movieRequests": "{quotaLimit} {фильмов} за {quotaDays} {дней}", @@ -505,7 +505,7 @@ "components.NotificationTypeSelector.usermediarequestedDescription": "Получать уведомления, когда другие пользователи отправляют новые медиа-запросы, требующие одобрения.", "components.NotificationTypeSelector.usermediafailedDescription": "Получать уведомления, когда медиа-запросы не удаётся добавить в Radarr или Sonarr.", "components.NotificationTypeSelector.usermediadeclinedDescription": "Получать уведомления, когда ваши медиа-запросы отклоняются.", - "components.NotificationTypeSelector.usermediaavailableDescription": "Получать уведомления, когда ваши медиа-запросы становятся доступны.", + "components.NotificationTypeSelector.usermediaavailableDescription": "Получать уведомления, когда запрошенные вами медиафайлы становятся доступны.", "components.NotificationTypeSelector.usermediaapprovedDescription": "Получать уведомления, когда ваши медиа-запросы получают одобрение.", "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {коммит} other {коммитов}} позади", "components.MovieDetails.studio": "{studioCount, plural, one {Студия} other {Студии}}", @@ -516,5 +516,370 @@ "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {запросов {type} не осталось} other {осталось # запроса(ов) {type}}}", "components.RequestModal.QuotaDisplay.quotaLinkUser": "Вы можете посмотреть сводку ограничений на количество запросов этого пользователя на странице его профиля.", "components.RequestModal.QuotaDisplay.quotaLink": "Вы можете посмотреть сводку ваших ограничений на количество запросов на странице вашего профиля.", - "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Запросов на TV-сезоны не осталось" + "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "Осталось недостаточно запросов на сезоны", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "Отправка тестового уведомления веб-перехватчику…", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Не удалось отправить тестовое уведомление веб-перехватчику.", + "components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Помощь по переменным шаблона", + "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "Полезная нагрузка JSON успешно сброшена к настройкам по умолчанию!", + "components.Settings.Notifications.NotificationsWebhook.customJson": "Полезная нагрузка JSON", + "components.Settings.Notifications.NotificationsWebhook.authheader": "Заголовок авторизации", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "Настройки веб-push-уведомлений успешно сохранены!", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "Не удалось сохранить настройки веб-push-уведомлений.", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "Тестовое веб-push-уведомление отправлено!", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "Отправка тестового веб-push-уведомления…", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "Не удалось отправить тестовое веб-push-уведомление.", + "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Чтобы получать веб-push-уведомления, Overseerr должен обслуживаться по протоколу HTTPS.", + "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Создайте интеграцию входящего веб-перехватчика", + "components.Settings.Notifications.NotificationsSlack.webhookUrl": "URL веб-перехватчика", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Тестовое уведомление отправлено в Slack!", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSending": "Отправка тестового уведомления в Slack…", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestFailed": "Не удалось отправить тестовое уведомление в Slack.", + "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Настройки уведомлений Slack успешно сохранены!", + "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Не удалось сохранить настройки уведомлений Slack.", + "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Вы должны предоставить действительный ключ пользователя или группы", + "components.Settings.Notifications.validationTypes": "Вы должны выбрать хотя бы один тип уведомлений", + "components.Settings.Notifications.NotificationsWebhook.validationTypes": "Вы должны выбрать хотя бы один тип уведомлений", + "components.Settings.Notifications.NotificationsSlack.validationTypes": "Вы должны выбрать хотя бы один тип уведомлений", + "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Вы должны выбрать хотя бы один тип уведомлений", + "components.Settings.Notifications.NotificationsLunaSea.validationTypes": "Вы должны выбрать хотя бы один тип уведомлений", + "components.Settings.Notifications.NotificationsPushover.validationTypes": "Вы должны выбрать хотя бы один тип уведомлений", + "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Вы должны предоставить действительный токен приложения", + "components.Settings.Notifications.NotificationsPushover.userTokenTip": "Ваш тридцатизначный идентификатор пользователя или группы", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Тестовое уведомление отправлено в Pushover!", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "Отправка тестового уведомления в Pushover…", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestFailed": "Не удалось отправить тестовое уведомление в Pushover.", + "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Настройки уведомлений Pushover успешно сохранены!", + "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Не удалось сохранить настройки уведомлений Pushover.", + "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Зарегистрируйте приложение для использования с Overseerr", + "i18n.view": "Вид", + "i18n.notrequested": "Не запрошено", + "i18n.noresults": "Результатов нет.", + "i18n.delimitedlist": "{a}, {b}", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrl": "URL веб-перехватчика", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "URL веб-перехватчика для уведомлений на основе вашего пользователя или устройства", + "components.Settings.Notifications.NotificationsPushover.accessToken": "Токен API приложения", + "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "Вы должны предоставить токен доступа", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Тестовое уведомление отправлено в Pushbullet!", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "Отправка тестового уведомления в Pushbullet…", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestFailed": "Не удалось отправить тестовое уведомление в Pushbullet.", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Настройки уведомлений Pushbullet успешно сохранены!", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Не удалось сохранить настройки уведомлений Pushbullet.", + "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Создайте токен в настройках учётной записи", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "Тестовое уведомление отправлено в LunaSea!", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSending": "Отправка тестового уведомления в LunaSea…", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestFailed": "Не удалось отправить тестовое уведомление в LunaSea.", + "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "Настройки уведомлений LunaSea успешно сохранены!", + "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "Не удалось сохранить настройки уведомлений LunaSea.", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Требуется только в том случае, если не используется профиль default", + "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Активировать службу", + "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "Активировать службу", + "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Активировать службу", + "components.Settings.notificationAgentSettingsDescription": "Настройте и активируйте службы уведомлений.", + "components.ResetPassword.emailresetlink": "Отправить ссылку для восстановления по электронной почте", + "pages.somethingwentwrong": "Что-то пошло не так", + "pages.serviceunavailable": "Сервис недоступен", + "pages.pagenotfound": "Страница не найдена", + "pages.internalservererror": "Внутренняя ошибка сервера", + "components.ResetPassword.validationpasswordrequired": "Вы должны предоставить пароль", + "components.ResetPassword.validationpasswordminchars": "Пароль слишком короткий: он должен содержать не менее 8 символов", + "components.ResetPassword.validationpasswordmatch": "Пароли должны совпадать", + "components.ResetPassword.validationemailrequired": "Вы должны указать действительный адрес электронной почты", + "components.ResetPassword.requestresetlinksuccessmessage": "Ссылка для сброса пароля будет отправлена на указанный адрес электронной почты, если он связан с действительным пользователем.", + "components.ResetPassword.gobacklogin": "Вернуться на страницу входа", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguage": "Языки для поиска фильмов и сериалов", + "components.Settings.region": "Регион для поиска фильмов и сериалов", + "components.Settings.originallanguage": "Языки для поиска фильмов и сериалов", + "components.TvDetails.seasons": "{seasonCount, plural, one {# сезон} other {# сезонов}}", + "components.RequestModal.QuotaDisplay.requiredquotaUser": "Этому пользователю необходимо иметь по крайней мере {seasons} {seasons, plural, one {запрос на сезоны} other {запроса(ов) на сезоны}} для того, чтобы отправить запрос на этот сериал.", + "components.RequestModal.QuotaDisplay.requiredquota": "Вам необходимо иметь по крайней мере {seasons} {seasons, plural, one {запрос на сезоны} other {запроса(ов) на сезоны}} для того, чтобы отправить запрос на этот сериал.", + "components.RequestModal.request4ktitle": "Запросить {title} в 4К", + "components.RequestModal.pending4krequest": "В ожидании 4К запрос на {title}", + "components.RequestModal.autoapproval": "Автоматическое одобрение", + "i18n.usersettings": "Настройки пользователя", + "i18n.showingresults": "Показываются результаты с {from} по {to} из {total}", + "i18n.save": "Сохранить изменения", + "i18n.retrying": "Повтор…", + "i18n.resultsperpage": "Отобразить {pageSize} результатов на странице", + "i18n.requesting": "Запрос…", + "i18n.request4k": "Запросить в 4К", + "i18n.areyousure": "Вы уверены?", + "components.StatusChacker.newversionDescription": "Overseerr был обновлён! Пожалуйста, нажмите кнопку ниже, чтобы перезагрузить страницу.", + "components.RequestModal.alreadyrequested": "Уже запрошен", + "components.RequestModal.SearchByNameModal.notvdbiddescription": "Мы не смогли автоматически выполнить ваш запрос. Пожалуйста, выберите правильное совпадение из списка ниже.", + "components.TvDetails.originaltitle": "Название оригинала", + "components.Settings.validationApplicationTitle": "Вы должны указать название приложения", + "components.RequestModal.SearchByNameModal.nosummary": "Аннотации для этого названия не найдено.", + "i18n.tvshow": "Сериал", + "components.Settings.partialRequestsEnabled": "Разрешить частичные запросы сериалов", + "components.Settings.mediaTypeSeries": "сериал", + "components.UserProfile.UserSettings.UserGeneralSettings.region": "Регион для поиска фильмов и сериалов", + "components.TvDetails.allseasonsmarkedavailable": "* Все сезоны будут помечены как доступные.", + "components.RequestModal.QuotaDisplay.seasonlimit": "{limit, plural, one {сезон} other {сезона(ов)}}", + "components.RequestModal.QuotaDisplay.season": "сезон", + "components.RequestModal.requestall": "Запросить все сезоны", + "components.RequestModal.pendingapproval": "Ваш запрос ожидает одобрения.", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "Настройки уведомлений по электронной почте успешно сохранены!", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "Не удалось сохранить настройки уведомлений по электронной почте.", + "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "Язык интерфейса", + "components.UserList.validationpasswordminchars": "Пароль слишком короткий: он должен содержать не менее 8 символов", + "components.UserList.nouserstoimport": "Нет новых пользователей для импорта из Plex.", + "components.UserList.autogeneratepasswordTip": "Отправить пользователю пароль, сгенерированный сервером, по электронной почте", + "components.TvDetails.viewfullcrew": "Посмотреть полную cъёмочную группу", + "components.TvDetails.showtype": "Тип сериала", + "components.TvDetails.TvCrew.fullseriescrew": "Полная съёмочная группа сериала", + "components.TvDetails.TvCast.fullseriescast": "Полный актёрский состав сериала", + "components.Settings.trustProxyTip": "Позволяет Overseerr корректно регистрировать IP-адреса клиентов за прокси-сервером (Overseerr необходимо перезагрузить, чтобы изменения вступили в силу)", + "components.Settings.originallanguageTip": "Контент фильтруется по языку оригинала", + "components.Settings.noDefaultNon4kServer": "Если вы используете один сервер {serverType} для контента, в том числе и для 4К, или если вы загружаете только контент 4K, ваш сервер {serverType} НЕ должен быть помечен как 4К сервер.", + "components.UserList.localLoginDisabled": "Параметр Включить локальный вход в настоящее время отключен.", + "components.Settings.SettingsLogs.showall": "Показать все логи", + "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Ограничение количества запросов на сериалы", + "components.UserProfile.UserSettings.UserGeneralSettings.regionTip": "Контент фильтруется по доступности в выбранном регионе", + "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Ограничение количества запросов на фильмы", + "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Переопределить глобальные ограничения", + "components.Settings.noDefaultServer": "По крайней мере один сервер {serverType} должен быть помечен как сервер по умолчанию для обработки запросов на {mediaType}.", + "components.Settings.noDefault4kServer": "4K сервер {serverType} должен быть помечен как сервер по умолчанию, чтобы пользователи могли отправлять запросы на 4K {mediaType}.", + "components.Settings.SonarrModal.validationLanguageProfileRequired": "Вы должны выбрать языковой профиль", + "components.Settings.validationApplicationUrlTrailingSlash": "URL-адрес не должен заканчиваться косой чертой", + "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "Базовый URL-адрес должен иметь косую черту в начале", + "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Базовый URL-адрес должен иметь косую черту в начале", + "components.Settings.SonarrModal.testFirstLanguageProfiles": "Протестировать подключение для загрузки языковых профилей", + "components.Settings.SonarrModal.loadingTags": "Загрузка тегов…", + "components.Settings.SonarrModal.enableSearch": "Включить автоматический поиск", + "components.Settings.SonarrModal.edit4ksonarr": "Редактировать 4К сервер Sonarr", + "components.Settings.toastApiKeyFailure": "Что-то пошло не так при создании нового ключа API.", + "components.Settings.csrfProtectionTip": "Устанавливает доступ к API извне только для чтения (требуется HTTPS, для вступления изменений в силу необходимо перезагрузить Overseerr)", + "components.Settings.SonarrModal.animequalityprofile": "Профиль качества для аниме", + "components.Settings.SonarrModal.animelanguageprofile": "Языковой профиль для аниме", + "components.Settings.SonarrModal.animeTags": "Теги для аниме", + "components.Settings.SettingsUsers.userSettings": "Настройки пользователей", + "components.Settings.SettingsUsers.tvRequestLimitLabel": "Общее ограничение на количество запросов сериалов", + "components.Settings.SettingsUsers.toastSettingsSuccess": "Настройки пользователей успешно сохранены!", + "components.Settings.SettingsUsers.toastSettingsFailure": "Что-то пошло не так при сохранении настроек.", + "components.Settings.SettingsUsers.newPlexLoginTip": "Разрешить пользователям Plex входить в систему без предварительного импорта", + "components.Settings.SettingsUsers.newPlexLogin": "Включить вход через Plex для новых пользователей", + "components.Settings.SettingsUsers.movieRequestLimitLabel": "Общее ограничение на количество запросов фильмов", + "components.Settings.SettingsUsers.localLoginTip": "Разрешить пользователям входить в систему, используя свой адрес электронной почты и пароль вместо Plex OAuth", + "components.Settings.SettingsUsers.localLogin": "Включить локальный вход", + "components.Settings.SettingsUsers.defaultPermissionsTip": "Начальные разрешения, присваемые новым пользователям", + "components.Settings.SettingsUsers.defaultPermissions": "Разрешения по умолчанию", + "components.Settings.SettingsLogs.time": "Время", + "components.Settings.SettingsLogs.level": "Важность", + "components.Settings.SettingsLogs.filterDebug": "Отладочные", + "components.Settings.SettingsLogs.extraData": "Дополнительная информация", + "components.Settings.SettingsLogs.copyToClipboard": "Скопировать в буфер обмена", + "components.Settings.serverpresetRefreshing": "Получение списка серверов…", + "components.Settings.SettingsJobsCache.jobstarted": "Задание \"{jobname}\" запущено.", + "components.Settings.cacheImagesTip": "Оптимизировать и хранить все изображения локально (потребляет значительный объем дискового пространства)", + "components.Settings.cacheImages": "Включить кэширование изображений", + "components.Settings.SettingsJobsCache.unknownJob": "Неизвестное задание", + "components.Settings.SettingsJobsCache.sonarr-scan": "Сканирование Sonarr", + "components.Settings.SettingsJobsCache.runnow": "Выполнить сейчас", + "components.Settings.SettingsJobsCache.radarr-scan": "Сканирование Radarr", + "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Сканирование недавно добавленных медиафайлов в Plex", + "components.Settings.SettingsJobsCache.plex-full-scan": "Полное сканирование библиотек Plex", + "components.Settings.SettingsJobsCache.nextexecution": "Следующее выполнение", + "components.Settings.SettingsJobsCache.jobsandcache": "Задания и кэш", + "components.Settings.SettingsJobsCache.jobs": "Задания", + "components.Settings.SettingsJobsCache.jobname": "Название задания", + "components.Settings.SettingsJobsCache.jobcancelled": "Задание \"{jobname}\" отменено.", + "components.Settings.SettingsJobsCache.canceljob": "Отменить задание", + "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr выполняет определенные задачи по обслуживанию в виде регулярно запланированных заданий, но они также могут быть запущены вручную ниже. Выполнение задания вручную не изменит его расписание.", + "components.Settings.SettingsJobsCache.flushcache": "Очистить кэш", + "components.Settings.SettingsJobsCache.download-sync-reset": "Сбросить синхронизацию загрузок", + "components.Settings.SettingsJobsCache.download-sync": "Синхронизировать загрузки", + "components.Settings.SettingsJobsCache.cachehits": "Удачных обращений", + "components.Settings.SettingsJobsCache.cachemisses": "Неудачных обращений", + "components.Settings.SettingsJobsCache.cachevsize": "Размер значений", + "components.Settings.SettingsAbout.uptodate": "Актуальная", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Изменения в версии", + "components.Settings.SettingsAbout.preferredmethod": "Предпочтительный способ", + "components.Settings.SettingsJobsCache.cacheflushed": "{cachename} кэш сброшен.", + "components.Settings.SettingsJobsCache.cacheDescription": "Overseerr кэширует запросы к внешним конечным точкам API, чтобы оптимизировать производительность и избежать ненужных вызовов API.", + "components.Settings.SettingsAbout.totalmedia": "Всего мультимедиа", + "components.Settings.SettingsAbout.outofdate": "Устарела", + "components.Settings.SettingsAbout.helppaycoffee": "Помочь оплатить кофе", + "components.Settings.SettingsAbout.gettingsupport": "Получить поддержку", + "components.Settings.SettingsAbout.betawarning": "Это бета-версия программного обеспечения. Некоторые функции могут не работать или работать нестабильно. Пожалуйста, сообщайте о любых проблемах на GitHub!", + "components.Settings.SettingsAbout.about": "О проекте", + "components.Settings.SettingsAbout.Releases.viewongithub": "Посмотреть на GitHub", + "components.Settings.SettingsAbout.Releases.viewchangelog": "Посмотреть список изменений", + "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Последние изменения в ветке develop проекта Overseerr не показаны ниже. Пожалуйста, просмотрите историю коммитов для этой ветки на GitHub, чтобы узнать подробности.", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Данные о релизе недоступны. GitHub не работает?", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPassword": "Вы должны ввести новый пароль", + "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "Вы должны предоставить действительный ID чата", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "Вы должны предоставить действительный открытый ключ PGP", + "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "Вы должны предоставить действительный ID пользователя", + "components.UserList.validationEmail": "Вы должны указать действительный адрес электронной почты", + "components.UserList.usercreatedfailedexisting": "Указанный адрес электронной почты уже используется другим пользователем.", + "components.TvDetails.streamingproviders": "Сейчас транслируется", + "components.Settings.validationWebAppUrl": "Вы должны указать действительный URL-адрес веб-приложения Plex", + "components.Settings.validationPortRequired": "Вы должны указать действительный номер порта", + "components.Settings.validationHostnameRequired": "Вы должны указать действительное имя хоста или IP-адрес", + "components.Settings.SonarrModal.validationNameRequired": "Вы должны указать имя сервера", + "components.Settings.RadarrModal.validationNameRequired": "Вы должны указать имя сервера", + "components.Settings.Notifications.validationEmail": "Вы должны указать действительный адрес электронной почты", + "components.MovieDetails.streamingproviders": "Сейчас транслируется", + "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "Базовый URL-адрес не должен заканчиваться косой чертой", + "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "Вы должны выбрать минимальную доступность", + "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "Базовый URL-адрес не должен заканчиваться косой чертой", + "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "URL-адрес не должен заканчиваться косой чертой", + "components.Settings.RadarrModal.testFirstTags": "Протестировать подключение для загрузки тегов", + "components.Settings.RadarrModal.testFirstQualityProfiles": "Протестировать подключение для загрузки профилей качества", + "components.Settings.RadarrModal.selecttags": "Выберите теги", + "components.Settings.RadarrModal.notagoptions": "Тегов нет.", + "components.Settings.RadarrModal.loadingprofiles": "Загрузка профилей качества…", + "components.Settings.RadarrModal.loadingTags": "Загрузка тегов…", + "components.Settings.RadarrModal.enableSearch": "Включить автоматический поиск", + "components.Settings.RadarrModal.default4kserver": "4К сервер по умолчанию", + "components.Settings.Notifications.webhookUrlTip": "Создайте интеграцию веб-перехватчика на своём сервере", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Настройки уведомлений веб-перехватчика успешно сохранены!", + "components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Не удалось сохранить настройки уведомлений веб-перехватчика.", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Тестовое уведомление веб-перехватчику отправлено!", + "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "URL веб-перехватчика", + "components.Settings.Notifications.validationPgpPrivateKey": "Вы должны предоставить действительный закрытый ключ PGP", + "components.Settings.Notifications.validationPgpPassword": "Вы должны предоставить пароль PGP", + "components.Settings.Notifications.validationChatIdRequired": "Вы должны предоставить действительный ID чата", + "components.Settings.Notifications.validationBotAPIRequired": "Вы должны предоставить токен авторизации бота", + "components.Settings.Notifications.telegramsettingsfailed": "Не удалось сохранить настройки уведомлений Telegram.", + "components.Settings.Notifications.pgpPrivateKeyTip": "Подписывать зашифрованные сообщения электронной почты с помощью OpenPGP", + "components.Settings.Notifications.pgpPrivateKey": "Закрытый ключ PGP", + "components.Settings.Notifications.pgpPasswordTip": "Подписывать зашифрованные сообщения электронной почты с помощью OpenPGP", + "components.Settings.Notifications.pgpPassword": "Пароль PGP", + "components.Settings.Notifications.encryptionTip": "В большинстве случаев неявный TLS использует порт 465, а STARTTLS – порт 587", + "components.Settings.Notifications.encryptionOpportunisticTls": "Всегда использовать STARTTLS", + "components.Settings.Notifications.encryptionNone": "Без шифрования", + "components.Settings.Notifications.encryptionImplicitTls": "Использовать неявный TLS", + "components.Settings.Notifications.encryptionDefault": "Использовать STARTTLS, если доступно", + "components.Settings.Notifications.encryption": "Метод шифрования", + "components.Settings.Notifications.emailsettingssaved": "Настройки уведомлений по электронной почте успешно сохранены!", + "components.Settings.Notifications.emailsettingsfailed": "Не удалось сохранить настройки уведомлений по электронной почте.", + "components.Settings.Notifications.discordsettingssaved": "Настройки уведомлений Discord успешно сохранены!", + "components.Settings.Notifications.discordsettingsfailed": "Не удалось сохранить настройки уведомлений Discord.", + "components.Settings.Notifications.chatIdTip": "Начните чат со своим ботом, добавьте @get_id_bot и выполните команду /my_id", + "components.Settings.Notifications.chatId": "ID чата", + "components.Settings.Notifications.botUsernameTip": "Разрешить пользователям начинать чат с вашим ботом и настраивать свои собственные уведомления", + "components.Settings.Notifications.botUsername": "Имя бота", + "components.Settings.Notifications.botAvatarUrl": "URL аватара бота", + "components.Settings.Notifications.botApiTip": "Создайте бота для использования с Overseerr", + "components.Settings.Notifications.allowselfsigned": "Разрешить самозаверенные сертификаты", + "components.Settings.Notifications.NotificationsWebhook.validationJsonPayloadRequired": "Вы должны предоставить допустимую полезную нагрузку JSON", + "components.Settings.Notifications.toastTelegramTestSuccess": "Тестовое уведомление отправлено в Telegram!", + "components.Settings.Notifications.toastTelegramTestSending": "Отправка тестового уведомления в Telegram…", + "components.Settings.Notifications.toastDiscordTestSuccess": "Тестовое уведомление отправлено в Discord!", + "components.Settings.Notifications.toastDiscordTestSending": "Отправка тестового уведомления в Discord…", + "components.Settings.Notifications.toastDiscordTestFailed": "Не удалось отправить тестовое уведомление в Discord.", + "components.Settings.Notifications.toastTelegramTestFailed": "Не удалось отправить тестовое уведомление в Telegram.", + "components.Settings.Notifications.toastEmailTestSuccess": "Тестовое уведомление отправлено по электронной почте!", + "components.Settings.Notifications.toastEmailTestSending": "Отправка тестового уведомления по электронной почте…", + "components.Settings.Notifications.toastEmailTestFailed": "Не удалось отправить тестовое уведомление по электронной почте.", + "components.UserProfile.unlimited": "Неограниченно", + "components.UserProfile.totalrequests": "Всего запросов", + "components.UserProfile.requestsperdays": "осталось {limit}", + "components.UserProfile.pastdays": "{type} (за {days} день(ей))", + "components.UserProfile.seriesrequest": "Запросов сериалов", + "components.UserProfile.movierequests": "Запросов фильмов", + "components.UserProfile.norequests": "Запросов нет.", + "components.UserProfile.limit": "{remaining} из {limit}", + "components.UserProfile.UserSettings.unauthorizedDescription": "У вас нет разрешения на изменение настроек этого пользователя.", + "components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "У вас нет разрешения на изменение пароля этого пользователя.", + "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPasswordSame": "Пароли должны совпадать", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailureVerifyCurrent": "Что-то пошло не так при сохранении пароля. Правильно ли введен ваш текущий пароль?", + "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Что-то пошло не так при сохранении пароля.", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "В настоящее время для этой учётной записи не установлен пароль. Установите пароль ниже, чтобы с этой учётной записью можно было войти в систему как \"локальный пользователь\".", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "В настоящее время для вашей учётной записи не установлен пароль. Установите пароль ниже, чтобы иметь возможность войти в систему как \"локальный пользователь\", используя свой адрес электронной почты.", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "Настройки веб-push-уведомлений успешно сохранены!", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Не удалось сохранить настройки веб-push-уведомлений.", + "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Веб-push", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Настройки уведомлений Telegram успешно сохранены!", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Не удалось сохранить настройки уведомлений Telegram.", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "Начните чат, добавьте @get_id_bot и выполните команду /my_id", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "ID чата", + "components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Отправлять без звука", + "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "Шифровать сообщения электронной почты с помощью OpenPGP", + "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "Открытый ключ PGP", + "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Настройки уведомлений", + "components.UserProfile.UserSettings.UserNotificationSettings.email": "Электронная почта", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Настройки уведомлений Discord успешно сохранены!", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Не удалось сохранить настройки уведомлений Discord.", + "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "ID вашей учётной записи", + "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "ID пользователя", + "components.Settings.locale": "Язык интерфейса", + "components.UserProfile.UserSettings.UserGeneralSettings.accounttype": "Тип учётной записи", + "components.UserProfile.ProfileHeader.userid": "ID пользователя: {userid}", + "components.UserProfile.ProfileHeader.settings": "Редактировать настройки", + "components.UserProfile.ProfileHeader.joindate": "Присоединился {joindate}", + "components.UserProfile.UserSettings.UserPasswordChange.validationNewPasswordLength": "Пароль слишком короткий: он должен содержать не менее 8 символов", + "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "Вы не можете изменять собственные разрешения.", + "components.UserList.userssaved": "Разрешения пользователя успешно сохранены!", + "components.UserList.userfail": "Что-то пошло не так при сохранении разрешений пользователя.", + "components.UserList.userdeleteerror": "Что-то пошло не так при удалении пользователя.", + "components.UserList.usercreatedfailed": "Что-то пошло не так при создании пользователя.", + "components.UserList.passwordinfodescription": "Настройте URL-адрес приложения и включите уведомления по электронной почте, чтобы обеспечить возможность автоматической генерации пароля.", + "components.UserList.importfromplexerror": "Что-то пошло не так при импорте пользователей из Plex.", + "components.UserList.importfromplex": "Импортировать пользователей из Plex", + "components.UserList.importedfromplex": "{userCount, plural, one {# новый пользователь} other {# новых пользователя(ей)}} успешно импортированы из Plex!", + "components.UserList.edituser": "Изменить разрешения пользователя", + "components.UserList.displayName": "Отображаемое имя", + "components.UserList.deleteconfirm": "Вы уверены, что хотите удалить этого пользователя? Все данные о его запросах будут удалены без возможности восстановления.", + "components.UserList.autogeneratepassword": "Сгенерировать пароль автоматически", + "components.UserList.accounttype": "Тип", + "components.TvDetails.playonplex": "Воспроизвести в Plex", + "components.TvDetails.play4konplex": "Воспроизвести в Plex в 4К", + "components.TvDetails.opensonarr4k": "Открыть сериал в 4К Sonarr", + "components.TvDetails.opensonarr": "Открыть сериал в Sonarr", + "components.TvDetails.nextAirDate": "Следующая дата выхода в эфир", + "components.TvDetails.mark4kavailable": "Пометить как доступный в 4К", + "components.TvDetails.firstAirDate": "Дата первого эфира", + "components.TvDetails.episodeRuntimeMinutes": "{runtime} минут", + "components.TvDetails.episodeRuntime": "Продолжительность эпизода", + "components.Setup.tip": "Подсказка", + "components.Setup.scanbackground": "Сканирование будет выполняться в фоновом режиме. А пока вы можете продолжить процесс настройки.", + "components.Settings.webpush": "Веб-push", + "components.Settings.webAppUrlTip": "При необходимости направляйте пользователей в веб-приложение на вашем сервере вместо размещённого на plex.tv", + "components.Settings.webAppUrl": "URL веб-приложения", + "components.Settings.trustProxy": "Включить поддержку прокси", + "components.Settings.toastPlexRefreshSuccess": "Список серверов Plex успешно получен!", + "components.Settings.toastPlexRefresh": "Получение списка серверов Plex…", + "components.Settings.toastPlexRefreshFailure": "Не удалось получить список серверов Plex.", + "components.Settings.toastPlexConnecting": "Попытка подключения к Plex…", + "components.Settings.settingUpPlexDescription": "Чтобы настроить Plex, вы можете либо ввести данные вручную, либо выбрать сервер, полученный со страницы plex.tv. Нажмите кнопку справа от выпадающего списка, чтобы получить список доступных серверов.", + "components.Settings.services": "Службы", + "components.Settings.serviceSettingsDescription": "Настройте сервер(ы) {serverType} ниже. Вы можете подключить несколько серверов {serverType}, но только два из них могут быть помечены как серверы по умолчанию (один не 4К и один 4К). Администраторы могут переопределить сервер для обработки новых запросов до их одобрения.", + "components.Settings.serverpresetLoad": "Нажмите кнопку, чтобы загрузить список доступных серверов", + "components.Settings.serverSecure": "защищённый", + "components.Settings.serverRemote": "удалённый", + "components.Settings.serverLocal": "локальный", + "components.Settings.scan": "Синхронизировать библиотеки", + "components.Settings.regionTip": "Контент фильтруется по доступности в выбранном регионе", + "components.Settings.SettingsLogs.logsDescription": "Вы также можете просматривать эти логи напрямую через stdout или в {configDir}/logs/overseerr.log.", + "components.Settings.SettingsLogs.logs": "Логи", + "components.Settings.SettingsLogs.logDetails": "Подробные сведения о логе", + "components.Settings.SettingsLogs.copiedLogMessage": "Сообщение лога скопировано в буфер обмена.", + "components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "Контент фильтруется по языку оригинала", + "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "По умолчанию ({language})", + "components.UserProfile.UserSettings.UserGeneralSettings.general": "Общее", + "components.Settings.general": "Общее", + "components.Settings.hideAvailable": "Скрывать доступные медиа", + "components.Settings.SettingsUsers.userSettingsDescription": "Настройте глобальные параметры и параметры по умолчанию для пользователей.", + "components.Settings.email": "Адрес электронной почты", + "components.Settings.csrfProtectionHoverTip": "НЕ включайте этот параметр, если вы не понимаете, что делаете!", + "components.Settings.csrfProtection": "Включить защиту от CSRF", + "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URL-адрес не должен заканчиваться косой чертой", + "components.Settings.toastPlexConnectingSuccess": "Соединение с Plex установлено успешно!", + "components.Settings.SonarrModal.toastSonarrTestSuccess": "Соединение с Sonarr установлено успешно!", + "components.Settings.toastPlexConnectingFailure": "Не удалось подключиться к Plex.", + "components.Settings.SonarrModal.toastSonarrTestFailure": "Не удалось подключиться к Sonarr.", + "components.Settings.SonarrModal.testFirstTags": "Протестировать подключение для загрузки тегов", + "components.Settings.SonarrModal.testFirstQualityProfiles": "Протестировать подключение для загрузки профилей качества", + "components.Settings.SonarrModal.selecttags": "Выберите теги", + "components.Settings.SonarrModal.selectLanguageProfile": "Выберите языковой профиль", + "components.Settings.SonarrModal.notagoptions": "Тегов нет.", + "components.Settings.SonarrModal.loadingprofiles": "Загрузка профилей качества…", + "components.Settings.SonarrModal.loadinglanguageprofiles": "Загрузка языковых профилей…", + "components.Settings.SonarrModal.create4ksonarr": "Добавить новый 4К сервер Sonarr", + "components.Settings.RadarrModal.edit4kradarr": "Редактировать 4К сервер Radarr", + "components.Settings.RadarrModal.create4kradarr": "Добавить новый 4К сервер Radarr", + "components.Settings.SonarrModal.default4kserver": "4К сервер по умолчанию", + "components.Settings.toastApiKeySuccess": "Новый ключ API успешно сгенерирован!" } diff --git a/src/i18n/locale/sv.json b/src/i18n/locale/sv.json index 63fe89bc1..d60d8ff3c 100644 --- a/src/i18n/locale/sv.json +++ b/src/i18n/locale/sv.json @@ -879,5 +879,7 @@ "components.Settings.SettingsAbout.betawarning": "Detta är en BETA-programvara. Funktioner kan vara trasiga och/eller instabila. Rapportera eventuella problem på GitHub!", "components.Layout.LanguagePicker.displaylanguage": "Visningsspråk", "components.MovieDetails.showmore": "Visa mer", - "components.MovieDetails.showless": "Visa mindre" + "components.MovieDetails.showless": "Visa mindre", + "components.TvDetails.streamingproviders": "Strömmas för närvarande på", + "components.MovieDetails.streamingproviders": "Strömmas för närvarande på" } diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json index aba217730..1f61734cb 100644 --- a/src/i18n/locale/zh_Hant.json +++ b/src/i18n/locale/zh_Hant.json @@ -879,5 +879,7 @@ "components.Settings.SettingsAbout.betawarning": "這是測試版軟體,所以可能會不穩定或被破壞。請向 GitHub 報告問題!", "components.Layout.LanguagePicker.displaylanguage": "顯示語言", "components.MovieDetails.showmore": "顯示更多", - "components.MovieDetails.showless": "顯示更少" + "components.MovieDetails.showless": "顯示更少", + "components.TvDetails.streamingproviders": "目前流式傳輸於", + "components.MovieDetails.streamingproviders": "目前流式傳輸於" } From 82d5d18ee7b985e5977205294c7fd5efbdaa3d61 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 15:23:20 +0000 Subject: [PATCH 054/238] docs: add Simoneu01 as a contributor for translation (#2175) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index f4c90938b..8b66f9b3e 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -566,6 +566,15 @@ "contributions": [ "translation" ] + }, + { + "login": "Simoneu01", + "name": "Simone", + "avatar_url": "https://avatars.githubusercontent.com/u/43807696?v=4", + "profile": "http://ungaro.me", + "contributions": [ + "translation" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index 8a15af81c..03edf1167 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -152,6 +152,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
sootylunatic

🌍
JoKerIsCraZy

🌍
Daddie0

🌍 +
Simone

🌍 From 8d8db6cf5d98d4e498a31db339d02f8a98057c8d Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Fri, 8 Oct 2021 11:47:30 -0400 Subject: [PATCH 055/238] feat(lang): add Czech and Danish display languages (#2176) --- src/context/LanguageContext.tsx | 10 ++++++++++ src/pages/_app.tsx | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/context/LanguageContext.tsx b/src/context/LanguageContext.tsx index 28e30007e..2114274c7 100644 --- a/src/context/LanguageContext.tsx +++ b/src/context/LanguageContext.tsx @@ -2,6 +2,8 @@ import React, { ReactNode } from 'react'; export type AvailableLocale = | 'ca' + | 'cs' + | 'da' | 'de' | 'en' | 'el' @@ -30,6 +32,14 @@ export const availableLanguages: AvailableLanguageObject = { code: 'ca', display: 'Català', }, + cs: { + code: 'cs', + display: 'Čeština', + }, + da: { + code: 'da', + display: 'Dansk', + }, de: { code: 'de', display: 'Deutsch', diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 4de73712b..ab881702b 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -25,6 +25,10 @@ const loadLocaleData = (locale: AvailableLocale): Promise => { switch (locale) { case 'ca': return import('../i18n/locale/ca.json'); + case 'cs': + return import('../i18n/locale/cs.json'); + case 'da': + return import('../i18n/locale/da.json'); case 'de': return import('../i18n/locale/de.json'); case 'el': From 1286d6f0f9793446a3ef3bb329ff0af189e0db67 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 8 Oct 2021 15:56:48 +0000 Subject: [PATCH 056/238] docs: add adan89lion as a contributor for translation (#2177) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 8b66f9b3e..ebb42b6c6 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -575,6 +575,15 @@ "contributions": [ "translation" ] + }, + { + "login": "adan89lion", + "name": "Seohyun Joo", + "avatar_url": "https://avatars.githubusercontent.com/u/6585644?v=4", + "profile": "https://github.com/adan89lion", + "contributions": [ + "translation" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index 03edf1167..3036009c5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -153,6 +153,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
JoKerIsCraZy

🌍
Daddie0

🌍
Simone

🌍 +
Seohyun Joo

🌍 From daf64532bf9db409a2fa61a9a7ea7cc538bd64f1 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sat, 9 Oct 2021 18:44:07 -0400 Subject: [PATCH 057/238] docs: add FAQ entry about broken logos in email notifications (#2134) [skip ci] --- docs/support/faq.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/support/faq.md b/docs/support/faq.md index 2add67a41..19d71e931 100644 --- a/docs/support/faq.md +++ b/docs/support/faq.md @@ -119,3 +119,9 @@ Language profile support for Sonarr was added in [v1.20.0](https://github.com/sc ### I am getting "Username and Password not accepted" when attempting to send email notifications via Gmail! If you have 2-Step Verification enabled on your account, you will need to create an [app password](https://support.google.com/mail/answer/185833). + +### The logo image in email notifications is broken! + +This may be an issue with how you are proxying your Overseerr instance. A good first troubleshooting step is to verify that the [`Content-Security-Policy` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) being set by your proxy (if any) is configured appropriately to allow external embedding of the image. + +For Gmail users, another possible issue is that Google's image URL proxy is being blocked from fetching the image. If using Cloudflare, overzealous firewall rules could be the culprit. From 2957e156a51f3bfe163793fe05323221c9a8fa10 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 9 Oct 2021 22:51:41 +0000 Subject: [PATCH 058/238] docs: add ty4ko as a contributor for translation (#2178) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index ebb42b6c6..fcc757ad5 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -584,6 +584,15 @@ "contributions": [ "translation" ] + }, + { + "login": "ty4ko", + "name": "Sergey", + "avatar_url": "https://avatars.githubusercontent.com/u/21213535?v=4", + "profile": "https://github.com/ty4ko", + "contributions": [ + "translation" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index 3036009c5..5da4349f2 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -155,6 +155,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Simone

🌍
Seohyun Joo

🌍 + +
Sergey

🌍 + From e3312cef33821c8cb76a4a63bd565c78d67b3e0b Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sun, 10 Oct 2021 01:04:18 +0200 Subject: [PATCH 059/238] feat(lang): translations update from Weblate (#2179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Kobe Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (883 of 883 strings) feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (883 of 883 strings) feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (883 of 883 strings) feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Co-authored-by: 주서현 Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/ Translation: Overseerr/Overseerr Frontend Co-authored-by: Kobe Co-authored-by: TheCatLady Co-authored-by: 주서현 --- src/i18n/locale/nl.json | 2 +- src/i18n/locale/zh_Hant.json | 218 +++++++++++++++++------------------ 2 files changed, 110 insertions(+), 110 deletions(-) diff --git a/src/i18n/locale/nl.json b/src/i18n/locale/nl.json index 41ea6fd76..9b5491145 100644 --- a/src/i18n/locale/nl.json +++ b/src/i18n/locale/nl.json @@ -25,7 +25,7 @@ "components.MovieDetails.overview": "Overzicht", "components.MovieDetails.overviewunavailable": "Overzicht niet beschikbaar.", "components.MovieDetails.recommendations": "Aanbevelingen", - "components.MovieDetails.releasedate": "Releasedatum", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Releasedatum} other {Releasedata}}", "components.MovieDetails.revenue": "Omzet", "components.MovieDetails.runtime": "{minutes} minuten", "components.MovieDetails.similar": "Vergelijkbare titels", diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json index 1f61734cb..048554c6b 100644 --- a/src/i18n/locale/zh_Hant.json +++ b/src/i18n/locale/zh_Hant.json @@ -2,7 +2,7 @@ "components.Settings.Notifications.webhookUrl": "Webhook 網址", "components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook 網址", "components.Settings.Notifications.NotificationsSlack.webhookUrl": "Webhook 網址", - "components.Settings.applicationurl": "應用程式網址(URL)", + "components.Settings.applicationurl": "應用程式網址", "components.Settings.SonarrModal.apiKey": "應用程式密鑰", "components.Settings.apikey": "應用程式密鑰", "components.Settings.RadarrModal.apiKey": "應用程式密鑰", @@ -33,7 +33,7 @@ "i18n.failed": "失敗", "i18n.close": "關閉", "i18n.cancel": "取消", - "i18n.request": "提交請求", + "i18n.request": "提出請求", "i18n.requested": "已經有請求", "i18n.retry": "重試", "pages.returnHome": "返回首頁", @@ -93,12 +93,12 @@ "components.RequestList.showallrequests": "查看所有請求", "components.RequestList.requests": "請求", "components.RequestList.RequestItem.seasons": "季數", - "components.RequestList.RequestItem.failedretry": "重試提交請求中出了點問題。", + "components.RequestList.RequestItem.failedretry": "重試提出請求中出了點問題。", "components.RequestCard.seasons": "季數", "components.RequestButton.viewrequest4k": "查看 4K 請求", "components.RequestButton.viewrequest": "查看請求", - "components.RequestButton.requestmore4k": "再提交 4K 請求", - "components.RequestButton.requestmore": "提交更多季數的請求", + "components.RequestButton.requestmore4k": "再提出 4K 請求", + "components.RequestButton.requestmore": "提出更多季數的請求", "components.RequestButton.declinerequests": "拒絕{requestCount, plural, one {請求} other {{requestCount} 個請求}}", "components.RequestButton.declinerequest4k": "拒絕 4K 請求", "components.RequestButton.declinerequest": "拒絕請求", @@ -109,7 +109,7 @@ "components.RequestButton.approve4krequests": "批准{requestCount, plural, one { 4K 請求} other { {requestCount} 個 4K 請求}}", "components.RequestBlock.seasons": "季數", "components.PersonDetails.crewmember": "製作群成員", - "components.NotificationTypeSelector.mediarequested": "請求提交", + "components.NotificationTypeSelector.mediarequested": "請求提出", "components.NotificationTypeSelector.mediafailed": "請求失敗", "components.NotificationTypeSelector.mediaapproved": "請求批准", "components.NotificationTypeSelector.mediaavailableDescription": "當請求的媒體可觀看時發送通知。", @@ -146,15 +146,15 @@ "components.Discover.upcomingmovies": "即將上映的電影", "components.Discover.upcoming": "即將上映的電影", "components.Discover.trending": "趨勢", - "components.Discover.recentlyAdded": "最新添加", + "components.Discover.recentlyAdded": "最新新增", "components.Discover.recentrequests": "最新請求", "components.Discover.populartv": "熱門電視節目", "components.Discover.popularmovies": "熱門電影", "components.Discover.discovertv": "熱門電視節目", "components.Discover.discovermovies": "熱門電影", - "components.CollectionDetails.requestswillbecreated": "為以下的電影提交請求:", - "components.CollectionDetails.requestcollection": "提交系列請求", - "components.CollectionDetails.requestSuccess": "為 {title} 提交請求成功!", + "components.CollectionDetails.requestswillbecreated": "為以下的電影提出請求:", + "components.CollectionDetails.requestcollection": "提出系列請求", + "components.CollectionDetails.requestSuccess": "為 {title} 提出請求成功!", "components.CollectionDetails.overview": "概要", "components.UserList.userdeleteerror": "刪除使用者中出了點問題。", "components.UserList.userdeleted": "使用者刪除成功!", @@ -178,7 +178,7 @@ "components.Settings.notrunning": "未運行", "components.Settings.activeProfile": "現行品質設定", "components.Settings.notificationsettings": "通知設定", - "components.Settings.default4k": "設定 4K 為默認分辨率", + "components.Settings.default4k": "設定 4K 為預設分辨率", "components.Settings.currentlibrary": "當前媒體庫: {name}", "components.Settings.SonarrModal.seasonfolders": "季數檔案夾", "components.Settings.SettingsAbout.overseerrinformation": "關於 Overseerr", @@ -198,10 +198,10 @@ "components.Settings.SonarrModal.testFirstQualityProfiles": "請先測試連線", "components.Settings.RadarrModal.testFirstRootFolders": "請先測試連線", "components.Settings.RadarrModal.testFirstQualityProfiles": "請先測試連線", - "components.Settings.SonarrModal.defaultserver": "默認伺服器", + "components.Settings.SonarrModal.defaultserver": "預設伺服器", "components.Settings.deleteserverconfirm": "確定要刪除這個伺服器嗎?", - "components.Settings.addradarr": "添加 Radarr 伺服器", - "components.Settings.addsonarr": "添加 Sonarr 伺服器", + "components.Settings.addradarr": "新增 Radarr 伺服器", + "components.Settings.addsonarr": "新增 Sonarr 伺服器", "components.Settings.SonarrModal.server4k": "4K 伺服器", "components.Settings.SettingsAbout.supportoverseerr": "支持 Overseerr", "components.Settings.SonarrModal.validationProfileRequired": "必須設定品質", @@ -228,18 +228,18 @@ "components.Settings.SonarrModal.animerootfolder": "動漫根目錄", "components.Settings.SonarrModal.rootfolder": "根目錄", "components.Settings.RadarrModal.rootfolder": "根目錄", - "components.Settings.Notifications.NotificationsWebhook.resetPayload": "重置為默認", + "components.Settings.Notifications.NotificationsWebhook.resetPayload": "重設為預設", "components.Settings.Notifications.NotificationsWebhook.customJson": "JSON 有效負載", - "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON 有效負載重設為默認負載成功!", - "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "請輸入有效的使用者或群組金鑰", + "components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON 有效負載重設為預設負載成功!", + "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "請輸入有效的使用者或群組令牌", "components.Settings.menuJobs": "作業和快取", "components.Settings.toastApiKeyFailure": "生成應用程式密鑰出了點問題。", "components.Settings.toastSettingsFailure": "保存設定中出了點問題。", "components.UserList.deleteconfirm": "確定要刪除這個使用者嗎?此使用者的所有儲存資料將被清除。", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "無法獲取軟體版本資料。GitHub 崩潰了嗎?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "無法獲取軟體版本資料。", "components.UserList.passwordinfodescription": "設定應用程式網址以及啟用電子郵件通知,才能自動生成密碼。", - "components.Settings.Notifications.validationBotAPIRequired": "請輸入機器人授權金鑰", - "components.Settings.Notifications.botAPI": "Bot 機器人授權金鑰", + "components.Settings.Notifications.validationBotAPIRequired": "請輸入機器人授權令牌", + "components.Settings.Notifications.botAPI": "Bot 機器人授權令牌", "components.Settings.menuServices": "伺服器", "components.Settings.address": "網址", "components.Settings.ssl": "SSL", @@ -248,15 +248,15 @@ "components.Settings.port": "通訊埠", "components.Settings.SonarrModal.port": "通訊埠", "components.Settings.RadarrModal.port": "通訊埠", - "components.Settings.Notifications.NotificationsPushover.userToken": "使用者或群組金鑰", - "components.Settings.Notifications.NotificationsPushover.accessToken": "應用程式 API 金鑰", + "components.Settings.Notifications.NotificationsPushover.userToken": "使用者或群組令牌", + "components.Settings.Notifications.NotificationsPushover.accessToken": "應用程式 API 令牌", "components.Settings.menuNotifications": "通知", "components.Settings.menuLogs": "日誌", "components.Settings.menuAbout": "關於 Overseerr", - "components.Settings.default": "默認", + "components.Settings.default": "預設", "components.Settings.SettingsAbout.version": "軟體版本", - "components.Settings.SettingsAbout.Releases.latestversion": "最新軟體版本", - "components.Settings.SettingsAbout.Releases.currentversion": "目前的軟體版本", + "components.Settings.SettingsAbout.Releases.latestversion": "最新版本", + "components.Settings.SettingsAbout.Releases.currentversion": "目前的版本", "components.Settings.SettingsAbout.timezone": "時區", "components.Settings.SettingsAbout.documentation": "文檔", "components.RequestModal.pending4krequest": "{title} 的 4K 請求", @@ -266,12 +266,12 @@ "components.Settings.SettingsAbout.Releases.releases": "軟體版本", "components.Settings.plexsettings": "Plex 設定", "components.RequestModal.selectseason": "季數選擇", - "components.RequestModal.requesttitle": "為 {title} 提交請求", - "components.RequestModal.requestseasons": "提交請求", + "components.RequestModal.requesttitle": "為 {title} 提出請求", + "components.RequestModal.requestseasons": "提出請求", "components.RequestModal.requestadmin": "此請求將自動被批准。", - "components.RequestModal.requestSuccess": "為 {title} 提交請求成功!", + "components.RequestModal.requestSuccess": "為 {title} 提出請求成功!", "components.RequestModal.requestCancel": "{title} 的請求已被取消。", - "components.RequestModal.request4ktitle": "為 {title} 提交 4K 請求", + "components.RequestModal.request4ktitle": "為 {title} 提出 4K 請求", "components.PersonDetails.appearsin": "演出", "components.PersonDetails.ascharacter": "飾演 {character}", "components.TvDetails.overviewunavailable": "沒有概要。", @@ -296,7 +296,7 @@ "components.Settings.Notifications.NotificationsWebhook.authheader": "Authorization 頭欄位", "components.Settings.RadarrModal.minimumAvailability": "最低狀態", "components.Settings.Notifications.allowselfsigned": "允許自簽名證書", - "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "請輸入應用程式 API 金鑰", + "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "請輸入應用程式 API 令牌", "components.Settings.RadarrModal.hostname": "主機名稱或 IP 位址", "components.Settings.SonarrModal.hostname": "主機名稱或 IP 位址", "components.Settings.hostname": "主機名稱或 IP 位址", @@ -310,21 +310,21 @@ "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover 通知設定保存成功!", "components.UserList.created": "建立日期", "components.UserList.create": "建立", - "components.Settings.SonarrModal.createsonarr": "添加 Sonarr 伺服器", - "components.Settings.RadarrModal.createradarr": "添加 Radarr 伺服器", + "components.Settings.SonarrModal.createsonarr": "新增 Sonarr 伺服器", + "components.Settings.RadarrModal.createradarr": "新增 Radarr 伺服器", "components.Settings.SonarrModal.servername": "伺服器名稱", "components.Settings.SonarrModal.editsonarr": "編輯 Sonarr 伺服器", - "components.Settings.SonarrModal.add": "添加伺服器", + "components.Settings.SonarrModal.add": "新增伺服器", "components.Settings.RadarrModal.servername": "伺服器名稱", "components.Settings.RadarrModal.editradarr": "編輯 Radarr 伺服器", - "components.Settings.RadarrModal.defaultserver": "默認伺服器", - "components.Settings.RadarrModal.add": "添加伺服器", + "components.Settings.RadarrModal.defaultserver": "預設伺服器", + "components.Settings.RadarrModal.add": "新增伺服器", "components.StatusChacker.newversionDescription": "Overseerr 軟體已更新。請點擊以下的按鈕刷新頁面。", "components.RequestModal.requestcancelled": "{title} 的請求已被取消。", "components.RequestModal.AdvancedRequester.qualityprofile": "品質設定", "components.RequestModal.AdvancedRequester.animenote": "*這是個動漫節目。", "components.RequestModal.AdvancedRequester.advancedoptions": "進階選項", - "components.RequestModal.AdvancedRequester.default": "{name}(默認)", + "components.RequestModal.AdvancedRequester.default": "{name}(預設)", "components.RequestModal.AdvancedRequester.destinationserver": "目標伺服器", "components.RequestBlock.server": "目標伺服器", "components.RequestModal.AdvancedRequester.rootfolder": "根目錄", @@ -348,7 +348,7 @@ "components.UserList.userssaved": "使用者權限保存成功!", "components.Settings.hideAvailable": "隱藏可觀看的電影和電視節目", "components.Settings.SonarrModal.externalUrl": "外部網址", - "components.Settings.RadarrModal.externalUrl": "外部網址(URL)", + "components.Settings.RadarrModal.externalUrl": "外部網址", "components.Settings.csrfProtection": "防止跨站請求偽造(CSRF)攻擊", "components.RequestBlock.requestoverrides": "覆寫請求", "components.Settings.toastPlexConnectingSuccess": "Plex 伺服器連線成功!", @@ -375,10 +375,10 @@ "components.PlexLoginButton.signingin": "登入中…", "components.PermissionEdit.users": "管理使用者", "components.PermissionEdit.settings": "設定管理", - "components.PermissionEdit.request4kTv": "提交 4K 電視節目請求", - "components.PermissionEdit.request4kMovies": "提交 4K 電影請求", - "components.PermissionEdit.request4k": "提交 4K 請求", - "components.PermissionEdit.request": "提交請求", + "components.PermissionEdit.request4kTv": "提出 4K 電視節目請求", + "components.PermissionEdit.request4kMovies": "提出 4K 電影請求", + "components.PermissionEdit.request4k": "提出 4K 請求", + "components.PermissionEdit.request": "提出請求", "components.PermissionEdit.managerequests": "請求管理", "components.MovieDetails.downloadstatus": "下載狀態", "components.RequestBlock.profilechanged": "品質設定", @@ -386,7 +386,7 @@ "components.RequestModal.requestedited": "{title} 的請求編輯成功!", "components.Settings.trustProxy": "啟用代理伺服器所需功能", "components.RequestModal.errorediting": "編輯請求中出了點問題。", - "components.RequestModal.requesterror": "提交請求中出了點問題。", + "components.RequestModal.requesterror": "提出請求中出了點問題。", "components.Settings.SettingsJobsCache.cachekeys": "鍵數", "components.Settings.SettingsJobsCache.cachevsize": "值儲存大小", "components.Settings.SettingsJobsCache.cacheksize": "鍵儲存大小", @@ -416,8 +416,8 @@ "components.Settings.toastPlexConnectingFailure": "Plex 伺服器連線失敗。", "components.TvDetails.mark4kavailable": "標記 4K 版為可觀看", "components.TvDetails.markavailable": "標記為可觀看", - "components.TvDetails.manageModalClearMediaWarning": "*這電視節目的所有儲存資料將被永久刪除(包括使用者提交的請求)。如果節目存在於您的 Plex 伺服器,資料會在媒體庫掃描時重新建立。", - "components.MovieDetails.manageModalClearMediaWarning": "*這將會刪除包括使用者請求在內所有有關這部電影的資料。如果電影存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", + "components.TvDetails.manageModalClearMediaWarning": "*這將會刪除包括使用者請求在內所有有關這個電視節目的資料。如果這個節目存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", + "components.MovieDetails.manageModalClearMediaWarning": "*這將會刪除包括使用者請求在內所有有關這部電影的資料。如果這部電影存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", "components.TvDetails.allseasonsmarkedavailable": "*每季將被標記為可觀看。", "components.Settings.csrfProtectionHoverTip": "除非您了解此功能,請勿啟用它!", "components.UserList.users": "使用者", @@ -425,7 +425,7 @@ "components.Search.search": "搜尋", "components.Setup.setup": "配置", "components.Discover.discover": "探索", - "components.AppDataWarning.dockerVolumeMissingDescription": "必須使用繫結掛載(bind mount)指定某個宿主機器的資料夾跟容器內的 {appDataPath} 資料夾連通,才能保存 Overseerr 的配置和數據。", + "components.AppDataWarning.dockerVolumeMissingDescription": "您必須使用繫結掛載(bind mount)來繫結主機上的目錄跟 Docker 容器內的 {appDataPath} 目錄,才能保存 Overseerr 的配置和數據。", "components.RequestModal.AdvancedRequester.requestas": "請求者", "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "必須刪除結尾斜線", "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "必須刪除結尾斜線", @@ -435,8 +435,8 @@ "components.Settings.SonarrModal.validationApplicationUrl": "請輸入有效的網址", "components.Settings.RadarrModal.validationApplicationUrl": "請輸入有效的網址", "components.PermissionEdit.viewrequests": "查看請求", - "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "必須添加前置斜線", - "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "必須添加前置斜線", + "components.Settings.RadarrModal.validationBaseUrlLeadingSlash": "必須加前置斜線", + "components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "必須加前置斜線", "components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "必須刪除結尾斜線", "components.Settings.RadarrModal.validationBaseUrlTrailingSlash": "必須刪除結尾斜線", "components.UserList.validationEmail": "請輸入有效的電子郵件地址", @@ -455,13 +455,13 @@ "components.ResetPassword.password": "密碼", "components.Login.forgotpassword": "忘記密碼?", "components.TvDetails.nextAirDate": "下一次播出日期", - "components.NotificationTypeSelector.mediarequestedDescription": "當使用者提交需要管理員批准的請求時發送通知。", + "components.NotificationTypeSelector.mediarequestedDescription": "當使用者提出需要管理員批准的請求時發送通知。", "components.NotificationTypeSelector.mediafailedDescription": "當 Radarr 或 Sonarr 處理請求失敗時發送通知。", "components.NotificationTypeSelector.mediadeclinedDescription": "當請求拒被絕時發送通知。", - "components.PermissionEdit.request4kDescription": "授予提交 4K 請求的權限。", - "components.PermissionEdit.request4kMoviesDescription": "授予為電影提交 4K 請求的權限。", - "components.PermissionEdit.request4kTvDescription": "授予提交 4K 電視節目請求的權限。", - "components.PermissionEdit.requestDescription": "授予提交非 4K 請求的權限。", + "components.PermissionEdit.request4kDescription": "授予提出 4K 請求的權限。", + "components.PermissionEdit.request4kMoviesDescription": "授予為電影提出 4K 請求的權限。", + "components.PermissionEdit.request4kTvDescription": "授予提出 4K 電視節目請求的權限。", + "components.PermissionEdit.requestDescription": "授予提出非 4K 請求的權限。", "components.PermissionEdit.viewrequestsDescription": "授予查看其他使用者的請求的權限。", "components.Settings.SonarrModal.validationLanguageProfileRequired": "必須設定語言", "components.Settings.SonarrModal.testFirstLanguageProfiles": "請先測試連線", @@ -508,7 +508,7 @@ "components.UserList.edituser": "編輯使用者權限", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet 通知設定保存失敗。", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet 通知設定保存成功!", - "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "請輸入 API 金鑰", + "components.Settings.Notifications.NotificationsPushbullet.validationAccessTokenRequired": "請輸入 API 令牌", "components.UserProfile.UserSettings.UserNotificationSettings.discordId": "使用者 ID", "components.UserProfile.ProfileHeader.profile": "顯示個人資料", "components.UserProfile.ProfileHeader.settings": "使用者設定", @@ -520,7 +520,7 @@ "components.UserProfile.UserSettings.UserPermissions.toastSettingsFailure": "保存設定中出了點問題。", "components.UserProfile.UserSettings.UserGeneralSettings.toastSettingsFailure": "保存設定中出了點問題。", "components.Settings.Notifications.NotificationsPushbullet.agentEnabled": "啟用通知", - "components.Settings.Notifications.NotificationsPushbullet.accessToken": "API 金鑰", + "components.Settings.Notifications.NotificationsPushbullet.accessToken": "API 令牌", "components.Layout.UserDropdown.settings": "設定", "components.Layout.UserDropdown.myprofile": "個人資料", "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "密碼必須匹配", @@ -531,11 +531,11 @@ "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsSuccess": "密碼設定成功!", "components.RequestModal.SearchByNameModal.nosummary": "沒有概要。", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "請輸入有效的使用者 ID", - "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "您的使用者 ID", + "components.UserProfile.UserSettings.UserNotificationSettings.discordIdTip": "您的使用者 ID 號碼", "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "重設密碼中出了點問題。", "components.RequestModal.SearchByNameModal.notvdbiddescription": "無法自動配對您的請求。請從以下列表中選擇正確的媒體項。", - "components.CollectionDetails.requestswillbecreated4k": "為以下的電影提交 4K 請求:", - "components.CollectionDetails.requestcollection4k": "提交 4K 系列請求", + "components.CollectionDetails.requestswillbecreated4k": "為以下的電影提出 4K 請求:", + "components.CollectionDetails.requestcollection4k": "提出 4K 系列請求", "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "develop 分支的變更日誌不會顯示在以下。請直接到 GitHub 查看變更日誌。", "components.Settings.trustProxyTip": "使用代理伺服器時,允許 Overseerr 探明客戶端 IP 位址(Overseerr 必須重新啟動)", "components.Settings.csrfProtectionTip": "設定外部訪問權限為只讀(Overseerr 必須重新啟動)", @@ -557,7 +557,7 @@ "components.Settings.originallanguageTip": "以原始語言篩選結果", "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr 將定時運行以下的維護任務。手動執行工作不會影響它正常的時間表。", "components.Settings.plexsettingsDescription": "關於 Plex 伺服器的設定。Overseerr 將定時執行媒體庫掃描。", - "components.Settings.manualscanDescription": "在正常情況下,Overseerr 會每24小時掃描您的 Plex 媒體庫。最新添加的媒體將更頻繁掃描。設定新的 Plex 伺服器時,我們建議您執行一次手動掃描!", + "components.Settings.manualscanDescription": "在正常情況下,Overseerr 會每24小時掃描您的 Plex 媒體庫。最新新增的媒體將更頻繁掃描。設定新的 Plex 伺服器時,我們建議您執行一次手動掃描!", "components.RegionSelector.regionServerDefault": "預設設定({region})", "components.Settings.settingUpPlexDescription": "您可以手動輸入您的 Plex 伺服器資料,或從 plex.tv 返回的設定做選擇以及自動配置。請點下拉式選單右邊的按鈕獲取伺服器列表。", "components.Settings.plexlibrariesDescription": "Overseerr 將掃描的媒體庫。", @@ -587,7 +587,7 @@ "components.Discover.NetworkSlider.networks": "電視網", "components.Discover.StudioSlider.studios": "製作公司", "components.Settings.Notifications.validationUrl": "請輸入有效的網址", - "components.Settings.Notifications.botAvatarUrl": "Bot 機器人頭像網址(URL)", + "components.Settings.Notifications.botAvatarUrl": "Bot 機器人頭像網址", "components.RequestList.RequestItem.modified": "最後修改者", "components.RequestList.RequestItem.modifieduserdate": "{user}({date})", "components.RequestList.RequestItem.requested": "請求者", @@ -596,20 +596,20 @@ "components.Settings.scan": "媒體庫同步", "components.Settings.SettingsJobsCache.sonarr-scan": "Sonarr 掃描", "components.Settings.SettingsJobsCache.radarr-scan": "Radarr 掃描", - "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex 最新添加掃描", + "components.Settings.SettingsJobsCache.plex-recently-added-scan": "Plex 最新新增掃描", "components.Settings.SettingsJobsCache.plex-full-scan": "Plex 媒體庫掃描", "components.Discover.DiscoverTvLanguage.languageSeries": "{language}電視節目", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language}電影", "components.UserProfile.ProfileHeader.userid": "使用者 ID:{userid}", "components.UserProfile.ProfileHeader.joindate": "建立日期:{joindate}", "components.Settings.SettingsUsers.localLogin": "允許本地登入", - "components.Settings.SettingsUsers.defaultPermissions": "默認權限", + "components.Settings.SettingsUsers.defaultPermissions": "預設權限", "components.Settings.SettingsUsers.userSettingsDescription": "關於使用者的全局和預設設定。", "components.Settings.SettingsUsers.userSettings": "使用者設定", "components.Settings.menuUsers": "使用者", "components.Settings.SettingsUsers.toastSettingsSuccess": "使用者設定保存成功!", "components.Settings.SettingsUsers.toastSettingsFailure": "保存設定中出了點問題。", - "components.NotificationTypeSelector.mediaAutoApprovedDescription": "當使用者提交自動批准的請求時發送通知。", + "components.NotificationTypeSelector.mediaAutoApprovedDescription": "當使用者提出自動批准的請求時發送通知。", "components.NotificationTypeSelector.mediaAutoApproved": "請求自動批准", "components.UserProfile.UserSettings.UserPermissions.unauthorizedDescription": "您不能編輯自己的權限。", "components.UserProfile.UserSettings.unauthorizedDescription": "您無權編輯此使用者的設定。", @@ -626,7 +626,7 @@ "components.Discover.MovieGenreList.moviegenres": "電影類型", "components.Discover.TvGenreList.seriesgenres": "電視節目類型", "components.Settings.partialRequestsEnabled": "允許不完整的電視節目請求", - "components.RequestModal.requestall": "提交請求", + "components.RequestModal.requestall": "提出請求", "components.RequestModal.alreadyrequested": "已經有請求", "components.Settings.SettingsLogs.time": "時間戳", "components.Settings.SettingsLogs.resumeLogs": "恢復", @@ -678,8 +678,8 @@ "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {電影請求剩餘數不足} other {剩餘 # 個{type}請求}}", "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "請求剩餘數不足", "components.RequestModal.QuotaDisplay.movielimit": "部電影", - "components.RequestModal.QuotaDisplay.allowedRequestsUser": "此使用者每 {days} 天能提交 {limit} {type}個請求。", - "components.RequestModal.QuotaDisplay.allowedRequests": "您每 {days} 天能為 {limit} {type}提交請求。", + "components.RequestModal.QuotaDisplay.allowedRequestsUser": "此使用者每 {days} 天能提出 {limit} 個{type}請求。", + "components.RequestModal.QuotaDisplay.allowedRequests": "您每 {days} 天能提出 {limit} 個{type}請求。", "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "電視節目請求限制", "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "電影請求限制", "components.UserProfile.movierequests": "電影請求", @@ -701,8 +701,8 @@ "i18n.saving": "保存中…", "i18n.save": "保存", "i18n.resultsperpage": "每頁顯示 {pageSize} 列", - "i18n.requesting": "提交請求中…", - "i18n.request4k": "提交 4K 請求", + "i18n.requesting": "提出請求中…", + "i18n.request4k": "提出 4K 請求", "i18n.previous": "上一頁", "i18n.notrequested": "沒有請求", "i18n.noresults": "沒有結果。", @@ -712,8 +712,8 @@ "i18n.back": "返回", "i18n.all": "所有", "i18n.areyousure": "確定嗎?", - "components.RequestModal.QuotaDisplay.requiredquotaUser": "此使用者的電視節目請求數量必須至少剩餘 {seasons} 個季數才能為此節目提交請求。", - "components.RequestModal.QuotaDisplay.requiredquota": "您的電視節目請求數量必須至少剩餘 {seasons} 個季數才能為此節目提交請求。", + "components.RequestModal.QuotaDisplay.requiredquotaUser": "此使用者的電視節目請求數量必須至少剩餘 {seasons} 個季數才能提出此節目請求。", + "components.RequestModal.QuotaDisplay.requiredquota": "您的電視節目請求數量必須至少剩餘 {seasons} 個季數才能提出此節目請求。", "components.TvDetails.originaltitle": "原始標題", "components.MovieDetails.originaltitle": "原始標題", "components.RequestModal.QuotaDisplay.quotaLinkUser": "訪問此使用者的個人資料頁面以查看使用者的請求限制 。", @@ -728,10 +728,10 @@ "components.RequestModal.AdvancedRequester.notagoptions": "沒有標籤。", "components.Settings.SonarrModal.edit4ksonarr": "編輯 4K Sonarr 伺服器", "components.Settings.RadarrModal.edit4kradarr": "編輯 4K Radarr 伺服器", - "components.Settings.RadarrModal.create4kradarr": "添加 4K Radarr 伺服器", - "components.Settings.SonarrModal.create4ksonarr": "添加 4K Sonarr 伺服器", - "components.Settings.SonarrModal.default4kserver": "默認 4K 伺服器", - "components.Settings.RadarrModal.default4kserver": "默認 4K 伺服器", + "components.Settings.RadarrModal.create4kradarr": "新增 4K Radarr 伺服器", + "components.Settings.SonarrModal.create4ksonarr": "新增 4K Sonarr 伺服器", + "components.Settings.SonarrModal.default4kserver": "預設 4K 伺服器", + "components.Settings.RadarrModal.default4kserver": "預設 4K 伺服器", "components.Settings.SonarrModal.testFirstTags": "請先測試連線", "components.Settings.RadarrModal.testFirstTags": "請先測試連線", "components.Settings.SonarrModal.loadingTags": "載入中…", @@ -761,15 +761,15 @@ "components.RequestList.RequestItem.cancelRequest": "取消請求", "components.NotificationTypeSelector.notificationTypes": "通知類型", "components.Discover.noRequests": "沒有請求。", - "components.Layout.VersionStatus.commitsbehind": "落後 {commitsBehind} 次提交", - "components.Layout.VersionStatus.outofdate": "過時", + "components.Layout.VersionStatus.commitsbehind": "落後 {commitsBehind} 個提交", + "components.Layout.VersionStatus.outofdate": "非最新版本", "components.Layout.VersionStatus.streamstable": "Overseerr 穩定版", - "components.Layout.VersionStatus.streamdevelop": "Overseerr「develop」開發版", - "components.Settings.SettingsAbout.outofdate": "過時", + "components.Layout.VersionStatus.streamdevelop": "Overseerr 開發版", + "components.Settings.SettingsAbout.outofdate": "非最新版本", "components.Settings.SettingsAbout.uptodate": "最新", "components.Settings.noDefaultNon4kServer": "如果您只有一個 {serverType} 伺服器,請勿把它設定為 4K 伺服器。", - "components.Settings.noDefaultServer": "您必須至少指定一個 {serverType} 伺服器為默認,才能處理{mediaType}請求。", - "components.Settings.serviceSettingsDescription": "關於 {serverType} 伺服器的設定。{serverType} 伺服器數沒有最大值限制,但您只能指定兩個伺服器為默認(一個非 4K、一個 4K)。", + "components.Settings.noDefaultServer": "您必須至少指定一個預設 {serverType} 伺服器,才能處理{mediaType}請求。", + "components.Settings.serviceSettingsDescription": "關於 {serverType} 伺服器的設定。{serverType} 伺服器數沒有最大值限制,但您只能指定兩個預設伺服器(一個非 4K、一個 4K)。", "components.Settings.mediaTypeSeries": "電視節目", "components.Settings.mediaTypeMovie": "電影", "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此使用者目前沒有密碼。設定密碼以允許此使用者使用電子郵件地址登入。", @@ -782,10 +782,10 @@ "components.RequestList.RequestItem.editrequest": "編輯請求", "components.Settings.RadarrModal.enableSearch": "啟用自動搜尋", "components.Settings.SonarrModal.enableSearch": "啟用自動搜尋", - "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "網路推播", - "components.Settings.webpush": "網路推播", - "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "網路推播通知設定保存成功!", - "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "網路推播通知設定保存失敗。", + "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "網路推送", + "components.Settings.webpush": "網路推送", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingssaved": "網路推送通知設定保存成功!", + "components.Settings.Notifications.NotificationsWebPush.webpushsettingsfailed": "網路推送通知設定保存失敗。", "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "顯示語言", "components.Settings.Notifications.NotificationsWebPush.agentenabled": "啟用通知", "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea 通知設定保存成功!", @@ -794,29 +794,29 @@ "components.Settings.Notifications.NotificationsLunaSea.validationWebhookUrl": "請輸入有效的網址", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "啟用通知", "components.Settings.is4k": "4K", - "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "網路推播知設定保存失敗。", - "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "網路推播知設定保存成功!", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "網路推送通知設定保存失敗。", + "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingssaved": "網路推送通知設定保存成功!", "components.Settings.Notifications.toastEmailTestSuccess": "電子郵件測試通知已發送!", - "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "網路推播測試通知已發送!", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSuccess": "網路推送測試通知已發送!", "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram 測試通知已發送!", "components.Settings.Notifications.toastDiscordTestSuccess": "Discord 測試通知已發送!", "components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Slack 測試通知已發送!", "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover 測試通知已發送!", "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet 測試通知已發送!", "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea 測試通知已發送!", - "components.Settings.noDefault4kServer": "您必須指定一個 4K {serverType} 伺服器為默認,才能處理 4K 的{mediaType}請求。", - "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "不使用 default 默認設定檔才必須輸入", + "components.Settings.noDefault4kServer": "您必須指定一個 4K {serverType} 伺服器為預設,才能處理 4K 的{mediaType}請求。", + "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "不使用 default 預設設定檔才必須輸入", "components.Settings.Notifications.NotificationsLunaSea.profileName": "設定檔名", "components.Settings.Notifications.toastTelegramTestSending": "發送 Telegram 測試通知中…", "components.Settings.Notifications.toastEmailTestSending": "發送電子郵件測試通知中…", "components.Settings.Notifications.toastDiscordTestSending": "發送 Discord 測試通知中…", - "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "發送 Webhook 測試通知中…", - "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "發送網路推播測試通知中…", + "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "發送 webhook 測試通知中…", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestSending": "發送網路推送測試通知中…", "components.Settings.Notifications.NotificationsSlack.toastSlackTestSending": "發送 Slack 測試通知中…", "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSending": "發送 Pushover 測試通知中…", "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSending": "發送 Pushbullet 測試通知中…", "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSending": "發送 LunaSea 測試通知中…", - "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "網路推播測試通知發送失敗。", + "components.Settings.Notifications.NotificationsWebPush.toastWebPushTestFailed": "網路推送測試通知發送失敗。", "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Webhook 測試通知發送失敗。", "components.Settings.Notifications.toastEmailTestFailed": "電子郵件測試通知發送失敗。", "components.Settings.Notifications.toastTelegramTestFailed": "Telegram 測試通知發送失敗。", @@ -828,10 +828,10 @@ "components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSuccess": "Webhook 測試通知已發送!", "components.Settings.SettingsUsers.newPlexLoginTip": "讓還沒匯入的 Plex 使用者登入", "components.Settings.SettingsUsers.newPlexLogin": "允許新的 Plex 登入", - "components.PermissionEdit.requestTv": "提交電視節目請求", - "components.PermissionEdit.requestMovies": "提交電影請求", - "components.PermissionEdit.requestMoviesDescription": "授予提交非 4K 電影請求的權限。", - "components.PermissionEdit.requestTvDescription": "授予提交非 4K 電視節目請求的權限。", + "components.PermissionEdit.requestTv": "提出電視節目請求", + "components.PermissionEdit.requestMovies": "提出電影請求", + "components.PermissionEdit.requestMoviesDescription": "授予提出非 4K 電影請求的權限。", + "components.PermissionEdit.requestTvDescription": "授予提出非 4K 電視節目請求的權限。", "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "預設設定({language})", "components.Settings.locale": "顯示語言", "components.DownloadBlock.estimatedtime": "預計:{time}", @@ -841,21 +841,21 @@ "components.Settings.Notifications.encryptionOpportunisticTls": "始終使用 STARTTLS", "components.Settings.Notifications.encryptionNone": "不使用加密", "components.Settings.Notifications.encryption": "加密方式", - "components.Settings.Notifications.NotificationsPushover.userTokenTip": "您 30 個字符的使用者 或群組識別符", - "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "從您的帳號設定取得 API 金鑰", + "components.Settings.Notifications.NotificationsPushover.userTokenTip": "您 30 個字符的使用者或群組識別碼", + "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "從您的帳號設定取得 API 令牌", "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "建立一個 Overseerr 專用的應用程式", - "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "創建一個「incoming webhook」整合", - "components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "使用者或設備通知的Webhook 網址", - "components.Settings.Notifications.webhookUrlTip": "在您的伺服器裡建立一個Webhook", + "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "創建一個「Incoming Webhook」整合", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "使用者或設備通知的 webhook 網址", + "components.Settings.Notifications.webhookUrlTip": "在您的伺服器裡建立一個 webhook", "components.Settings.Notifications.botApiTip": "建立一個 Overseerr 專用的機器人", "components.Settings.Notifications.chatIdTip": "先與您的機器人建立一個聊天室以及把 @get_id_bot 也加到聊天室,然後在聊天室裡發出 /my_id 命令", "components.Settings.webAppUrlTip": "使用伺服器的網路應用代替「託管」的網路應用", - "components.Settings.webAppUrl": "網路應用網址(URL)", + "components.Settings.webAppUrl": "網路應用網址", "components.Settings.validationWebAppUrl": "請輸入有效的 Plex 網路應用網址", - "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Overseerr 必須通過 HTTPS 投放才能使用網路推播通知。", + "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Overseerr 必須通過 HTTPS 投放才能使用網路推送通知。", "components.UserList.localLoginDisabled": "允許本地登入的設定目前被禁用。", "components.RequestList.RequestItem.requesteddate": "請求日期", - "components.RequestCard.failedretry": "重試提交請求中出了點問題。", + "components.RequestCard.failedretry": "重試提出請求中出了點問題。", "components.UserList.displayName": "顯示名稱", "components.Settings.SettingsUsers.localLoginTip": "讓使用者使用電子郵件地址和密碼登入", "components.Settings.SettingsUsers.defaultPermissionsTip": "授予給新使用者的權限", @@ -870,12 +870,12 @@ "components.Settings.Notifications.NotificationsPushover.validationTypes": "請選擇通知類型", "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "請選擇通知類型", "components.Settings.Notifications.NotificationsLunaSea.validationTypes": "請選擇通知類型", - "components.NotificationTypeSelector.usermediarequestedDescription": "當其他使用者提交需要管理員批准的請求時取得通知。", - "components.NotificationTypeSelector.usermediafailedDescription": "當 Radarr 或 Sonarr 處理請求失敗時得到通知。", - "components.NotificationTypeSelector.usermediadeclinedDescription": "當您的請求被拒絕時得到通知。", - "components.NotificationTypeSelector.usermediaavailableDescription": "當您請求的媒體可觀看時得到通知。", - "components.NotificationTypeSelector.usermediaapprovedDescription": "當您的請求被手動批准時得到通知。", - "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "當其他使用者提交自動批准的請求時取得通知。", + "components.NotificationTypeSelector.usermediarequestedDescription": "當其他使用者提出需要管理員批准的請求時取得通知。", + "components.NotificationTypeSelector.usermediafailedDescription": "當 Radarr 或 Sonarr 處理請求失敗時取得通知。", + "components.NotificationTypeSelector.usermediadeclinedDescription": "當您的請求被拒絕時取得通知。", + "components.NotificationTypeSelector.usermediaavailableDescription": "當您請求的媒體可觀看時取得通知。", + "components.NotificationTypeSelector.usermediaapprovedDescription": "當您的請求被手動批准時取得通知。", + "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "當其他使用者提出自動批准的請求時取得通知。", "components.Settings.SettingsAbout.betawarning": "這是測試版軟體,所以可能會不穩定或被破壞。請向 GitHub 報告問題!", "components.Layout.LanguagePicker.displaylanguage": "顯示語言", "components.MovieDetails.showmore": "顯示更多", From 00ee535077e60adff3a71681699f1a936bc2c13b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Sat, 9 Oct 2021 23:13:55 +0000 Subject: [PATCH 060/238] docs: add skafte1990 as a contributor for translation (#2182) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index fcc757ad5..9dac63991 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -593,6 +593,15 @@ "contributions": [ "translation" ] + }, + { + "login": "skafte1990", + "name": "Shaaft", + "avatar_url": "https://avatars.githubusercontent.com/u/31465453?v=4", + "profile": "https://github.com/skafte1990", + "contributions": [ + "translation" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index 5da4349f2..0203e48d1 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -157,6 +157,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Sergey

🌍 +
Shaaft

🌍 From b7ba90d67b53071a9bce472b4ebc5f988d0c16da Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sat, 9 Oct 2021 19:29:27 -0400 Subject: [PATCH 061/238] docs: improve Docker documentation (#2109) [skip ci] * docs: improve Docker documentation * docs: suggested changes * docs: minor formatting changes * docs: add Doplarr to list of third-party integrations * docs: rename docker run tab * docs: add link to official Docker CLI docs * docs: minor edits --- docs/extending-overseerr/reverse-proxy.md | 3 +- docs/extending-overseerr/third-party.md | 1 + docs/getting-started/installation.md | 109 +++++++++++++--------- 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/docs/extending-overseerr/reverse-proxy.md b/docs/extending-overseerr/reverse-proxy.md index 1ebb4b469..5aa6fd462 100644 --- a/docs/extending-overseerr/reverse-proxy.md +++ b/docs/extending-overseerr/reverse-proxy.md @@ -145,8 +145,7 @@ location ^~ /overseerr { sub_filter '/android-' '/$app/android-'; sub_filter '/apple-' '/$app/apple-'; sub_filter '/favicon' '/$app/favicon'; - sub_filter '/logo_full.svg' '/$app/logo_full.svg'; - sub_filter '/logo_stacked.svg' '/$app/logo_stacked.svg'; + sub_filter '/logo_' '/$app/logo_'; sub_filter '/site.webmanifest' '/$app/site.webmanifest'; } ``` diff --git a/docs/extending-overseerr/third-party.md b/docs/extending-overseerr/third-party.md index c7d57fd4f..cf2946d91 100644 --- a/docs/extending-overseerr/third-party.md +++ b/docs/extending-overseerr/third-party.md @@ -8,6 +8,7 @@ We do not officially support these third-party integrations. If you run into any - [Heimdall](https://github.com/linuxserver/Heimdall), an application dashboard and launcher - [LunaSea](https://docs.lunasea.app/modules/overseerr), a self-hosted controller for mobile and macOS - [Requestrr](https://github.com/darkalfx/requestrr/wiki/Configuring-Overseerr), a Discord chatbot +- [Doplarr](https://github.com/kiranshila/Doplarr), a Discord request bot - [ha-overseerr](https://github.com/vaparr/ha-overseerr), a custom Home Assistant component - [OverCLIrr](https://github.com/WillFantom/OverCLIrr), a command-line tool - [Overseerr Exporter](https://github.com/WillFantom/overseerr-exporter), a Prometheus exporter diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 205aa99f7..f3dce15f4 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -10,8 +10,18 @@ After running Overseerr for the first time, configure it by visiting the web UI ## Docker +{% hint style="warning" %} +Be sure to replace `/path/to/appdata/config` in the below examples with a valid host directory path. If this volume mount is not configured correctly, your Overseerr settings/data will not be persisted when the container is recreated (e.g., when updating the image or rebooting your machine). + +The `TZ` environment variable value should also be set to the [TZ database name](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) of your time zone! +{% endhint %} + {% tabs %} -{% tab title="Basic" %} +{% tab title="Docker CLI" %} + +For details on the Docker CLI, please [review the official `docker run` documentation](https://docs.docker.com/engine/reference/run/). + +**Installation:** ```bash docker run -d \ @@ -24,11 +34,41 @@ docker run -d \ sctx/overseerr ``` +To run the container as a specific user/group, you may optionally add `--user=[ user | user:group | uid | uid:gid | user:gid | uid:group ]` to the above command. + +**Updating:** + +Stop and remove the existing container: + +```bash +docker stop overseerr && docker rm overseerr +``` + +Pull the latest image: + +```bash +docker pull sctx/overseerr +``` + +Finally, run the container with the same parameters originally used to create the container: + +```bash +docker run -d ... +``` + +{% hint style="info" %} +You may alternatively use a third-party updating mechanism, such as [Watchtower](https://github.com/containrrr/watchtower) or [Ouroboros](https://github.com/pyouroboros/ouroboros), to keep Overseerr up-to-date automatically. +{% endhint %} + {% endtab %} -{% tab title="Compose" %} +{% tab title="Docker Compose" %} -**docker-compose.yml:** +For details on how to use Docker Compose, please [review the official Compose documentation](https://docs.docker.com/compose/reference/). + +**Installation:** + +Define the `overseerr` service in your `docker-compose.yml` as follows: ```yaml --- @@ -48,47 +88,29 @@ services: restart: unless-stopped ``` -{% endtab %} - -{% tab title="UID/GID" %} - -```text -docker run -d \ - --name overseerr \ - --user=[ user | user:group | uid | uid:gid | user:gid | uid:group ] \ - -e LOG_LEVEL=debug \ - -e TZ=Asia/Tokyo \ - -p 5055:5055 \ - -v /path/to/appdata/config:/app/config \ - --restart unless-stopped \ - sctx/overseerr -``` - -{% endtab %} - -{% tab title="Manual Update" %} +Then, start all services defined in the your Compose file: ```bash -# Stop the Overseerr container -docker stop overseerr +docker-compose up -d +``` -# Remove the Overseerr container -docker rm overseerr +**Updating:** -# Pull the latest update -docker pull sctx/overseerr +Pull the latest image: -# Run the Overseerr container with the same parameters as before -docker run -d ... +```bash +docker-compose pull overseerr +``` + +Then, restart all services defined in the Compose file: + +```bash +docker-compose up -d ``` {% endtab %} {% endtabs %} -{% hint style="info" %} -Use a 3rd party updating mechanism such as [Watchtower](https://github.com/containrrr/watchtower) or [Ouroboros](https://github.com/pyouroboros/ouroboros) to keep Overseerr up-to-date automatically. -{% endhint %} - ## Unraid 1. Ensure you have the **Community Applications** plugin installed. @@ -144,29 +166,24 @@ The [Overseerr snap](https://snapcraft.io/overseerr) is the only officially supp Currently, the listening port cannot be changed, so port `5055` will need to be available on your host. To install `snapd`, please refer to the [Snapcraft documentation](https://snapcraft.io/docs/installing-snapd). {% endhint %} -**To install:** +**Installation:** ``` sudo snap install overseerr ``` +{% hint style="danger" %} +To install the development build, add the `--edge` argument to the above command (i.e., `sudo snap install overseerr --edge`). However, note that this version can break any moment. Be prepared to troubleshoot any issues that arise! +{% endhint %} + **Updating:** + Snap will keep Overseerr up-to-date automatically. You can force a refresh by using the following command. -``` +```bash sudo snap refresh ``` -**To install the development build:** - -``` -sudo snap install overseerr --edge -``` - -{% hint style="danger" %} -This version can break any moment. Be prepared to troubleshoot any issues that arise! -{% endhint %} - ## Third-Party {% tabs %} From dce10f743f52cb04036e2cdaee280e26a81b253b Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Wed, 13 Oct 2021 16:54:01 +0200 Subject: [PATCH 062/238] feat(lang): translations update from Weblate (#2185) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(lang): translated using Weblate (German) Currently translated at 99.8% (882 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: JoKerIsCraZy Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (883 of 883 strings) feat(lang): translated using Weblate (Spanish) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: Ricardo González Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/es/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Danish) Currently translated at 5.4% (48 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/da/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: Shjosan Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 99.7% (881 of 883 strings) feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 99.7% (881 of 883 strings) feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 99.7% (881 of 883 strings) Co-authored-by: Eric Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hans/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (883 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Portuguese (Portugal)) Currently translated at 99.3% (877 of 883 strings) feat(lang): translated using Weblate (Portuguese (Portugal)) Currently translated at 99.2% (876 of 883 strings) Co-authored-by: Hosted Weblate Co-authored-by: JoKerIsCraZy Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_PT/ Translation: Overseerr/Overseerr Frontend Co-authored-by: JoKerIsCraZy Co-authored-by: Ricardo González Co-authored-by: TheCatLady Co-authored-by: Shjosan Co-authored-by: Eric --- src/i18n/locale/da.json | 2 +- src/i18n/locale/de.json | 6 ++- src/i18n/locale/es.json | 86 +++++++++++++++++++++++++----------- src/i18n/locale/pt_PT.json | 17 ++++--- src/i18n/locale/sv.json | 2 +- src/i18n/locale/zh_Hans.json | 8 ++-- src/i18n/locale/zh_Hant.json | 12 ++--- 7 files changed, 88 insertions(+), 45 deletions(-) diff --git a/src/i18n/locale/da.json b/src/i18n/locale/da.json index 4cebcdf7b..47f629619 100644 --- a/src/i18n/locale/da.json +++ b/src/i18n/locale/da.json @@ -33,7 +33,7 @@ "components.Discover.recentlyAdded": "Nyligt tilføjet", "components.Discover.populartv": "Populære Serier", "components.Discover.popularmovies": "Populære Film", - "components.Discover.noRequests": "Ingen ønsker", + "components.Discover.noRequests": "Ingen ønsker.", "components.Discover.discovertv": "Populære Serier", "components.Discover.discover": "Udforsk", "components.Discover.TvGenreSlider.tvgenres": "Serie Genre", diff --git a/src/i18n/locale/de.json b/src/i18n/locale/de.json index e90e59305..610c77ca1 100644 --- a/src/i18n/locale/de.json +++ b/src/i18n/locale/de.json @@ -25,7 +25,7 @@ "components.MovieDetails.overview": "Übersicht", "components.MovieDetails.overviewunavailable": "Übersicht nicht verfügbar.", "components.MovieDetails.recommendations": "Empfehlungen", - "components.MovieDetails.releasedate": "Erscheinungsdatum", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Erscheinungsdatum} other {Erscheinungsdaten}}", "components.MovieDetails.revenue": "Einnahmen", "components.MovieDetails.runtime": "{minutes} Minuten", "components.MovieDetails.similar": "Ähnliche Titel", @@ -879,5 +879,7 @@ "components.Layout.LanguagePicker.displaylanguage": "Sprache darstellen", "components.Settings.SettingsAbout.betawarning": "Dies ist eine BETA Software. Einige Funktionen könnten nicht funktionieren oder nicht stabil funktionieren. Bitte auf GitHub alle Fehler melden!", "components.MovieDetails.showless": "Weniger Anzeigen", - "components.MovieDetails.showmore": "Mehr Anzeigen" + "components.MovieDetails.showmore": "Mehr Anzeigen", + "components.MovieDetails.streamingproviders": "Streamt derzeit auf", + "components.TvDetails.streamingproviders": "Streamt derzeit auf" } diff --git a/src/i18n/locale/es.json b/src/i18n/locale/es.json index 7d20e9dcc..dc234b182 100644 --- a/src/i18n/locale/es.json +++ b/src/i18n/locale/es.json @@ -1,5 +1,5 @@ { - "components.Settings.SonarrModal.ssl": "Habilitar SSL", + "components.Settings.SonarrModal.ssl": "Usar SSL", "components.Settings.SonarrModal.servername": "Nombre del Servidor", "components.Settings.SonarrModal.server4k": "Servidor 4K", "components.Settings.SonarrModal.selectRootFolder": "Selecciona la carpeta raíz", @@ -29,7 +29,7 @@ "components.Settings.RadarrModal.validationApiKeyRequired": "Debes proporcionar la clave API", "components.Settings.RadarrModal.toastRadarrTestSuccess": "¡Conexión con Radarr establecida con éxito!", "components.Settings.RadarrModal.toastRadarrTestFailure": "Error al connectar al Radarr.", - "components.Settings.RadarrModal.ssl": "Habilitar SSL", + "components.Settings.RadarrModal.ssl": "Usar SSL", "components.Settings.RadarrModal.servername": "Nombre del Servidor", "components.Settings.RadarrModal.server4k": "Servidor 4K", "components.Settings.RadarrModal.selectRootFolder": "Selecciona la carpeta raíz", @@ -82,7 +82,7 @@ "components.MovieDetails.similar": "Títulos Similares", "components.MovieDetails.runtime": "{minutes} minutos", "components.MovieDetails.revenue": "Recaudado", - "components.MovieDetails.releasedate": "Fecha de Lanzamiento", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Fecha} other {Fechas}} de Lanzamiento", "components.MovieDetails.cast": "Reparto", "components.MovieDetails.MovieCast.fullcast": "Reparto Completo", "components.MovieDetails.recommendations": "Recomendaciones", @@ -138,10 +138,10 @@ "i18n.approve": "Aprobar", "components.UserList.userlist": "Lista de usuarios", "components.UserList.user": "Usuario", - "components.UserList.totalrequests": "Solicitudes totales", + "components.UserList.totalrequests": "Solicitudes", "components.UserList.role": "Rol", "components.UserList.plexuser": "Usuario de Plex", - "components.UserList.lastupdated": "Última actualización", + "components.UserList.lastupdated": "Actualizado", "components.UserList.created": "Creado", "components.UserList.admin": "Administrador", "components.TvDetails.similar": "Series Similares", @@ -259,33 +259,33 @@ "components.Settings.Notifications.NotificationsSlack.agentenabled": "Habilitar Agente", "components.RequestList.RequestItem.failedretry": "Algo salió mal al reintentar la solicitud.", "components.MovieDetails.watchtrailer": "Ver Trailer", - "components.NotificationTypeSelector.mediarequestedDescription": "Envía una notificación cuando se solicitan medios que requieren ser aprobados.", + "components.NotificationTypeSelector.mediarequestedDescription": "Envía notificaciones cuando los usuarios soliciten contenidos que requieran ser aprobados.", "components.StatusChacker.reloadOverseerr": "Recargar", "components.StatusChacker.newversionavailable": "Actualización de Aplicación", "components.StatusChacker.newversionDescription": "¡Overseerr se ha actualizado!Haga clic en el botón de abajo para volver a cargar la aplicación.", "components.Settings.SettingsAbout.documentation": "Documentación", "components.Settings.Notifications.validationChatIdRequired": "Debes proporcionar un ID de chat válido", - "components.Settings.Notifications.validationBotAPIRequired": "Debes proporcionar un token de autenticación del bot", + "components.Settings.Notifications.validationBotAPIRequired": "Debes proporcionar un token de autorización del bot", "components.Settings.Notifications.telegramsettingssaved": "¡Se han guardado los ajustes de notificación de Telegram con éxito!", "components.Settings.Notifications.telegramsettingsfailed": "La configuración de notificaciones de Telegram no se pudo guardar.", "components.Settings.Notifications.senderName": "Nombre del remitente", "components.Settings.Notifications.chatId": "ID de chat", - "components.Settings.Notifications.botAPI": "Token de Autenticación del Bot", + "components.Settings.Notifications.botAPI": "Token de Autorización del Bot", "components.NotificationTypeSelector.mediarequested": "Contenido Solicitado", - "components.NotificationTypeSelector.mediafailedDescription": "Envía una notificación cuando los medios no se agregan a los servicios (Radarr / Sonarr).", + "components.NotificationTypeSelector.mediafailedDescription": "Envía notificaciones cuando los contenidos solicitados fallen al agregarse a Radarr o Sonarr.", "components.NotificationTypeSelector.mediafailed": "Contenido Fallido", - "components.NotificationTypeSelector.mediaavailableDescription": "Envía una notificación cuando los medios solicitados están disponibles.", + "components.NotificationTypeSelector.mediaavailableDescription": "Envía notificaciones cuando las peticiones realizadas están disponibles.", "components.NotificationTypeSelector.mediaavailable": "Contenido Disponible", - "components.NotificationTypeSelector.mediaapprovedDescription": "Envía una notificación cuando los medios pedidos son aprobados manualmente.", + "components.NotificationTypeSelector.mediaapprovedDescription": "Envía notificaciones cuando los medios solicitados son aprobados manualmente.", "components.NotificationTypeSelector.mediaapproved": "Contenido Aprobado", "i18n.request": "Solicitar", - "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Debes proporcionar una clave de usuario válida", + "components.Settings.Notifications.NotificationsPushover.validationUserTokenRequired": "Debes proporcionar una clave de usuario o grupo válida", "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Debes proporcionar un token de aplicación válido", "components.Settings.Notifications.NotificationsPushover.userToken": "Clave de usuario o grupo", "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "¡Se han guardado los ajustes de notificación de Pushover!", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "No se pudo guardar la configuración de notificaciones de Pushover.", "components.Settings.Notifications.NotificationsPushover.agentenabled": "Agente habilitado", - "components.Settings.Notifications.NotificationsPushover.accessToken": "Token de aplicación/API", + "components.Settings.Notifications.NotificationsPushover.accessToken": "Token de aplicación API", "components.RequestList.sortModified": "Última modificación", "components.RequestList.sortAdded": "Fecha de solicitud", "components.RequestList.showallrequests": "Mostrar todas las solicitudes", @@ -294,7 +294,7 @@ "components.UserList.validationpasswordminchars": "La contraseña es demasiado corta; debe tener 8 caracteres como mínimo", "components.UserList.usercreatedsuccess": "¡Usuario creado con éxito!", "components.UserList.usercreatedfailed": "Algo salió mal al intentar crear al usuario.", - "components.UserList.passwordinfodescription": "Habilita las notificaciones por email para poder utilizar las contraseñas generadas automáticamente.", + "components.UserList.passwordinfodescription": "Configura una URL de aplicación y habilita las notificaciones por email para poder utilizar las contraseñas generadas automáticamente.", "components.UserList.password": "Contraseña", "components.UserList.localuser": "Usuario local", "components.UserList.email": "Dirección de correo electrónico", @@ -323,7 +323,7 @@ "components.RequestModal.AdvancedRequester.destinationserver": "Servidor de destino", "components.RequestModal.AdvancedRequester.default": "{name} (Predeterminado)", "components.RequestModal.AdvancedRequester.animenote": "* Esta serie es un anime.", - "components.RequestModal.AdvancedRequester.advancedoptions": "Opciones avanzadas", + "components.RequestModal.AdvancedRequester.advancedoptions": "Avanzadas", "components.RequestButton.viewrequest4k": "Ver Petición 4K", "components.RequestButton.viewrequest": "Ver Petición", "components.RequestButton.requestmore4k": "Solicitar más en 4K", @@ -348,7 +348,7 @@ "components.Login.email": "Dirección de correo electrónico", "components.NotificationTypeSelector.mediadeclined": "Contenido Rechazado", "components.RequestModal.autoapproval": "Aprobación Automática", - "components.NotificationTypeSelector.mediadeclinedDescription": "Envía una notificación cuando una solicitud es rechazada.", + "components.NotificationTypeSelector.mediadeclinedDescription": "Envía notificaciones cuando las peticiones sean rechazadas.", "i18n.experimental": "Experimental", "components.Settings.hideAvailable": "Ocultar los Medios Disponibles", "components.Login.signingin": "Iniciando sesión…", @@ -413,7 +413,7 @@ "components.PermissionEdit.advancedrequestDescription": "Concede permisos para configurar opciones avanzadas en las peticiones.", "components.PermissionEdit.advancedrequest": "Peticiones Avanzadas", "components.PermissionEdit.adminDescription": "Acceso completo de administrador. Ignora otras comprobaciones de permisos.", - "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Envía una notificación cuando el contenido solicitado se apruebe automáticamente.", + "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Envía notificaciones cuando los usuarios solicitan nuevos contenidos que se aprueban automáticamente.", "components.NotificationTypeSelector.mediaAutoApproved": "Contenidos Aprobados Automáticamente", "components.MovieDetails.playonplex": "Ver en Plex", "components.MovieDetails.play4konplex": "Ver en Plex en 4K", @@ -524,7 +524,7 @@ "components.UserList.owner": "Propietario", "components.UserList.edituser": "Editar Permisos de Usuario", "components.UserList.bulkedit": "Edición Masiva", - "components.UserList.accounttype": "Tipo de Cuenta", + "components.UserList.accounttype": "Tipo", "components.TvDetails.playonplex": "Ver en Plex", "components.TvDetails.opensonarr4k": "Abrir Serie 4K en Sonarr", "components.TvDetails.play4konplex": "Ver en Plex en 4K", @@ -659,7 +659,7 @@ "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "Algo fue mal al guardar la contraseña.", "components.UserProfile.UserSettings.UserPasswordChange.password": "Contraseña", "components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "No tienes permiso para modificar la contraseña del usuario.", - "components.Settings.enablessl": "Habilitar SSL", + "components.Settings.enablessl": "Usar SSL", "components.Settings.cacheImagesTip": "Optimizar y guardar todas las imágenes localmente (consume mucho espacio en disco)", "components.Settings.cacheImages": "Habilitar Cacheado de Imagen", "components.Settings.SettingsLogs.logDetails": "Detalles del Log", @@ -700,7 +700,7 @@ "components.UserProfile.limit": "{remaining} de {limit}", "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "Límite de Peticiones de Series", "components.UserProfile.UserSettings.UserGeneralSettings.movierequestlimit": "Límite de Peticiones de Películas", - "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Habilitar Sobreescritura", + "components.UserProfile.UserSettings.UserGeneralSettings.enableOverride": "Límite global de Sobreescritura", "components.TvDetails.originaltitle": "Título Original", "components.Settings.SettingsUsers.tvRequestLimitLabel": "Límite Global de Peticiones de Series", "components.Settings.SettingsUsers.movieRequestLimitLabel": "Límite Global de Peticiones de Películas", @@ -757,9 +757,9 @@ "components.Settings.RadarrModal.edit4kradarr": "Modificar servidor Radarr 4K", "components.Settings.RadarrModal.default4kserver": "Servidor 4K por defecto", "components.Settings.RadarrModal.create4kradarr": "Añadir un nuevo servidor Radarr 4K", - "components.Settings.Notifications.validationPgpPrivateKey": "Debes indicar una clave privada PGP si se ha introducido una contraseña PGP", - "components.Settings.Notifications.validationPgpPassword": "Debes indicar una contraseña PGP si se ha introducido una clave privada PGP", - "components.Settings.Notifications.botUsernameTip": "Permite a los usuarios iniciar un chat con el bot y configurar sus propias notificaciones", + "components.Settings.Notifications.validationPgpPrivateKey": "Debes indicar una clave privada PGP", + "components.Settings.Notifications.validationPgpPassword": "Debes indicar una contraseña PGP", + "components.Settings.Notifications.botUsernameTip": "Permite a los usuarios iniciar también un chat con tu bot y configurar sus propias notificaciones", "components.RequestModal.pendingapproval": "Tu petición está pendiente de aprobación.", "components.RequestModal.AdvancedRequester.tags": "Etiquetas", "components.RequestModal.AdvancedRequester.selecttags": "Seleccionar etiquetas", @@ -790,7 +790,7 @@ "components.Settings.noDefault4kServer": "Un servidor 4K de {serverType} debe ser marcado por defecto para poder habilitar las peticiones 4K de {mediaType} de los usuarios.", "components.Settings.is4k": "4K", "components.Settings.SettingsUsers.newPlexLoginTip": "Habilitar inicio de sesión de usuarios de Plex sin importarse previamente", - "components.Settings.SettingsUsers.newPlexLogin": "Habilitar inicio de sesión de nuevo usuario de Plex", + "components.Settings.SettingsUsers.newPlexLogin": "Habilitar nuevo inicio de sesión de Plex", "components.Settings.Notifications.toastTelegramTestSuccess": "¡Notificación de Telegram enviada con éxito!", "components.Settings.Notifications.toastTelegramTestSending": "Enviando notificación de prueba de Telegram…", "components.Settings.Notifications.toastTelegramTestFailed": "Fallo al enviar notificación de prueba de Telegram.", @@ -845,5 +845,41 @@ "components.MovieDetails.showmore": "Mostrar más", "components.MovieDetails.showless": "Mostrar menos", "components.Layout.LanguagePicker.displaylanguage": "Mostrar idioma", - "components.DownloadBlock.estimatedtime": "Estimación de {time}" + "components.DownloadBlock.estimatedtime": "Estimación de {time}", + "components.Settings.Notifications.encryptionOpportunisticTls": "Usa siempre STARTTLS", + "components.TvDetails.streamingproviders": "Emisión Actual en", + "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "{{Language}} por defecto", + "components.Settings.Notifications.NotificationsWebPush.httpsRequirement": "Para recibir notificaciones web push, Overseerr debe servirse mediante HTTPS.", + "components.Settings.Notifications.NotificationsWebhook.validationTypes": "Debes seleccionar, al menos, un tipo de notificación", + "components.Settings.Notifications.validationTypes": "Debes seleccionar, al menos, un tipo de notificación", + "components.Settings.SettingsUsers.localLoginTip": "Permite a los usuarios registrarse consumo email y password, en lugar de la OAuth de Plex", + "components.Settings.webAppUrl": "Url de la Web App", + "components.Settings.locale": "Idioma en Pantalla", + "components.UserList.displayName": "Nombre en Pantalla", + "components.Settings.Notifications.encryption": "Método de Encriptación", + "components.Settings.Notifications.encryptionDefault": "Usa STARTTLS si está disponible", + "components.Settings.Notifications.encryptionNone": "Ninguna", + "components.Settings.Notifications.NotificationsLunaSea.webhookUrlTip": "Tu URL del webhook de notificación basado en tu usuario o dispositivo", + "components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Crea un token desde tu Opciones de Cuenta", + "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Registrar una aplicación para su uso con Overseerr", + "components.Settings.Notifications.NotificationsPushover.userTokenTip": "Tu identificador de usuario o grupo de 30 caracteres", + "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Debes seleccionar, al menos, un tipo de notificación", + "components.Settings.Notifications.NotificationsPushover.validationTypes": "Debes seleccionar, al menos, un tipo de notificación", + "components.QuotaSelector.seasons": "{count, plural, one {temporada} other {temporadas}}", + "components.QuotaSelector.movies": "{count, plural, one {película} other {películas}}", + "components.Settings.Notifications.NotificationsSlack.validationTypes": "Debes seleccionar, al menos, un tipo de notificación", + "components.Settings.Notifications.chatIdTip": "Empieza un chat con tu bot, añade el @get_id_bot e indica el comando /my_id", + "components.Settings.Notifications.encryptionImplicitTls": "Usa TLS Implícito", + "components.Settings.Notifications.webhookUrlTip": "Crea una integración webhook en tu servidor", + "components.MovieDetails.streamingproviders": "Emisión Actual en", + "components.QuotaSelector.movieRequests": "{quotaLimit} {películas} per {quotaDays} {días}", + "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Crea una integración con un Webhook de Entrada", + "components.Settings.validationWebAppUrl": "Debes proporcionar una URL válida para la Plex Web App", + "components.Settings.webAppUrlTip": "Dirige a los usuarios, opcionalmente, a la web app en tu servidor, en lugar de la app alojada en Plex", + "components.QuotaSelector.days": "{count, plural, one {día} other {días}}", + "components.QuotaSelector.tvRequests": "{quotaLimit} {temporadas} per {quotaDays} {días}", + "components.Settings.Notifications.botApiTip": "Crea un bot para usar con Overseerr", + "components.Settings.Notifications.encryptionTip": "Normalmente, TLS Implícito usa el puerto 465 y STARTTLS usa el puerto 587", + "components.UserList.localLoginDisabled": "El ajuste para Habilitar el Inicio de Sesión Local está actualmente deshabilitado.", + "components.Settings.SettingsUsers.defaultPermissionsTip": "Permisos iniciales asignados a nuevos usuarios" } diff --git a/src/i18n/locale/pt_PT.json b/src/i18n/locale/pt_PT.json index 133ebba4d..c5c876f0d 100644 --- a/src/i18n/locale/pt_PT.json +++ b/src/i18n/locale/pt_PT.json @@ -136,7 +136,7 @@ "components.MovieDetails.similar": "Títulos Similares", "components.MovieDetails.runtime": "{minutes} minutos", "components.MovieDetails.revenue": "Receita", - "components.MovieDetails.releasedate": "Data de Estreia", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Data} other {Datas}} de Estreia", "components.MovieDetails.recommendations": "Recomendações", "components.MovieDetails.overviewunavailable": "Sinopse indisponível.", "components.MovieDetails.overview": "Sinopse", @@ -200,12 +200,12 @@ "components.UserList.usercreatedsuccess": "Utilizador criado com sucesso!", "components.UserList.usercreatedfailed": "Ocorreu um erro ao criar o utilizador.", "components.UserList.user": "Utilizador", - "components.UserList.totalrequests": "Total de Pedidos", + "components.UserList.totalrequests": "Pedidos", "components.UserList.role": "Função", "components.UserList.plexuser": "Utilizador Plex", "components.UserList.passwordinfodescription": "Configurar um URL de aplicação e ativar as notificações por e-mail para permitir a geração automática de palavra-passe.", "components.UserList.localuser": "Utilizador Local", - "components.UserList.lastupdated": "Última Atualização", + "components.UserList.lastupdated": "Atualizado", "components.UserList.importfromplexerror": "Ocorreu um erro ao importar utilizadores do Plex.", "components.UserList.importfromplex": "Importar Utilizadores do Plex", "components.UserList.importedfromplex": "{userCount, plural, one {# novo utilizador} other {# novos utilizadores}} importados do Plex com sucesso!", @@ -483,7 +483,7 @@ "components.ResetPassword.password": "Palavra-passe", "components.ResetPassword.gobacklogin": "Voltar a Página de Inicio de Sessão", "components.ResetPassword.resetpassword": "Repor a sua Palavra-passe", - "components.ResetPassword.emailresetlink": "Enviar um endereço de Recuperação por E-mail", + "components.ResetPassword.emailresetlink": "Link de Recuperação por E-mail", "components.ResetPassword.email": "Endereço E-mail", "components.ResetPassword.confirmpassword": "Confirmar Palavra-passe", "components.Login.forgotpassword": "Esqueceu a Palavra-passe?", @@ -568,7 +568,7 @@ "components.UserProfile.UserSettings.UserGeneralSettings.admin": "Administrador", "components.UserProfile.UserSettings.UserGeneralSettings.accounttype": "Tipo de Conta", "components.UserList.owner": "Proprietário", - "components.UserList.accounttype": "Tipo de Conta", + "components.UserList.accounttype": "Tipo", "i18n.loading": "A carregar…", "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "Deve fornecer um ID de chat válido", "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatIdTipLong": "Iniciar uma conversa, adicionar @get_id_bot, e enviar o comando /my_id", @@ -876,5 +876,10 @@ "components.NotificationTypeSelector.usermediadeclinedDescription": "Notificar quando seus pedidos de multimédia forem recusados.", "components.NotificationTypeSelector.usermediaavailableDescription": "Notificar quando os seus pedidos de multimédia ficarem disponíveis.", "components.QuotaSelector.days": "{conta, plural, um {dia} outro {dias}}", - "components.Settings.SettingsAbout.betawarning": "Isto é um software em BETA. As funcionalidades podem estar quebradas e/ou instáveis. Relate qualquer problema no GitHub!" + "components.Settings.SettingsAbout.betawarning": "Isto é um software em BETA. As funcionalidades podem estar quebradas e/ou instáveis. Relate qualquer problema no GitHub!", + "components.MovieDetails.streamingproviders": "Atualmente a Exibir em", + "components.TvDetails.streamingproviders": "Atualmente a Exibir em", + "components.MovieDetails.showmore": "Mostrar Mais", + "components.Layout.LanguagePicker.displaylanguage": "Idioma da Interface", + "components.MovieDetails.showless": "Mostrar Menos" } diff --git a/src/i18n/locale/sv.json b/src/i18n/locale/sv.json index d60d8ff3c..7d2b920a4 100644 --- a/src/i18n/locale/sv.json +++ b/src/i18n/locale/sv.json @@ -146,7 +146,7 @@ "components.MovieDetails.similar": "Liknande Titlar", "components.MovieDetails.runtime": "{minutes} minuter", "components.MovieDetails.revenue": "Inkomster", - "components.MovieDetails.releasedate": "Utgivningsdatum", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Utgivningsdatum} other {Utgivningsdatum}}", "components.MovieDetails.recommendations": "Rekommendationer", "components.MovieDetails.overview": "Beskrivning", "components.MovieDetails.overviewunavailable": "Beskrivning otillgänglig.", diff --git a/src/i18n/locale/zh_Hans.json b/src/i18n/locale/zh_Hans.json index 3f888abdb..b3da166e5 100644 --- a/src/i18n/locale/zh_Hans.json +++ b/src/i18n/locale/zh_Hans.json @@ -143,7 +143,7 @@ "components.Settings.notifications": "通知", "components.Settings.notificationAgentSettingsDescription": "设置通知类型和代理服务。", "components.Settings.noDefaultServer": "您必须至少指定一个 {serverType} 服务器为默认,才能处理{mediaType}请求。", - "components.Settings.noDefaultNon4kServer": "如果您只有一个 {serverType} 服务器,请勿把它设置为 4K 服务器。", + "components.Settings.noDefaultNon4kServer": "如果你只有一台 {serverType} 服务器用于非 4K 和 4K 内容(或者如果你只下载 4k 内容),你的 {serverType} 服务器 不应该被指定为 4K 服务器。", "components.Settings.noDefault4kServer": "您必须指定一个 4K {serverType} 服务器为默认,才能处理 4K 的{mediaType}请求。", "components.Settings.menuUsers": "用户", "components.Settings.menuServices": "服务器", @@ -423,8 +423,8 @@ "components.UserProfile.UserSettings.UserPasswordChange.toastSettingsFailure": "重设密码中出了点问题。", "components.UserProfile.UserSettings.UserPasswordChange.password": "密码设置", "components.UserProfile.UserSettings.UserPasswordChange.nopermissionDescription": "您无权设置此用户的密码。", - "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "您的账户目前没有密码。设置密码以允许使用电子邮件地址登录。", - "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此用户目前没有密码。设置密码以允许此用户使用电子邮件地址登录。", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "您的帐户目前没有设置密码。在下方配置密码,您能够作为「本地用户」登录。", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此用户帐户目前没有设置密码。在下方配置密码,使该帐户能够作为「本地用户」登录。", "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "新密码", "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "当前的密码", "components.UserProfile.UserSettings.UserPasswordChange.confirmpassword": "确认密码", @@ -516,7 +516,7 @@ "components.Settings.SettingsLogs.resumeLogs": "恢复", "components.Settings.SettingsLogs.pauseLogs": "暫停", "components.Settings.SettingsLogs.message": "消息", - "components.Settings.SettingsLogs.logsDescription": "日志档案位置:{configDir}/logs/overseerr.log", + "components.Settings.SettingsLogs.logsDescription": "你也可以直接查看这些日志,方法是借助 stdout, 或者打开 {configDir}/logs/overseerr.log。", "components.Settings.SettingsLogs.logs": "日志", "components.Settings.SettingsLogs.logDetails": "日志详細信息", "components.Settings.SettingsLogs.level": "等級", diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json index 048554c6b..d924c99a5 100644 --- a/src/i18n/locale/zh_Hant.json +++ b/src/i18n/locale/zh_Hant.json @@ -524,7 +524,7 @@ "components.Layout.UserDropdown.settings": "設定", "components.Layout.UserDropdown.myprofile": "個人資料", "components.UserProfile.UserSettings.UserPasswordChange.validationConfirmPassword": "密碼必須匹配", - "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "目前密碼", + "components.UserProfile.UserSettings.UserPasswordChange.currentpassword": "目前的密碼", "components.UserProfile.UserSettings.UserPasswordChange.newpassword": "新密碼", "components.UserProfile.UserSettings.UserPasswordChange.validationCurrentPassword": "請輸入當前的密碼", "components.UserProfile.UserSettings.UserPasswordChange.validationNewPassword": "請輸入新密碼", @@ -764,7 +764,7 @@ "components.Layout.VersionStatus.commitsbehind": "落後 {commitsBehind} 個提交", "components.Layout.VersionStatus.outofdate": "非最新版本", "components.Layout.VersionStatus.streamstable": "Overseerr 穩定版", - "components.Layout.VersionStatus.streamdevelop": "Overseerr 開發版", + "components.Layout.VersionStatus.streamdevelop": "Overseerr「develop」開發版", "components.Settings.SettingsAbout.outofdate": "非最新版本", "components.Settings.SettingsAbout.uptodate": "最新", "components.Settings.noDefaultNon4kServer": "如果您只有一個 {serverType} 伺服器,請勿把它設定為 4K 伺服器。", @@ -772,8 +772,8 @@ "components.Settings.serviceSettingsDescription": "關於 {serverType} 伺服器的設定。{serverType} 伺服器數沒有最大值限制,但您只能指定兩個預設伺服器(一個非 4K、一個 4K)。", "components.Settings.mediaTypeSeries": "電視節目", "components.Settings.mediaTypeMovie": "電影", - "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此使用者目前沒有密碼。設定密碼以允許此使用者使用電子郵件地址登入。", - "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "您的帳戶目前沒有密碼。設定密碼以允許使用電子郵件地址登入。", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSet": "此使用者的帳戶目前沒有設密碼。若在以下設定密碼,此使用者就能使用「本地登入」。", + "components.UserProfile.UserSettings.UserPasswordChange.noPasswordSetOwnAccount": "您的帳戶目前沒有設密碼。若在以下設定密碼,您就能使用「本地登入」。", "components.UserList.autogeneratepasswordTip": "通過電子郵件發送伺服器生成的密碼給使用者", "i18n.retrying": "重試中…", "components.Settings.serverSecure": "SSL", @@ -880,6 +880,6 @@ "components.Layout.LanguagePicker.displaylanguage": "顯示語言", "components.MovieDetails.showmore": "顯示更多", "components.MovieDetails.showless": "顯示更少", - "components.TvDetails.streamingproviders": "目前流式傳輸於", - "components.MovieDetails.streamingproviders": "目前流式傳輸於" + "components.TvDetails.streamingproviders": "目前的流媒體服務", + "components.MovieDetails.streamingproviders": "目前的流媒體服務" } From 5683f55ebf3d292dc0a65c04e23e54f3c54673bb Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Wed, 13 Oct 2021 11:05:11 -0400 Subject: [PATCH 063/238] docs: add sr093906 as a contributor for translation (#2192) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 9dac63991..5e707445a 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -602,6 +602,15 @@ "contributions": [ "translation" ] + }, + { + "login": "sr093906", + "name": "sr093906", + "avatar_url": "https://avatars.githubusercontent.com/u/8369201?v=4", + "profile": "https://github.com/sr093906", + "contributions": [ + "translation" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index 0203e48d1..299a618be 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -158,6 +158,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Sergey

🌍
Shaaft

🌍 +
sr093906

🌍 From 82614ca4410782a12d65b4c0a6526ff064be1241 Mon Sep 17 00:00:00 2001 From: Danshil Kokil Mungur Date: Fri, 15 Oct 2021 16:23:39 +0400 Subject: [PATCH 064/238] feat(jobs): allow modifying job schedules (#1440) * feat(jobs): backend implementation * feat(jobs): initial frontend implementation * feat(jobs): store job settings as Record * feat(jobs): use heroicons/react instead of inline svgs * feat(jobs): use presets instead of cron expressions * feat(jobs): ran `yarn i18n:extract` * feat(jobs): suggested changes - use job ids in settings - add intervalDuration to jobs to allow choosing only minutes or hours for the job schedule - move job schedule defaults to settings.json - better TS types for jobs in settings cache component - make suggested changes to wording - plural form for label when job schedule can be defined in minutes - add fixed job interval duration - add predefined interval choices for minutes and hours - add new schema for job to overseerr api * feat(jobs): required change for CI to not fail * feat(jobs): suggested changes * fix(jobs): revert offending type refactor --- overseerr-api.yml | 106 ++++++------ server/job/schedule.ts | 34 ++-- server/lib/settings.ts | 41 +++++ server/routes/settings/index.ts | 38 ++++- .../Settings/SettingsJobsCache/index.tsx | 151 +++++++++++++++++- src/i18n/locale/en.json | 6 + 6 files changed, 310 insertions(+), 66 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index 0a1ef5be3..63638eeac 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1278,6 +1278,27 @@ components: allowSelfSigned: type: boolean example: false + Job: + type: object + properties: + id: + type: string + example: job-name + type: + type: string + enum: [process, command] + interval: + type: string + enum: [short, long, fixed] + name: + type: string + example: A Job Name + nextExecutionTime: + type: string + example: '2020-09-02T05:02:23.000Z' + running: + type: boolean + example: false PersonDetail: type: object properties: @@ -2214,23 +2235,7 @@ paths: schema: type: array items: - type: object - properties: - id: - type: string - example: job-name - name: - type: string - example: A Job Name - type: - type: string - enum: [process, command] - nextExecutionTime: - type: string - example: '2020-09-02T05:02:23.000Z' - running: - type: boolean - example: false + $ref: '#/components/schemas/Job' /settings/jobs/{jobId}/run: post: summary: Invoke a specific job @@ -2249,23 +2254,7 @@ paths: content: application/json: schema: - type: object - properties: - id: - type: string - example: job-name - type: - type: string - enum: [process, command] - name: - type: string - example: A Job Name - nextExecutionTime: - type: string - example: '2020-09-02T05:02:23.000Z' - running: - type: boolean - example: false + $ref: '#/components/schemas/Job' /settings/jobs/{jobId}/cancel: post: summary: Cancel a specific job @@ -2284,23 +2273,36 @@ paths: content: application/json: schema: - type: object - properties: - id: - type: string - example: job-name - type: - type: string - enum: [process, command] - name: - type: string - example: A Job Name - nextExecutionTime: - type: string - example: '2020-09-02T05:02:23.000Z' - running: - type: boolean - example: false + $ref: '#/components/schemas/Job' + /settings/jobs/{jobId}/schedule: + post: + summary: Modify job schedule + description: Re-registers the job with the schedule specified. Will return the job in JSON format. + tags: + - settings + parameters: + - in: path + name: jobId + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + schedule: + type: string + example: '0 */5 * * * *' + responses: + '200': + description: Rescheduled job + content: + application/json: + schema: + $ref: '#/components/schemas/Job' /settings/cache: get: summary: Get a list of active caches @@ -2398,7 +2400,7 @@ paths: example: Server ready on port 5055 timestamp: type: string - example: 2020-12-15T16:20:00.069Z + example: '2020-12-15T16:20:00.069Z' /settings/notifications/email: get: summary: Get email notification settings diff --git a/server/job/schedule.ts b/server/job/schedule.ts index 1e3665b80..568b28c97 100644 --- a/server/job/schedule.ts +++ b/server/job/schedule.ts @@ -1,15 +1,17 @@ import schedule from 'node-schedule'; -import logger from '../logger'; import downloadTracker from '../lib/downloadtracker'; import { plexFullScanner, plexRecentScanner } from '../lib/scanners/plex'; import { radarrScanner } from '../lib/scanners/radarr'; import { sonarrScanner } from '../lib/scanners/sonarr'; +import { getSettings, JobId } from '../lib/settings'; +import logger from '../logger'; interface ScheduledJob { - id: string; + id: JobId; job: schedule.Job; name: string; type: 'process' | 'command'; + interval: 'short' | 'long' | 'fixed'; running?: () => boolean; cancelFn?: () => void; } @@ -17,12 +19,15 @@ interface ScheduledJob { export const scheduledJobs: ScheduledJob[] = []; export const startJobs = (): void => { + const jobs = getSettings().jobs; + // Run recently added plex scan every 5 minutes scheduledJobs.push({ id: 'plex-recently-added-scan', name: 'Plex Recently Added Scan', type: 'process', - job: schedule.scheduleJob('0 */5 * * * *', () => { + interval: 'short', + job: schedule.scheduleJob(jobs['plex-recently-added-scan'].schedule, () => { logger.info('Starting scheduled job: Plex Recently Added Scan', { label: 'Jobs', }); @@ -37,7 +42,8 @@ export const startJobs = (): void => { id: 'plex-full-scan', name: 'Plex Full Library Scan', type: 'process', - job: schedule.scheduleJob('0 0 3 * * *', () => { + interval: 'long', + job: schedule.scheduleJob(jobs['plex-full-scan'].schedule, () => { logger.info('Starting scheduled job: Plex Full Library Scan', { label: 'Jobs', }); @@ -52,7 +58,8 @@ export const startJobs = (): void => { id: 'radarr-scan', name: 'Radarr Scan', type: 'process', - job: schedule.scheduleJob('0 0 4 * * *', () => { + interval: 'long', + job: schedule.scheduleJob(jobs['radarr-scan'].schedule, () => { logger.info('Starting scheduled job: Radarr Scan', { label: 'Jobs' }); radarrScanner.run(); }), @@ -65,7 +72,8 @@ export const startJobs = (): void => { id: 'sonarr-scan', name: 'Sonarr Scan', type: 'process', - job: schedule.scheduleJob('0 30 4 * * *', () => { + interval: 'long', + job: schedule.scheduleJob(jobs['sonarr-scan'].schedule, () => { logger.info('Starting scheduled job: Sonarr Scan', { label: 'Jobs' }); sonarrScanner.run(); }), @@ -73,23 +81,27 @@ export const startJobs = (): void => { cancelFn: () => sonarrScanner.cancel(), }); - // Run download sync + // Run download sync every minute scheduledJobs.push({ id: 'download-sync', name: 'Download Sync', type: 'command', - job: schedule.scheduleJob('0 * * * * *', () => { - logger.debug('Starting scheduled job: Download Sync', { label: 'Jobs' }); + interval: 'fixed', + job: schedule.scheduleJob(jobs['download-sync'].schedule, () => { + logger.debug('Starting scheduled job: Download Sync', { + label: 'Jobs', + }); downloadTracker.updateDownloads(); }), }); - // Reset download sync + // Reset download sync everyday at 01:00 am scheduledJobs.push({ id: 'download-sync-reset', name: 'Download Sync Reset', type: 'command', - job: schedule.scheduleJob('0 0 1 * * *', () => { + interval: 'long', + job: schedule.scheduleJob(jobs['download-sync-reset'].schedule, () => { logger.info('Starting scheduled job: Download Sync Reset', { label: 'Jobs', }); diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 8ece986e9..b4729e585 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -215,6 +215,18 @@ interface NotificationSettings { agents: NotificationAgents; } +interface JobSettings { + schedule: string; +} + +export type JobId = + | 'plex-recently-added-scan' + | 'plex-full-scan' + | 'radarr-scan' + | 'sonarr-scan' + | 'download-sync' + | 'download-sync-reset'; + interface AllSettings { clientId: string; vapidPublic: string; @@ -225,6 +237,7 @@ interface AllSettings { sonarr: SonarrSettings[]; public: PublicSettings; notifications: NotificationSettings; + jobs: Record; } const SETTINGS_PATH = process.env.CONFIG_DIRECTORY @@ -346,6 +359,26 @@ class Settings { }, }, }, + jobs: { + 'plex-recently-added-scan': { + schedule: '0 */5 * * * *', + }, + 'plex-full-scan': { + schedule: '0 0 3 * * *', + }, + 'radarr-scan': { + schedule: '0 0 4 * * *', + }, + 'sonarr-scan': { + schedule: '0 30 4 * * *', + }, + 'download-sync': { + schedule: '0 * * * * *', + }, + 'download-sync-reset': { + schedule: '0 0 1 * * *', + }, + }, }; if (initialSettings) { this.data = merge(this.data, initialSettings); @@ -428,6 +461,14 @@ class Settings { this.data.notifications = data; } + get jobs(): Record { + return this.data.jobs; + } + + set jobs(data: Record) { + this.data.jobs = data; + } + get clientId(): string { if (!this.data.clientId) { this.data.clientId = randomUUID(); diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index bf8cfcdcc..f58edb748 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -2,6 +2,7 @@ import { Router } from 'express'; import rateLimit from 'express-rate-limit'; import fs from 'fs'; import { merge, omit } from 'lodash'; +import { rescheduleJob } from 'node-schedule'; import path from 'path'; import { getRepository } from 'typeorm'; import { URL } from 'url'; @@ -49,7 +50,7 @@ settingsRoutes.get('/main', (req, res, next) => { const settings = getSettings(); if (!req.user) { - return next({ status: 500, message: 'User missing from request' }); + return next({ status: 400, message: 'User missing from request' }); } res.status(200).json(filteredMainSettings(req.user, settings.main)); @@ -310,6 +311,7 @@ settingsRoutes.get('/jobs', (_req, res) => { id: job.id, name: job.name, type: job.type, + interval: job.interval, nextExecutionTime: job.job.nextInvocation(), running: job.running ? job.running() : false, })) @@ -329,6 +331,7 @@ settingsRoutes.post<{ jobId: string }>('/jobs/:jobId/run', (req, res, next) => { id: scheduledJob.id, name: scheduledJob.name, type: scheduledJob.type, + interval: scheduledJob.interval, nextExecutionTime: scheduledJob.job.nextInvocation(), running: scheduledJob.running ? scheduledJob.running() : false, }); @@ -353,12 +356,45 @@ settingsRoutes.post<{ jobId: string }>( id: scheduledJob.id, name: scheduledJob.name, type: scheduledJob.type, + interval: scheduledJob.interval, nextExecutionTime: scheduledJob.job.nextInvocation(), running: scheduledJob.running ? scheduledJob.running() : false, }); } ); +settingsRoutes.post<{ jobId: string }>( + '/jobs/:jobId/schedule', + (req, res, next) => { + const scheduledJob = scheduledJobs.find( + (job) => job.id === req.params.jobId + ); + + if (!scheduledJob) { + return next({ status: 404, message: 'Job not found' }); + } + + const result = rescheduleJob(scheduledJob.job, req.body.schedule); + const settings = getSettings(); + + if (result) { + settings.jobs[scheduledJob.id].schedule = req.body.schedule; + settings.save(); + + return res.status(200).json({ + id: scheduledJob.id, + name: scheduledJob.name, + type: scheduledJob.type, + interval: scheduledJob.interval, + nextExecutionTime: scheduledJob.job.nextInvocation(), + running: scheduledJob.running ? scheduledJob.running() : false, + }); + } else { + return next({ status: 400, message: 'Invalid job schedule' }); + } + } +); + settingsRoutes.get('/cache', (req, res) => { const caches = cacheManager.getAllCaches(); diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx index a621228b2..c0e50e02a 100644 --- a/src/components/Settings/SettingsJobsCache/index.tsx +++ b/src/components/Settings/SettingsJobsCache/index.tsx @@ -1,6 +1,7 @@ import { PlayIcon, StopIcon, TrashIcon } from '@heroicons/react/outline'; +import { PencilIcon } from '@heroicons/react/solid'; import axios from 'axios'; -import React from 'react'; +import React, { useState } from 'react'; import { defineMessages, FormattedRelativeTime, @@ -10,14 +11,17 @@ import { import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; import { CacheItem } from '../../../../server/interfaces/api/settingsInterfaces'; +import { JobId } from '../../../../server/lib/settings'; import Spinner from '../../../assets/spinner.svg'; import globalMessages from '../../../i18n/globalMessages'; import { formatBytes } from '../../../utils/numberHelpers'; import Badge from '../../Common/Badge'; import Button from '../../Common/Button'; import LoadingSpinner from '../../Common/LoadingSpinner'; +import Modal from '../../Common/Modal'; import PageTitle from '../../Common/PageTitle'; import Table from '../../Common/Table'; +import Transition from '../../Transition'; const messages: { [messageName: string]: MessageDescriptor } = defineMessages({ jobsandcache: 'Jobs & Cache', @@ -51,12 +55,21 @@ const messages: { [messageName: string]: MessageDescriptor } = defineMessages({ 'sonarr-scan': 'Sonarr Scan', 'download-sync': 'Download Sync', 'download-sync-reset': 'Download Sync Reset', + editJobSchedule: 'Modify Job', + jobScheduleEditSaved: 'Job edited successfully!', + jobScheduleEditFailed: 'Something went wrong while saving the job.', + editJobSchedulePrompt: 'Frequency', + editJobScheduleSelectorHours: + 'Every {jobScheduleHours, plural, one {hour} other {{jobScheduleHours} hours}}', + editJobScheduleSelectorMinutes: + 'Every {jobScheduleMinutes, plural, one {minute} other {{jobScheduleMinutes} minutes}}', }); interface Job { - id: string; + id: JobId; name: string; type: 'process' | 'command'; + interval: 'short' | 'long' | 'fixed'; nextExecutionTime: string; running: boolean; } @@ -74,6 +87,16 @@ const SettingsJobs: React.FC = () => { } ); + const [jobEditModal, setJobEditModal] = useState<{ + isOpen: boolean; + job?: Job; + }>({ + isOpen: false, + }); + const [isSaving, setIsSaving] = useState(false); + const [jobScheduleMinutes, setJobScheduleMinutes] = useState(5); + const [jobScheduleHours, setJobScheduleHours] = useState(1); + if (!data && !error) { return ; } @@ -118,6 +141,42 @@ const SettingsJobs: React.FC = () => { cacheRevalidate(); }; + const scheduleJob = async () => { + const jobScheduleCron = ['0', '0', '*', '*', '*', '*']; + + try { + if (jobEditModal.job?.interval === 'short') { + jobScheduleCron[1] = `*/${jobScheduleMinutes}`; + } else if (jobEditModal.job?.interval === 'long') { + jobScheduleCron[2] = `*/${jobScheduleHours}`; + } else { + // jobs with interval: fixed should not be editable + throw new Error(); + } + + setIsSaving(true); + await axios.post( + `/api/v1/settings/jobs/${jobEditModal.job?.id}/schedule`, + { + schedule: jobScheduleCron.join(' '), + } + ); + addToast(intl.formatMessage(messages.jobScheduleEditSaved), { + appearance: 'success', + autoDismiss: true, + }); + setJobEditModal({ isOpen: false }); + revalidate(); + } catch (e) { + addToast(intl.formatMessage(messages.jobScheduleEditFailed), { + appearance: 'error', + autoDismiss: true, + }); + } finally { + setIsSaving(false); + } + }; + return ( <> { intl.formatMessage(globalMessages.settings), ]} /> + + } + onCancel={() => setJobEditModal({ isOpen: false })} + okDisabled={isSaving} + onOk={() => scheduleJob()} + > +
+
+
+ +
+ {jobEditModal.job?.interval === 'short' ? ( + + ) : ( + + )} +
+
+
+
+
+
+

{intl.formatMessage(messages.jobs)}

@@ -179,6 +314,18 @@ const SettingsJobs: React.FC = () => {

+ {job.interval !== 'fixed' && ( + + )} {job.running ? (
)} -
+
{iconSvg &&
{iconSvg}
}
{title && ( {title} diff --git a/src/components/Settings/SettingsAbout/Releases/index.tsx b/src/components/Settings/SettingsAbout/Releases/index.tsx index f3ef21fe6..cf8767946 100644 --- a/src/components/Settings/SettingsAbout/Releases/index.tsx +++ b/src/components/Settings/SettingsAbout/Releases/index.tsx @@ -4,7 +4,6 @@ import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import ReactMarkdown from 'react-markdown'; import useSWR from 'swr'; import globalMessages from '../../../../i18n/globalMessages'; -import Alert from '../../../Common/Alert'; import Badge from '../../../Common/Badge'; import Button from '../../../Common/Button'; import LoadingSpinner from '../../../Common/LoadingSpinner'; @@ -13,14 +12,12 @@ import Transition from '../../../Transition'; const messages = defineMessages({ releases: 'Releases', - releasedataMissing: 'Release data unavailable. Is GitHub down?', - versionChangelog: 'Version Changelog', + releasedataMissing: 'Release data is currently unavailable.', + versionChangelog: '{version} Changelog', viewongithub: 'View on GitHub', latestversion: 'Latest', - currentversion: 'Current Version', + currentversion: 'Current', viewchangelog: 'View Changelog', - runningDevelopMessage: - 'The latest changes to the develop branch of Overseerr are not shown below. Please see the commit history for this branch on GitHub for details.', }); const REPO_RELEASE_API = @@ -58,8 +55,9 @@ const Release: React.FC = ({ }) => { const intl = useIntl(); const [isModalOpen, setModalOpen] = useState(false); + return ( -
+
= ({ setModalOpen(false)} iconSvg={} - title={intl.formatMessage(messages.versionChangelog)} + title={intl.formatMessage(messages.versionChangelog, { + version: release.name, + })} cancelText={intl.formatMessage(globalMessages.close)} okText={intl.formatMessage(messages.viewongithub)} onOk={() => { @@ -84,38 +84,34 @@ const Release: React.FC = ({
-
- - - - {release.name} - {isLatest && ( - - - {intl.formatMessage(messages.latestversion)} - +
+ + + + {release.name} + + {isLatest && ( + + {intl.formatMessage(messages.latestversion)} + )} {release.name.includes(currentVersion) && ( - - - {intl.formatMessage(messages.currentversion)} - - + + {intl.formatMessage(messages.currentversion)} + )}
-
- -
+
); }; @@ -143,31 +139,10 @@ const Releases: React.FC = ({ currentVersion }) => { return (

{intl.formatMessage(messages.releases)}

-
- {currentVersion.startsWith('develop-') && ( - {msg}; - }, - GithubLink: function GithubLink(msg) { - return ( - - {msg} - - ); - }, - })} - /> - )} - {data?.map((release, index) => { +
+ {data.map((release, index) => { return ( -
+
develop branch of Overseerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.', }); const SettingsAbout: React.FC = () => { @@ -81,22 +84,58 @@ const SettingsAbout: React.FC = () => {
+ {data.version.startsWith('develop-') && ( + {msg}; + }, + })} + /> + )} - {data.version.replace('develop-', '')} - {status?.updateAvailable ? ( - - {intl.formatMessage(messages.outofdate)} - - ) : ( - status?.commitTag !== 'local' && ( - - {intl.formatMessage(messages.uptodate)} - - ) - )} + + {data.version.replace('develop-', '')} + + {status?.commitTag !== 'local' && + (status?.updateAvailable ? ( + + + {intl.formatMessage(messages.outofdate)} + + + ) : ( + + + {intl.formatMessage(messages.uptodate)} + + + ))} {intl.formatNumber(data.totalMediaItems)} @@ -118,7 +157,7 @@ const SettingsAbout: React.FC = () => { href="https://docs.overseerr.dev" target="_blank" rel="noreferrer" - className="text-indigo-500 hover:underline" + className="text-indigo-500 transition duration-300 hover:underline" > https://docs.overseerr.dev @@ -128,7 +167,7 @@ const SettingsAbout: React.FC = () => { href="https://github.com/sct/overseerr/discussions" target="_blank" rel="noreferrer" - className="text-indigo-500 hover:underline" + className="text-indigo-500 transition duration-300 hover:underline" > https://github.com/sct/overseerr/discussions @@ -138,7 +177,7 @@ const SettingsAbout: React.FC = () => { href="https://discord.gg/overseerr" target="_blank" rel="noreferrer" - className="text-indigo-500 hover:underline" + className="text-indigo-500 transition duration-300 hover:underline" > https://discord.gg/overseerr @@ -154,7 +193,7 @@ const SettingsAbout: React.FC = () => { href="https://github.com/sponsors/sct" target="_blank" rel="noreferrer" - className="text-indigo-500 hover:underline" + className="text-indigo-500 transition duration-300 hover:underline" > https://github.com/sponsors/sct @@ -167,7 +206,7 @@ const SettingsAbout: React.FC = () => { href="https://patreon.com/overseerr" target="_blank" rel="noreferrer" - className="text-indigo-500 hover:underline" + className="text-indigo-500 transition duration-300 hover:underline" > https://patreon.com/overseerr diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 9b4c297ce..6fd4f737b 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -426,12 +426,11 @@ "components.Settings.RadarrModal.validationPortRequired": "You must provide a valid port number", "components.Settings.RadarrModal.validationProfileRequired": "You must select a quality profile", "components.Settings.RadarrModal.validationRootFolderRequired": "You must select a root folder", - "components.Settings.SettingsAbout.Releases.currentversion": "Current Version", + "components.Settings.SettingsAbout.Releases.currentversion": "Current", "components.Settings.SettingsAbout.Releases.latestversion": "Latest", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Release data unavailable. Is GitHub down?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Release data is currently unavailable.", "components.Settings.SettingsAbout.Releases.releases": "Releases", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "The latest changes to the develop branch of Overseerr are not shown below. Please see the commit history for this branch on GitHub for details.", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Version Changelog", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} Changelog", "components.Settings.SettingsAbout.Releases.viewchangelog": "View Changelog", "components.Settings.SettingsAbout.Releases.viewongithub": "View on GitHub", "components.Settings.SettingsAbout.about": "About", @@ -441,8 +440,9 @@ "components.Settings.SettingsAbout.githubdiscussions": "GitHub Discussions", "components.Settings.SettingsAbout.helppaycoffee": "Help Pay for Coffee", "components.Settings.SettingsAbout.outofdate": "Out of Date", - "components.Settings.SettingsAbout.overseerrinformation": "Overseerr Information", + "components.Settings.SettingsAbout.overseerrinformation": "About Overseerr", "components.Settings.SettingsAbout.preferredmethod": "Preferred", + "components.Settings.SettingsAbout.runningDevelop": "You are running the develop branch of Overseerr, which is only recommended for those contributing to development or assisting with bleeding-edge testing.", "components.Settings.SettingsAbout.supportoverseerr": "Support Overseerr", "components.Settings.SettingsAbout.timezone": "Time Zone", "components.Settings.SettingsAbout.totalmedia": "Total Media", diff --git a/src/styles/globals.css b/src/styles/globals.css index 54385d4b4..4970c54b5 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -296,6 +296,10 @@ select.short { @apply w-min; } +button > span { + @apply whitespace-nowrap; +} + button.input-action { @apply relative inline-flex items-center px-3 sm:px-3.5 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 hover:bg-indigo-500 active:bg-gray-100 active:text-gray-700 last:rounded-r-md; } From 032c14a22680f62f8106943297b081b68645ce61 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sat, 16 Oct 2021 11:53:38 -0400 Subject: [PATCH 076/238] feat(ui): link processing/requested status badges to service URL (#1761) * feat(ui): link processing/requested status badges to service URL where available * refactor: add URL prop to Badge component * fix(css): tweak font weight of media rating values and request card link hover effect * fix: only set StatusBadge serviceUrl for admins --- src/components/Common/Badge/index.tsx | 32 +++- src/components/MovieDetails/index.tsx | 12 +- src/components/RequestCard/index.tsx | 14 +- .../RequestList/RequestItem/index.tsx | 14 +- src/components/StatusBadge/index.tsx | 177 +++++------------- src/components/TvDetails/index.tsx | 12 +- src/i18n/locale/en.json | 1 + src/styles/globals.css | 7 +- 8 files changed, 124 insertions(+), 145 deletions(-) diff --git a/src/components/Common/Badge/index.tsx b/src/components/Common/Badge/index.tsx index 9118415f2..3868e8a40 100644 --- a/src/components/Common/Badge/index.tsx +++ b/src/components/Common/Badge/index.tsx @@ -3,36 +3,64 @@ import React from 'react'; interface BadgeProps { badgeType?: 'default' | 'primary' | 'danger' | 'warning' | 'success'; className?: string; + url?: string; } const Badge: React.FC = ({ badgeType = 'default', className, + url, children, }) => { const badgeStyle = [ - 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full cursor-default', + 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full', ]; + if (url) { + badgeStyle.push('transition cursor-pointer'); + } else { + badgeStyle.push('cursor-default'); + } + switch (badgeType) { case 'danger': badgeStyle.push('bg-red-600 text-red-100'); + if (url) { + badgeStyle.push('hover:bg-red-500'); + } break; case 'warning': badgeStyle.push('bg-yellow-500 text-yellow-100'); + if (url) { + badgeStyle.push('hover:bg-yellow-400'); + } break; case 'success': badgeStyle.push('bg-green-500 text-green-100'); + if (url) { + badgeStyle.push('hover:bg-green-400'); + } break; default: badgeStyle.push('bg-indigo-500 text-indigo-100'); + if (url) { + badgeStyle.push('hover:bg-indigo-400'); + } } if (className) { badgeStyle.push(className); } - return {children}; + if (url) { + return ( + + {children} + + ); + } else { + return {children}; + } }; export default Badge; diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 3faf23636..b1d5bcdf9 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -420,6 +420,11 @@ const MovieDetails: React.FC = ({ movie }) => { status={data.mediaInfo?.status} inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0} plexUrl={data.mediaInfo?.plexUrl} + serviceUrl={ + hasPermission(Permission.ADMIN) + ? data.mediaInfo?.serviceUrl + : undefined + } /> {settings.currentSettings.movie4kEnabled && hasPermission( @@ -434,7 +439,12 @@ const MovieDetails: React.FC = ({ movie }) => { inProgress={ (data.mediaInfo?.downloadStatus4k ?? []).length > 0 } - plexUrl4k={data.mediaInfo?.plexUrl4k} + plexUrl={data.mediaInfo?.plexUrl4k} + serviceUrl={ + hasPermission(Permission.ADMIN) + ? data.mediaInfo?.serviceUrl4k + : undefined + } /> )}
diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 13d6119a2..08cc9ec69 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -292,8 +292,18 @@ const RequestCard: React.FC = ({ request, onTitleData }) => { ).length > 0 } is4k={requestData.is4k} - plexUrl={requestData.media.plexUrl} - plexUrl4k={requestData.media.plexUrl4k} + plexUrl={ + requestData.is4k + ? requestData.media.plexUrl4k + : requestData.media.plexUrl + } + serviceUrl={ + hasPermission(Permission.ADMIN) + ? requestData.is4k + ? requestData.media.serviceUrl4k + : requestData.media.serviceUrl + : undefined + } /> )}
diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index 93e28caf8..a3a203e7f 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -294,8 +294,18 @@ const RequestItem: React.FC = ({ ).length > 0 } is4k={requestData.is4k} - plexUrl={requestData.media.plexUrl} - plexUrl4k={requestData.media.plexUrl4k} + plexUrl={ + requestData.is4k + ? requestData.media.plexUrl4k + : requestData.media.plexUrl + } + serviceUrl={ + hasPermission(Permission.ADMIN) + ? requestData.is4k + ? requestData.media.serviceUrl4k + : requestData.media.serviceUrl + : undefined + } /> )}
diff --git a/src/components/StatusBadge/index.tsx b/src/components/StatusBadge/index.tsx index ece5a512a..86c935bcc 100644 --- a/src/components/StatusBadge/index.tsx +++ b/src/components/StatusBadge/index.tsx @@ -6,6 +6,7 @@ import globalMessages from '../../i18n/globalMessages'; import Badge from '../Common/Badge'; const messages = defineMessages({ + status: '{status}', status4k: '4K {status}', }); @@ -14,7 +15,7 @@ interface StatusBadgeProps { is4k?: boolean; inProgress?: boolean; plexUrl?: string; - plexUrl4k?: string; + serviceUrl?: string; } const StatusBadge: React.FC = ({ @@ -22,158 +23,64 @@ const StatusBadge: React.FC = ({ is4k = false, inProgress = false, plexUrl, - plexUrl4k, + serviceUrl, }) => { const intl = useIntl(); - if (is4k) { - switch (status) { - case MediaStatus.AVAILABLE: - if (plexUrl4k) { - return ( - - - {intl.formatMessage(messages.status4k, { - status: intl.formatMessage(globalMessages.available), - })} - - - ); - } - - return ( - - {intl.formatMessage(messages.status4k, { - status: intl.formatMessage(globalMessages.available), - })} - - ); - case MediaStatus.PARTIALLY_AVAILABLE: - if (plexUrl4k) { - return ( - - - {intl.formatMessage(messages.status4k, { - status: intl.formatMessage(globalMessages.partiallyavailable), - })} - - - ); - } - - return ( - - {intl.formatMessage(messages.status4k, { - status: intl.formatMessage(globalMessages.partiallyavailable), - })} - - ); - case MediaStatus.PROCESSING: - return ( - -
- - {intl.formatMessage(messages.status4k, { - status: inProgress - ? intl.formatMessage(globalMessages.processing) - : intl.formatMessage(globalMessages.requested), - })} - - {inProgress && } -
-
- ); - case MediaStatus.PENDING: - return ( - - {intl.formatMessage(messages.status4k, { - status: intl.formatMessage(globalMessages.pending), - })} - - ); - default: - return null; - } - } - switch (status) { case MediaStatus.AVAILABLE: - if (plexUrl) { - return ( - - -
- {intl.formatMessage(globalMessages.available)} - {inProgress && } -
-
-
- ); - } - return ( - -
- {intl.formatMessage(globalMessages.available)} - {inProgress && } -
-
- ); - case MediaStatus.PARTIALLY_AVAILABLE: - if (plexUrl) { - return ( - - -
- - {intl.formatMessage(globalMessages.partiallyavailable)} - - {inProgress && } -
-
-
- ); - } - - return ( - -
- {intl.formatMessage(globalMessages.partiallyavailable)} - {inProgress && } -
-
- ); - case MediaStatus.PROCESSING: - return ( - +
- {inProgress - ? intl.formatMessage(globalMessages.processing) - : intl.formatMessage(globalMessages.requested)} + {intl.formatMessage(is4k ? messages.status4k : messages.status, { + status: intl.formatMessage(globalMessages.available), + })} {inProgress && }
); + + case MediaStatus.PARTIALLY_AVAILABLE: + return ( + +
+ + {intl.formatMessage(is4k ? messages.status4k : messages.status, { + status: intl.formatMessage(globalMessages.partiallyavailable), + })} + + {inProgress && } +
+
+ ); + + case MediaStatus.PROCESSING: + return ( + +
+ + {intl.formatMessage(is4k ? messages.status4k : messages.status, { + status: inProgress + ? intl.formatMessage(globalMessages.processing) + : intl.formatMessage(globalMessages.requested), + })} + + {inProgress && } +
+
+ ); + case MediaStatus.PENDING: return ( - {intl.formatMessage(globalMessages.pending)} + {intl.formatMessage(is4k ? messages.status4k : messages.status, { + status: intl.formatMessage(globalMessages.pending), + })} ); + default: return null; } diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 44554ba56..6ccc60499 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -430,6 +430,11 @@ const TvDetails: React.FC = ({ tv }) => { status={data.mediaInfo?.status} inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0} plexUrl={data.mediaInfo?.plexUrl} + serviceUrl={ + hasPermission(Permission.ADMIN) + ? data.mediaInfo?.serviceUrl + : undefined + } /> {settings.currentSettings.series4kEnabled && hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { @@ -441,7 +446,12 @@ const TvDetails: React.FC = ({ tv }) => { inProgress={ (data.mediaInfo?.downloadStatus4k ?? []).length > 0 } - plexUrl4k={data.mediaInfo?.plexUrl4k} + plexUrl={data.mediaInfo?.plexUrl4k} + serviceUrl={ + hasPermission(Permission.ADMIN) + ? data.mediaInfo?.serviceUrl4k + : undefined + } /> )}
diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 6fd4f737b..789d05a7e 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -673,6 +673,7 @@ "components.Setup.signinMessage": "Get started by signing in with your Plex account", "components.Setup.tip": "Tip", "components.Setup.welcome": "Welcome to Overseerr", + "components.StatusBadge.status": "{status}", "components.StatusBadge.status4k": "4K {status}", "components.StatusChacker.newversionDescription": "Overseerr has been updated! Please click the button below to reload the page.", "components.StatusChacker.newversionavailable": "Application Update", diff --git a/src/styles/globals.css b/src/styles/globals.css index 4970c54b5..fec0c7127 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1,7 +1,6 @@ @tailwind base; @tailwind components; @tailwind utilities; -@tailwind screens; html { min-height: calc(100% + env(safe-area-inset-top)); @@ -182,7 +181,7 @@ a.crew-name, } .media-ratings { - @apply flex items-center justify-center px-4 py-2 border-b border-gray-700 last:border-b-0; + @apply flex items-center justify-center px-4 py-2 font-medium border-b border-gray-700 last:border-b-0; } .media-rating { @@ -213,6 +212,10 @@ img.avatar-sm { @apply mr-2 font-bold; } +.card-field a { + @apply transition duration-300 hover:text-white hover:underline; +} + .section { @apply mt-6 mb-10 text-white; } From b3b421a67408a4a48d23c15341fcdf7aaf19b25a Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Mon, 18 Oct 2021 10:08:50 -0400 Subject: [PATCH 077/238] fix(email): do not attempt to display logo if app URL not configured (#2125) * fix(email): do not attempt to display logo if app URL not configured * fix(email): prevent Gmail from turning usernames with periods into hyperlinks * fix(email): fix(email): use displayName instead of username/plexUserName and improve Gmail link fix --- server/entity/User.ts | 3 + server/lib/notifications/agents/email.ts | 22 +++++-- server/routes/user/index.ts | 2 +- .../email/generatedpassword/html.pug | 54 +++++++--------- server/templates/email/media-request/html.pug | 52 ++++++--------- server/templates/email/resetpassword/html.pug | 63 ++++++++----------- server/templates/email/test-email/html.pug | 45 +++++-------- 7 files changed, 106 insertions(+), 135 deletions(-) diff --git a/server/entity/User.ts b/server/entity/User.ts index fc2729a5f..77f0e8b11 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -178,6 +178,7 @@ export class User { password: password, applicationUrl, applicationTitle, + recipientName: this.username, }, }); } catch (e) { @@ -214,6 +215,8 @@ export class User { resetPasswordLink, applicationUrl, applicationTitle, + recipientName: this.displayName, + recipientEmail: this.email, }, }); } catch (e) { diff --git a/server/lib/notifications/agents/email.ts b/server/lib/notifications/agents/email.ts index 6a06d718a..7cf45b47f 100644 --- a/server/lib/notifications/agents/email.ts +++ b/server/lib/notifications/agents/email.ts @@ -46,7 +46,8 @@ class EmailAgent private buildMessage( type: Notification, payload: NotificationPayload, - toEmail: string + recipientEmail: string, + recipientName?: string ): EmailOptions | undefined { const { applicationUrl, applicationTitle } = getSettings().main; @@ -54,12 +55,14 @@ class EmailAgent return { template: path.join(__dirname, '../../../templates/email/test-email'), message: { - to: toEmail, + to: recipientEmail, }, locals: { body: payload.message, applicationUrl, applicationTitle, + recipientName, + recipientEmail, }, }; } @@ -127,7 +130,7 @@ class EmailAgent '../../../templates/email/media-request' ), message: { - to: toEmail, + to: recipientEmail, }, locals: { requestType, @@ -143,6 +146,8 @@ class EmailAgent : undefined, applicationUrl, applicationTitle, + recipientName, + recipientEmail, }, }; } @@ -179,7 +184,12 @@ class EmailAgent payload.notifyUser.settings?.pgpKey ); await email.send( - this.buildMessage(type, payload, payload.notifyUser.email) + this.buildMessage( + type, + payload, + payload.notifyUser.email, + payload.notifyUser.displayName + ) ); } catch (e) { logger.error('Error sending email notification', { @@ -228,7 +238,9 @@ class EmailAgent this.getSettings(), user.settings?.pgpKey ); - await email.send(this.buildMessage(type, payload, user.email)); + await email.send( + this.buildMessage(type, payload, user.email, user.displayName) + ); } catch (e) { logger.error('Error sending email notification', { label: 'Notifications', diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index bb58e68b6..e6fa09cdb 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -109,7 +109,7 @@ router.post( const user = new User({ avatar: body.avatar ?? avatar, - username: body.username ?? body.email, + username: body.username, email: body.email, password: body.password, permissions: settings.main.defaultPermissions, diff --git a/server/templates/email/generatedpassword/html.pug b/server/templates/email/generatedpassword/html.pug index 129695abb..2fcb2e095 100644 --- a/server/templates/email/generatedpassword/html.pug +++ b/server/templates/email/generatedpassword/html.pug @@ -6,25 +6,6 @@ head meta(name='viewport' content='width=device-width, initial-scale=1') meta(name='format-detection' content='telephone=no, date=no, address=no, email=no') link(href='https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap' rel='stylesheet' media='screen') - //if mso - xml - o:officedocumentsettings - o:pixelsperinch 96 - style. - td, - th, - div, - p, - a, - h1, - h2, - h3, - h4, - h5, - h6 { - font-family: 'Segoe UI', sans-serif; - mso-line-height-rule: exactly; - } style. .title:hover * { text-decoration: underline; @@ -35,28 +16,35 @@ head width: 100% !important; } } -div(style='display: block; background-color: #111827;') - table(style='margin: 0 auto; font-family: Inter, Arial, Sans-Serif; color: #fff; font-size: 16px; width: 26rem;') +div(style='display: block; background-color: #111827; padding: 2.5rem 0;') + table(style='margin: 0 auto; font-family: Inter, Arial, sans-serif; color: #fff; font-size: 16px; width: 26rem;') tr td(style="text-align: center;") - a(href=applicationUrl) - img(src=applicationUrl +'/logo_full.png' style='width: 26rem; padding: 1rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') + if applicationUrl + a(href=applicationUrl style='margin: 0 1rem;') + img(src=applicationUrl +'/logo_full.png' style='width: 26rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') + else + div(style='margin: 0 1rem 2.5rem; font-size: 3em; font-weight: 700;') + | #{applicationTitle} + if recipientName !== recipientEmail + tr + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | Hi, #{recipientName.replace(/\.|@/g, ((x) => x + '\ufeff'))}! tr td(style='text-align: center;') - div(style='margin: 0rem 1rem 1rem; font-size: 1.25em;') - span - | An account has been created for you at #{applicationTitle}. + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | An account has been created for you at #{applicationTitle}. tr td(style='text-align: center;') - div(style='margin: 1rem 1rem 1rem; font-size: 1.25em;') - span - | Your new password is: - div(style='margin: 0rem 1rem 1rem; font-size: 1.25em;') - span + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | Your password is: + div(style='font-size: 1.25em; font-weight: 500; line-height: 2.25em;') + span(style='padding: 0.5rem; font-weight: 500; border: 1px solid rgb(100,100,100); font-family: monospace;') | #{password} if applicationUrl tr td - a(href=applicationUrl style='display: block; margin: 1.5rem 3rem 2.5rem 3rem; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') - span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255, 255, 255, 0.2);') + a(href=applicationUrl style='display: block; margin: 1.5rem 3rem 0; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') + span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255,255,255,0.2);') | Open #{applicationTitle} diff --git a/server/templates/email/media-request/html.pug b/server/templates/email/media-request/html.pug index 6d3a97403..83db48d4b 100644 --- a/server/templates/email/media-request/html.pug +++ b/server/templates/email/media-request/html.pug @@ -6,25 +6,6 @@ head meta(name='viewport' content='width=device-width, initial-scale=1') meta(name='format-detection' content='telephone=no, date=no, address=no, email=no') link(href='https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap' rel='stylesheet' media='screen') - //if mso - xml - o:officedocumentsettings - o:pixelsperinch 96 - style. - td, - th, - div, - p, - a, - h1, - h2, - h3, - h4, - h5, - h6 { - font-family: 'Segoe UI', sans-serif; - mso-line-height-rule: exactly; - } style. .title:hover * { text-decoration: underline; @@ -35,31 +16,38 @@ head width: 100% !important; } } -div(style='display: block; background-color: #111827;') - table(style='margin: 0 auto; font-family: Inter, Arial, Sans-Serif; color: #fff; font-size: 16px; width: 26rem;') +div(style='display: block; background-color: #111827; padding: 2.5rem 0;') + table(style='margin: 0 auto; font-family: Inter, Arial, sans-serif; color: #fff; font-size: 16px; width: 26rem;') tr td(style="text-align: center;") - a(href=applicationUrl) - img(src=applicationUrl +'/logo_full.png' style='width: 26rem; padding: 1rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') + if applicationUrl + a(href=applicationUrl style='margin: 0 1rem;') + img(src=applicationUrl +'/logo_full.png' style='width: 26rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') + else + div(style='margin: 0 1rem 2.5rem; font-size: 3em; font-weight: 700;') + | #{applicationTitle} + if recipientName !== recipientEmail + tr + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | Hi, #{recipientName.replace(/\.|@/g, ((x) => x + '\ufeff'))}! tr td(style='text-align: center;') - div(style='margin: 0rem 1rem 1rem; font-size: 1.25em;') - span - | #{body} + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | #{body} tr td - div(style='box-sizing: border-box; margin: 0; width: 100%; color: #fff; border-radius: .75rem; padding: 1rem; border: 1px solid rgba(100, 100, 100, 1); background: linear-gradient(135deg, rgba(17, 24, 39, 0.47) 0%, rgb(17, 24, 39) 75%), url(' + imageUrl + ') center 25%/cover') + div(style='box-sizing: border-box; margin: 1.5rem 0 0; width: 100%; color: #fff; border-radius: .75rem; padding: 1rem; border: 1px solid rgb(100,100,100); background: linear-gradient(135deg, rgba(17,24,39,0.47) 0%, rgb(17,24,39) 75%), url(' + imageUrl + ') center 25%/cover') table(style='color: #fff; width: 100%;') tr td(style='vertical-align: top;') a(href=actionUrl style='display: block; max-width: 20rem; color: #fff; font-weight: 700; text-decoration: none; margin: 0 1rem 0.25rem 0; font-size: 1.3em; line-height: 1.25em; margin-bottom: 5px;' class='title') - span - | #{mediaName} + | #{mediaName} div(style='overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #d1d5db; font-size: .975em; line-height: 1.45em; padding-top: .25rem; padding-bottom: .25rem;') span(style='display: block;') b(style='color: #9ca3af; font-weight: 700;') | Requested By  - | #{requestedBy} + | #{requestedBy.replace(/\.|@/g, ((x) => x + '\ufeff'))} each extra in mediaExtra span(style='display: block;') b(style='color: #9ca3af; font-weight: 700;') @@ -76,6 +64,6 @@ div(style='display: block; background-color: #111827;') if actionUrl tr td - a(href=actionUrl style='display: block; margin: 1.5rem 3rem 2.5rem 3rem; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') - span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255, 255, 255, 0.2);') + a(href=actionUrl style='display: block; margin: 1.5rem 3rem 0; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') + span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255,255,255,0.2);') | Open in #{applicationTitle} diff --git a/server/templates/email/resetpassword/html.pug b/server/templates/email/resetpassword/html.pug index a6fcc6468..3c800663a 100644 --- a/server/templates/email/resetpassword/html.pug +++ b/server/templates/email/resetpassword/html.pug @@ -6,25 +6,6 @@ head meta(name='viewport' content='width=device-width, initial-scale=1') meta(name='format-detection' content='telephone=no, date=no, address=no, email=no') link(href='https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap' rel='stylesheet' media='screen') - //if mso - xml - o:officedocumentsettings - o:pixelsperinch 96 - style. - td, - th, - div, - p, - a, - h1, - h2, - h3, - h4, - h5, - h6 { - font-family: 'Segoe UI', sans-serif; - mso-line-height-rule: exactly; - } style. .title:hover * { text-decoration: underline; @@ -35,25 +16,35 @@ head width: 100% !important; } } -div(style='display: block; background-color: #111827;') - table(style='margin: 0 auto; font-family: Inter, Arial, Sans-Serif; color: #fff; font-size: 16px; width: 26rem;') +div(style='display: block; background-color: #111827; padding: 2.5rem 0;') + table(style='margin: 0 auto; font-family: Inter, Arial, sans-serif; color: #fff; font-size: 16px; width: 26rem;') tr td(style="text-align: center;") - a(href=applicationUrl) - img(src=applicationUrl +'/logo_full.png' style='width: 26rem; padding: 1rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') - tr - td(style='text-align: center;') - div(style='margin: 0rem 1rem 1rem; font-size: 1.25em;') - span - | Your #{applicationTitle} account password was requested to be reset. Click below to reset your password. - if resetPasswordLink + if applicationUrl + a(href=applicationUrl style='margin: 0 1rem;') + img(src=applicationUrl +'/logo_full.png' style='width: 26rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') + else + div(style='margin: 0 1rem 2.5rem; font-size: 3em; font-weight: 700;') + | #{applicationTitle} + if recipientName !== recipientEmail tr - td - a(href=resetPasswordLink style='display: block; margin: 1.5rem 3rem 2.5rem 3rem; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') - span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255, 255, 255, 0.2);') - | Reset Password + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | Hi, #{recipientName.replace(/\.|@/g, ((x) => x + '\ufeff'))}! tr td(style='text-align: center;') - div(style='margin: 1rem; font-size: .85em;') - span - | If you did not request that your password be reset, you can safely ignore this email. + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | A request has been received to change the password for your #{applicationTitle} account. + tr + td + a(href=resetPasswordLink style='display: block; margin: 1.5rem 3rem; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') + span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255,255,255,0.2);') + | Reset Password + tr + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | The above link will expire in 24 hours. + tr + td(style='text-align: center;') + div(style='margin: 1rem 1rem 0; font-size: 1.25em;') + | If you did not initiate this request, you may safely disregard this message. \ No newline at end of file diff --git a/server/templates/email/test-email/html.pug b/server/templates/email/test-email/html.pug index 9dc9044c0..3ba83a936 100644 --- a/server/templates/email/test-email/html.pug +++ b/server/templates/email/test-email/html.pug @@ -6,25 +6,6 @@ head meta(name='viewport' content='width=device-width, initial-scale=1') meta(name='format-detection' content='telephone=no, date=no, address=no, email=no') link(href='https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap' rel='stylesheet' media='screen') - //if mso - xml - o:officedocumentsettings - o:pixelsperinch 96 - style. - td, - th, - div, - p, - a, - h1, - h2, - h3, - h4, - h5, - h6 { - font-family: 'Segoe UI', sans-serif; - mso-line-height-rule: exactly; - } style. .title:hover * { text-decoration: underline; @@ -35,20 +16,28 @@ head width: 100% !important; } } -div(style='display: block; background-color: #111827;') - table(style='margin: 0 auto; font-family: Inter, Arial, Sans-Serif; color: #fff; font-size: 16px; width: 26rem;') +div(style='display: block; background-color: #111827; padding: 2.5rem 0;') + table(style='margin: 0 auto; font-family: Inter, Arial, sans-serif; color: #fff; font-size: 16px; width: 26rem;') tr td(style="text-align: center;") - a(href=applicationUrl) - img(src=applicationUrl +'/logo_full.png' style='width: 26rem; padding: 1rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') + if applicationUrl + a(href=applicationUrl style='margin: 0 1rem;') + img(src=applicationUrl +'/logo_full.png' style='width: 26rem; image-rendering: crisp-edges; image-rendering: -webkit-optimize-contrast;') + else + div(style='margin: 0 1rem 2.5rem; font-size: 3em; font-weight: 700;') + | #{applicationTitle} + if recipientName !== recipientEmail + tr + td(style='text-align: center;') + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | Hi, #{recipientName.replace(/\.|@/g, ((x) => x + '\ufeff'))}! tr td(style='text-align: center;') - div(style='margin: 0rem 1rem 1rem; font-size: 1.25em;') - span - | #{body} + div(style='margin: 1rem 0 0; font-size: 1.25em;') + | #{body} if applicationUrl tr td - a(href=applicationUrl style='display: block; margin: 1.5rem 3rem 2.5rem 3rem; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') - span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255, 255, 255, 0.2);') + a(href=applicationUrl style='display: block; margin: 1.5rem 3rem 0; text-decoration: none; font-size: 1.0em; line-height: 2.25em;') + span(style='padding: 0.2rem; font-weight: 500; text-align: center; border-radius: 10px; background-color: rgb(99,102,241); color: #fff; display: block; border: 1px solid rgba(255,255,255,0.2);') | Open #{applicationTitle} From 0a6ef6cc81376f7a02f1483109be7ae4ab851c48 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Mon, 18 Oct 2021 16:45:58 +0200 Subject: [PATCH 078/238] feat(lang): translations update from Weblate (#2210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(lang): translated using Weblate (Greek) Currently translated at 93.8% (835 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/el/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Italian) Currently translated at 98.6% (878 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/it/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Japanese) Currently translated at 56.8% (506 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Japanese) Currently translated at 54.5% (485 of 889 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ja/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Serbian) Currently translated at 16.1% (144 of 890 strings) Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Norwegian Bokmål) Currently translated at 74.8% (666 of 890 strings) Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nb_NO/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (French) Currently translated at 98.7% (879 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (French) Currently translated at 99.3% (883 of 889 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/fr/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (German) Currently translated at 98.6% (878 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Spanish) Currently translated at 99.5% (886 of 890 strings) feat(lang): translated using Weblate (Spanish) Currently translated at 99.5% (886 of 890 strings) feat(lang): translated using Weblate (Spanish) Currently translated at 98.8% (880 of 890 strings) feat(lang): translated using Weblate (Spanish) Currently translated at 98.8% (880 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: Ricardo González Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/es/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Russian) Currently translated at 99.7% (888 of 890 strings) feat(lang): translated using Weblate (Russian) Currently translated at 99.7% (888 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: Sergey Moiseev Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ru/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Catalan) Currently translated at 98.4% (876 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ca/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Czech) Currently translated at 44.3% (395 of 890 strings) Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/cs/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Danish) Currently translated at 5.5% (49 of 890 strings) Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/da/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Polish) Currently translated at 0.0% (0 of 890 strings) Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pl/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (890 of 890 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (890 of 890 strings) feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (890 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: Shjosan Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 98.9% (881 of 890 strings) feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 98.9% (881 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 99.1% (881 of 889 strings) Co-authored-by: Eric Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hans/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (890 of 890 strings) feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (890 of 890 strings) feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (890 of 890 strings) feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 99.8% (888 of 889 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (889 of 889 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Co-authored-by: 주서현 Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Portuguese (Portugal)) Currently translated at 98.0% (873 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_PT/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Hungarian) Currently translated at 98.3% (875 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/hu/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (890 of 890 strings) feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (890 of 890 strings) feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (890 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. feat(lang): translated using Weblate (Dutch) Currently translated at 100.0% (889 of 889 strings) Co-authored-by: Hosted Weblate Co-authored-by: Kobe Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/nl/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Portuguese (Brazil)) Currently translated at 99.5% (886 of 890 strings) feat(lang): translated using Weblate (Portuguese (Brazil)) Currently translated at 99.5% (886 of 890 strings) Update translation files Updated by "Cleanup translation files" hook in Weblate. Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Co-authored-by: Tijuco Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/pt_BR/ Translation: Overseerr/Overseerr Frontend Co-authored-by: TheCatLady Co-authored-by: Ricardo González Co-authored-by: Sergey Moiseev Co-authored-by: Shjosan Co-authored-by: Eric Co-authored-by: 주서현 Co-authored-by: Kobe Co-authored-by: Tijuco --- src/i18n/locale/ca.json | 4 +- src/i18n/locale/cs.json | 3 +- src/i18n/locale/da.json | 3 +- src/i18n/locale/de.json | 4 +- src/i18n/locale/el.json | 4 +- src/i18n/locale/es.json | 11 ++- src/i18n/locale/fr.json | 6 +- src/i18n/locale/hu.json | 4 +- src/i18n/locale/it.json | 4 +- src/i18n/locale/ja.json | 181 +++++++++++++++++++---------------- src/i18n/locale/nb_NO.json | 4 +- src/i18n/locale/nl.json | 19 ++-- src/i18n/locale/pl.json | 4 +- src/i18n/locale/pt_BR.json | 8 +- src/i18n/locale/pt_PT.json | 4 +- src/i18n/locale/ru.json | 21 ++-- src/i18n/locale/sr.json | 3 +- src/i18n/locale/sv.json | 19 ++-- src/i18n/locale/zh_Hans.json | 12 +-- src/i18n/locale/zh_Hant.json | 37 ++++--- 20 files changed, 206 insertions(+), 149 deletions(-) diff --git a/src/i18n/locale/ca.json b/src/i18n/locale/ca.json index 41a6fb452..8f472a06c 100644 --- a/src/i18n/locale/ca.json +++ b/src/i18n/locale/ca.json @@ -666,7 +666,6 @@ "components.Settings.Notifications.smtpHost": "Amfitrió SMTP", "components.Settings.Notifications.emailsettingsfailed": "No s'ha pogut desar la configuració de les notificacions per correu electrònic.", "components.Settings.RadarrModal.apiKey": "Clau API", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Els últims canvis a la branca develop de Overseerr no es mostren a continuació. Vegeu l'historial de pujades d'aquesta branca a GitHub per a més detalls.", "components.Settings.SettingsAbout.Releases.releases": "Versions", "components.Settings.SettingsAbout.Releases.releasedataMissing": "La informació de la versió no està disponible. GitHub està fora de línia?", "components.Settings.SettingsAbout.Releases.latestversion": "Última versió", @@ -879,5 +878,6 @@ "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Rep notificacions quan altres usuaris envien sol·licituds de mitjans nous que s’aprovin automàticament.", "components.MovieDetails.showmore": "Mostra més", "components.MovieDetails.showless": "Mostra menys", - "components.Layout.LanguagePicker.displaylanguage": "Idioma de visualització" + "components.Layout.LanguagePicker.displaylanguage": "Idioma de visualització", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/cs.json b/src/i18n/locale/cs.json index 8f3306b4f..881bc5ab3 100644 --- a/src/i18n/locale/cs.json +++ b/src/i18n/locale/cs.json @@ -437,5 +437,6 @@ "components.Settings.region": "Region pro vyhledávání", "components.Settings.startscan": "Spustit skenování", "components.Settings.serverpresetManualMessage": "Manuální konfigurace", - "components.Settings.sonarrsettings": "Nastavení Sonarru" + "components.Settings.sonarrsettings": "Nastavení Sonarru", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/da.json b/src/i18n/locale/da.json index 47f629619..7f2c663ba 100644 --- a/src/i18n/locale/da.json +++ b/src/i18n/locale/da.json @@ -47,5 +47,6 @@ "components.Discover.DiscoverMovieGenre.genreMovies": "{genre} Film", "components.CollectionDetails.requestcollection4k": "Ønskesamling i 4k", "components.CollectionDetails.requestcollection": "Ønskesamling", - "components.CollectionDetails.overview": "Overblik" + "components.CollectionDetails.overview": "Overblik", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/de.json b/src/i18n/locale/de.json index 610c77ca1..fb29bcea7 100644 --- a/src/i18n/locale/de.json +++ b/src/i18n/locale/de.json @@ -229,7 +229,6 @@ "components.Settings.SettingsAbout.Releases.viewongithub": "Auf GitHub anzeigen", "components.Settings.SettingsAbout.Releases.viewchangelog": "Änderungsprotokoll anzeigen", "components.Settings.SettingsAbout.Releases.versionChangelog": "Änderungsprotokoll", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Die neuesten Änderungen am Entwicklungszweig von Overseerr werden unten nicht angezeigt. Weitere Informationen findest du im Commit-Verlauf für diesen Zweig auf GitHub.", "components.Settings.SettingsAbout.Releases.releases": "Veröffentlichungen", "components.Settings.SettingsAbout.Releases.releasedataMissing": "Informationen der Version nicht verfügbar. Ist GitHub offline?", "components.Settings.SettingsAbout.Releases.latestversion": "Neuste", @@ -881,5 +880,6 @@ "components.MovieDetails.showless": "Weniger Anzeigen", "components.MovieDetails.showmore": "Mehr Anzeigen", "components.MovieDetails.streamingproviders": "Streamt derzeit auf", - "components.TvDetails.streamingproviders": "Streamt derzeit auf" + "components.TvDetails.streamingproviders": "Streamt derzeit auf", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/el.json b/src/i18n/locale/el.json index 49a8b5c56..5d5805052 100644 --- a/src/i18n/locale/el.json +++ b/src/i18n/locale/el.json @@ -406,7 +406,6 @@ "components.Settings.SettingsJobsCache.cacheDescription": "Το Overseerr αποθηκεύει προσωρινά αιτήματα σε εξωτερικά τελικά σημεία API για τη βελτιστοποίηση της απόδοσης και την αποφυγή περιττών κλήσεων API.", "components.Settings.SettingsJobsCache.cache": "Κρυφή μνήμη", "components.Settings.SettingsAbout.version": "Έκδοση", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Οι τελευταίες αλλαγές στο develop branch του Overseerr δεν φαίνονται παρακάτω. Παρακαλώ ανάτρεξε στο ιστορικό δευσμεύσεων του branch στο GitHub για λεπτομέρειες.", "components.Settings.SettingsAbout.Releases.releases": "Εκδόσεις", "components.Settings.SettingsAbout.Releases.releasedataMissing": "Οι εκδόσεις λογισμικού δεν είναι διαθέσιμες. Μήπως έχει πέσει το GitHub;", "components.Settings.SettingsAbout.Releases.latestversion": "Πιο πρόσφατη", @@ -854,5 +853,6 @@ "i18n.test": "Δοκιμή", "i18n.status": "Κατάσταση", "i18n.showingresults": "Εμφάνιση {from} έως {to} από {total} αποτελέσματα", - "components.Settings.SettingsAbout.betawarning": "Αυτό είναι λογισμικό BETA. Οι λειτουργίες ενδέχεται να είναι σπασμένες ή/και ασταθείς. Παρακαλώ αναφέρετε τυχόν προβλήματα με το GitHub!" + "components.Settings.SettingsAbout.betawarning": "Αυτό είναι λογισμικό BETA. Οι λειτουργίες ενδέχεται να είναι σπασμένες ή/και ασταθείς. Παρακαλώ αναφέρετε τυχόν προβλήματα με το GitHub!", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/es.json b/src/i18n/locale/es.json index dc234b182..a37e0b288 100644 --- a/src/i18n/locale/es.json +++ b/src/i18n/locale/es.json @@ -228,7 +228,6 @@ "components.Settings.SettingsAbout.Releases.viewongithub": "Ver en GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Ver registro de cambios", "components.Settings.SettingsAbout.Releases.versionChangelog": "Cambios de la versión", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Los últimos cambios de la rama de desarrollo de Overserr no se muestran a continuación. Por favor, consulta la historia de subidas de esta rama en GitHub para obtener los detalles.", "components.Settings.SettingsAbout.Releases.releases": "Versiones", "components.Settings.SettingsAbout.Releases.releasedataMissing": "Información de la versión no disponible. ¿GitHub está caído?", "components.Settings.SettingsAbout.Releases.latestversion": "Última Versión", @@ -881,5 +880,13 @@ "components.Settings.Notifications.botApiTip": "Crea un bot para usar con Overseerr", "components.Settings.Notifications.encryptionTip": "Normalmente, TLS Implícito usa el puerto 465 y STARTTLS usa el puerto 587", "components.UserList.localLoginDisabled": "El ajuste para Habilitar el Inicio de Sesión Local está actualmente deshabilitado.", - "components.Settings.SettingsUsers.defaultPermissionsTip": "Permisos iniciales asignados a nuevos usuarios" + "components.Settings.SettingsUsers.defaultPermissionsTip": "Permisos iniciales asignados a nuevos usuarios", + "components.Settings.SettingsAbout.runningDevelop": "Estás utilizando la rama de develop de Overseerr, la cual solo se recomienda para aquellos que contribuyen al desarrollo o al soporte de las pruebas de nuevos desarrollos.", + "components.StatusBadge.status": "{status}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Cada {jobScheduleMinutes, plural, one {minuto} other {{jobScheduleMinutes} minutos}}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Cada {jobScheduleHours, plural, one {hora} other {{jobScheduleHours} horas}}", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Algo fue mal al guardar la tarea programada.", + "components.Settings.SettingsJobsCache.editJobSchedule": "Modificar tarea programada", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frecuencia", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "¡Tarea programada modificada con éxito!" } diff --git a/src/i18n/locale/fr.json b/src/i18n/locale/fr.json index 1b351b079..395af4b6f 100644 --- a/src/i18n/locale/fr.json +++ b/src/i18n/locale/fr.json @@ -221,7 +221,7 @@ "components.Settings.toastApiKeyFailure": "Une erreur s'est produite lors de la génération de la nouvelle clé API.", "components.Settings.SonarrModal.animerootfolder": "Dossier racine pour anime", "components.Settings.SonarrModal.animequalityprofile": "Profil qualité pour anime", - "components.MovieDetails.studio": "{studioCount, plural, one {Studio} autre {Studios}}", + "components.MovieDetails.studio": "{studioCount, plural, one {Studio} other {Studios}}", "components.Settings.SettingsAbout.supportoverseerr": "Soutenez Overseerr", "i18n.close": "Fermer", "components.Settings.SettingsAbout.timezone": "Fuseau horaire", @@ -229,7 +229,6 @@ "components.Settings.SettingsAbout.Releases.viewongithub": "Voir sur GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Voir le journal des modifications", "components.Settings.SettingsAbout.Releases.versionChangelog": "Journal des modifications de version", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Les dernières modifications apportées à la branche develop d'Overseerr ne sont pas affichées ci-dessous. Veuillez consulter l'historique des validations de cette branche sur GitHub pour plus de détails.", "components.Settings.SettingsAbout.Releases.releases": "Versions", "components.Settings.SettingsAbout.Releases.releasedataMissing": "Données de sortie indisponibles. GitHub est-il en panne ?", "components.Settings.SettingsAbout.Releases.latestversion": "Dernière version", @@ -881,5 +880,6 @@ "components.UserList.localLoginDisabled": "Le paramètre Activer la connexion locale est actuellement désactivé.", "components.TvDetails.streamingproviders": "Disponible en streaming sur", "components.MovieDetails.streamingproviders": "Actuellement diffusé sur", - "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Créer une Webhook entrant intégration" + "components.Settings.Notifications.NotificationsSlack.webhookUrlTip": "Créer une Webhook entrant intégration", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/hu.json b/src/i18n/locale/hu.json index 0ae6c90fd..2f15b95dd 100644 --- a/src/i18n/locale/hu.json +++ b/src/i18n/locale/hu.json @@ -518,7 +518,6 @@ "components.Settings.SettingsAbout.Releases.viewongithub": "Megtekintés a GitHubon", "components.Settings.SettingsAbout.Releases.viewchangelog": "Változásnapló megtekintése", "components.Settings.SettingsAbout.Releases.versionChangelog": "Verzió változásnapló", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Az Overseerr develop ágának legutóbbi módosításait az alábbiakban nem mutatjuk be. A részletekért lásd az ág \"commit history\"-ját a GitHub-on.", "components.Settings.SettingsAbout.Releases.releases": "Kiadások", "components.Settings.SettingsAbout.Releases.releasedataMissing": "Kiadási adatok nem állnak rendelkezésre. A GitHub nem elérhető?", "components.Settings.SettingsAbout.Releases.latestversion": "Legújabb", @@ -879,5 +878,6 @@ "components.ResetPassword.gobacklogin": "Vissza a bejelentkező lapra", "components.ResetPassword.emailresetlink": "Email visszaállítási link", "components.ResetPassword.email": "Email cím", - "components.ResetPassword.confirmpassword": "Jelszó megerősítése" + "components.ResetPassword.confirmpassword": "Jelszó megerősítése", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/it.json b/src/i18n/locale/it.json index de8699116..593673b8f 100644 --- a/src/i18n/locale/it.json +++ b/src/i18n/locale/it.json @@ -229,7 +229,6 @@ "components.Settings.SettingsAbout.Releases.viewongithub": "Visualizza su GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Visualizza il registro modifiche", "components.Settings.SettingsAbout.Releases.versionChangelog": "Registro versione", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Le ultime modifiche di Overserr develop non sono mostrate di seguito. Consulta GitHub per maggiori dettagli.", "components.Settings.SettingsAbout.Releases.releases": "Versioni", "components.Settings.SettingsAbout.Releases.releasedataMissing": "Dati di versione non disponibili. GitHub è down?", "components.Settings.SettingsAbout.Releases.latestversion": "Versione più recente", @@ -881,5 +880,6 @@ "components.MovieDetails.showmore": "Mostra di più", "components.MovieDetails.showless": "Mostra meno", "components.TvDetails.streamingproviders": "Ora in streaming su", - "components.MovieDetails.streamingproviders": "Ora in streaming su" + "components.MovieDetails.streamingproviders": "Ora in streaming su", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/ja.json b/src/i18n/locale/ja.json index af905713f..fd4713dec 100644 --- a/src/i18n/locale/ja.json +++ b/src/i18n/locale/ja.json @@ -38,9 +38,9 @@ "components.RequestModal.cancel": "キャンセルリクエスト", "components.RequestModal.extras": "おまけ", "components.RequestModal.numberofepisodes": "エピソード数", - "components.RequestModal.pendingrequest": "{title}がリクエスト中", - "components.RequestModal.requestCancel": "{title}のリクエストは取り消されました。", - "components.RequestModal.requestSuccess": "{title}のリクエストは完了しました。", + "components.RequestModal.pendingrequest": "{title} がリクエスト中", + "components.RequestModal.requestCancel": "{title} のリクエストは取り消されました。", + "components.RequestModal.requestSuccess": "{title} のリクエストは完了しました。", "components.RequestModal.requestadmin": "このリクエストは今すぐ承認されます。", "components.RequestModal.requestfrom": "{username} はすでにリクエストを上げています。", "components.RequestModal.requestseasons": "{seasonCount} シーズンをリクエスト", @@ -55,8 +55,8 @@ "components.Settings.Notifications.emailsender": "配信元メールアドレス", "components.Settings.Notifications.smtpHost": "SMTP ホスト", "components.Settings.Notifications.smtpPort": "SMTP ポート", - "components.Settings.Notifications.validationSmtpHostRequired": "SMTPホストの入力は必要です", - "components.Settings.Notifications.validationSmtpPortRequired": "SMTPポートの入力は必要です", + "components.Settings.Notifications.validationSmtpHostRequired": "有効なホスト名・IP アドレスを入力してください", + "components.Settings.Notifications.validationSmtpPortRequired": "有効なポートを入力してください", "components.Settings.Notifications.webhookUrl": "ウェブフック URL", "components.Settings.RadarrModal.add": "サーバーを追加", "components.Settings.RadarrModal.apiKey": "API キー", @@ -64,7 +64,7 @@ "components.Settings.RadarrModal.createradarr": "Radarr サーバーを追加", "components.Settings.RadarrModal.defaultserver": "デフォルトサーバー", "components.Settings.RadarrModal.editradarr": "Radarr サーバーを編集", - "components.Settings.RadarrModal.hostname": "ホスト名", + "components.Settings.RadarrModal.hostname": "ホスト名・IP アドレス", "components.Settings.RadarrModal.minimumAvailability": "最低リリース状況", "components.Settings.RadarrModal.port": "ポート", "components.Settings.RadarrModal.qualityprofile": "画質プロファイル", @@ -76,19 +76,19 @@ "components.Settings.RadarrModal.servername": "サーバー名", "components.Settings.RadarrModal.ssl": "SSL を有効にする", "components.Settings.RadarrModal.toastRadarrTestFailure": "Radarr サーバーの接続は失敗しました。", - "components.Settings.RadarrModal.toastRadarrTestSuccess": "Radarrサーバーの接続は成功しました!", - "components.Settings.RadarrModal.validationApiKeyRequired": "API キーの入力が必要です", - "components.Settings.RadarrModal.validationHostnameRequired": "ホスト名/IPの入力が必要です", - "components.Settings.RadarrModal.validationPortRequired": "ポートの入力が必要です", - "components.Settings.RadarrModal.validationProfileRequired": "プロファイルの選択が必要です", - "components.Settings.RadarrModal.validationRootFolderRequired": "ルートフォルダーの選択が必要です", + "components.Settings.RadarrModal.toastRadarrTestSuccess": "Radarr サーバーの接続は成功しました!", + "components.Settings.RadarrModal.validationApiKeyRequired": "API キーを入力してください", + "components.Settings.RadarrModal.validationHostnameRequired": "有効なホスト名・IP アドレスを入力してください", + "components.Settings.RadarrModal.validationPortRequired": "有効なポートを入力してください", + "components.Settings.RadarrModal.validationProfileRequired": "プロファイルを選択してください", + "components.Settings.RadarrModal.validationRootFolderRequired": "ルートフォルダーを選択してください", "components.Settings.SonarrModal.add": "サーバーを追加", "components.Settings.SonarrModal.apiKey": "API キー", "components.Settings.SonarrModal.baseUrl": "ベース URL", "components.Settings.SonarrModal.createsonarr": "Sonarr サーバーを追加", "components.Settings.SonarrModal.defaultserver": "デフォルトサーバー", "components.Settings.SonarrModal.editsonarr": "Sonarr サーバーを編集", - "components.Settings.SonarrModal.hostname": "ホスト名", + "components.Settings.SonarrModal.hostname": "ホスト名・IP アドレス", "components.Settings.SonarrModal.port": "ポート", "components.Settings.SonarrModal.qualityprofile": "画質プロファイル", "components.Settings.SonarrModal.rootfolder": "ルートフォルダー", @@ -99,10 +99,10 @@ "components.Settings.SonarrModal.servername": "サーバー名", "components.Settings.SonarrModal.ssl": "SSL を有効にする", "components.Settings.SonarrModal.validationApiKeyRequired": "API キーの入力が必要です", - "components.Settings.SonarrModal.validationHostnameRequired": "ホスト名/IPの入力が必要です", - "components.Settings.SonarrModal.validationPortRequired": "ポートの入力が必要です", - "components.Settings.SonarrModal.validationProfileRequired": "プロファイルの選択が必要です", - "components.Settings.SonarrModal.validationRootFolderRequired": "ルートフォルダーの選択が必要です", + "components.Settings.SonarrModal.validationHostnameRequired": "有効なホスト名・IP アドレスを入力してください", + "components.Settings.SonarrModal.validationPortRequired": "有効なポートを入力してください", + "components.Settings.SonarrModal.validationProfileRequired": "プロファイルを選択してください", + "components.Settings.SonarrModal.validationRootFolderRequired": "ルートフォルダーを選択してください", "components.Settings.activeProfile": "アクティブプロファイル", "components.Settings.addradarr": "Radarr サーバーを追加", "components.Settings.address": "アドレス", @@ -117,10 +117,10 @@ "components.Settings.deleteserverconfirm": "このサーバーを削除しますか?", "components.Settings.generalsettings": "一般設定", "components.Settings.generalsettingsDescription": "Overseerr の構成に関する設定です。", - "components.Settings.hostname": "ホスト名/IP", + "components.Settings.hostname": "ホスト名・IP アドレス", "components.Settings.librariesRemaining": "残りのライブラリー:{count}", "components.Settings.manualscan": "手動ライブラリースキャン", - "components.Settings.manualscanDescription": "通常は24時間に一度しか実行されません。Overseerr は、Plex サーバーの最近追加されたフォルダをより頻繁にチェックします。初めて Plex を設定する場合は、一度手動でライブラリーをスキャンすることをお勧めします!", + "components.Settings.manualscanDescription": "通常は 24 時間に一度しか実行されません。Overseerr は、Plex サーバーの最近追加されたフォルダをより頻繁にチェックします。初めて Plex を設定する場合は、一度手動でライブラリーをスキャンすることをお勧めします。", "components.Settings.menuAbout": "Overseerr について", "components.Settings.menuGeneralSettings": "一般", "components.Settings.menuJobs": "ジョブ", @@ -160,10 +160,10 @@ "components.TvDetails.similar": "類似シリーズ", "components.UserList.admin": "管理者", "components.UserList.created": "作成日", - "components.UserList.lastupdated": "最終更新日", + "components.UserList.lastupdated": "更新日", "components.UserList.plexuser": "Plexユーザー", "components.UserList.role": "役割", - "components.UserList.totalrequests": "総リクエスト数", + "components.UserList.totalrequests": "リクエスト数", "components.UserList.user": "ユーザー", "components.UserList.userlist": "ユーザーリスト", "i18n.approve": "承認", @@ -182,20 +182,20 @@ "pages.oops": "ああ", "pages.returnHome": "ホームへ戻る", "components.TvDetails.TvCast.fullseriescast": "すべての出演者", - "components.Settings.validationPortRequired": "ポートの入力が必要です", - "components.Settings.validationHostnameRequired": "ホスト名/IPの入力が必要です", - "components.Settings.SonarrModal.validationNameRequired": "サーバー名を指定してください", + "components.Settings.validationPortRequired": "有効なポートを入力してください", + "components.Settings.validationHostnameRequired": "有効なホスト名・IP アドレスを入力してください", + "components.Settings.SonarrModal.validationNameRequired": "サーバー名を入力してください", "components.Settings.SettingsAbout.version": "バージョン", "components.Settings.SettingsAbout.totalrequests": "総リクエスト数", "components.Settings.SettingsAbout.totalmedia": "総メディア数", "components.Settings.SettingsAbout.overseerrinformation": "Overseerr 情報", "components.Settings.SettingsAbout.githubdiscussions": "GitHub ディスカッション", "components.Settings.SettingsAbout.gettingsupport": "サポート", - "components.Settings.RadarrModal.validationNameRequired": "サーバー名を指定してください", - "components.Settings.Notifications.emailsettingssaved": "メール通知設定が保存されました!", + "components.Settings.RadarrModal.validationNameRequired": "サーバー名を入力してください", + "components.Settings.Notifications.emailsettingssaved": "メール通知設定が保存されました。", "components.Settings.Notifications.emailsettingsfailed": "メール通知設定の保存に失敗しました。", "components.Settings.Notifications.discordsettingsfailed": "Discord の通知設定の保存に失敗しました。", - "components.Settings.Notifications.discordsettingssaved": "Discord の通知設定が保存されました!", + "components.Settings.Notifications.discordsettingssaved": "Discord の通知設定が保存されました。", "components.MovieDetails.MovieCast.fullcast": "すべての出演者", "i18n.deleting": "削除中…", "components.UserList.userdeleteerror": "ユーザーの削除する時に問題が発生しました。", @@ -208,7 +208,7 @@ "components.Setup.tip": "ヒント", "components.Settings.toastSettingsSuccess": "設定の変更は保存しました。", "components.Settings.toastSettingsFailure": "設定保存中に問題が発生しました。", - "components.Settings.toastApiKeySuccess": "新しい API キーが生成されました!", + "components.Settings.toastApiKeySuccess": "新しい API キーが生成されました。", "components.Settings.toastApiKeyFailure": "新しい API キーの生成中に問題が発生しました。", "components.Settings.SonarrModal.testFirstRootFolders": "ルートフォルダーをロードするには先に接続をテストしてください", "components.Settings.SonarrModal.testFirstQualityProfiles": "画質プロファイルをロードするには先に接続をテストしてください", @@ -219,7 +219,7 @@ "components.Settings.RadarrModal.validationMinimumAvailabilityRequired": "最低リリース状況を選択してください", "components.Settings.RadarrModal.testFirstRootFolders": "ルートフォルダーをロードするには先に接続をテストしてください", "components.Settings.RadarrModal.testFirstQualityProfiles": "画質プロファイルをロードするには先に接続をテストしてください", - "components.Settings.RadarrModal.loadingprofiles": "画質プロファイルの読込中…", + "components.Settings.RadarrModal.loadingprofiles": "画質プロファイル読込中…", "components.Settings.RadarrModal.loadingrootfolders": "ルートフォルダー読込中…", "components.MovieDetails.studio": "制作会社", "i18n.close": "閉じる", @@ -228,17 +228,16 @@ "components.Settings.SettingsAbout.helppaycoffee": "開発者のコーヒーのためにチップを", "components.Settings.SettingsAbout.Releases.viewongithub": "GitHub で見る", "components.Settings.SettingsAbout.Releases.viewchangelog": "変更履歴参照", - "components.Settings.SettingsAbout.Releases.versionChangelog": "バージョン変更履歴", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "お使いのバージョンのパッチノート以下に記載されていません。最新のアップデートはGitHubリポジトリをご覧ください。", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} の変更履歴", "components.Settings.SettingsAbout.Releases.releases": "リリース", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "リリースデータがありません。GitHub はダウンしていますか?", - "components.Settings.SettingsAbout.Releases.latestversion": "最新", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "リリースデータがありません。", + "components.Settings.SettingsAbout.Releases.latestversion": "最新のバージョン", "components.Settings.SettingsAbout.Releases.currentversion": "現在のバージョン", "components.MovieDetails.MovieCrew.fullcrew": "フルクルー", "components.MovieDetails.viewfullcrew": "フルクルーを表示", "components.CollectionDetails.requestswillbecreated": "以下のタイトルをリクエストします:", "components.CollectionDetails.requestcollection": "リクエストコレクション", - "components.CollectionDetails.requestSuccess": "{title}をリクエストしました!", + "components.CollectionDetails.requestSuccess": "{title} をリクエストしました。", "components.CollectionDetails.overview": "ストーリー", "components.CollectionDetails.numberofmovies": "{count} 本の映画", "i18n.requested": "リクエスト済み", @@ -246,7 +245,7 @@ "components.MovieDetails.watchtrailer": "予告編を見る", "components.UserList.importfromplexerror": "Plexからユーザーをインポート中に問題が発生しました。", "components.UserList.importfromplex": "Plexからユーザーをインポート", - "components.UserList.importedfromplex": "Plexから{userCount, plural, =0 {新ユーザーはインポートされませんでした。} one {新ユーザー #名をインポートしました。} other {新ユーザー #名をインポートしました。}}", + "components.UserList.importedfromplex": "Plex から新ユーザー {userCount} 名をインポートしました。", "components.TvDetails.viewfullcrew": "フルクルーを表示", "components.TvDetails.firstAirDate": "初放送日", "components.TvDetails.TvCrew.fullseriescrew": "フルシリーズクルー", @@ -255,7 +254,7 @@ "i18n.retry": "リトライ", "i18n.failed": "失敗", "components.Settings.Notifications.NotificationsSlack.webhookUrl": "ウェブフック URL", - "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack の通知設定が保存されました!", + "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Slack の通知設定が保存されました。", "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Slack の通知設定の保存に失敗しました。", "components.Login.signin": "ログイン", "components.Login.password": "パスワード", @@ -278,7 +277,7 @@ "components.Login.signinheader": "続けるにはログインしてください", "components.MovieDetails.downloadstatus": "ダウンロード状況", "components.MediaSlider.ShowMoreCard.seemore": "もっと見る", - "components.Login.validationpasswordrequired": "パスワードの入力が必要です", + "components.Login.validationpasswordrequired": "パスワードを入力してください", "components.ResetPassword.validationemailrequired": "有効なメールアドレスを入力してください", "components.Settings.Notifications.validationEmail": "有効なメールアドレスを入力してください", "components.UserList.validationEmail": "有効なメールアドレスを入力してください", @@ -334,13 +333,13 @@ "i18n.status": "状態", "components.TvDetails.originaltitle": "原題", "components.MovieDetails.originaltitle": "原題", - "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram のテスト通知が送信されました!", - "components.Settings.Notifications.toastEmailTestSuccess": "メールテスト通知が送信されました!", - "components.Settings.Notifications.toastDiscordTestSuccess": "Discord のテスト通知が送信されました!", - "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea のテスト通知が送信されました!", - "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet のテスト通知が送信されました!", - "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover のテスト通知が送信されました!", - "components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Slack のテスト通知が送信されました!", + "components.Settings.Notifications.toastTelegramTestSuccess": "Telegram のテスト通知が送信されました。", + "components.Settings.Notifications.toastEmailTestSuccess": "メールテスト通知が送信されました。", + "components.Settings.Notifications.toastDiscordTestSuccess": "Discord のテスト通知が送信されました。", + "components.Settings.Notifications.NotificationsLunaSea.toastLunaSeaTestSuccess": "LunaSea のテスト通知が送信されました。", + "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet のテスト通知が送信されました。", + "components.Settings.Notifications.NotificationsPushover.toastPushoverTestSuccess": "Pushover のテスト通知が送信されました。", + "components.Settings.Notifications.NotificationsSlack.toastSlackTestSuccess": "Slack のテスト通知が送信されました。", "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingsfailed": "Telegram の通知設定の保存に失敗しました。", "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingsfailed": "メール通知設定の保存に失敗しました。", "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingsfailed": "Discord の通知設定の保存に失敗しました。", @@ -348,19 +347,19 @@ "components.Settings.Notifications.NotificationsLunaSea.settingsFailed": "LunaSea の通知設定の保存に失敗しました。", "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsFailed": "Pushbullet の通知設定の保存に失敗しました。", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Pushover の通知設定の保存に失敗しました。", - "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram の通知設定が保存されました!", - "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "メール通知設定が保存されました!", - "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Discord の通知設定が保存されました!", - "components.Settings.Notifications.telegramsettingssaved": "Telegram の通知設定が保存されました!", - "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea の通知設定が保存されました!", - "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet の通知設定が保存されました!", - "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover の通知設定が保存されました!", - "components.ResetPassword.validationpasswordrequired": "パスワードの入力が必要です", - "components.Layout.VersionStatus.commitsbehind": "{commitsBehind} {commitsBehind, plural, one {commit} other {commits}} behind", + "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram の通知設定が保存されました。", + "components.UserProfile.UserSettings.UserNotificationSettings.emailsettingssaved": "メール通知設定が保存されました。", + "components.UserProfile.UserSettings.UserNotificationSettings.discordsettingssaved": "Discord の通知設定が保存されました。", + "components.Settings.Notifications.telegramsettingssaved": "Telegram の通知設定が保存されました。", + "components.Settings.Notifications.NotificationsLunaSea.settingsSaved": "LunaSea の通知設定が保存されました。", + "components.Settings.Notifications.NotificationsPushbullet.pushbulletSettingsSaved": "Pushbullet の通知設定が保存されました。", + "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Pushover の通知設定が保存されました。", + "components.ResetPassword.validationpasswordrequired": "パスワードを入力してください", + "components.Layout.VersionStatus.commitsbehind": "", "components.Layout.LanguagePicker.displaylanguage": "表示言語", "components.LanguageSelector.originalLanguageDefault": "すべての言語", - "components.LanguageSelector.languageServerDefault": "デフォルト言語 ({language})", - "components.DownloadBlock.estimatedtime": "所要時間 {time}", + "components.LanguageSelector.languageServerDefault": "デフォルト言語({language})", + "components.DownloadBlock.estimatedtime": "所要時間:{time}", "components.Discover.upcomingtv": "今後リリースされるシリーズ", "components.Discover.noRequests": "リクエストが有りません。", "components.Discover.TvGenreSlider.tvgenres": "シリーズのジャンル", @@ -373,7 +372,7 @@ "components.Discover.DiscoverNetwork.networkSeries": "{network}シーリーズ", "components.Discover.DiscoverMovieLanguage.languageMovies": "{language}の映画", "components.Discover.DiscoverMovieGenre.genreMovies": "{genre}映画", - "components.RequestModal.pending4krequest": "{title}が4Kリクエスト中", + "components.RequestModal.pending4krequest": "{title} が 4K リクエスト中", "components.RequestModal.errorediting": "リクエストを編集するときに問題が発生しました。", "components.RequestModal.edit": "リクエストを編集", "components.RequestModal.autoapproval": "自動承認", @@ -382,15 +381,15 @@ "components.RequestModal.SearchByNameModal.nosummary": "このタイトルの概要は見つかりませんでした。", "components.RequestModal.QuotaDisplay.seasonlimit": "シーズン", "components.RequestModal.QuotaDisplay.season": "シーズン", - "components.RequestModal.QuotaDisplay.requiredquotaUser": "このユーザーはこのシリーズをリクエストするには、最低でも{シーズン}シーズンリクエストが残っている必要があります。", - "components.RequestModal.QuotaDisplay.requiredquota": "このシリーズをリクエストするには、最低でも{シーズン}シーズンリクエストが残っている必要があります。", - "components.RequestModal.QuotaDisplay.requestsremaining": "残り{remaining, plural, =0 {0} other {#}} {type} {remaining, plural, one {リクエスト} other {リクエスト}}", + "components.RequestModal.QuotaDisplay.requiredquotaUser": "このユーザーはこのシリーズをリクエストするには、最低でも {seasons} シーズンリクエストが残っている必要があります。", + "components.RequestModal.QuotaDisplay.requiredquota": "このシリーズをリクエストするには、最低でも {seasons} シーズンリクエストが残っている必要があります。", + "components.RequestModal.QuotaDisplay.requestsremaining": "残り {remaining} {type}リクエスト", "components.RequestModal.QuotaDisplay.quotaLinkUser": "このユーザーのリクエスト制限はプロフィールページで確認できます。", "components.RequestModal.QuotaDisplay.quotaLink": "リクエスト制限はプロフィールページで確認できます。", "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "シーズンリクエストが足りません。", - "components.RequestModal.QuotaDisplay.movielimit": "{limit, plural, one {映画} other {映画}}", - "components.RequestModal.QuotaDisplay.allowedRequestsUser": "このユーザーは {days} 日ごとに {limit} {type}をリクエストできます。", - "components.RequestModal.QuotaDisplay.allowedRequests": " {days} 日ごとに {limit} {type}をリクエストできます。", + "components.RequestModal.QuotaDisplay.movielimit": "映画", + "components.RequestModal.QuotaDisplay.allowedRequestsUser": "このユーザーは {days} 日ごとに {limit} {type}をリクエストできます。", + "components.RequestModal.QuotaDisplay.allowedRequests": "{days} 日ごとに {limit} {type}をリクエストできます。", "components.RequestModal.AdvancedRequester.tags": "タグ", "components.RequestModal.AdvancedRequester.selecttags": "タグの選択", "components.RequestModal.AdvancedRequester.rootfolder": "ルートフォルダ", @@ -398,9 +397,9 @@ "components.RequestModal.AdvancedRequester.qualityprofile": "画質プロファイル", "components.RequestModal.AdvancedRequester.notagoptions": "タグなし。", "components.RequestModal.AdvancedRequester.languageprofile": "言語プロフィール", - "components.RequestModal.AdvancedRequester.folder": "{path} ({space})", + "components.RequestModal.AdvancedRequester.folder": "{path}({space})", "components.RequestModal.AdvancedRequester.destinationserver": "送信先サーバー", - "components.RequestModal.AdvancedRequester.default": "{name} (デフォルト)", + "components.RequestModal.AdvancedRequester.default": "{name}(デフォルト)", "components.RequestModal.AdvancedRequester.animenote": "* このシリーズはアニメです。", "components.RequestModal.AdvancedRequester.advancedoptions": "詳細オプション", "components.RequestButton.viewrequest4k": "4Kリクエストを表示", @@ -409,10 +408,10 @@ "components.RequestList.sortModified": "最終更新日", "components.RequestList.sortAdded": "リクエスト日", "components.RequestList.showallrequests": "すべてのリクエストを表示", - "components.RequestList.RequestItem.requesteddate": "リクエスト", + "components.RequestList.RequestItem.requesteddate": "リクエストユーザー", "components.RequestList.RequestItem.requested": "リクエスト", - "components.RequestList.RequestItem.modifieduserdate": "{date} - {user}", - "components.RequestList.RequestItem.modified": "更新", + "components.RequestList.RequestItem.modifieduserdate": "{user}({date})", + "components.RequestList.RequestItem.modified": "最終更新者", "components.RequestList.RequestItem.mediaerror": "このリクエストのタイトルはもうありません。", "components.RequestList.RequestItem.failedretry": "リクエストを再試行するときに問題が発生しました。", "components.RequestList.RequestItem.editrequest": "リクエストを編集", @@ -423,26 +422,26 @@ "components.RequestCard.deleterequest": "リクエストを削除", "components.RequestButton.requestmore4k": "もっと4Kリクエストする", "components.RequestButton.requestmore": "もっとリクエストする", - "components.RequestButton.declinerequests": "{requestCount, plural, one {1つのリクエスト} other {{requestCount}つのリクエスト}}を拒否", + "components.RequestButton.declinerequests": "{requestCount} つのリクエストを拒否", "components.RequestButton.declinerequest4k": "4Kリクエストを拒否", "components.RequestButton.declinerequest": "リクエストを拒否", - "components.RequestButton.decline4krequests": "{requestCount, plural, one {1つの4Kリクエスト} other {{requestCount}つの4Kリクエスト}}を拒否", - "components.RequestButton.approverequests": "{requestCount, plural, one {1つのリクエスト} other {{requestCount}つのリクエスト}}を承認", + "components.RequestButton.decline4krequests": "{requestCount} つの 4K リクエストを拒否", + "components.RequestButton.approverequests": "{requestCount} つのリクエストを承認", "components.RequestButton.approverequest4k": "4Kリクエストを承認", "components.RequestButton.approverequest": "リクエストを承認", - "components.RequestButton.approve4krequests": "{requestCount, plural, one {1つ4Kリクエスト} other {{requestCount}つ 4Kリクエスト}}を承認", + "components.RequestButton.approve4krequests": "{requestCount} つ 4K リクエストを承認", "components.RequestBlock.server": "送信先サーバー", "components.RequestBlock.rootfolder": "ルートフォルダ", "components.RequestBlock.requestoverrides": "リクエストのオーバーライド", "components.RequestBlock.profilechanged": "画質プロファイル", - "components.RegionSelector.regionServerDefault": "デフォルト ({region})", + "components.RegionSelector.regionServerDefault": "デフォルト({region})", "components.RegionSelector.regionDefault": "全地域", "components.QuotaSelector.unlimited": "無制限", - "components.QuotaSelector.tvRequests": "{quotaLimit} {quotaDays} {days}に{seasons}", - "components.QuotaSelector.seasons": "{count, plural, one {シーズン} other {シーズン}}", - "components.QuotaSelector.movies": "{count, plural, one {映画} other {映画}}", - "components.QuotaSelector.movieRequests": "{quotaLimit} {quotaDays} {days}に{movies}", - "components.QuotaSelector.days": "{count, plural, one {日} other {日}}", + "components.QuotaSelector.tvRequests": "{quotaDays} {days}に {quotaLimit} {seasons}", + "components.QuotaSelector.seasons": "シーズン", + "components.QuotaSelector.movies": "映画", + "components.QuotaSelector.movieRequests": "{quotaDays} {days}に {quotaLimit} {movies}", + "components.QuotaSelector.days": "日", "components.PermissionEdit.viewrequests": "リクエストを見る", "components.PermissionEdit.usersDescription": "Overseerrユーザーを管理する権限を付与する。この権限を持つユーザーは、Admin 権限を持つユーザーの変更や、Admin 権限を付与することはできません。", "components.NotificationTypeSelector.mediarequestedDescription": "ユーザーが承認を必要とする新メディアリクエストをすると通知する。", @@ -454,8 +453,8 @@ "components.PermissionEdit.requestMoviesDescription": "4K以外の映画をリクエストする権限を与える。", "components.PermissionEdit.requestMovies": "映画をリクエスト", "components.PermissionEdit.requestDescription": "4K以外のメディアをリクエストする権限を与える。", - "components.PermissionEdit.request4kTvDescription": "4Kシリーズをリクエストする権限を与える。", - "components.PermissionEdit.request4kTv": "4Kシリーズをリクエスト", + "components.PermissionEdit.request4kTvDescription": "4K シリーズをリクエストする権限を与える。", + "components.PermissionEdit.request4kTv": "4K シリーズをリクエスト", "components.PermissionEdit.request4kDescription": "4Kメディアをリクエストする権限を与える。", "components.PermissionEdit.request4kMoviesDescription": "4K映画をリクエストする権限を与える。", "components.PermissionEdit.request4kMovies": "4K映画をリクエスト", @@ -468,14 +467,14 @@ "components.Layout.VersionStatus.streamstable": "Overseerr安定版", "components.Layout.VersionStatus.streamdevelop": "Overseerr開発版", "components.Layout.VersionStatus.outofdate": "期限切れ", - "components.PermissionEdit.autoapprove4kSeriesDescription": "4Kシリーズのリクエストを自動的に承認する。", + "components.PermissionEdit.autoapprove4kSeriesDescription": "4K シリーズのリクエストを自動的に承認する。", "components.PermissionEdit.autoapprove4kMoviesDescription": "4K映画のリクエストを自動的に承認する。", - "components.PermissionEdit.autoapproveSeriesDescription": "4K以外のシリーズのリクエストを自動的に承認する。", + "components.PermissionEdit.autoapproveSeriesDescription": "4K 以外のシリーズのリクエストを自動的に承認する。", "components.PermissionEdit.autoapproveSeries": "シリーズを自動的に承認する", "components.PermissionEdit.autoapproveMoviesDescription": "4K以外の映画のリクエストを自動的に承認する。", "components.PermissionEdit.autoapproveMovies": "映画を自動的に承認する", "components.PermissionEdit.autoapproveDescription": "4K以外のリクエストをすべて自動的に承認する。", - "components.PermissionEdit.autoapprove4kSeries": "4Kシリーズを自動的に承認する", + "components.PermissionEdit.autoapprove4kSeries": "4K シリーズを自動的に承認する", "components.PermissionEdit.autoapprove4kMovies": "4K映画の自動的に承認する", "components.PermissionEdit.autoapprove4kDescription": "すべての4Kリクエストを自動的に承認する。", "components.PermissionEdit.autoapprove4k": "4Kの自動承認", @@ -506,5 +505,21 @@ "components.MovieDetails.showless": "少なく表示", "components.MovieDetails.playonplex": "Plexで再生", "components.MovieDetails.play4konplex": "Plexで4K再生", - "components.RequestModal.pendingapproval": "リクエストは承認待ちです。" + "components.RequestModal.pendingapproval": "リクエストは承認待ちです。", + "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "デフォルト言語({language})", + "components.TvDetails.episodeRuntimeMinutes": "{runtime} 分", + "components.Settings.RadarrModal.loadingTags": "タグ読込中…", + "components.Settings.RadarrModal.notagoptions": "タグなし。", + "components.Settings.SonarrModal.animeTags": "アニメタグ", + "components.Settings.SonarrModal.animelanguageprofile": "アニメ言語プロフィール", + "components.Settings.SonarrModal.notagoptions": "タグなし。", + "components.Settings.SonarrModal.selectLanguageProfile": "言語プロフィールを選ぶ", + "components.Settings.RadarrModal.selecttags": "タグを選ぶ", + "components.Settings.SonarrModal.loadingTags": "タグ読込中…", + "components.Settings.SonarrModal.languageprofile": "言語プロフィール", + "components.Settings.SonarrModal.loadinglanguageprofiles": "言語プロフィール読込中…", + "components.Settings.SonarrModal.validationLanguageProfileRequired": "言語プロフィールを選択してください", + "components.UserProfile.UserSettings.UserGeneralSettings.applanguage": "表示言語", + "components.Settings.locale": "表示言語", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/nb_NO.json b/src/i18n/locale/nb_NO.json index 4cc5f1462..43195696a 100644 --- a/src/i18n/locale/nb_NO.json +++ b/src/i18n/locale/nb_NO.json @@ -671,5 +671,7 @@ "components.Settings.Notifications.NotificationsWebPush.agentenabled": "Aktiver", "components.Settings.Notifications.NotificationsSlack.validationTypes": "Du må velge minst én varseltype", "components.Settings.Notifications.NotificationsPushover.validationAccessTokenRequired": "Du må oppgi en gyldig tilgangsnøkkel", - "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Du må velge minst én varseltype" + "components.Settings.Notifications.NotificationsPushbullet.validationTypes": "Du må velge minst én varseltype", + "components.StatusBadge.status4k": "", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/nl.json b/src/i18n/locale/nl.json index 9b5491145..73b919442 100644 --- a/src/i18n/locale/nl.json +++ b/src/i18n/locale/nl.json @@ -194,7 +194,7 @@ "components.Settings.SettingsAbout.version": "Versie", "components.Settings.SettingsAbout.totalrequests": "Totaal aantal verzoeken", "components.Settings.SettingsAbout.totalmedia": "Totale aantal media", - "components.Settings.SettingsAbout.overseerrinformation": "Overseerr-informatie", + "components.Settings.SettingsAbout.overseerrinformation": "Over Overseerr", "components.Settings.SettingsAbout.githubdiscussions": "GitHub-discussies", "components.Setup.tip": "Tip", "components.Settings.SonarrModal.testFirstRootFolders": "Test verbinding om hoofdmappen te laden", @@ -207,9 +207,9 @@ "components.Settings.RadarrModal.testFirstQualityProfiles": "Test verbinding om kwaliteitsprofielen te laden", "components.Settings.RadarrModal.loadingrootfolders": "Bezig met laden van hoofdmappen…", "components.Settings.RadarrModal.loadingprofiles": "Bezig met laden van kwaliteitsprofielen…", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Versiegegevens niet beschikbaar. Is GitHub offline?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Versiegegevens zijn momenteel niet beschikbaar.", "components.Settings.SettingsAbout.Releases.latestversion": "Nieuwste", - "components.Settings.SettingsAbout.Releases.currentversion": "Huidige versie", + "components.Settings.SettingsAbout.Releases.currentversion": "Huidig", "components.MovieDetails.studio": "{studioCount, plural, one {Studio} other {Studio's}}", "components.CollectionDetails.overview": "Overzicht", "components.CollectionDetails.numberofmovies": "{count} films", @@ -249,7 +249,6 @@ "components.Settings.SettingsAbout.documentation": "Documentatie", "components.Settings.SettingsAbout.Releases.viewongithub": "Bekijken op GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Changelog bekijken", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "De laatste wijzigingen aan de tak develop van Overseerr worden hieronder niet getoond. Zie de commit-geschiedenis voor deze tak op GitHub voor details.", "components.Settings.Notifications.validationChatIdRequired": "Je moet een geldige chat-ID opgeven", "components.Settings.Notifications.validationBotAPIRequired": "Je moet een bot-authorisatietoken opgeven", "components.Settings.Notifications.telegramsettingssaved": "Instellingen Telegrammeldingen met succes opgeslagen!", @@ -271,7 +270,7 @@ "components.MovieDetails.MovieCrew.fullcrew": "Volledige crew", "components.CollectionDetails.requestcollection": "Collectie aanvragen", "components.UserList.importedfromplex": "{userCount, plural, one {# nieuwe gebruiker} other {# nieuwe gebruikers}} succesvol geïmporteerd vanuit Plex!", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Changelog", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} changelog", "components.Settings.SettingsAbout.Releases.releases": "Versies", "components.Settings.Notifications.NotificationsSlack.slacksettingssaved": "Instellingen voor Slack-meldingen met succes opgeslagen!", "components.Settings.Notifications.NotificationsSlack.slacksettingsfailed": "Instellingen voor Slack-meldingen konden niet opgeslagen worden.", @@ -881,5 +880,13 @@ "components.MovieDetails.showmore": "Meer tonen", "components.MovieDetails.showless": "Minder tonen", "components.TvDetails.streamingproviders": "Momenteel te streamen op", - "components.MovieDetails.streamingproviders": "Momenteel te streamen op" + "components.MovieDetails.streamingproviders": "Momenteel te streamen op", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Taak met succes bewerkt!", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Elk(e) {jobScheduleMinutes, plural, one {minuut} other {{jobScheduleMinutes} minuten}}", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frequentie", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Er ging iets mis bij het opslaan van de taak.", + "components.Settings.SettingsJobsCache.editJobSchedule": "Taak wijzigen", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Elk(e) {jobScheduleHours, plural, one {uur} other {{jobScheduleHours} uren}}", + "components.Settings.SettingsAbout.runningDevelop": "Je voert de developversie van Overseerr uit, die alleen wordt aanbevolen als je bijdraagt aan de ontwikkeling of de allereerste versies helpt testen.", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/pl.json b/src/i18n/locale/pl.json index 0967ef424..cd737acb5 100644 --- a/src/i18n/locale/pl.json +++ b/src/i18n/locale/pl.json @@ -1 +1,3 @@ -{} +{ + "components.StatusBadge.status": "" +} diff --git a/src/i18n/locale/pt_BR.json b/src/i18n/locale/pt_BR.json index 4890a713f..5d18ad2d1 100644 --- a/src/i18n/locale/pt_BR.json +++ b/src/i18n/locale/pt_BR.json @@ -179,12 +179,11 @@ "components.Settings.SettingsAbout.supportoverseerr": "Apoie o Overseerr", "components.Settings.SettingsAbout.Releases.viewongithub": "Ver no GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Ver Mudanças", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Mudanças Nessa Versão", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "As últimas mudanças na versão em desenvolvimento não serão exibidas abaixo. Por favor acesse o histórico de mudanças no GitHub para detalhes.", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Mudanças em {version}", "components.Settings.SettingsAbout.Releases.releases": "Versões", "components.Settings.SettingsAbout.Releases.releasedataMissing": "Informações de versão indisponíveis. O GitHub está indisponível?", "components.Settings.SettingsAbout.Releases.latestversion": "Última Versão", - "components.Settings.SettingsAbout.Releases.currentversion": "Versão Atual", + "components.Settings.SettingsAbout.Releases.currentversion": "Atual", "components.TvDetails.recommendations": "Recomendações", "components.TvDetails.overviewunavailable": "Sinopse indisponível.", "components.TvDetails.overview": "Sinopse", @@ -885,5 +884,6 @@ "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Tarefa editada com sucesso!", "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frequência", "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "A cada {jobScheduleHours, plural, one {hora} other {{jobScheduleHours} horas}}", - "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "A cada {jobScheduleMinutes, plural, one {minuto} other {{jobScheduleMinutes} minutos}}" + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "A cada {jobScheduleMinutes, plural, one {minuto} other {{jobScheduleMinutes} minutos}}", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/pt_PT.json b/src/i18n/locale/pt_PT.json index c5c876f0d..08d0aa2f0 100644 --- a/src/i18n/locale/pt_PT.json +++ b/src/i18n/locale/pt_PT.json @@ -306,7 +306,6 @@ "components.Settings.SettingsAbout.Releases.viewongithub": "Ver no GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Ver Mudanças", "components.Settings.SettingsAbout.Releases.versionChangelog": "Mudanças Nesta Versão", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "As últimas mudanças na branch develop do Overseerr não são mostradas abaixo. Por favor, veja o histórico de commits para esta branch no GitHub para detalhes.", "components.Settings.SettingsAbout.Releases.releases": "Versões", "components.Settings.SettingsAbout.Releases.releasedataMissing": "Informações de versão indisponíveis. O GitHub está abaixo?", "components.Settings.SettingsAbout.Releases.latestversion": "Mais Recente", @@ -881,5 +880,6 @@ "components.TvDetails.streamingproviders": "Atualmente a Exibir em", "components.MovieDetails.showmore": "Mostrar Mais", "components.Layout.LanguagePicker.displaylanguage": "Idioma da Interface", - "components.MovieDetails.showless": "Mostrar Menos" + "components.MovieDetails.showless": "Mostrar Menos", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/ru.json b/src/i18n/locale/ru.json index 45f29352c..842bb5f6b 100644 --- a/src/i18n/locale/ru.json +++ b/src/i18n/locale/ru.json @@ -25,7 +25,7 @@ "components.MovieDetails.overview": "Обзор", "components.MovieDetails.overviewunavailable": "Обзор недоступен.", "components.MovieDetails.recommendations": "Рекомендации", - "components.MovieDetails.releasedate": "Дата релиза", + "components.MovieDetails.releasedate": "{releaseCount, plural, one {Дата релиза} other {Даты релиза}}", "components.MovieDetails.revenue": "Доход", "components.MovieDetails.runtime": "{minutes} минут", "components.MovieDetails.similar": "Похожие фильмы", @@ -210,7 +210,7 @@ "i18n.deleting": "Удаление…", "components.Settings.applicationTitle": "Название приложения", "components.Settings.SettingsAbout.Releases.latestversion": "Последняя", - "components.Settings.SettingsAbout.Releases.currentversion": "Текущая версия", + "components.Settings.SettingsAbout.Releases.currentversion": "Текущая", "components.Settings.SonarrModal.syncEnabled": "Включить сканирование", "components.Settings.RadarrModal.syncEnabled": "Включить сканирование", "components.Settings.Notifications.sendSilentlyTip": "Отправлять уведомления без звука", @@ -343,7 +343,7 @@ "components.Discover.DiscoverStudio.studioMovies": "Фильмы {studio}", "components.Discover.DiscoverMovieLanguage.languageMovies": "Фильмы на языке \"{language}\"", "components.Discover.DiscoverMovieGenre.genreMovies": "Фильмы в жанре \"{genre}\"", - "components.Settings.SettingsAbout.overseerrinformation": "Информация о Overseerr", + "components.Settings.SettingsAbout.overseerrinformation": "Об Overseerr", "components.Settings.SettingsAbout.githubdiscussions": "Обсуждения на GitHub", "components.Settings.enablessl": "Использовать SSL", "components.Settings.is4k": "4К", @@ -692,7 +692,7 @@ "components.Settings.SettingsJobsCache.cachemisses": "Неудачных обращений", "components.Settings.SettingsJobsCache.cachevsize": "Размер значений", "components.Settings.SettingsAbout.uptodate": "Актуальная", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Изменения в версии", + "components.Settings.SettingsAbout.Releases.versionChangelog": "Изменения в версии {version}", "components.Settings.SettingsAbout.preferredmethod": "Предпочтительный способ", "components.Settings.SettingsJobsCache.cacheflushed": "{cachename} кэш сброшен.", "components.Settings.SettingsJobsCache.cacheDescription": "Overseerr кэширует запросы к внешним конечным точкам API, чтобы оптимизировать производительность и избежать ненужных вызовов API.", @@ -704,8 +704,7 @@ "components.Settings.SettingsAbout.about": "О проекте", "components.Settings.SettingsAbout.Releases.viewongithub": "Посмотреть на GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Посмотреть список изменений", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "Последние изменения в ветке develop проекта Overseerr не показаны ниже. Пожалуйста, просмотрите историю коммитов для этой ветки на GitHub, чтобы узнать подробности.", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Данные о релизе недоступны. GitHub не работает?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Данные о релизе в настоящее время недоступны.", "components.UserProfile.UserSettings.UserPasswordChange.validationNewPassword": "Вы должны ввести новый пароль", "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "Вы должны предоставить действительный ID чата", "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "Вы должны предоставить действительный открытый ключ PGP", @@ -881,5 +880,13 @@ "components.Settings.RadarrModal.edit4kradarr": "Редактировать 4К сервер Radarr", "components.Settings.RadarrModal.create4kradarr": "Добавить новый 4К сервер Radarr", "components.Settings.SonarrModal.default4kserver": "4К сервер по умолчанию", - "components.Settings.toastApiKeySuccess": "Новый ключ API успешно сгенерирован!" + "components.Settings.toastApiKeySuccess": "Новый ключ API успешно сгенерирован!", + "components.Settings.SettingsJobsCache.editJobSchedule": "Изменить задание", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Частота", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Задание успешно отредактировано!", + "components.Settings.SettingsAbout.runningDevelop": "Вы используете ветку develop проекта Overseerr, которая рекомендуется только для тех, кто вносит вклад в разработку или помогает в тестировании.", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Что-то пошло не так при сохранении задания.", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Каждый {jobScheduleHours, plural, one {час} other {{jobScheduleHours} часа(ов)}}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "Каждую {jobScheduleMinutes, plural, one {минуту} other {{jobScheduleMinutes} минут(ы)}}", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/sr.json b/src/i18n/locale/sr.json index 6c5f6c7fa..b919c907c 100644 --- a/src/i18n/locale/sr.json +++ b/src/i18n/locale/sr.json @@ -222,5 +222,6 @@ "components.Discover.popularmovies": "Popularni Filmovi", "components.Discover.discovertv": "Popularne Serije", "components.Discover.discovermovies": "Popunarni Filmov", - "pages.errormessagewithcode": "{statusCode} - {error}" + "pages.errormessagewithcode": "{statusCode} - {error}", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/sv.json b/src/i18n/locale/sv.json index 7d2b920a4..a4d8015d4 100644 --- a/src/i18n/locale/sv.json +++ b/src/i18n/locale/sv.json @@ -77,7 +77,7 @@ "components.Settings.SettingsAbout.version": "Version", "components.Settings.SettingsAbout.totalrequests": "Totalt antal förfrågningar", "components.Settings.SettingsAbout.totalmedia": "Totalt antal mediaobjekt", - "components.Settings.SettingsAbout.overseerrinformation": "Information om Overseerr", + "components.Settings.SettingsAbout.overseerrinformation": "Om Overseerr", "components.Settings.SettingsAbout.githubdiscussions": "GitHub Diskussioner", "components.Settings.SettingsAbout.gettingsupport": "Få Support", "components.Settings.RadarrModal.validationRootFolderRequired": "Du måste ange en root-mapp", @@ -228,12 +228,11 @@ "components.Settings.SettingsAbout.helppaycoffee": "Stötta med en kopp kaffe", "components.Settings.SettingsAbout.Releases.viewongithub": "Visa på GitHub", "components.Settings.SettingsAbout.Releases.viewchangelog": "Visa ändringslogg", - "components.Settings.SettingsAbout.Releases.versionChangelog": "Ändringslogg", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "De senaste ändringarna i utvecklings-grenen av Overseerr visas inte nedan. Se ändringshistoriken på GitHub för mer information.", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} Ändringslogg", "components.Settings.SettingsAbout.Releases.releases": "Versioner", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "Versionsdata saknas. Ligger GitHub nere?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "Versionsdata är för närvarande inte tillgänglig.", "components.Settings.SettingsAbout.Releases.latestversion": "Senaste Versionen", - "components.Settings.SettingsAbout.Releases.currentversion": "Aktuell Version", + "components.Settings.SettingsAbout.Releases.currentversion": "Aktuell", "components.UserList.importfromplex": "Importera användare från Plex", "components.UserList.importfromplexerror": "Någonting gick fel vid importen av användare från Plex.", "components.TvDetails.watchtrailer": "Kolla Trailer", @@ -881,5 +880,13 @@ "components.MovieDetails.showmore": "Visa mer", "components.MovieDetails.showless": "Visa mindre", "components.TvDetails.streamingproviders": "Strömmas för närvarande på", - "components.MovieDetails.streamingproviders": "Strömmas för närvarande på" + "components.MovieDetails.streamingproviders": "Strömmas för närvarande på", + "components.Settings.SettingsJobsCache.editJobSchedule": "Ändra jobb", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "Frekvens", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "Varje {jobScheduleHours, plural, one {timme} other {{jobScheduleHours} timmar}}", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "varje{jobScheduleMinutes, plural, one {minut} other {{jobScheduleMinutes} minuter}}", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "Något gick fel vid sparning av jobbet.", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "Jobbet har redigerats!", + "components.Settings.SettingsAbout.runningDevelop": "Du använder develop grenen av Overseerr, som endast rekommenderas för dem som bidrar till utvecklingen eller hjälper till med tester i den absoluta framkanten.", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/zh_Hans.json b/src/i18n/locale/zh_Hans.json index b3da166e5..a8b956c13 100644 --- a/src/i18n/locale/zh_Hans.json +++ b/src/i18n/locale/zh_Hans.json @@ -542,7 +542,7 @@ "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr 将定时运行以下的維護任務。手动执行工作不会影响它正常的时间表。", "components.Settings.SettingsJobsCache.jobs": "作业", "components.Settings.SettingsJobsCache.jobname": "作业名", - "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} 已被取消。", + "components.Settings.SettingsJobsCache.jobcancelled": "{jobname}已被取消。", "components.Settings.SettingsJobsCache.flushcache": "清除缓存", "components.Settings.SettingsJobsCache.download-sync-reset": "下载状态同步复位", "components.Settings.SettingsJobsCache.download-sync": "下载状态同步", @@ -574,12 +574,11 @@ "components.Settings.SettingsAbout.about": "关于 Overseerr", "components.Settings.SettingsAbout.Releases.viewongithub": "在 GitHub 上查看", "components.Settings.SettingsAbout.Releases.viewchangelog": "查看变更日志", - "components.Settings.SettingsAbout.Releases.versionChangelog": "变更日志", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "develop 分支的变更日志不会显示在以下。请直接到 GitHub 查看变更日志。", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} 更新日志", "components.Settings.SettingsAbout.Releases.releases": "软件版本", - "components.Settings.SettingsAbout.Releases.releasedataMissing": "无法获取软件版本资料。GitHub 崩潰了吗?", + "components.Settings.SettingsAbout.Releases.releasedataMissing": "软件发行数据当前不可用。", "components.Settings.SettingsAbout.Releases.latestversion": "最新软件版本", - "components.Settings.SettingsAbout.Releases.currentversion": "目前的软件版本", + "components.Settings.SettingsAbout.Releases.currentversion": "当前版本", "components.Settings.RadarrModal.validationRootFolderRequired": "必须设置根目录", "components.Settings.RadarrModal.validationProfileRequired": "必须设置质量", "components.Settings.RadarrModal.validationPortRequired": "请输入有效的端口", @@ -879,5 +878,6 @@ "components.Settings.Notifications.encryptionNone": "不使用加密", "components.Settings.Notifications.encryptionImplicitTls": "使用传输层安全标准(TLS)", "components.Settings.Notifications.encryptionDefault": "盡可能使用 STARTTLS", - "components.Settings.Notifications.encryption": "加密方式" + "components.Settings.Notifications.encryption": "加密方式", + "components.StatusBadge.status": "{status}" } diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json index d924c99a5..ad1d2784c 100644 --- a/src/i18n/locale/zh_Hant.json +++ b/src/i18n/locale/zh_Hant.json @@ -102,8 +102,8 @@ "components.RequestButton.declinerequests": "拒絕{requestCount, plural, one {請求} other {{requestCount} 個請求}}", "components.RequestButton.declinerequest4k": "拒絕 4K 請求", "components.RequestButton.declinerequest": "拒絕請求", - "components.RequestButton.decline4krequests": "拒絕{requestCount, plural, one { 4K 請求} other { {requestCount} 個 4K 請求}}", - "components.RequestButton.approverequests": "批准{requestCount, plural, one {請求} other {{requestCount} 個請求}}", + "components.RequestButton.decline4krequests": "拒絕 {requestCount, plural, one {4K 請求} other {{requestCount} 個 4K 請求}}", + "components.RequestButton.approverequests": "批准{requestCount, plural, one {請求} other { {requestCount} 個請求}}", "components.RequestButton.approverequest4k": "批准 4K 請求", "components.RequestButton.approverequest": "批准請求", "components.RequestButton.approve4krequests": "批准{requestCount, plural, one { 4K 請求} other { {requestCount} 個 4K 請求}}", @@ -176,7 +176,7 @@ "components.Settings.RadarrModal.baseUrl": "網站根目錄", "components.StatusChacker.reloadOverseerr": "刷新頁面", "components.Settings.notrunning": "未運行", - "components.Settings.activeProfile": "現行品質設定", + "components.Settings.activeProfile": "目前的品質設定", "components.Settings.notificationsettings": "通知設定", "components.Settings.default4k": "設定 4K 為預設分辨率", "components.Settings.currentlibrary": "當前媒體庫: {name}", @@ -258,11 +258,11 @@ "components.Settings.SettingsAbout.Releases.latestversion": "最新版本", "components.Settings.SettingsAbout.Releases.currentversion": "目前的版本", "components.Settings.SettingsAbout.timezone": "時區", - "components.Settings.SettingsAbout.documentation": "文檔", + "components.Settings.SettingsAbout.documentation": "使用說明", "components.RequestModal.pending4krequest": "{title} 的 4K 請求", "components.RequestModal.pendingrequest": "{title} 的請求", "components.RequestModal.extras": "特輯", - "components.Settings.SettingsAbout.Releases.versionChangelog": "變更日誌", + "components.Settings.SettingsAbout.Releases.versionChangelog": "{version} 變更日誌", "components.Settings.SettingsAbout.Releases.releases": "軟體版本", "components.Settings.plexsettings": "Plex 設定", "components.RequestModal.selectseason": "季數選擇", @@ -398,14 +398,14 @@ "components.Settings.SettingsJobsCache.nextexecution": "下一次執行時間", "components.Settings.SettingsJobsCache.jobtype": "作業類型", "components.Settings.SettingsJobsCache.jobstarted": "{jobname} 已開始運行。", - "components.Settings.SettingsJobsCache.jobcancelled": "{jobname} 已被取消。", + "components.Settings.SettingsJobsCache.jobcancelled": "{jobname}已被取消。", "components.Settings.SettingsJobsCache.jobs": "作業", "components.Settings.SettingsJobsCache.jobname": "作業名", "components.Settings.SettingsJobsCache.flushcache": "清除快取", "components.Settings.SettingsJobsCache.canceljob": "取消作業", - "components.Settings.SettingsJobsCache.cache": "快取記憶體", + "components.Settings.SettingsJobsCache.cache": "快取", "components.Settings.SonarrModal.toastSonarrTestSuccess": "Sonarr 伺服器連線成功!", - "components.Settings.SettingsJobsCache.command": "命令", + "components.Settings.SettingsJobsCache.command": "指令", "components.Settings.SettingsJobsCache.process": "程序", "components.Settings.SettingsAbout.preferredmethod": "首選", "i18n.advanced": "進階", @@ -536,7 +536,6 @@ "components.RequestModal.SearchByNameModal.notvdbiddescription": "無法自動配對您的請求。請從以下列表中選擇正確的媒體項。", "components.CollectionDetails.requestswillbecreated4k": "為以下的電影提出 4K 請求:", "components.CollectionDetails.requestcollection4k": "提出 4K 系列請求", - "components.Settings.SettingsAbout.Releases.runningDevelopMessage": "develop 分支的變更日誌不會顯示在以下。請直接到 GitHub 查看變更日誌。", "components.Settings.trustProxyTip": "使用代理伺服器時,允許 Overseerr 探明客戶端 IP 位址(Overseerr 必須重新啟動)", "components.Settings.csrfProtectionTip": "設定外部訪問權限為只讀(Overseerr 必須重新啟動)", "components.ResetPassword.requestresetlinksuccessmessage": "通過電子郵件發送了密碼重設鏈接。", @@ -555,7 +554,7 @@ "components.Settings.regionTip": "以地區可用性篩選結果", "components.UserProfile.UserSettings.UserGeneralSettings.originallanguageTip": "以原始語言篩選結果", "components.Settings.originallanguageTip": "以原始語言篩選結果", - "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr 將定時運行以下的維護任務。手動執行工作不會影響它正常的時間表。", + "components.Settings.SettingsJobsCache.jobsDescription": "Overseerr 將定時運行以下的維護任務。手動執行工作不會影響它正常的行程。", "components.Settings.plexsettingsDescription": "關於 Plex 伺服器的設定。Overseerr 將定時執行媒體庫掃描。", "components.Settings.manualscanDescription": "在正常情況下,Overseerr 會每24小時掃描您的 Plex 媒體庫。最新新增的媒體將更頻繁掃描。設定新的 Plex 伺服器時,我們建議您執行一次手動掃描!", "components.RegionSelector.regionServerDefault": "預設設定({region})", @@ -659,7 +658,7 @@ "components.Settings.SettingsJobsCache.jobsandcache": "作業和快取", "components.Settings.SettingsAbout.about": "關於 Overseerr", "components.Settings.cacheImages": "啟用圖像緩存", - "components.Settings.SettingsLogs.logsDescription": "日誌檔案位置:{configDir}/logs/overseerr.log", + "components.Settings.SettingsLogs.logsDescription": "您也能直接查看 stdout 數據流或位置於 {configDir}/logs/overseerr.log 的日誌檔案。", "components.Settings.cacheImagesTip": "把所有的圖像優化和保存到快取記憶體(需要大量的磁碟空間)", "components.Settings.SettingsLogs.logDetails": "日誌詳細信息", "components.Settings.SettingsLogs.extraData": "附加數據", @@ -673,11 +672,11 @@ "i18n.delimitedlist": "{a}、{b}", "components.Settings.SettingsUsers.tvRequestLimitLabel": "電視節目請求全局限制", "components.Settings.SettingsUsers.movieRequestLimitLabel": "電影請求全局限制", - "components.RequestModal.QuotaDisplay.seasonlimit": "個季數", + "components.RequestModal.QuotaDisplay.seasonlimit": "季數", "components.RequestModal.QuotaDisplay.season": "電視節目季數", "components.RequestModal.QuotaDisplay.requestsremaining": "{remaining, plural, =0 {電影請求剩餘數不足} other {剩餘 # 個{type}請求}}", "components.RequestModal.QuotaDisplay.notenoughseasonrequests": "請求剩餘數不足", - "components.RequestModal.QuotaDisplay.movielimit": "部電影", + "components.RequestModal.QuotaDisplay.movielimit": "電影", "components.RequestModal.QuotaDisplay.allowedRequestsUser": "此使用者每 {days} 天能提出 {limit} 個{type}請求。", "components.RequestModal.QuotaDisplay.allowedRequests": "您每 {days} 天能提出 {limit} 個{type}請求。", "components.UserProfile.UserSettings.UserGeneralSettings.seriesrequestlimit": "電視節目請求限制", @@ -876,10 +875,18 @@ "components.NotificationTypeSelector.usermediaavailableDescription": "當您請求的媒體可觀看時取得通知。", "components.NotificationTypeSelector.usermediaapprovedDescription": "當您的請求被手動批准時取得通知。", "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "當其他使用者提出自動批准的請求時取得通知。", - "components.Settings.SettingsAbout.betawarning": "這是測試版軟體,所以可能會不穩定或被破壞。請向 GitHub 報告問題!", + "components.Settings.SettingsAbout.betawarning": "這是測試版軟體,有些功能可能損壞或不穩定。請在 GitHub 上回報問題!", "components.Layout.LanguagePicker.displaylanguage": "顯示語言", "components.MovieDetails.showmore": "顯示更多", "components.MovieDetails.showless": "顯示更少", "components.TvDetails.streamingproviders": "目前的流媒體服務", - "components.MovieDetails.streamingproviders": "目前的流媒體服務" + "components.MovieDetails.streamingproviders": "目前的流媒體服務", + "components.Settings.SettingsJobsCache.editJobSchedule": "編輯作業", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorHours": "每 {jobScheduleHours} 小時", + "components.Settings.SettingsJobsCache.editJobSchedulePrompt": "頻率", + "components.Settings.SettingsJobsCache.editJobScheduleSelectorMinutes": "每 {jobScheduleMinutes} 分鐘", + "components.Settings.SettingsJobsCache.jobScheduleEditFailed": "保存作業設定中出了點問題。", + "components.Settings.SettingsJobsCache.jobScheduleEditSaved": "作業編輯成功!", + "components.Settings.SettingsAbout.runningDevelop": "您正在使用 Overseerr 的 develop 開發版。我們只建議開發者和協助測試的人員使用。", + "components.StatusBadge.status": "{status}" } From 6d55b50e5e86fa5deae74d8b86aacd460542a363 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 19 Oct 2021 14:01:17 +0000 Subject: [PATCH 079/238] chore(release): 1.27.0 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c79bc6eb..e4ec8c618 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +# [1.27.0](https://github.com/sct/overseerr/compare/v1.26.1...v1.27.0) (2021-10-19) + + +### Bug Fixes + +* **api:** return queried user's requests instead of own requests ([#2174](https://github.com/sct/overseerr/issues/2174)) ([0edb1f4](https://github.com/sct/overseerr/commit/0edb1f452b6ff4a49ae2bde15f7273769788cf4f)) +* **api:** use query builder for user requests endpoint ([#2119](https://github.com/sct/overseerr/issues/2119)) ([a20f395](https://github.com/sct/overseerr/commit/a20f395c94c97dd7ddbc25590f15def2c9bf13c9)) +* apply request overrides iff override & selected servers match ([#2164](https://github.com/sct/overseerr/issues/2164)) ([50ce198](https://github.com/sct/overseerr/commit/50ce198471b1a3777a183d68904bbfb39ebd4523)) +* **email:** do not attempt to display logo if app URL not configured ([#2125](https://github.com/sct/overseerr/issues/2125)) ([b3b421a](https://github.com/sct/overseerr/commit/b3b421a67408a4a48d23c15341fcdf7aaf19b25a)) +* **frontend:** notification type validation ([#2207](https://github.com/sct/overseerr/issues/2207)) ([2f204b9](https://github.com/sct/overseerr/commit/2f204b995269a53ae36f2a8733f27ae6ab70da5a)) +* **scripts:** update migration scripts ([#2208](https://github.com/sct/overseerr/issues/2208)) [skip ci] ([d0ac74e](https://github.com/sct/overseerr/commit/d0ac74ea4bbfcf3d25d30cbd422d9df1c1259a18)) +* **ui:** refinements for 'About' page ([#2173](https://github.com/sct/overseerr/issues/2173)) ([084a842](https://github.com/sct/overseerr/commit/084a842a4f9b6caaed22edbe77bc9e414bc1f387)) + + +### Features + +* display release dates for theatrical, digital, and physical release types ([#1492](https://github.com/sct/overseerr/issues/1492)) ([a4dca23](https://github.com/sct/overseerr/commit/a4dca2356b7605026f7bc45b691496e765c3328c)) +* dynamically fetch login screen backdrop images ([#2206](https://github.com/sct/overseerr/issues/2206)) ([3486d0b](https://github.com/sct/overseerr/commit/3486d0bf5520cbdff60bd8fd023caed76c452973)) +* **frontend:** add Hulu to network slider ([#2204](https://github.com/sct/overseerr/issues/2204)) ([1e402f7](https://github.com/sct/overseerr/commit/1e402f710b53c11855aab0abdb4b12c51c30b022)) +* **jobs:** allow modifying job schedules ([#1440](https://github.com/sct/overseerr/issues/1440)) ([82614ca](https://github.com/sct/overseerr/commit/82614ca4410782a12d65b4c0a6526ff064be1241)) +* **lang:** add Czech and Danish display languages ([#2176](https://github.com/sct/overseerr/issues/2176)) ([8d8db6c](https://github.com/sct/overseerr/commit/8d8db6cf5d98d4e498a31db339d02f8a98057c8d)) +* **lang:** translations update from Weblate ([#2101](https://github.com/sct/overseerr/issues/2101)) ([c73cf7b](https://github.com/sct/overseerr/commit/c73cf7b19cbc19e97a777c0facb9264fb0113093)) +* **lang:** translations update from Weblate ([#2179](https://github.com/sct/overseerr/issues/2179)) ([e3312ce](https://github.com/sct/overseerr/commit/e3312cef33821c8cb76a4a63bd565c78d67b3e0b)) +* **lang:** translations update from Weblate ([#2185](https://github.com/sct/overseerr/issues/2185)) ([dce10f7](https://github.com/sct/overseerr/commit/dce10f743f52cb04036e2cdaee280e26a81b253b)) +* **lang:** translations update from Weblate ([#2202](https://github.com/sct/overseerr/issues/2202)) ([492d8e3](https://github.com/sct/overseerr/commit/492d8e3daa5fb99aa9df2a18978085d5ddd581e7)) +* **lang:** translations update from Weblate ([#2210](https://github.com/sct/overseerr/issues/2210)) ([0a6ef6c](https://github.com/sct/overseerr/commit/0a6ef6cc81376f7a02f1483109be7ae4ab851c48)) +* **plex-scan:** plex scanner improvements ([#2105](https://github.com/sct/overseerr/issues/2105)) ([afda9c7](https://github.com/sct/overseerr/commit/afda9c7dc222137b0e6654a6beb4737cf2c1752e)) +* **servarr:** auto fill base url when testing service if missing ([#1995](https://github.com/sct/overseerr/issues/1995)) ([739f667](https://github.com/sct/overseerr/commit/739f667b54d8dec258b74d0cd8fd8b3b88dcf8d5)) +* **ui:** link processing/requested status badges to service URL ([#1761](https://github.com/sct/overseerr/issues/1761)) ([032c14a](https://github.com/sct/overseerr/commit/032c14a22680f62f8106943297b081b68645ce61)) + ## [1.26.1](https://github.com/sct/overseerr/compare/v1.26.0...v1.26.1) (2021-09-20) diff --git a/package.json b/package.json index 5178d6d30..3c98738b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "overseerr", - "version": "1.26.1", + "version": "1.27.0", "private": true, "scripts": { "dev": "nodemon -e ts --watch server --watch overseerr-api.yml -e .json,.ts,.yml -x ts-node --files --project server/tsconfig.json server/index.ts", From 85aec4f8925746ebae9bcc99d8480b78ccfd851e Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sun, 24 Oct 2021 00:50:36 +0200 Subject: [PATCH 080/238] fix(lang): translations update from Weblate (#2212) * feat(lang): translated using Weblate (Japanese) Currently translated at 56.8% (506 of 890 strings) Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ja/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Russian) Currently translated at 99.7% (888 of 890 strings) Co-authored-by: Hosted Weblate Co-authored-by: Sergey Moiseev Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/ru/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Simplified)) Currently translated at 98.9% (881 of 890 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hans/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (890 of 890 strings) feat(lang): translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (890 of 890 strings) Co-authored-by: Hosted Weblate Co-authored-by: TheCatLady Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/zh_Hant/ Translation: Overseerr/Overseerr Frontend Co-authored-by: TheCatLady Co-authored-by: Sergey Moiseev --- src/i18n/locale/ja.json | 4 ++-- src/i18n/locale/ru.json | 2 +- src/i18n/locale/zh_Hans.json | 2 +- src/i18n/locale/zh_Hant.json | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/i18n/locale/ja.json b/src/i18n/locale/ja.json index fd4713dec..16c558857 100644 --- a/src/i18n/locale/ja.json +++ b/src/i18n/locale/ja.json @@ -17,7 +17,7 @@ "components.MovieDetails.budget": "予算", "components.MovieDetails.cast": "出演者", "components.MovieDetails.manageModalClearMedia": "メディアのデータを消去", - "components.MovieDetails.manageModalClearMediaWarning": "*リクエストを含め、すべての詳細情報が消去されます。この操作は元に戻すことができません。この作品が Plex ライブラリに存在する場合、詳細情報は次のスキャンで再作成されます。", + "components.MovieDetails.manageModalClearMediaWarning": "※リクエストを含め、すべての詳細情報が消去されます。この操作は元に戻すことができません。この作品が Plex ライブラリに存在する場合、詳細情報は次のスキャンで再作成されます。", "components.MovieDetails.manageModalNoRequests": "リクエストがありません。", "components.MovieDetails.manageModalRequests": "リクエスト", "components.MovieDetails.manageModalTitle": "映画を管理", @@ -149,7 +149,7 @@ "components.Setup.welcome": "Overseerr へようこそ", "components.TvDetails.cast": "出演者", "components.TvDetails.manageModalClearMedia": "メディアのデータを消去", - "components.TvDetails.manageModalClearMediaWarning": "*リクエストを含め、メーディアのデータをすべて消去されます。この操作は元に戻すことができません。このアイテムが Plex ライブラリに存在する場合、メディア情報は次の同期で再作成されます。", + "components.TvDetails.manageModalClearMediaWarning": "※リクエストを含め、メーディアのデータをすべて消去されます。この操作は元に戻すことができません。このアイテムが Plex ライブラリに存在する場合、メディア情報は次の同期で再作成されます。", "components.TvDetails.manageModalNoRequests": "リクエストがありません。", "components.TvDetails.manageModalRequests": "リクエスト", "components.TvDetails.manageModalTitle": "シリーズの管理", diff --git a/src/i18n/locale/ru.json b/src/i18n/locale/ru.json index 842bb5f6b..ff790660c 100644 --- a/src/i18n/locale/ru.json +++ b/src/i18n/locale/ru.json @@ -551,7 +551,7 @@ "components.Settings.Notifications.NotificationsPushover.pushoversettingssaved": "Настройки уведомлений Pushover успешно сохранены!", "components.Settings.Notifications.NotificationsPushover.pushoversettingsfailed": "Не удалось сохранить настройки уведомлений Pushover.", "components.Settings.Notifications.NotificationsPushover.accessTokenTip": "Зарегистрируйте приложение для использования с Overseerr", - "i18n.view": "Вид", + "i18n.view": "Посмотреть", "i18n.notrequested": "Не запрошено", "i18n.noresults": "Результатов нет.", "i18n.delimitedlist": "{a}, {b}", diff --git a/src/i18n/locale/zh_Hans.json b/src/i18n/locale/zh_Hans.json index a8b956c13..55899e73c 100644 --- a/src/i18n/locale/zh_Hans.json +++ b/src/i18n/locale/zh_Hans.json @@ -77,7 +77,7 @@ "components.StatusChacker.reloadOverseerr": "刷新页面", "components.StatusChacker.newversionavailable": "软件更新", "components.StatusChacker.newversionDescription": "Overseerr 软件已更新。请点击以下的按钮刷新页面。", - "components.StatusBadge.status4k": "4K 版 {status}", + "components.StatusBadge.status4k": "4K 版{status}", "components.Setup.welcome": "欢迎來到 Overseerr", "components.Setup.tip": "提示", "components.Setup.signinMessage": "首先,请使用您的 Plex 账户登入", diff --git a/src/i18n/locale/zh_Hant.json b/src/i18n/locale/zh_Hant.json index ad1d2784c..ab9dc42b3 100644 --- a/src/i18n/locale/zh_Hant.json +++ b/src/i18n/locale/zh_Hant.json @@ -21,7 +21,7 @@ "i18n.partiallyavailable": "部分可觀看", "i18n.unavailable": "不可觀看", "components.StatusChacker.newversionavailable": "軟體更新", - "components.StatusBadge.status4k": "4K 版 {status}", + "components.StatusBadge.status4k": "4K 版{status}", "components.Setup.tip": "提示", "components.Setup.welcome": "歡迎來到 Overseerr", "components.TvDetails.TvCast.fullseriescast": "演員陣容", @@ -322,7 +322,7 @@ "components.StatusChacker.newversionDescription": "Overseerr 軟體已更新。請點擊以下的按鈕刷新頁面。", "components.RequestModal.requestcancelled": "{title} 的請求已被取消。", "components.RequestModal.AdvancedRequester.qualityprofile": "品質設定", - "components.RequestModal.AdvancedRequester.animenote": "*這是個動漫節目。", + "components.RequestModal.AdvancedRequester.animenote": "※這是個動漫節目。", "components.RequestModal.AdvancedRequester.advancedoptions": "進階選項", "components.RequestModal.AdvancedRequester.default": "{name}(預設)", "components.RequestModal.AdvancedRequester.destinationserver": "目標伺服器", @@ -416,9 +416,9 @@ "components.Settings.toastPlexConnectingFailure": "Plex 伺服器連線失敗。", "components.TvDetails.mark4kavailable": "標記 4K 版為可觀看", "components.TvDetails.markavailable": "標記為可觀看", - "components.TvDetails.manageModalClearMediaWarning": "*這將會刪除包括使用者請求在內所有有關這個電視節目的資料。如果這個節目存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", - "components.MovieDetails.manageModalClearMediaWarning": "*這將會刪除包括使用者請求在內所有有關這部電影的資料。如果這部電影存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", - "components.TvDetails.allseasonsmarkedavailable": "*每季將被標記為可觀看。", + "components.TvDetails.manageModalClearMediaWarning": "※這將會刪除包括使用者請求在內所有有關這個電視節目的資料。如果這個節目存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", + "components.MovieDetails.manageModalClearMediaWarning": "※這將會刪除包括使用者請求在內所有有關這部電影的資料。如果這部電影存在於您的 Plex 伺服器,資料將會在媒體庫掃描時重新建立。", + "components.TvDetails.allseasonsmarkedavailable": "※每季將被標記為可觀看。", "components.Settings.csrfProtectionHoverTip": "除非您了解此功能,請勿啟用它!", "components.UserList.users": "使用者", "components.Settings.applicationTitle": "應用程式名", From dca363809dbf3d08eca3bce0d1bee4f77b5fa0ac Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sun, 24 Oct 2021 07:58:40 -0400 Subject: [PATCH 081/238] docs: add Overseerr-Extension to 3rd-party integrations (#2227) [skip ci] --- docs/extending-overseerr/third-party.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/extending-overseerr/third-party.md b/docs/extending-overseerr/third-party.md index cf2946d91..df82fa239 100644 --- a/docs/extending-overseerr/third-party.md +++ b/docs/extending-overseerr/third-party.md @@ -10,5 +10,6 @@ We do not officially support these third-party integrations. If you run into any - [Requestrr](https://github.com/darkalfx/requestrr/wiki/Configuring-Overseerr), a Discord chatbot - [Doplarr](https://github.com/kiranshila/Doplarr), a Discord request bot - [ha-overseerr](https://github.com/vaparr/ha-overseerr), a custom Home Assistant component +- [Overseerr-Extension](https://github.com/RemiRigal/Overseerr-Extension), a browser extension TMDb and IMDb - [OverCLIrr](https://github.com/WillFantom/OverCLIrr), a command-line tool - [Overseerr Exporter](https://github.com/WillFantom/overseerr-exporter), a Prometheus exporter From 6565c7dd9b7023bb494126290a339a98a69e2042 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sun, 24 Oct 2021 08:35:36 -0400 Subject: [PATCH 082/238] docs: add FAQ entry about mobile apps (#2228) [skip ci] * docs: grammar correction * docs: add FAQ about mobile apps * docs: remove Overseerr-Extension for now --- docs/extending-overseerr/third-party.md | 1 - docs/support/faq.md | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/extending-overseerr/third-party.md b/docs/extending-overseerr/third-party.md index df82fa239..cf2946d91 100644 --- a/docs/extending-overseerr/third-party.md +++ b/docs/extending-overseerr/third-party.md @@ -10,6 +10,5 @@ We do not officially support these third-party integrations. If you run into any - [Requestrr](https://github.com/darkalfx/requestrr/wiki/Configuring-Overseerr), a Discord chatbot - [Doplarr](https://github.com/kiranshila/Doplarr), a Discord request bot - [ha-overseerr](https://github.com/vaparr/ha-overseerr), a custom Home Assistant component -- [Overseerr-Extension](https://github.com/RemiRigal/Overseerr-Extension), a browser extension TMDb and IMDb - [OverCLIrr](https://github.com/WillFantom/OverCLIrr), a command-line tool - [Overseerr Exporter](https://github.com/WillFantom/overseerr-exporter), a Prometheus exporter diff --git a/docs/support/faq.md b/docs/support/faq.md index 19d71e931..d3ea3cdce 100644 --- a/docs/support/faq.md +++ b/docs/support/faq.md @@ -20,6 +20,12 @@ A more advanced, user-friendly, and secure (if using SSL) method is to set up a The most secure method (but also the most inconvenient method) is to set up a VPN tunnel to your home server. You would then be able to access Overseerr as if you were on your local network, via `http://LOCAL-IP-ADDRESS:5055`. +### Are there mobile apps for Overseerr? + +Since Overseerr has an almost native app experience when installed as a Progressive Web App (PWA), there are no plans to develop mobile apps for Overseerr. + +Out of the box, Overseerr already fulfills most of the [PWA install criteria](https://web.dev/install-criteria/). You simply need to make sure that your Overseerr instance is being served over HTTPS (e.g., via a [reverse proxy](../extending-overseerr/reverse-proxy.md)). + ### Overseerr is amazing! But it is not translated in my language yet! Can I help with translations? You sure can! We are using [Weblate](https://hosted.weblate.org/engage/overseerr/) for translations. If your language is not listed, please [open a feature request on GitHub](https://github.com/sct/overseerr/issues/new/choose). From e402c42aaa7d795cd724856a2e23615bb1a3695d Mon Sep 17 00:00:00 2001 From: Ryan Cohen Date: Sun, 24 Oct 2021 21:44:20 +0900 Subject: [PATCH 083/238] feat: issues (#2180) --- overseerr-api.yml | 274 ++++++ package.json | 4 + server/constants/issue.ts | 18 + server/entity/Issue.ts | 68 ++ server/entity/IssueComment.ts | 42 + server/entity/Media.ts | 6 +- server/entity/User.ts | 4 + server/interfaces/api/issueInterfaces.ts | 6 + server/lib/notifications/agents/agent.ts | 4 +- server/lib/notifications/agents/discord.ts | 54 ++ server/lib/notifications/agents/webpush.ts | 10 +- server/lib/notifications/index.ts | 3 + server/lib/permissions.ts | 3 + server/migration/1634904083966-AddIssues.ts | 55 ++ server/routes/index.ts | 4 + server/routes/issue.ts | 325 +++++++ server/routes/issueComment.ts | 132 +++ server/routes/request.ts | 228 ++--- server/subscriber/IssueCommentSubscriber.ts | 65 ++ server/subscriber/IssueSubscriber.ts | 50 + server/subscriber/MediaSubscriber.ts | 6 +- src/components/CollectionDetails/index.tsx | 4 +- src/components/Common/SlideOver/index.tsx | 2 +- src/components/IssueBlock/index.tsx | 68 ++ .../IssueDetails/IssueComment/index.tsx | 263 +++++ .../IssueDetails/IssueDescription/index.tsx | 152 +++ src/components/IssueDetails/index.tsx | 600 ++++++++++++ src/components/IssueList/IssueItem/index.tsx | 257 +++++ src/components/IssueList/index.tsx | 256 +++++ .../IssueModal/CreateIssueModal/index.tsx | 303 ++++++ src/components/IssueModal/constants.ts | 34 + src/components/IssueModal/index.tsx | 36 + src/components/Layout/Sidebar/index.tsx | 25 +- src/components/ManageSlideOver/index.tsx | 271 ++++++ src/components/MovieDetails/index.tsx | 214 +--- .../NotificationTypeSelector/index.tsx | 43 + src/components/PermissionEdit/index.tsx | 26 + .../RequestList/RequestItem/index.tsx | 2 +- src/components/TvDetails/index.tsx | 224 +---- src/i18n/globalMessages.ts | 2 + src/i18n/locale/en.json | 125 ++- src/pages/issues/[issueId]/index.tsx | 9 + src/pages/issues/index.tsx | 9 + src/styles/globals.css | 910 +++++++++--------- stylelint.config.js | 1 + 45 files changed, 4260 insertions(+), 937 deletions(-) create mode 100644 server/constants/issue.ts create mode 100644 server/entity/Issue.ts create mode 100644 server/entity/IssueComment.ts create mode 100644 server/interfaces/api/issueInterfaces.ts create mode 100644 server/migration/1634904083966-AddIssues.ts create mode 100644 server/routes/issue.ts create mode 100644 server/routes/issueComment.ts create mode 100644 server/subscriber/IssueCommentSubscriber.ts create mode 100644 server/subscriber/IssueSubscriber.ts create mode 100644 src/components/IssueBlock/index.tsx create mode 100644 src/components/IssueDetails/IssueComment/index.tsx create mode 100644 src/components/IssueDetails/IssueDescription/index.tsx create mode 100644 src/components/IssueDetails/index.tsx create mode 100644 src/components/IssueList/IssueItem/index.tsx create mode 100644 src/components/IssueList/index.tsx create mode 100644 src/components/IssueModal/CreateIssueModal/index.tsx create mode 100644 src/components/IssueModal/constants.ts create mode 100644 src/components/IssueModal/index.tsx create mode 100644 src/components/ManageSlideOver/index.tsx create mode 100644 src/pages/issues/[issueId]/index.tsx create mode 100644 src/pages/issues/index.tsx diff --git a/overseerr-api.yml b/overseerr-api.yml index bf324b029..87d8061ea 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1687,6 +1687,36 @@ components: type: number name: type: string + Issue: + type: object + properties: + id: + type: number + example: 1 + issueType: + type: number + example: 1 + media: + $ref: '#/components/schemas/MediaInfo' + createdBy: + $ref: '#/components/schemas/User' + modifiedBy: + $ref: '#/components/schemas/User' + comments: + type: array + items: + $ref: '#/components/schemas/IssueComment' + IssueComment: + type: object + properties: + id: + type: number + example: 1 + user: + $ref: '#/components/schemas/User' + message: + type: string + example: A comment securitySchemes: cookieAuth: type: apiKey @@ -5183,7 +5213,251 @@ paths: type: array items: type: string + /issue: + get: + summary: Get all issues + description: | + Returns a list of issues in JSON format. + tags: + - issue + parameters: + - in: query + name: take + schema: + type: number + nullable: true + example: 20 + - in: query + name: skip + schema: + type: number + nullable: true + example: 0 + - in: query + name: sort + schema: + type: string + enum: [added, modified] + default: added + - in: query + name: filter + schema: + type: string + enum: [all, open, resolved] + default: open + - in: query + name: requestedBy + schema: + type: number + nullable: true + example: 1 + responses: + '200': + description: Issues returned + content: + application/json: + schema: + type: object + properties: + pageInfo: + $ref: '#/components/schemas/PageInfo' + results: + type: array + items: + $ref: '#/components/schemas/Issue' + post: + summary: Create new issue + description: | + Creates a new issue + tags: + - issue + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + issueType: + type: number + message: + type: string + mediaId: + type: number + responses: + '201': + description: Succesfully created the issue + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' + /issue/{issueId}: + get: + summary: Get issue + description: | + Returns a single issue in JSON format. + tags: + - issue + parameters: + - in: path + name: issueId + required: true + schema: + type: number + example: 1 + responses: + '200': + description: Issues returned + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' + delete: + summary: Delete issue + description: Removes an issue. If the user has the `MANAGE_ISSUES` permission, any issue can be removed. Otherwise, only a users own issues can be removed. + tags: + - issue + parameters: + - in: path + name: issueId + description: Issue ID + required: true + example: '1' + schema: + type: string + responses: + '204': + description: Succesfully removed issue + /issue/{issueId}/comment: + post: + summary: Create a comment + description: | + Creates a comment and returns associated issue in JSON format. + tags: + - issue + parameters: + - in: path + name: issueId + required: true + schema: + type: number + example: 1 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + required: + - message + responses: + '200': + description: Issue returned with new comment + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' + /issueComment/{commentId}: + get: + summary: Get issue comment + description: | + Returns a single issue comment in JSON format. + tags: + - issue + parameters: + - in: path + name: commentId + required: true + schema: + type: string + example: 1 + responses: + '200': + description: Comment returned + content: + application/json: + schema: + $ref: '#/components/schemas/IssueComment' + put: + summary: Update issue comment + description: | + Updates and returns a single issue comment in JSON format. + tags: + - issue + parameters: + - in: path + name: commentId + required: true + schema: + type: string + example: 1 + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + message: + type: string + responses: + '200': + description: Comment updated + content: + application/json: + schema: + $ref: '#/components/schemas/IssueComment' + delete: + summary: Delete issue comment + description: | + Deletes an issue comment. Only users with `MANAGE_ISSUES` or the user who created the comment can perform this action. + tags: + - issue + parameters: + - in: path + name: commentId + description: Issue Comment ID + required: true + example: '1' + schema: + type: string + responses: + '204': + description: Succesfully removed issue comment + /issue/{issueId}/{status}: + post: + summary: Update an issue's status + description: | + Updates an issue's status to approved or declined. Also returns the issue in a JSON object. + Requires the `MANAGE_ISSUES` permission or `ADMIN`. + tags: + - issue + parameters: + - in: path + name: issueId + description: Issue ID + required: true + schema: + type: string + example: '1' + - in: path + name: status + description: New status + required: true + schema: + type: string + enum: [open, resolved] + responses: + '200': + description: Issue status changed + content: + application/json: + schema: + $ref: '#/components/schemas/Issue' security: - cookieAuth: [] - apiKey: [] diff --git a/package.json b/package.json index 0b1ca3623..3f0d86545 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ "migration:run": "ts-node --project server/tsconfig.json ./node_modules/typeorm/cli.js migration:run", "format": "prettier --write ." }, + "repository": { + "type": "git", + "url": "https://github.com/sct/overseerr.git" + }, "license": "MIT", "dependencies": { "@headlessui/react": "^1.4.1", diff --git a/server/constants/issue.ts b/server/constants/issue.ts new file mode 100644 index 000000000..859118538 --- /dev/null +++ b/server/constants/issue.ts @@ -0,0 +1,18 @@ +export enum IssueType { + VIDEO = 1, + AUDIO = 2, + SUBTITLES = 3, + OTHER = 4, +} + +export enum IssueStatus { + OPEN = 1, + RESOLVED = 2, +} + +export const IssueTypeNames = { + [IssueType.AUDIO]: 'Audio', + [IssueType.VIDEO]: 'Video', + [IssueType.SUBTITLES]: 'Subtitles', + [IssueType.OTHER]: 'Other', +}; diff --git a/server/entity/Issue.ts b/server/entity/Issue.ts new file mode 100644 index 000000000..d8e05c565 --- /dev/null +++ b/server/entity/Issue.ts @@ -0,0 +1,68 @@ +import { + Column, + CreateDateColumn, + Entity, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { IssueStatus, IssueType } from '../constants/issue'; +import IssueComment from './IssueComment'; +import Media from './Media'; +import { User } from './User'; + +@Entity() +class Issue { + @PrimaryGeneratedColumn() + public id: number; + + @Column({ type: 'int' }) + public issueType: IssueType; + + @Column({ type: 'int', default: IssueStatus.OPEN }) + public status: IssueStatus; + + @Column({ type: 'int', default: 0 }) + public problemSeason: number; + + @Column({ type: 'int', default: 0 }) + public problemEpisode: number; + + @ManyToOne(() => Media, (media) => media.issues, { + eager: true, + onDelete: 'CASCADE', + }) + public media: Media; + + @ManyToOne(() => User, (user) => user.createdIssues, { + eager: true, + onDelete: 'CASCADE', + }) + public createdBy: User; + + @ManyToOne(() => User, { + eager: true, + onDelete: 'CASCADE', + nullable: true, + }) + public modifiedBy?: User; + + @OneToMany(() => IssueComment, (comment) => comment.issue, { + cascade: true, + eager: true, + }) + public comments: IssueComment[]; + + @CreateDateColumn() + public createdAt: Date; + + @UpdateDateColumn() + public updatedAt: Date; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} + +export default Issue; diff --git a/server/entity/IssueComment.ts b/server/entity/IssueComment.ts new file mode 100644 index 000000000..e45216392 --- /dev/null +++ b/server/entity/IssueComment.ts @@ -0,0 +1,42 @@ +import { + Column, + CreateDateColumn, + Entity, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import Issue from './Issue'; +import { User } from './User'; + +@Entity() +class IssueComment { + @PrimaryGeneratedColumn() + public id: number; + + @ManyToOne(() => User, { + eager: true, + onDelete: 'CASCADE', + }) + public user: User; + + @ManyToOne(() => Issue, (issue) => issue.comments, { + onDelete: 'CASCADE', + }) + public issue: Issue; + + @Column({ type: 'text' }) + public message: string; + + @CreateDateColumn() + public createdAt: Date; + + @UpdateDateColumn() + public updatedAt: Date; + + constructor(init?: Partial) { + Object.assign(this, init); + } +} + +export default IssueComment; diff --git a/server/entity/Media.ts b/server/entity/Media.ts index 9666ac289..9cb8cd793 100644 --- a/server/entity/Media.ts +++ b/server/entity/Media.ts @@ -16,6 +16,7 @@ import { MediaStatus, MediaType } from '../constants/media'; import downloadTracker, { DownloadingItem } from '../lib/downloadtracker'; import { getSettings } from '../lib/settings'; import logger from '../logger'; +import Issue from './Issue'; import { MediaRequest } from './MediaRequest'; import Season from './Season'; @@ -54,7 +55,7 @@ class Media { try { const media = await mediaRepository.findOne({ where: { tmdbId: id, mediaType }, - relations: ['requests'], + relations: ['requests', 'issues'], }); return media; @@ -97,6 +98,9 @@ class Media { }) public seasons: Season[]; + @OneToMany(() => Issue, (issue) => issue.media, { cascade: true }) + public issues: Issue[]; + @CreateDateColumn() public createdAt: Date; diff --git a/server/entity/User.ts b/server/entity/User.ts index 77f0e8b11..d54e31ae5 100644 --- a/server/entity/User.ts +++ b/server/entity/User.ts @@ -27,6 +27,7 @@ import { } from '../lib/permissions'; import { getSettings } from '../lib/settings'; import logger from '../logger'; +import Issue from './Issue'; import { MediaRequest } from './MediaRequest'; import SeasonRequest from './SeasonRequest'; import { UserPushSubscription } from './UserPushSubscription'; @@ -115,6 +116,9 @@ export class User { @OneToMany(() => UserPushSubscription, (pushSub) => pushSub.user) public pushSubscriptions: UserPushSubscription[]; + @OneToMany(() => Issue, (issue) => issue.createdBy, { cascade: true }) + public createdIssues: Issue[]; + @CreateDateColumn() public createdAt: Date; diff --git a/server/interfaces/api/issueInterfaces.ts b/server/interfaces/api/issueInterfaces.ts new file mode 100644 index 000000000..bd17f1958 --- /dev/null +++ b/server/interfaces/api/issueInterfaces.ts @@ -0,0 +1,6 @@ +import Issue from '../../entity/Issue'; +import { PaginatedResponse } from './common'; + +export interface IssueResultsResponse extends PaginatedResponse { + results: Issue[]; +} diff --git a/server/lib/notifications/agents/agent.ts b/server/lib/notifications/agents/agent.ts index 66c52a16e..3de828f9c 100644 --- a/server/lib/notifications/agents/agent.ts +++ b/server/lib/notifications/agents/agent.ts @@ -1,5 +1,6 @@ import { Notification } from '..'; -import Media from '../../../entity/Media'; +import type Issue from '../../../entity/Issue'; +import type Media from '../../../entity/Media'; import { MediaRequest } from '../../../entity/MediaRequest'; import { User } from '../../../entity/User'; import { NotificationAgentConfig } from '../../settings'; @@ -12,6 +13,7 @@ export interface NotificationPayload { message?: string; extra?: { name: string; value: string }[]; request?: MediaRequest; + issue?: Issue; } export abstract class BaseAgent { diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts index 97be2cba5..8e08e9834 100644 --- a/server/lib/notifications/agents/discord.ts +++ b/server/lib/notifications/agents/discord.ts @@ -1,6 +1,8 @@ import axios from 'axios'; import { getRepository } from 'typeorm'; import { hasNotificationType, Notification } from '..'; +import { IssueStatus, IssueTypeNames } from '../../../constants/issue'; +import { MediaType } from '../../../constants/media'; import { User } from '../../../entity/User'; import logger from '../../../logger'; import { Permission } from '../../permissions'; @@ -120,6 +122,48 @@ class DiscordAgent }); } + // If payload has an issue attached, push issue specific fields + if (payload.issue) { + fields.push( + { + name: 'Created By', + value: payload.issue.createdBy.displayName, + inline: true, + }, + { + name: 'Issue Type', + value: IssueTypeNames[payload.issue.issueType], + inline: true, + }, + { + name: 'Issue Status', + value: + payload.issue.status === IssueStatus.OPEN ? 'Open' : 'Resolved', + inline: true, + } + ); + + if (payload.issue.media.mediaType === MediaType.TV) { + fields.push({ + name: 'Affected Season', + value: + payload.issue.problemSeason > 0 + ? `Season ${payload.issue.problemSeason}` + : 'All Seasons', + }); + + if (payload.issue.problemSeason > 0) { + fields.push({ + name: 'Affected Episode', + value: + payload.issue.problemEpisode > 0 + ? `Episode ${payload.issue.problemEpisode}` + : 'All Episodes', + }); + } + } + } + switch (type) { case Notification.MEDIA_PENDING: color = EmbedColors.ORANGE; @@ -161,6 +205,16 @@ class DiscordAgent value: 'Failed', inline: true, }); + break; + case Notification.ISSUE_CREATED: + case Notification.ISSUE_COMMENT: + case Notification.ISSUE_RESOLVED: + color = EmbedColors.ORANGE; + + if (payload.issue && payload.issue.status === IssueStatus.RESOLVED) { + color = EmbedColors.GREEN; + } + break; } diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts index afe4b7c10..624dab223 100644 --- a/server/lib/notifications/agents/webpush.ts +++ b/server/lib/notifications/agents/webpush.ts @@ -43,11 +43,6 @@ class WebPushAgent payload: NotificationPayload ): PushNotificationPayload { switch (type) { - case Notification.NONE: - return { - notificationType: Notification[type], - subject: 'Unknown', - }; case Notification.TEST_NOTIFICATION: return { notificationType: Notification[type], @@ -132,6 +127,11 @@ class WebPushAgent requestId: payload.request?.id, actionUrl: `/${payload.media?.mediaType}/${payload.media?.tmdbId}`, }; + default: + return { + notificationType: Notification[type], + subject: 'Unknown', + }; } } diff --git a/server/lib/notifications/index.ts b/server/lib/notifications/index.ts index a2eb01419..8769360fd 100644 --- a/server/lib/notifications/index.ts +++ b/server/lib/notifications/index.ts @@ -10,6 +10,9 @@ export enum Notification { TEST_NOTIFICATION = 32, MEDIA_DECLINED = 64, MEDIA_AUTO_APPROVED = 128, + ISSUE_CREATED = 256, + ISSUE_COMMENT = 512, + ISSUE_RESOLVED = 1024, } export const hasNotificationType = ( diff --git a/server/lib/permissions.ts b/server/lib/permissions.ts index fbf36e6b8..95160d380 100644 --- a/server/lib/permissions.ts +++ b/server/lib/permissions.ts @@ -19,6 +19,9 @@ export enum Permission { AUTO_APPROVE_4K_TV = 131072, REQUEST_MOVIE = 262144, REQUEST_TV = 524288, + MANAGE_ISSUES = 1048576, + VIEW_ISSUES = 2097152, + CREATE_ISSUES = 4194304, } export interface PermissionCheckOptions { diff --git a/server/migration/1634904083966-AddIssues.ts b/server/migration/1634904083966-AddIssues.ts new file mode 100644 index 000000000..0c6116f9d --- /dev/null +++ b/server/migration/1634904083966-AddIssues.ts @@ -0,0 +1,55 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddIssues1634904083966 implements MigrationInterface { + name = 'AddIssues1634904083966'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "issue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "issueType" integer NOT NULL, "status" integer NOT NULL DEFAULT (1), "problemSeason" integer NOT NULL DEFAULT (0), "problemEpisode" integer NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "mediaId" integer, "createdById" integer, "modifiedById" integer)` + ); + await queryRunner.query( + `CREATE TABLE "issue_comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "message" text NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "issueId" integer)` + ); + await queryRunner.query( + `CREATE TABLE "temporary_issue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "issueType" integer NOT NULL, "status" integer NOT NULL DEFAULT (1), "problemSeason" integer NOT NULL DEFAULT (0), "problemEpisode" integer NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "mediaId" integer, "createdById" integer, "modifiedById" integer, CONSTRAINT "FK_276e20d053f3cff1645803c95d8" FOREIGN KEY ("mediaId") REFERENCES "media" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_10b17b49d1ee77e7184216001e0" FOREIGN KEY ("createdById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_da88a1019c850d1a7b143ca02e5" FOREIGN KEY ("modifiedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_issue"("id", "issueType", "status", "problemSeason", "problemEpisode", "createdAt", "updatedAt", "mediaId", "createdById", "modifiedById") SELECT "id", "issueType", "status", "problemSeason", "problemEpisode", "createdAt", "updatedAt", "mediaId", "createdById", "modifiedById" FROM "issue"` + ); + await queryRunner.query(`DROP TABLE "issue"`); + await queryRunner.query(`ALTER TABLE "temporary_issue" RENAME TO "issue"`); + await queryRunner.query( + `CREATE TABLE "temporary_issue_comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "message" text NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "issueId" integer, CONSTRAINT "FK_707b033c2d0653f75213614789d" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_180710fead1c94ca499c57a7d42" FOREIGN KEY ("issueId") REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_issue_comment"("id", "message", "createdAt", "updatedAt", "userId", "issueId") SELECT "id", "message", "createdAt", "updatedAt", "userId", "issueId" FROM "issue_comment"` + ); + await queryRunner.query(`DROP TABLE "issue_comment"`); + await queryRunner.query( + `ALTER TABLE "temporary_issue_comment" RENAME TO "issue_comment"` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "issue_comment" RENAME TO "temporary_issue_comment"` + ); + await queryRunner.query( + `CREATE TABLE "issue_comment" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "message" text NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "issueId" integer)` + ); + await queryRunner.query( + `INSERT INTO "issue_comment"("id", "message", "createdAt", "updatedAt", "userId", "issueId") SELECT "id", "message", "createdAt", "updatedAt", "userId", "issueId" FROM "temporary_issue_comment"` + ); + await queryRunner.query(`DROP TABLE "temporary_issue_comment"`); + await queryRunner.query(`ALTER TABLE "issue" RENAME TO "temporary_issue"`); + await queryRunner.query( + `CREATE TABLE "issue" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "issueType" integer NOT NULL, "status" integer NOT NULL DEFAULT (1), "problemSeason" integer NOT NULL DEFAULT (0), "problemEpisode" integer NOT NULL DEFAULT (0), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "mediaId" integer, "createdById" integer, "modifiedById" integer)` + ); + await queryRunner.query( + `INSERT INTO "issue"("id", "issueType", "status", "problemSeason", "problemEpisode", "createdAt", "updatedAt", "mediaId", "createdById", "modifiedById") SELECT "id", "issueType", "status", "problemSeason", "problemEpisode", "createdAt", "updatedAt", "mediaId", "createdById", "modifiedById" FROM "temporary_issue"` + ); + await queryRunner.query(`DROP TABLE "temporary_issue"`); + await queryRunner.query(`DROP TABLE "issue_comment"`); + await queryRunner.query(`DROP TABLE "issue"`); + } +} diff --git a/server/routes/index.ts b/server/routes/index.ts index 25386e05b..3f57e8154 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -14,6 +14,8 @@ import { isPerson } from '../utils/typeHelpers'; import authRoutes from './auth'; import collectionRoutes from './collection'; import discoverRoutes, { createTmdbWithRegionLanguage } from './discover'; +import issueRoutes from './issue'; +import issueCommentRoutes from './issueComment'; import mediaRoutes from './media'; import movieRoutes from './movie'; import personRoutes from './person'; @@ -108,6 +110,8 @@ router.use('/media', isAuthenticated(), mediaRoutes); router.use('/person', isAuthenticated(), personRoutes); router.use('/collection', isAuthenticated(), collectionRoutes); router.use('/service', isAuthenticated(), serviceRoutes); +router.use('/issue', isAuthenticated(), issueRoutes); +router.use('/issueComment', isAuthenticated(), issueCommentRoutes); router.use('/auth', authRoutes); router.get('/regions', isAuthenticated(), async (req, res) => { diff --git a/server/routes/issue.ts b/server/routes/issue.ts new file mode 100644 index 000000000..d2774208d --- /dev/null +++ b/server/routes/issue.ts @@ -0,0 +1,325 @@ +import { Router } from 'express'; +import { getRepository } from 'typeorm'; +import { IssueStatus } from '../constants/issue'; +import Issue from '../entity/Issue'; +import IssueComment from '../entity/IssueComment'; +import Media from '../entity/Media'; +import { IssueResultsResponse } from '../interfaces/api/issueInterfaces'; +import { Permission } from '../lib/permissions'; +import logger from '../logger'; +import { isAuthenticated } from '../middleware/auth'; + +const issueRoutes = Router(); + +issueRoutes.get, IssueResultsResponse>( + '/', + isAuthenticated( + [ + Permission.MANAGE_ISSUES, + Permission.VIEW_ISSUES, + Permission.CREATE_ISSUES, + ], + { type: 'or' } + ), + async (req, res, next) => { + const pageSize = req.query.take ? Number(req.query.take) : 10; + const skip = req.query.skip ? Number(req.query.skip) : 0; + const createdBy = req.query.createdBy ? Number(req.query.createdBy) : null; + + let sortFilter: string; + + switch (req.query.sort) { + case 'modified': + sortFilter = 'issue.updatedAt'; + break; + default: + sortFilter = 'issue.createdAt'; + } + + let statusFilter: IssueStatus[]; + + switch (req.query.filter) { + case 'open': + statusFilter = [IssueStatus.OPEN]; + break; + case 'resolved': + statusFilter = [IssueStatus.RESOLVED]; + break; + default: + statusFilter = [IssueStatus.OPEN, IssueStatus.RESOLVED]; + } + + let query = getRepository(Issue) + .createQueryBuilder('issue') + .leftJoinAndSelect('issue.createdBy', 'createdBy') + .leftJoinAndSelect('issue.media', 'media') + .leftJoinAndSelect('issue.modifiedBy', 'modifiedBy') + .where('issue.status IN (:...issueStatus)', { + issueStatus: statusFilter, + }); + + if ( + !req.user?.hasPermission( + [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], + { type: 'or' } + ) + ) { + if (createdBy && createdBy !== req.user?.id) { + return next({ + status: 403, + message: + 'You do not have permission to view issues created by other users', + }); + } + query = query.andWhere('createdBy.id = :id', { id: req.user?.id }); + } else if (createdBy) { + query = query.andWhere('createdBy.id = :id', { id: createdBy }); + } + + const [issues, issueCount] = await query + .orderBy(sortFilter, 'DESC') + .take(pageSize) + .skip(skip) + .getManyAndCount(); + + return res.status(200).json({ + pageInfo: { + pages: Math.ceil(issueCount / pageSize), + pageSize, + results: issueCount, + page: Math.ceil(skip / pageSize) + 1, + }, + results: issues, + }); + } +); + +issueRoutes.post< + Record, + Issue, + { + message: string; + mediaId: number; + issueType: number; + problemSeason: number; + problemEpisode: number; + } +>( + '/', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + // Satisfy typescript here. User is set, we assure you! + if (!req.user) { + return next({ status: 500, message: 'User missing from request.' }); + } + + const issueRepository = getRepository(Issue); + const mediaRepository = getRepository(Media); + + const media = await mediaRepository.findOne({ + where: { id: req.body.mediaId }, + }); + + if (!media) { + return next({ status: 404, message: 'Media does not exist.' }); + } + + const issue = new Issue({ + createdBy: req.user, + issueType: req.body.issueType, + problemSeason: req.body.problemSeason, + problemEpisode: req.body.problemEpisode, + media, + comments: [ + new IssueComment({ + user: req.user, + message: req.body.message, + }), + ], + }); + + const newIssue = await issueRepository.save(issue); + + return res.status(200).json(newIssue); + } +); + +issueRoutes.get<{ issueId: string }>( + '/:issueId', + isAuthenticated( + [ + Permission.MANAGE_ISSUES, + Permission.VIEW_ISSUES, + Permission.CREATE_ISSUES, + ], + { type: 'or' } + ), + async (req, res, next) => { + const issueRepository = getRepository(Issue); + // Satisfy typescript here. User is set, we assure you! + if (!req.user) { + return next({ status: 500, message: 'User missing from request.' }); + } + + try { + const issue = await issueRepository + .createQueryBuilder('issue') + .leftJoinAndSelect('issue.comments', 'comments') + .leftJoinAndSelect('issue.createdBy', 'createdBy') + .leftJoinAndSelect('comments.user', 'user') + .leftJoinAndSelect('issue.media', 'media') + .where('issue.id = :issueId', { issueId: Number(req.params.issueId) }) + .getOneOrFail(); + + if ( + issue.createdBy.id !== req.user.id && + !req.user.hasPermission( + [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], + { type: 'or' } + ) + ) { + return next({ + status: 403, + message: 'You do not have permission to view this issue.', + }); + } + + return res.status(200).json(issue); + } catch (e) { + logger.debug('Failed to retrieve issue.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 500, message: 'Issue not found.' }); + } + } +); + +issueRoutes.post<{ issueId: string }, Issue, { message: string }>( + '/:issueId/comment', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + const issueRepository = getRepository(Issue); + // Satisfy typescript here. User is set, we assure you! + if (!req.user) { + return next({ status: 500, message: 'User missing from request.' }); + } + + try { + const issue = await issueRepository.findOneOrFail({ + where: { id: Number(req.params.issueId) }, + }); + + if ( + issue.createdBy.id !== req.user.id && + !req.user.hasPermission(Permission.MANAGE_ISSUES) + ) { + return next({ + status: 403, + message: 'You do not have permission to comment on this issue.', + }); + } + + const comment = new IssueComment({ + message: req.body.message, + user: req.user, + }); + + issue.comments = [...issue.comments, comment]; + + await issueRepository.save(issue); + + return res.status(200).json(issue); + } catch (e) { + logger.debug('Something went wrong creating an issue comment.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 500, message: 'Issue not found.' }); + } + } +); + +issueRoutes.post<{ issueId: string; status: string }, Issue>( + '/:issueId/:status', + isAuthenticated(Permission.MANAGE_ISSUES), + async (req, res, next) => { + const issueRepository = getRepository(Issue); + // Satisfy typescript here. User is set, we assure you! + if (!req.user) { + return next({ status: 500, message: 'User missing from request.' }); + } + + try { + const issue = await issueRepository.findOneOrFail({ + where: { id: Number(req.params.issueId) }, + }); + + let newStatus: IssueStatus | undefined; + + switch (req.params.status) { + case 'resolved': + newStatus = IssueStatus.RESOLVED; + break; + case 'open': + newStatus = IssueStatus.OPEN; + } + + if (!newStatus) { + return next({ + status: 400, + message: 'You must provide a valid status', + }); + } + + issue.status = newStatus; + + await issueRepository.save(issue); + + return res.status(200).json(issue); + } catch (e) { + logger.debug('Something went wrong creating an issue comment.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 500, message: 'Issue not found.' }); + } + } +); + +issueRoutes.delete('/:issueId', async (req, res, next) => { + const issueRepository = getRepository(Issue); + + try { + const issue = await issueRepository.findOneOrFail({ + where: { id: Number(req.params.issueId) }, + relations: ['createdBy'], + }); + + if ( + !req.user?.hasPermission(Permission.MANAGE_ISSUES) && + issue.createdBy.id !== req.user?.id + ) { + return next({ + status: 401, + message: 'You do not have permission to delete this issue.', + }); + } + + await issueRepository.remove(issue); + + return res.status(204).send(); + } catch (e) { + logger.error('Something went wrong deleting an issue.', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Issue not found.' }); + } +}); + +export default issueRoutes; diff --git a/server/routes/issueComment.ts b/server/routes/issueComment.ts new file mode 100644 index 000000000..9bc4e27b9 --- /dev/null +++ b/server/routes/issueComment.ts @@ -0,0 +1,132 @@ +import { Router } from 'express'; +import { getRepository } from 'typeorm'; +import IssueComment from '../entity/IssueComment'; +import { Permission } from '../lib/permissions'; +import logger from '../logger'; +import { isAuthenticated } from '../middleware/auth'; + +const issueCommentRoutes = Router(); + +issueCommentRoutes.get<{ commentId: string }, IssueComment>( + '/:commentId', + isAuthenticated( + [ + Permission.MANAGE_ISSUES, + Permission.VIEW_ISSUES, + Permission.CREATE_ISSUES, + ], + { + type: 'or', + } + ), + async (req, res, next) => { + const issueCommentRepository = getRepository(IssueComment); + + try { + const comment = await issueCommentRepository.findOneOrFail({ + where: { id: Number(req.params.commentId) }, + }); + + if ( + !req.user?.hasPermission( + [Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], + { type: 'or' } + ) && + comment.user.id !== req.user?.id + ) { + return next({ + status: 403, + message: 'You do not have permission to view this comment.', + }); + } + + return res.status(200).json(comment); + } catch (e) { + logger.debug('Request for unknown issue comment failed', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Issue comment not found.' }); + } + } +); + +issueCommentRoutes.put< + { commentId: string }, + IssueComment, + { message: string } +>( + '/:commentId', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + const issueCommentRepository = getRepository(IssueComment); + + try { + const comment = await issueCommentRepository.findOneOrFail({ + where: { id: Number(req.params.commentId) }, + }); + + if ( + !req.user?.hasPermission([Permission.MANAGE_ISSUES], { type: 'or' }) && + comment.user.id !== req.user?.id + ) { + return next({ + status: 403, + message: 'You do not have permission to edit this comment.', + }); + } + + comment.message = req.body.message; + + await issueCommentRepository.save(comment); + + return res.status(200).json(comment); + } catch (e) { + logger.debug('Put request for issue comment failed', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Issue comment not found.' }); + } + } +); + +issueCommentRoutes.delete<{ commentId: string }, IssueComment>( + '/:commentId', + isAuthenticated([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), + async (req, res, next) => { + const issueCommentRepository = getRepository(IssueComment); + + try { + const comment = await issueCommentRepository.findOneOrFail({ + where: { id: Number(req.params.commentId) }, + }); + + if ( + !req.user?.hasPermission([Permission.MANAGE_ISSUES], { type: 'or' }) && + comment.user.id !== req.user?.id + ) { + return next({ + status: 403, + message: 'You do not have permission to delete this comment.', + }); + } + + await issueCommentRepository.remove(comment); + + return res.status(204).send(); + } catch (e) { + logger.debug('Delete request for issue comment failed', { + label: 'API', + errorMessage: e.message, + }); + next({ status: 404, message: 'Issue comment not found.' }); + } + } +); + +export default issueCommentRoutes; diff --git a/server/routes/request.ts b/server/routes/request.ts index 8fed74107..2eac90f00 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -13,131 +13,134 @@ import { isAuthenticated } from '../middleware/auth'; const requestRoutes = Router(); -requestRoutes.get('/', async (req, res, next) => { - try { - const pageSize = req.query.take ? Number(req.query.take) : 10; - const skip = req.query.skip ? Number(req.query.skip) : 0; - const requestedBy = req.query.requestedBy - ? Number(req.query.requestedBy) - : null; +requestRoutes.get, RequestResultsResponse>( + '/', + async (req, res, next) => { + try { + const pageSize = req.query.take ? Number(req.query.take) : 10; + const skip = req.query.skip ? Number(req.query.skip) : 0; + const requestedBy = req.query.requestedBy + ? Number(req.query.requestedBy) + : null; - let statusFilter: MediaRequestStatus[]; + let statusFilter: MediaRequestStatus[]; - switch (req.query.filter) { - case 'approved': - case 'processing': - case 'available': - statusFilter = [MediaRequestStatus.APPROVED]; - break; - case 'pending': - statusFilter = [MediaRequestStatus.PENDING]; - break; - case 'unavailable': - statusFilter = [ - MediaRequestStatus.PENDING, - MediaRequestStatus.APPROVED, - ]; - break; - default: - statusFilter = [ - MediaRequestStatus.PENDING, - MediaRequestStatus.APPROVED, - MediaRequestStatus.DECLINED, - ]; - } + switch (req.query.filter) { + case 'approved': + case 'processing': + case 'available': + statusFilter = [MediaRequestStatus.APPROVED]; + break; + case 'pending': + statusFilter = [MediaRequestStatus.PENDING]; + break; + case 'unavailable': + statusFilter = [ + MediaRequestStatus.PENDING, + MediaRequestStatus.APPROVED, + ]; + break; + default: + statusFilter = [ + MediaRequestStatus.PENDING, + MediaRequestStatus.APPROVED, + MediaRequestStatus.DECLINED, + ]; + } - let mediaStatusFilter: MediaStatus[]; + let mediaStatusFilter: MediaStatus[]; - switch (req.query.filter) { - case 'available': - mediaStatusFilter = [MediaStatus.AVAILABLE]; - break; - case 'processing': - case 'unavailable': - mediaStatusFilter = [ - MediaStatus.UNKNOWN, - MediaStatus.PENDING, - MediaStatus.PROCESSING, - MediaStatus.PARTIALLY_AVAILABLE, - ]; - break; - default: - mediaStatusFilter = [ - MediaStatus.UNKNOWN, - MediaStatus.PENDING, - MediaStatus.PROCESSING, - MediaStatus.PARTIALLY_AVAILABLE, - MediaStatus.AVAILABLE, - ]; - } + switch (req.query.filter) { + case 'available': + mediaStatusFilter = [MediaStatus.AVAILABLE]; + break; + case 'processing': + case 'unavailable': + mediaStatusFilter = [ + MediaStatus.UNKNOWN, + MediaStatus.PENDING, + MediaStatus.PROCESSING, + MediaStatus.PARTIALLY_AVAILABLE, + ]; + break; + default: + mediaStatusFilter = [ + MediaStatus.UNKNOWN, + MediaStatus.PENDING, + MediaStatus.PROCESSING, + MediaStatus.PARTIALLY_AVAILABLE, + MediaStatus.AVAILABLE, + ]; + } - let sortFilter: string; + let sortFilter: string; - switch (req.query.sort) { - case 'modified': - sortFilter = 'request.updatedAt'; - break; - default: - sortFilter = 'request.id'; - } + switch (req.query.sort) { + case 'modified': + sortFilter = 'request.updatedAt'; + break; + default: + sortFilter = 'request.id'; + } - let query = getRepository(MediaRequest) - .createQueryBuilder('request') - .leftJoinAndSelect('request.media', 'media') - .leftJoinAndSelect('request.seasons', 'seasons') - .leftJoinAndSelect('request.modifiedBy', 'modifiedBy') - .leftJoinAndSelect('request.requestedBy', 'requestedBy') - .where('request.status IN (:...requestStatus)', { - requestStatus: statusFilter, - }) - .andWhere( - '((request.is4k = 0 AND media.status IN (:...mediaStatus)) OR (request.is4k = 1 AND media.status4k IN (:...mediaStatus)))', - { - mediaStatus: mediaStatusFilter, + let query = getRepository(MediaRequest) + .createQueryBuilder('request') + .leftJoinAndSelect('request.media', 'media') + .leftJoinAndSelect('request.seasons', 'seasons') + .leftJoinAndSelect('request.modifiedBy', 'modifiedBy') + .leftJoinAndSelect('request.requestedBy', 'requestedBy') + .where('request.status IN (:...requestStatus)', { + requestStatus: statusFilter, + }) + .andWhere( + '((request.is4k = 0 AND media.status IN (:...mediaStatus)) OR (request.is4k = 1 AND media.status4k IN (:...mediaStatus)))', + { + mediaStatus: mediaStatusFilter, + } + ); + + if ( + !req.user?.hasPermission( + [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], + { type: 'or' } + ) + ) { + if (requestedBy && requestedBy !== req.user?.id) { + return next({ + status: 403, + message: "You do not have permission to view this user's requests.", + }); } - ); - if ( - !req.user?.hasPermission( - [Permission.MANAGE_REQUESTS, Permission.REQUEST_VIEW], - { type: 'or' } - ) - ) { - if (requestedBy && requestedBy !== req.user?.id) { - return next({ - status: 403, - message: "You do not have permission to view this user's requests.", + query = query.andWhere('requestedBy.id = :id', { + id: req.user?.id, + }); + } else if (requestedBy) { + query = query.andWhere('requestedBy.id = :id', { + id: requestedBy, }); } - query = query.andWhere('requestedBy.id = :id', { - id: req.user?.id, - }); - } else if (requestedBy) { - query = query.andWhere('requestedBy.id = :id', { - id: requestedBy, + const [requests, requestCount] = await query + .orderBy(sortFilter, 'DESC') + .take(pageSize) + .skip(skip) + .getManyAndCount(); + + return res.status(200).json({ + pageInfo: { + pages: Math.ceil(requestCount / pageSize), + pageSize, + results: requestCount, + page: Math.ceil(skip / pageSize) + 1, + }, + results: requests, }); + } catch (e) { + next({ status: 500, message: e.message }); } - - const [requests, requestCount] = await query - .orderBy(sortFilter, 'DESC') - .take(pageSize) - .skip(skip) - .getManyAndCount(); - - return res.status(200).json({ - pageInfo: { - pages: Math.ceil(requestCount / pageSize), - pageSize, - results: requestCount, - page: Math.ceil(skip / pageSize) + 1, - }, - results: requests, - } as RequestResultsResponse); - } catch (e) { - next({ status: 500, message: e.message }); } -}); +); requestRoutes.post('/', async (req, res, next) => { const tmdb = new TheMovieDb(); @@ -665,7 +668,10 @@ requestRoutes.delete('/:requestId', async (req, res, next) => { return res.status(204).send(); } catch (e) { - logger.error(e.message); + logger.error('Something went wrong deleting a request.', { + label: 'API', + errorMessage: e.message, + }); next({ status: 404, message: 'Request not found.' }); } }); diff --git a/server/subscriber/IssueCommentSubscriber.ts b/server/subscriber/IssueCommentSubscriber.ts new file mode 100644 index 000000000..aab6bd94d --- /dev/null +++ b/server/subscriber/IssueCommentSubscriber.ts @@ -0,0 +1,65 @@ +import { + EntitySubscriberInterface, + EventSubscriber, + getRepository, + InsertEvent, +} from 'typeorm'; +import TheMovieDb from '../api/themoviedb'; +import { MediaType } from '../constants/media'; +import IssueComment from '../entity/IssueComment'; +import notificationManager, { Notification } from '../lib/notifications'; + +@EventSubscriber() +export class IssueCommentSubscriber + implements EntitySubscriberInterface +{ + public listenTo(): typeof IssueComment { + return IssueComment; + } + + private async sendIssueCommentNotification(entity: IssueComment) { + const issueCommentRepository = getRepository(IssueComment); + let title: string; + let image: string; + const tmdb = new TheMovieDb(); + const issuecomment = await issueCommentRepository.findOne({ + where: { id: entity.id }, + relations: ['issue'], + }); + + const issue = issuecomment?.issue; + + if (!issue) { + return; + } + + if (issue.media.mediaType === MediaType.MOVIE) { + const movie = await tmdb.getMovie({ movieId: issue.media.tmdbId }); + + title = movie.title; + image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`; + } else { + const tvshow = await tmdb.getTvShow({ tvId: issue.media.tmdbId }); + + title = tvshow.name; + image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tvshow.poster_path}`; + } + + notificationManager.sendNotification(Notification.ISSUE_COMMENT, { + subject: `New Issue Comment: ${title}`, + message: entity.message, + issue, + image, + notifyUser: + issue.createdBy.id !== entity.user.id ? issue.createdBy : undefined, + }); + } + + public afterInsert(event: InsertEvent): void { + if (!event.entity) { + return; + } + + this.sendIssueCommentNotification(event.entity); + } +} diff --git a/server/subscriber/IssueSubscriber.ts b/server/subscriber/IssueSubscriber.ts new file mode 100644 index 000000000..e76d2fd7b --- /dev/null +++ b/server/subscriber/IssueSubscriber.ts @@ -0,0 +1,50 @@ +import { + EntitySubscriberInterface, + EventSubscriber, + InsertEvent, +} from 'typeorm'; +import TheMovieDb from '../api/themoviedb'; +import { MediaType } from '../constants/media'; +import Issue from '../entity/Issue'; +import notificationManager, { Notification } from '../lib/notifications'; + +@EventSubscriber() +export class IssueSubscriber implements EntitySubscriberInterface { + public listenTo(): typeof Issue { + return Issue; + } + + private async sendIssueCreatedNotification(entity: Issue) { + let title: string; + let image: string; + const tmdb = new TheMovieDb(); + if (entity.media.mediaType === MediaType.MOVIE) { + const movie = await tmdb.getMovie({ movieId: entity.media.tmdbId }); + + title = movie.title; + image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${movie.poster_path}`; + } else { + const tvshow = await tmdb.getTvShow({ tvId: entity.media.tmdbId }); + + title = tvshow.name; + image = `https://image.tmdb.org/t/p/w600_and_h900_bestv2${tvshow.poster_path}`; + } + + const [firstComment] = entity.comments; + + notificationManager.sendNotification(Notification.ISSUE_CREATED, { + subject: title, + message: firstComment.message, + issue: entity, + image, + }); + } + + public afterInsert(event: InsertEvent): void { + if (!event.entity) { + return; + } + + this.sendIssueCreatedNotification(event.entity); + } +} diff --git a/server/subscriber/MediaSubscriber.ts b/server/subscriber/MediaSubscriber.ts index fb9bf24c2..f50e1d664 100644 --- a/server/subscriber/MediaSubscriber.ts +++ b/server/subscriber/MediaSubscriber.ts @@ -13,7 +13,7 @@ import Season from '../entity/Season'; import notificationManager, { Notification } from '../lib/notifications'; @EventSubscriber() -export class MediaSubscriber implements EntitySubscriberInterface { +export class MediaSubscriber implements EntitySubscriberInterface { private async notifyAvailableMovie(entity: Media, dbEntity?: Media) { if ( entity.status === MediaStatus.AVAILABLE && @@ -169,4 +169,8 @@ export class MediaSubscriber implements EntitySubscriberInterface { this.updateChildRequestStatus(event.entity as Media, true); } } + + public listenTo(): typeof Media { + return Media; + } } diff --git a/src/components/CollectionDetails/index.tsx b/src/components/CollectionDetails/index.tsx index 56b368d90..bfd654e99 100644 --- a/src/components/CollectionDetails/index.tsx +++ b/src/components/CollectionDetails/index.tsx @@ -323,7 +323,9 @@ const CollectionDetails: React.FC = ({ .map((t, k) => {t}) .reduce((prev, curr) => ( <> - {prev} | {curr} + {prev} + | + {curr} ))} diff --git a/src/components/Common/SlideOver/index.tsx b/src/components/Common/SlideOver/index.tsx index 736a4a6e2..2d49e6a96 100644 --- a/src/components/Common/SlideOver/index.tsx +++ b/src/components/Common/SlideOver/index.tsx @@ -7,7 +7,7 @@ import Transition from '../../Transition'; interface SlideOverProps { show?: boolean; - title: string; + title: React.ReactNode; subText?: string; onClose: () => void; } diff --git a/src/components/IssueBlock/index.tsx b/src/components/IssueBlock/index.tsx new file mode 100644 index 000000000..7e8067c47 --- /dev/null +++ b/src/components/IssueBlock/index.tsx @@ -0,0 +1,68 @@ +import { + CalendarIcon, + ExclamationIcon, + EyeIcon, + UserIcon, +} from '@heroicons/react/solid'; +import Link from 'next/link'; +import React from 'react'; +import { useIntl } from 'react-intl'; +import type Issue from '../../../server/entity/Issue'; +import Button from '../Common/Button'; +import { issueOptions } from '../IssueModal/constants'; + +interface IssueBlockProps { + issue: Issue; +} + +const IssueBlock: React.FC = ({ issue }) => { + const intl = useIntl(); + const issueOption = issueOptions.find( + (opt) => opt.issueType === issue.issueType + ); + + if (!issueOption) { + return null; + } + + return ( +
+
+
+
+ + + {intl.formatMessage(issueOption.name)} + +
+
+ + + {issue.createdBy.displayName} + +
+
+ + + {intl.formatDate(issue.createdAt, { + year: 'numeric', + month: 'long', + day: 'numeric', + })} + +
+
+
+ + + +
+
+
+ ); +}; + +export default IssueBlock; diff --git a/src/components/IssueDetails/IssueComment/index.tsx b/src/components/IssueDetails/IssueComment/index.tsx new file mode 100644 index 000000000..603616da3 --- /dev/null +++ b/src/components/IssueDetails/IssueComment/index.tsx @@ -0,0 +1,263 @@ +import { Menu } from '@headlessui/react'; +import { ExclamationIcon } from '@heroicons/react/outline'; +import { DotsVerticalIcon } from '@heroicons/react/solid'; +import axios from 'axios'; +import { Field, Form, Formik } from 'formik'; +import React, { useState } from 'react'; +import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; +import ReactMarkdown from 'react-markdown'; +import * as Yup from 'yup'; +import type { default as IssueCommentType } from '../../../../server/entity/IssueComment'; +import { Permission, useUser } from '../../../hooks/useUser'; +import Button from '../../Common/Button'; +import Modal from '../../Common/Modal'; +import Transition from '../../Transition'; + +const messages = defineMessages({ + postedby: 'Posted by {username} {relativeTime}', + postedbyedited: 'Posted by {username} {relativeTime} (Edited)', + delete: 'Delete Comment', + areyousuredelete: 'Are you sure you want to delete this comment?', + validationComment: 'You must provide a message', + edit: 'Edit Comment', +}); + +interface IssueCommentProps { + comment: IssueCommentType; + isReversed?: boolean; + isActiveUser?: boolean; + onUpdate?: () => void; +} + +const IssueComment: React.FC = ({ + comment, + isReversed = false, + isActiveUser = false, + onUpdate, +}) => { + const intl = useIntl(); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const { user, hasPermission } = useUser(); + + const EditCommentSchema = Yup.object().shape({ + newMessage: Yup.string().required( + intl.formatMessage(messages.validationComment) + ), + }); + + const deleteComment = async () => { + try { + await axios.delete(`/api/v1/issueComment/${comment.id}`); + } catch (e) { + // something went wrong deleting the comment + } finally { + if (onUpdate) { + onUpdate(); + } + } + }; + + const belongsToUser = comment.user.id === user?.id; + + return ( +
+ + setShowDeleteModal(false)} + onOk={() => deleteComment()} + okText={intl.formatMessage(messages.delete)} + okButtonType="danger" + iconSvg={} + > + {intl.formatMessage(messages.areyousuredelete)} + + + +
+
+ {(belongsToUser || hasPermission(Permission.MANAGE_ISSUES)) && ( + + {({ open }) => ( + <> +
+ + Open options + +
+ + + +
+ + {({ active }) => ( + + )} + + + {({ active }) => ( + + )} + +
+
+
+ + )} +
+ )} +
+
+ {isEditing ? ( + { + await axios.put(`/api/v1/issueComment/${comment.id}`, { + message: values.newMessage, + }); + + if (onUpdate) { + onUpdate(); + } + + setIsEditing(false); + }} + validationSchema={EditCommentSchema} + > + {({ isValid, isSubmitting, errors, touched }) => { + return ( +
+ + {errors.newMessage && touched.newMessage && ( +
{errors.newMessage}
+ )} +
+ + +
+ + ); + }} +
+ ) : ( +
+ + {comment.message} + +
+ )} +
+
+
+ + {intl.formatMessage( + comment.createdAt !== comment.updatedAt + ? messages.postedbyedited + : messages.postedby, + { + username: ( + + {comment.user.displayName} + + ), + relativeTime: ( + + ), + } + )} + +
+
+
+ ); +}; + +export default IssueComment; diff --git a/src/components/IssueDetails/IssueDescription/index.tsx b/src/components/IssueDetails/IssueDescription/index.tsx new file mode 100644 index 000000000..ba550afbd --- /dev/null +++ b/src/components/IssueDetails/IssueDescription/index.tsx @@ -0,0 +1,152 @@ +import { Menu, Transition } from '@headlessui/react'; +import { DotsVerticalIcon } from '@heroicons/react/solid'; +import { Field, Form, Formik } from 'formik'; +import React, { Fragment, useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import ReactMarkdown from 'react-markdown'; +import { Permission, useUser } from '../../../hooks/useUser'; +import Button from '../../Common/Button'; + +const messages = defineMessages({ + description: 'Description', + edit: 'Edit Description', + cancel: 'Cancel', + save: 'Save Changes', + deleteissue: 'Delete Issue', +}); + +interface IssueDescriptionProps { + issueId: number; + description: string; + onEdit: (newDescription: string) => void; + onDelete: () => void; +} + +const IssueDescription: React.FC = ({ + issueId, + description, + onEdit, + onDelete, +}) => { + const intl = useIntl(); + const { user, hasPermission } = useUser(); + const [isEditing, setIsEditing] = useState(false); + + return ( +
+
+
+ {intl.formatMessage(messages.description)} +
+ {(hasPermission(Permission.MANAGE_ISSUES) || user?.id === issueId) && ( + + {({ open }) => ( + <> +
+ + Open options + +
+ + + +
+ + {({ active }) => ( + + )} + + + + {({ active }) => ( + + )} + +
+
+
+ + )} +
+ )} +
+ {isEditing ? ( + { + onEdit(values.newMessage); + setIsEditing(false); + }} + > + {() => { + return ( +
+ +
+ + +
+ + ); + }} +
+ ) : ( +
+ + {description} + +
+ )} +
+ ); +}; + +export default IssueDescription; diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx new file mode 100644 index 000000000..46ba759e8 --- /dev/null +++ b/src/components/IssueDetails/index.tsx @@ -0,0 +1,600 @@ +import { + ChatIcon, + CheckCircleIcon, + ExclamationIcon, + ExternalLinkIcon, +} from '@heroicons/react/outline'; +import { RefreshIcon } from '@heroicons/react/solid'; +import axios from 'axios'; +import { Field, Form, Formik } from 'formik'; +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import React, { useState } from 'react'; +import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR from 'swr'; +import * as Yup from 'yup'; +import { IssueStatus } from '../../../server/constants/issue'; +import { MediaType } from '../../../server/constants/media'; +import type Issue from '../../../server/entity/Issue'; +import type { MovieDetails } from '../../../server/models/Movie'; +import type { TvDetails } from '../../../server/models/Tv'; +import { Permission, useUser } from '../../hooks/useUser'; +import globalMessages from '../../i18n/globalMessages'; +import Error from '../../pages/_error'; +import Badge from '../Common/Badge'; +import Button from '../Common/Button'; +import CachedImage from '../Common/CachedImage'; +import LoadingSpinner from '../Common/LoadingSpinner'; +import Modal from '../Common/Modal'; +import PageTitle from '../Common/PageTitle'; +import { issueOptions } from '../IssueModal/constants'; +import Transition from '../Transition'; +import IssueComment from './IssueComment'; +import IssueDescription from './IssueDescription'; + +const messages = defineMessages({ + openedby: + '#{issueId} opened {relativeTime} by {username}', + closeissue: 'Close Issue', + closeissueandcomment: 'Close with Comment', + leavecomment: 'Comment', + comments: 'Comments', + reopenissue: 'Reopen Issue', + reopenissueandcomment: 'Reopen with Comment', + issuepagetitle: 'Issue', + openinradarr: 'Open in Radarr', + openinsonarr: 'Open in Sonarr', + toasteditdescriptionsuccess: 'Successfully edited the issue description.', + toasteditdescriptionfailed: 'Something went wrong editing the description.', + toaststatusupdated: 'Issue status updated.', + toaststatusupdatefailed: 'Something went wrong updating the issue status.', + issuetype: 'Issue Type', + mediatype: 'Media Type', + lastupdated: 'Last Updated', + statusopen: 'Open', + statusresolved: 'Resolved', + problemseason: 'Affected Season', + allseasons: 'All Seasons', + season: 'Season {seasonNumber}', + problemepisode: 'Affected Episode', + allepisodes: 'All Episodes', + episode: 'Episode {episodeNumber}', + deleteissue: 'Delete Issue', + deleteissueconfirm: 'Are you sure you want to delete this issue?', + toastissuedeleted: 'Issue deleted succesfully.', + toastissuedeletefailed: 'Something went wrong deleting the issue.', + nocomments: 'No comments.', + unknownissuetype: 'Unknown', +}); + +const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { + return (movie as MovieDetails).title !== undefined; +}; + +const IssueDetails: React.FC = () => { + const { addToast } = useToasts(); + const router = useRouter(); + const intl = useIntl(); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const { user: currentUser, hasPermission } = useUser(); + const { data: issueData, revalidate: revalidateIssue } = useSWR( + `/api/v1/issue/${router.query.issueId}` + ); + const { data, error } = useSWR( + issueData?.media.tmdbId + ? `/api/v1/${issueData.media.mediaType}/${issueData.media.tmdbId}` + : null + ); + + const CommentSchema = Yup.object().shape({ + message: Yup.string().required(), + }); + + const issueOption = issueOptions.find( + (opt) => opt.issueType === issueData?.issueType + ); + + const mediaType = issueData?.media.mediaType; + + if (!data && !error) { + return ; + } + + if (!data || !issueData) { + return ; + } + + const belongsToUser = issueData.createdBy.id === currentUser?.id; + + const [firstComment, ...otherComments] = issueData.comments; + + const editFirstComment = async (newMessage: string) => { + try { + await axios.put(`/api/v1/issueComment/${firstComment.id}`, { + message: newMessage, + }); + + addToast(intl.formatMessage(messages.toasteditdescriptionsuccess), { + appearance: 'success', + autoDismiss: true, + }); + revalidateIssue(); + } catch (e) { + addToast(intl.formatMessage(messages.toasteditdescriptionfailed), { + appearance: 'error', + autoDismiss: true, + }); + } + }; + + const updateIssueStatus = async (newStatus: 'open' | 'resolved') => { + try { + await axios.post(`/api/v1/issue/${issueData.id}/${newStatus}`); + + addToast(intl.formatMessage(messages.toaststatusupdated), { + appearance: 'success', + autoDismiss: true, + }); + revalidateIssue(); + } catch (e) { + addToast(intl.formatMessage(messages.toaststatusupdatefailed), { + appearance: 'error', + autoDismiss: true, + }); + } + }; + + const deleteIssue = async () => { + try { + await axios.delete(`/api/v1/issue/${issueData.id}`); + + addToast(intl.formatMessage(messages.toastissuedeleted), { + appearance: 'success', + autoDismiss: true, + }); + router.push('/issues'); + } catch (e) { + addToast(intl.formatMessage(messages.toastissuedeletefailed), { + appearance: 'error', + autoDismiss: true, + }); + } + }; + + const title = isMovie(data) ? data.title : data.name; + const releaseYear = isMovie(data) ? data.releaseDate : data.firstAirDate; + + return ( +
+ + + setShowDeleteModal(false)} + onOk={() => deleteIssue()} + okText={intl.formatMessage(messages.deleteissue)} + okButtonType="danger" + iconSvg={} + > + {intl.formatMessage(messages.deleteissueconfirm)} + + + {data.backdropPath && ( +
+ +
+
+ )} +
+
+ +
+
+
+ {issueData.status === IssueStatus.OPEN && ( + + {intl.formatMessage(messages.statusopen)} + + )} + {issueData.status === IssueStatus.RESOLVED && ( + + {intl.formatMessage(messages.statusresolved)} + + )} +
+

+ + + {title}{' '} + {releaseYear && ( + + ({releaseYear.slice(0, 4)}) + + )} + + +

+ + {intl.formatMessage(messages.openedby, { + issueId: issueData.id, + username: issueData.createdBy.displayName, + UserLink: function UserLink(msg) { + return ( +
+ + + + + + + + {msg} + + +
+ ); + }, + relativeTime: ( + + ), + })} +
+
+
+
+
+ { + editFirstComment(newMessage); + }} + onDelete={() => setShowDeleteModal(true)} + /> +
+
+
+ {intl.formatMessage(messages.mediatype)} + + {intl.formatMessage( + mediaType === MediaType.MOVIE + ? globalMessages.movie + : globalMessages.tvshow + )} + +
+
+ {intl.formatMessage(messages.issuetype)} + + {intl.formatMessage( + issueOption?.name ?? messages.unknownissuetype + )} + +
+ {issueData.media.mediaType === MediaType.TV && ( + <> +
+ {intl.formatMessage(messages.problemseason)} + + {intl.formatMessage( + issueData.problemSeason > 0 + ? messages.season + : messages.allseasons, + { seasonNumber: issueData.problemSeason } + )} + +
+ {issueData.problemSeason > 0 && ( +
+ {intl.formatMessage(messages.problemepisode)} + + {intl.formatMessage( + issueData.problemEpisode > 0 + ? messages.episode + : messages.allepisodes, + { episodeNumber: issueData.problemEpisode } + )} + +
+ )} + + )} +
+ {intl.formatMessage(messages.lastupdated)} + + + +
+
+ {hasPermission(Permission.MANAGE_ISSUES) && ( +
+ {issueData?.media.serviceUrl && ( + + )} +
+ )} +
+
+
+ {intl.formatMessage(messages.comments)} +
+ {otherComments.map((comment) => ( + revalidateIssue()} + /> + ))} + {otherComments.length === 0 && ( +
+ {intl.formatMessage(messages.nocomments)} +
+ )} + {(hasPermission(Permission.MANAGE_ISSUES) || belongsToUser) && ( + { + await axios.post(`/api/v1/issue/${issueData?.id}/comment`, { + message: values.message, + }); + revalidateIssue(); + resetForm(); + }} + > + {({ isValid, isSubmitting, values, handleSubmit }) => { + return ( +
+
+ +
+ {hasPermission(Permission.MANAGE_ISSUES) && ( + <> + {issueData.status === IssueStatus.OPEN ? ( + + ) : ( + + )} + + )} + +
+
+
+ ); + }} +
+ )} +
+
+
+
+
+ {intl.formatMessage(messages.issuetype)} + + {intl.formatMessage( + issueOption?.name ?? messages.unknownissuetype + )} + +
+
+ {intl.formatMessage(messages.mediatype)} + + {intl.formatMessage( + mediaType === MediaType.MOVIE + ? globalMessages.movie + : globalMessages.tvshow + )} + +
+ {issueData.media.mediaType === MediaType.TV && ( + <> +
+ {intl.formatMessage(messages.problemseason)} + + {intl.formatMessage( + issueData.problemSeason > 0 + ? messages.season + : messages.allseasons, + { seasonNumber: issueData.problemSeason } + )} + +
+ {issueData.problemSeason > 0 && ( +
+ {intl.formatMessage(messages.problemepisode)} + + {intl.formatMessage( + issueData.problemEpisode > 0 + ? messages.episode + : messages.allepisodes, + { episodeNumber: issueData.problemEpisode } + )} + +
+ )} + + )} +
+ {intl.formatMessage(messages.lastupdated)} + + + +
+
+ {hasPermission(Permission.MANAGE_ISSUES) && ( +
+ {issueData?.media.serviceUrl && ( + + )} +
+ )} +
+
+
+ ); +}; + +export default IssueDetails; diff --git a/src/components/IssueList/IssueItem/index.tsx b/src/components/IssueList/IssueItem/index.tsx new file mode 100644 index 000000000..25cb758ac --- /dev/null +++ b/src/components/IssueList/IssueItem/index.tsx @@ -0,0 +1,257 @@ +import { EyeIcon } from '@heroicons/react/solid'; +import Link from 'next/link'; +import React from 'react'; +import { useInView } from 'react-intersection-observer'; +import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; +import useSWR from 'swr'; +import { IssueStatus } from '../../../../server/constants/issue'; +import { MediaType } from '../../../../server/constants/media'; +import Issue from '../../../../server/entity/Issue'; +import { MovieDetails } from '../../../../server/models/Movie'; +import { TvDetails } from '../../../../server/models/Tv'; +import { Permission, useUser } from '../../../hooks/useUser'; +import globalMessages from '../../../i18n/globalMessages'; +import Badge from '../../Common/Badge'; +import Button from '../../Common/Button'; +import CachedImage from '../../Common/CachedImage'; +import { issueOptions } from '../../IssueModal/constants'; + +const messages = defineMessages({ + openeduserdate: '{date} by {user}', + allseasons: 'All Seasons', + season: 'Season {seasonNumber}', + problemepisode: 'Affected Episode', + allepisodes: 'All Episodes', + episode: 'Episode {episodeNumber}', + issuetype: 'Type', + issuestatus: 'Status', + opened: 'Opened', + viewissue: 'View Issue', + unknownissuetype: 'Unknown', +}); + +const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { + return (movie as MovieDetails).title !== undefined; +}; + +interface IssueItemProps { + issue: Issue; +} + +const IssueItem: React.FC = ({ issue }) => { + const intl = useIntl(); + const { hasPermission } = useUser(); + const { ref, inView } = useInView({ + triggerOnce: true, + }); + const url = + issue.media.mediaType === 'movie' + ? `/api/v1/movie/${issue.media.tmdbId}` + : `/api/v1/tv/${issue.media.tmdbId}`; + const { data: title, error } = useSWR( + inView ? url : null + ); + + if (!title && !error) { + return ( +
+ ); + } + + if (!title) { + return
uh oh
; + } + + const issueOption = issueOptions.find( + (opt) => opt.issueType === issue?.issueType + ); + + const problemSeasonEpisodeLine = []; + + if (!isMovie(title) && issue) { + problemSeasonEpisodeLine.push( + issue.problemSeason > 0 + ? intl.formatMessage(messages.season, { + seasonNumber: issue.problemSeason, + }) + : intl.formatMessage(messages.allseasons) + ); + + if (issue.problemSeason > 0) { + problemSeasonEpisodeLine.push( + issue.problemEpisode > 0 + ? intl.formatMessage(messages.episode, { + episodeNumber: issue.problemEpisode, + }) + : intl.formatMessage(messages.allepisodes) + ); + } + } + + return ( +
+ {title.backdropPath && ( +
+ +
+
+ )} +
+
+ + + + + +
+
+ {(isMovie(title) ? title.releaseDate : title.firstAirDate)?.slice( + 0, + 4 + )} +
+ + + {isMovie(title) ? title.title : title.name} + + + {problemSeasonEpisodeLine.length > 0 && ( +
+ {problemSeasonEpisodeLine.join(' | ')} +
+ )} +
+
+
+
+ + {intl.formatMessage(messages.issuestatus)} + + {issue.status === IssueStatus.OPEN ? ( + + {intl.formatMessage(globalMessages.open)} + + ) : ( + + {intl.formatMessage(globalMessages.resolved)} + + )} +
+
+ + {intl.formatMessage(messages.issuetype)} + + + {intl.formatMessage( + issueOption?.name ?? messages.unknownissuetype + )} + +
+
+ {hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], { + type: 'or', + }) ? ( + <> + + {intl.formatMessage(messages.opened)} + + + {intl.formatMessage(messages.openeduserdate, { + date: ( + + ), + user: ( + + + + + {issue.createdBy.displayName} + + + + ), + })} + + + ) : ( + <> + + {intl.formatMessage(messages.opened)} + + + + + + )} +
+
+
+
+ + + + + +
+
+ ); +}; + +export default IssueItem; diff --git a/src/components/IssueList/index.tsx b/src/components/IssueList/index.tsx new file mode 100644 index 000000000..8a2559a13 --- /dev/null +++ b/src/components/IssueList/index.tsx @@ -0,0 +1,256 @@ +import { + ChevronLeftIcon, + ChevronRightIcon, + FilterIcon, + SortDescendingIcon, +} from '@heroicons/react/solid'; +import { useRouter } from 'next/router'; +import React, { useEffect, useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import useSWR from 'swr'; +import { IssueResultsResponse } from '../../../server/interfaces/api/issueInterfaces'; +import Button from '../../components/Common/Button'; +import { useUpdateQueryParams } from '../../hooks/useUpdateQueryParams'; +import globalMessages from '../../i18n/globalMessages'; +import Header from '../Common/Header'; +import LoadingSpinner from '../Common/LoadingSpinner'; +import PageTitle from '../Common/PageTitle'; +import IssueItem from './IssueItem'; + +const messages = defineMessages({ + issues: 'Issues', + sortAdded: 'Request Date', + sortModified: 'Last Modified', + showallissues: 'Show All Issues', +}); + +enum Filter { + ALL = 'all', + OPEN = 'open', + RESOLVED = 'resolved', +} + +type Sort = 'added' | 'modified'; + +const IssueList: React.FC = () => { + const intl = useIntl(); + const router = useRouter(); + const [currentFilter, setCurrentFilter] = useState(Filter.OPEN); + const [currentSort, setCurrentSort] = useState('added'); + const [currentPageSize, setCurrentPageSize] = useState(10); + + const page = router.query.page ? Number(router.query.page) : 1; + const pageIndex = page - 1; + const updateQueryParams = useUpdateQueryParams({ page: page.toString() }); + + const { data, error } = useSWR( + `/api/v1/issue?take=${currentPageSize}&skip=${ + pageIndex * currentPageSize + }&filter=${currentFilter}&sort=${currentSort}` + ); + + // Restore last set filter values on component mount + useEffect(() => { + const filterString = window.localStorage.getItem('il-filter-settings'); + + if (filterString) { + const filterSettings = JSON.parse(filterString); + + setCurrentFilter(filterSettings.currentFilter); + setCurrentSort(filterSettings.currentSort); + setCurrentPageSize(filterSettings.currentPageSize); + } + + // If filter value is provided in query, use that instead + if (Object.values(Filter).includes(router.query.filter as Filter)) { + setCurrentFilter(router.query.filter as Filter); + } + }, [router.query.filter]); + + // Set filter values to local storage any time they are changed + useEffect(() => { + window.localStorage.setItem( + 'il-filter-settings', + JSON.stringify({ + currentFilter, + currentSort, + currentPageSize, + }) + ); + }, [currentFilter, currentSort, currentPageSize]); + + if (!data && !error) { + return ; + } + + if (!data) { + return ; + } + + const hasNextPage = data.pageInfo.pages > pageIndex + 1; + const hasPrevPage = pageIndex > 0; + + return ( + <> + +
+
Issues
+
+
+ + + + +
+
+ + + + +
+
+
+ {data.results.map((issue) => { + return ( +
+ +
+ ); + })} + {data.results.length === 0 && ( +
+ + {intl.formatMessage(globalMessages.noresults)} + + {currentFilter !== Filter.ALL && ( +
+ +
+ )} +
+ )} +
+ +
+ + ); +}; + +export default IssueList; diff --git a/src/components/IssueModal/CreateIssueModal/index.tsx b/src/components/IssueModal/CreateIssueModal/index.tsx new file mode 100644 index 000000000..187fe0e54 --- /dev/null +++ b/src/components/IssueModal/CreateIssueModal/index.tsx @@ -0,0 +1,303 @@ +import { RadioGroup } from '@headlessui/react'; +import { ExclamationIcon } from '@heroicons/react/outline'; +import { ArrowCircleRightIcon } from '@heroicons/react/solid'; +import axios from 'axios'; +import { Field, Formik } from 'formik'; +import Link from 'next/link'; +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR from 'swr'; +import * as Yup from 'yup'; +import type Issue from '../../../../server/entity/Issue'; +import { MovieDetails } from '../../../../server/models/Movie'; +import { TvDetails } from '../../../../server/models/Tv'; +import globalMessages from '../../../i18n/globalMessages'; +import Button from '../../Common/Button'; +import Modal from '../../Common/Modal'; +import { issueOptions } from '../constants'; + +const messages = defineMessages({ + validationMessageRequired: 'You must provide a description', + issomethingwrong: 'Is there a problem with {title}?', + whatswrong: "What's wrong?", + providedetail: 'Provide a detailed explanation of the issue.', + season: 'Season {seasonNumber}', + episode: 'Episode {episodeNumber}', + allseasons: 'All Seasons', + allepisodes: 'All Episodes', + problemseason: 'Affected Season', + problemepisode: 'Affected Episode', + toastSuccessCreate: + 'Issue report for {title} submitted successfully!', + toastFailedCreate: 'Something went wrong while submitting the issue.', + toastviewissue: 'View Issue', + reportissue: 'Report an Issue', + submitissue: 'Submit Issue', +}); + +const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { + return (movie as MovieDetails).title !== undefined; +}; + +const classNames = (...classes: string[]) => { + return classes.filter(Boolean).join(' '); +}; + +interface CreateIssueModalProps { + mediaType: 'movie' | 'tv'; + tmdbId?: number; + onCancel?: () => void; +} + +const CreateIssueModal: React.FC = ({ + onCancel, + mediaType, + tmdbId, +}) => { + const intl = useIntl(); + const { addToast } = useToasts(); + const { data, error } = useSWR( + tmdbId ? `/api/v1/${mediaType}/${tmdbId}` : null + ); + + if (!tmdbId) { + return null; + } + + const CreateIssueModalSchema = Yup.object().shape({ + message: Yup.string().required( + intl.formatMessage(messages.validationMessageRequired) + ), + }); + + return ( + { + try { + const newIssue = await axios.post('/api/v1/issue', { + issueType: values.selectedIssue.issueType, + message: values.message, + mediaId: data?.mediaInfo?.id, + problemSeason: values.problemSeason, + problemEpisode: + values.problemSeason > 0 ? values.problemEpisode : 0, + }); + + if (data) { + addToast( + <> +
+ {intl.formatMessage(messages.toastSuccessCreate, { + title: isMovie(data) ? data.title : data.name, + strong: function strong(msg) { + return {msg}; + }, + })} +
+ + + + , + { + appearance: 'success', + autoDismiss: true, + } + ); + } + + if (onCancel) { + onCancel(); + } + } catch (e) { + addToast(intl.formatMessage(messages.toastFailedCreate), { + appearance: 'error', + autoDismiss: true, + }); + } + }} + > + {({ handleSubmit, values, setFieldValue, errors, touched }) => { + return ( + } + title={intl.formatMessage(messages.reportissue)} + cancelText={intl.formatMessage(globalMessages.close)} + onOk={() => handleSubmit()} + okText={intl.formatMessage(messages.submitissue)} + loading={!data && !error} + backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} + > + {data && ( +
+ + {intl.formatMessage(messages.issomethingwrong, { + title: isMovie(data) ? data.title : data.name, + })} + +
+ )} + {mediaType === 'tv' && data && !isMovie(data) && ( + <> +
+ +
+
+ + + {data.seasons.map((season) => ( + + ))} + +
+
+
+ {values.problemSeason > 0 && ( +
+ +
+
+ + + {[ + ...Array( + data.seasons.find( + (season) => + Number(values.problemSeason) === + season.seasonNumber + )?.episodeCount ?? 0 + ), + ].map((i, index) => ( + + ))} + +
+
+
+ )} + + )} + setFieldValue('selectedIssue', issue)} + className="mt-4" + > + + Select an Issue + +
+ {issueOptions.map((setting, index) => ( + + classNames( + index === 0 ? 'rounded-tl-md rounded-tr-md' : '', + index === issueOptions.length - 1 + ? 'rounded-bl-md rounded-br-md' + : '', + checked + ? 'bg-indigo-600 border-indigo-500 z-10' + : 'border-gray-500', + 'relative border p-4 flex cursor-pointer focus:outline-none' + ) + } + > + {({ active, checked }) => ( + <> + + ))} +
+
+
+ + {intl.formatMessage(messages.whatswrong)}{' '} + * + + + {errors.message && touched.message && ( +
{errors.message}
+ )} +
+
+ ); + }} +
+ ); +}; + +export default CreateIssueModal; diff --git a/src/components/IssueModal/constants.ts b/src/components/IssueModal/constants.ts new file mode 100644 index 000000000..4c5b13e4b --- /dev/null +++ b/src/components/IssueModal/constants.ts @@ -0,0 +1,34 @@ +import { defineMessages, MessageDescriptor } from 'react-intl'; +import { IssueType } from '../../../server/constants/issue'; + +const messages = defineMessages({ + issueAudio: 'Audio', + issueVideo: 'Video', + issueSubtitles: 'Subtitles', + issueOther: 'Other', +}); + +interface IssueOption { + name: MessageDescriptor; + issueType: IssueType; + mediaType?: 'movie' | 'tv'; +} + +export const issueOptions: IssueOption[] = [ + { + name: messages.issueVideo, + issueType: IssueType.VIDEO, + }, + { + name: messages.issueAudio, + issueType: IssueType.AUDIO, + }, + { + name: messages.issueSubtitles, + issueType: IssueType.SUBTITLES, + }, + { + name: messages.issueOther, + issueType: IssueType.OTHER, + }, +]; diff --git a/src/components/IssueModal/index.tsx b/src/components/IssueModal/index.tsx new file mode 100644 index 000000000..f3f226de9 --- /dev/null +++ b/src/components/IssueModal/index.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import Transition from '../Transition'; +import CreateIssueModal from './CreateIssueModal'; + +interface IssueModalProps { + show?: boolean; + onCancel: () => void; + mediaType: 'movie' | 'tv'; + tmdbId: number; + issueId?: never; +} + +const IssueModal: React.FC = ({ + show, + mediaType, + onCancel, + tmdbId, +}) => ( + + + +); + +export default IssueModal; diff --git a/src/components/Layout/Sidebar/index.tsx b/src/components/Layout/Sidebar/index.tsx index 689faf909..92495db6b 100644 --- a/src/components/Layout/Sidebar/index.tsx +++ b/src/components/Layout/Sidebar/index.tsx @@ -1,6 +1,7 @@ import { ClockIcon, CogIcon, + ExclamationIcon, SparklesIcon, UsersIcon, XIcon, @@ -17,6 +18,7 @@ import VersionStatus from '../VersionStatus'; const messages = defineMessages({ dashboard: 'Discover', requests: 'Requests', + issues: 'Issues', users: 'Users', settings: 'Settings', }); @@ -33,6 +35,7 @@ interface SidebarLinkProps { activeRegExp: RegExp; as?: string; requiredPermission?: Permission | Permission[]; + permissionType?: 'and' | 'or'; } const SidebarLinks: SidebarLinkProps[] = [ @@ -48,6 +51,20 @@ const SidebarLinks: SidebarLinkProps[] = [ svgIcon: , activeRegExp: /^\/requests/, }, + { + href: '/issues', + messagesKey: 'issues', + svgIcon: ( + + ), + activeRegExp: /^\/issues/, + requiredPermission: [ + Permission.MANAGE_ISSUES, + Permission.CREATE_ISSUES, + Permission.VIEW_ISSUES, + ], + permissionType: 'or', + }, { href: '/users', messagesKey: 'users', @@ -121,7 +138,9 @@ const Sidebar: React.FC = ({ open, setClosed }) => {
)} - setShowIssueModal(false)} + show={showIssueModal} + mediaType="movie" + tmdbId={data.id} + /> + setShowManager(false)} - subText={data.title} - > - {((data?.mediaInfo?.downloadStatus ?? []).length > 0 || - (data?.mediaInfo?.downloadStatus4k ?? []).length > 0) && ( - <> -

- {intl.formatMessage(messages.downloadstatus)} -

-
-
    - {data.mediaInfo?.downloadStatus?.map((status, index) => ( -
  • - -
  • - ))} - {data.mediaInfo?.downloadStatus4k?.map((status, index) => ( -
  • - -
  • - ))} -
-
- - )} - {data?.mediaInfo && - (data.mediaInfo.status !== MediaStatus.AVAILABLE || - (data.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.movie4kEnabled)) && ( -
- {data?.mediaInfo && - data?.mediaInfo.status !== MediaStatus.AVAILABLE && ( -
- -
- )} - {data?.mediaInfo && - data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.movie4kEnabled && ( -
- -
- )} -
- )} -

- {intl.formatMessage(messages.manageModalRequests)} -

-
-
    - {data.mediaInfo?.requests?.map((request) => ( -
  • - revalidate()} /> -
  • - ))} - {(data.mediaInfo?.requests ?? []).length === 0 && ( -
  • - {intl.formatMessage(messages.manageModalNoRequests)} -
  • - )} -
-
- {(data?.mediaInfo?.serviceUrl || data?.mediaInfo?.serviceUrl4k) && ( -
- {data?.mediaInfo?.serviceUrl && ( - - - - )} - {data?.mediaInfo?.serviceUrl4k && ( - - - - )} -
- )} - {data?.mediaInfo && ( -
- deleteMedia()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - {intl.formatMessage(messages.manageModalClearMedia)} - -
- {intl.formatMessage(messages.manageModalClearMediaWarning)} -
-
- )} -
+ revalidate={() => revalidate()} + show={showManager} + />
= ({ movie }) => { .map((t, k) => {t}) .reduce((prev, curr) => ( <> - {prev} | {curr} + {prev} + | + {curr} ))} @@ -475,13 +329,39 @@ const MovieDetails: React.FC = ({ movie }) => { tmdbId={data.id} onUpdate={() => revalidate()} /> + {(data.mediaInfo?.status === MediaStatus.AVAILABLE || + data.mediaInfo?.status4k === MediaStatus.AVAILABLE) && + hasPermission( + [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], + { + type: 'or', + } + ) && ( + + )} {hasPermission(Permission.MANAGE_REQUESTS) && ( )}
diff --git a/src/components/NotificationTypeSelector/index.tsx b/src/components/NotificationTypeSelector/index.tsx index 0b71f6709..37ecf9859 100644 --- a/src/components/NotificationTypeSelector/index.tsx +++ b/src/components/NotificationTypeSelector/index.tsx @@ -37,6 +37,17 @@ const messages = defineMessages({ 'Send notifications when media requests are declined.', usermediadeclinedDescription: 'Get notified when your media requests are declined.', + issuecreated: 'Issue Created', + issuecreatedDescription: 'Send notifications when new issues are created.', + issuecomment: 'Issue Comment', + issuecommentDescription: + 'Send notifications when issues receive new comments.', + userissuecommentDescription: + 'Send notifications when your issue receives new comments.', + issueresolved: 'Issue Resolved', + issueresolvedDescription: 'Send notifications when issues are resolved.', + userissueresolvedDescription: + 'Send notifications when your issues are resolved.', }); export const hasNotificationType = ( @@ -74,6 +85,9 @@ export enum Notification { TEST_NOTIFICATION = 32, MEDIA_DECLINED = 64, MEDIA_AUTO_APPROVED = 128, + ISSUE_CREATED = 256, + ISSUE_COMMENT = 512, + ISSUE_RESOLVED = 1024, } export const ALL_NOTIFICATIONS = Object.values(Notification) @@ -232,6 +246,35 @@ const NotificationTypeSelector: React.FC = ({ value: Notification.MEDIA_FAILED, hidden: user && !hasPermission(Permission.MANAGE_REQUESTS), }, + { + id: 'issue-created', + name: intl.formatMessage(messages.issuecreated), + description: intl.formatMessage(messages.issuecreatedDescription), + value: Notification.ISSUE_CREATED, + hidden: user && !hasPermission(Permission.MANAGE_ISSUES), + }, + { + id: 'issue-comment', + name: intl.formatMessage(messages.issuecomment), + description: intl.formatMessage( + user + ? messages.userissuecommentDescription + : messages.issuecommentDescription + ), + value: Notification.ISSUE_COMMENT, + hasNotifyUser: true, + }, + { + id: 'issue-resolved', + name: intl.formatMessage(messages.issueresolved), + description: intl.formatMessage( + user + ? messages.userissueresolvedDescription + : messages.issueresolvedDescription + ), + value: Notification.ISSUE_RESOLVED, + hasNotifyUser: true, + }, ]; const filteredTypes = types.filter( diff --git a/src/components/PermissionEdit/index.tsx b/src/components/PermissionEdit/index.tsx index 71c6fc8b5..b4b738250 100644 --- a/src/components/PermissionEdit/index.tsx +++ b/src/components/PermissionEdit/index.tsx @@ -49,6 +49,12 @@ export const messages = defineMessages({ 'Grant permission to use advanced request options.', viewrequests: 'View Requests', viewrequestsDescription: "Grant permission to view other users' requests.", + manageissues: 'Manage Issues', + manageissuesDescription: 'Grant permission to manage Overseerr issues.', + createissues: 'Create Issues', + createissuesDescription: 'Grant permission to create new issues.', + viewissues: 'View Issues', + viewissuesDescription: "Grant permission to view other users' issues.", }); interface PermissionEditProps { @@ -223,6 +229,26 @@ export const PermissionEdit: React.FC = ({ }, ], }, + { + id: 'manageissues', + name: intl.formatMessage(messages.manageissues), + description: intl.formatMessage(messages.manageissuesDescription), + permission: Permission.MANAGE_ISSUES, + children: [ + { + id: 'createissues', + name: intl.formatMessage(messages.createissues), + description: intl.formatMessage(messages.createissuesDescription), + permission: Permission.CREATE_ISSUES, + }, + { + id: 'viewissues', + name: intl.formatMessage(messages.viewissues), + description: intl.formatMessage(messages.viewissuesDescription), + permission: Permission.VIEW_ISSUES, + }, + ], + }, ]; return ( diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index a3a203e7f..c625598b9 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -104,7 +104,7 @@ const RequestItem: React.FC = ({ ? `/api/v1/movie/${request.media.tmdbId}` : `/api/v1/tv/${request.media.tmdbId}`; const { data: title, error } = useSWR( - inView ? `${url}` : null + inView ? url : null ); const { data: requestData, diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 6ccc60499..8ff39fb68 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -1,15 +1,10 @@ import { ArrowCircleRightIcon, CogIcon, + ExclamationIcon, FilmIcon, PlayIcon, } from '@heroicons/react/outline'; -import { - CheckCircleIcon, - DocumentRemoveIcon, - ExternalLinkIcon, -} from '@heroicons/react/solid'; -import axios from 'axios'; import Link from 'next/link'; import { useRouter } from 'next/router'; import React, { useMemo, useState } from 'react'; @@ -17,6 +12,7 @@ import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; import type { RTRating } from '../../../server/api/rottentomatoes'; import { ANIME_KEYWORD_ID } from '../../../server/api/themoviedb/constants'; +import { IssueStatus } from '../../../server/constants/issue'; import { MediaStatus } from '../../../server/constants/media'; import { Crew } from '../../../server/models/common'; import { TvDetails as TvDetailsType } from '../../../server/models/Tv'; @@ -33,16 +29,14 @@ import Error from '../../pages/_error'; import { sortCrewPriority } from '../../utils/creditHelpers'; import Button from '../Common/Button'; import CachedImage from '../Common/CachedImage'; -import ConfirmButton from '../Common/ConfirmButton'; import LoadingSpinner from '../Common/LoadingSpinner'; import PageTitle from '../Common/PageTitle'; import PlayButton, { PlayButtonLink } from '../Common/PlayButton'; -import SlideOver from '../Common/SlideOver'; -import DownloadBlock from '../DownloadBlock'; import ExternalLinkBlock from '../ExternalLinkBlock'; +import IssueModal from '../IssueModal'; +import ManageSlideOver from '../ManageSlideOver'; import MediaSlider from '../MediaSlider'; import PersonCard from '../PersonCard'; -import RequestBlock from '../RequestBlock'; import RequestButton from '../RequestButton'; import RequestModal from '../RequestModal'; import Slider from '../Slider'; @@ -58,25 +52,13 @@ const messages = defineMessages({ similar: 'Similar Series', watchtrailer: 'Watch Trailer', overviewunavailable: 'Overview unavailable.', - manageModalTitle: 'Manage Series', - manageModalRequests: 'Requests', - manageModalNoRequests: 'No requests.', - manageModalClearMedia: 'Clear Media Data', - manageModalClearMediaWarning: - '* This will irreversibly remove all data for this series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.', originaltitle: 'Original Title', showtype: 'Series Type', anime: 'Anime', network: '{networkCount, plural, one {Network} other {Networks}}', viewfullcrew: 'View Full Crew', - opensonarr: 'Open Series in Sonarr', - opensonarr4k: 'Open Series in 4K Sonarr', - downloadstatus: 'Download Status', playonplex: 'Play on Plex', play4konplex: 'Play in 4K on Plex', - markavailable: 'Mark as Available', - mark4kavailable: 'Mark as Available in 4K', - allseasonsmarkedavailable: '* All seasons will be marked as available.', seasons: '{seasonCount, plural, one {# Season} other {# Seasons}}', episodeRuntime: 'Episode Runtime', episodeRuntimeMinutes: '{runtime} minutes', @@ -95,6 +77,7 @@ const TvDetails: React.FC = ({ tv }) => { const { locale } = useLocale(); const [showRequestModal, setShowRequestModal] = useState(false); const [showManager, setShowManager] = useState(false); + const [showIssueModal, setShowIssueModal] = useState(false); const { data, error, revalidate } = useSWR( `/api/v1/tv/${router.query.tvId}`, @@ -156,20 +139,6 @@ const TvDetails: React.FC = ({ tv }) => { }); } - const deleteMedia = async () => { - if (data?.mediaInfo?.id) { - await axios.delete(`/api/v1/media/${data?.mediaInfo?.id}`); - revalidate(); - } - }; - - const markAvailable = async (is4k = false) => { - await axios.post(`/api/v1/media/${data?.mediaInfo?.id}/available`, { - is4k, - }); - revalidate(); - }; - const region = user?.settings?.region ? user.settings.region : settings.currentSettings.region @@ -261,6 +230,12 @@ const TvDetails: React.FC = ({ tv }) => {
)} + setShowIssueModal(false)} + show={showIssueModal} + mediaType="tv" + tmdbId={data.id} + /> = ({ tv }) => { }} onCancel={() => setShowRequestModal(false)} /> - setShowManager(false)} - subText={data.name} - > - {((data?.mediaInfo?.downloadStatus ?? []).length > 0 || - (data?.mediaInfo?.downloadStatus4k ?? []).length > 0) && ( - <> -

- {intl.formatMessage(messages.downloadstatus)} -

-
-
    - {data.mediaInfo?.downloadStatus?.map((status, index) => ( -
  • - -
  • - ))} - {data.mediaInfo?.downloadStatus4k?.map((status, index) => ( -
  • - -
  • - ))} -
-
- - )} - {data?.mediaInfo && - (data.mediaInfo.status !== MediaStatus.AVAILABLE || - (data.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.series4kEnabled)) && ( -
- {data?.mediaInfo && - data?.mediaInfo.status !== MediaStatus.AVAILABLE && ( -
- -
- )} - {data?.mediaInfo && - data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.series4kEnabled && ( -
- -
- )} -
- {intl.formatMessage(messages.allseasonsmarkedavailable)} -
-
- )} -

- {intl.formatMessage(messages.manageModalRequests)} -

-
-
    - {data.mediaInfo?.requests?.map((request) => ( -
  • - revalidate()} /> -
  • - ))} - {(data.mediaInfo?.requests ?? []).length === 0 && ( -
  • - {intl.formatMessage(messages.manageModalNoRequests)} -
  • - )} -
-
- {(data?.mediaInfo?.serviceUrl || data?.mediaInfo?.serviceUrl4k) && ( -
- {data?.mediaInfo?.serviceUrl && ( - - - - )} - {data?.mediaInfo?.serviceUrl4k && ( - - - - )} -
- )} - {data?.mediaInfo && ( -
- deleteMedia()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - {intl.formatMessage(messages.manageModalClearMedia)} - -
- {intl.formatMessage(messages.manageModalClearMediaWarning)} -
-
- )} -
+ revalidate={() => revalidate()} + show={showManager} + />
= ({ tv }) => { .map((t, k) => {t}) .reduce((prev, curr) => ( <> - {prev} | {curr} + {prev} + | + {curr} ))} @@ -484,13 +330,41 @@ const TvDetails: React.FC = ({ tv }) => { isShowComplete={isComplete} is4kShowComplete={is4kComplete} /> + {(data.mediaInfo?.status === MediaStatus.AVAILABLE || + data.mediaInfo?.status4k === MediaStatus.AVAILABLE || + data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE || + data?.mediaInfo?.status4k === MediaStatus.PARTIALLY_AVAILABLE) && + hasPermission( + [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], + { + type: 'or', + } + ) && ( + + )} {hasPermission(Permission.MANAGE_REQUESTS) && ( )}
diff --git a/src/i18n/globalMessages.ts b/src/i18n/globalMessages.ts index 4c0ef7905..34a2b7101 100644 --- a/src/i18n/globalMessages.ts +++ b/src/i18n/globalMessages.ts @@ -49,6 +49,8 @@ const globalMessages = defineMessages({ 'Showing {from} to {to} of {total} results', resultsperpage: 'Display {pageSize} results per page', noresults: 'No results.', + open: 'Open', + resolved: 'Resolved', }); export default globalMessages; diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 789d05a7e..6abd1ba33 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -32,11 +32,88 @@ "components.Discover.upcomingmovies": "Upcoming Movies", "components.Discover.upcomingtv": "Upcoming Series", "components.DownloadBlock.estimatedtime": "Estimated {time}", + "components.IssueDetails.IssueComment.areyousuredelete": "Are you sure you want to delete this comment?", + "components.IssueDetails.IssueComment.delete": "Delete Comment", + "components.IssueDetails.IssueComment.edit": "Edit Comment", + "components.IssueDetails.IssueComment.postedby": "Posted by {username} {relativeTime}", + "components.IssueDetails.IssueComment.postedbyedited": "Posted by {username} {relativeTime} (Edited)", + "components.IssueDetails.IssueComment.validationComment": "You must provide a message", + "components.IssueDetails.IssueDescription.cancel": "Cancel", + "components.IssueDetails.IssueDescription.deleteissue": "Delete Issue", + "components.IssueDetails.IssueDescription.description": "Description", + "components.IssueDetails.IssueDescription.edit": "Edit Description", + "components.IssueDetails.IssueDescription.save": "Save Changes", + "components.IssueDetails.allepisodes": "All Episodes", + "components.IssueDetails.allseasons": "All Seasons", + "components.IssueDetails.closeissue": "Close Issue", + "components.IssueDetails.closeissueandcomment": "Close with Comment", + "components.IssueDetails.comments": "Comments", + "components.IssueDetails.deleteissue": "Delete Issue", + "components.IssueDetails.deleteissueconfirm": "Are you sure you want to delete this issue?", + "components.IssueDetails.episode": "Episode {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Issue", + "components.IssueDetails.issuetype": "Issue Type", + "components.IssueDetails.lastupdated": "Last Updated", + "components.IssueDetails.leavecomment": "Comment", + "components.IssueDetails.mediatype": "Media Type", + "components.IssueDetails.nocomments": "No comments.", + "components.IssueDetails.openedby": "#{issueId} opened {relativeTime} by {username}", + "components.IssueDetails.openinradarr": "Open in Radarr", + "components.IssueDetails.openinsonarr": "Open in Sonarr", + "components.IssueDetails.problemepisode": "Affected Episode", + "components.IssueDetails.problemseason": "Affected Season", + "components.IssueDetails.reopenissue": "Reopen Issue", + "components.IssueDetails.reopenissueandcomment": "Reopen with Comment", + "components.IssueDetails.season": "Season {seasonNumber}", + "components.IssueDetails.statusopen": "Open", + "components.IssueDetails.statusresolved": "Resolved", + "components.IssueDetails.toasteditdescriptionfailed": "Something went wrong editing the description.", + "components.IssueDetails.toasteditdescriptionsuccess": "Successfully edited the issue description.", + "components.IssueDetails.toastissuedeleted": "Issue deleted succesfully.", + "components.IssueDetails.toastissuedeletefailed": "Something went wrong deleting the issue.", + "components.IssueDetails.toaststatusupdated": "Issue status updated.", + "components.IssueDetails.toaststatusupdatefailed": "Something went wrong updating the issue status.", + "components.IssueDetails.unknownissuetype": "Unknown", + "components.IssueList.IssueItem.allepisodes": "All Episodes", + "components.IssueList.IssueItem.allseasons": "All Seasons", + "components.IssueList.IssueItem.episode": "Episode {episodeNumber}", + "components.IssueList.IssueItem.issuestatus": "Status", + "components.IssueList.IssueItem.issuetype": "Type", + "components.IssueList.IssueItem.opened": "Opened", + "components.IssueList.IssueItem.openeduserdate": "{date} by {user}", + "components.IssueList.IssueItem.problemepisode": "Affected Episode", + "components.IssueList.IssueItem.season": "Season {seasonNumber}", + "components.IssueList.IssueItem.unknownissuetype": "Unknown", + "components.IssueList.IssueItem.viewissue": "View Issue", + "components.IssueList.issues": "Issues", + "components.IssueList.showallissues": "Show All Issues", + "components.IssueList.sortAdded": "Request Date", + "components.IssueList.sortModified": "Last Modified", + "components.IssueModal.CreateIssueModal.allepisodes": "All Episodes", + "components.IssueModal.CreateIssueModal.allseasons": "All Seasons", + "components.IssueModal.CreateIssueModal.episode": "Episode {episodeNumber}", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Is there a problem with {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Affected Episode", + "components.IssueModal.CreateIssueModal.problemseason": "Affected Season", + "components.IssueModal.CreateIssueModal.providedetail": "Provide a detailed explanation of the issue.", + "components.IssueModal.CreateIssueModal.reportissue": "Report an Issue", + "components.IssueModal.CreateIssueModal.season": "Season {seasonNumber}", + "components.IssueModal.CreateIssueModal.submitissue": "Submit Issue", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Something went wrong while submitting the issue.", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Issue report for {title} submitted successfully!", + "components.IssueModal.CreateIssueModal.toastviewissue": "View Issue", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "You must provide a description", + "components.IssueModal.CreateIssueModal.whatswrong": "What's wrong?", + "components.IssueModal.issueAudio": "Audio", + "components.IssueModal.issueOther": "Other", + "components.IssueModal.issueSubtitles": "Subtitles", + "components.IssueModal.issueVideo": "Video", "components.LanguageSelector.languageServerDefault": "Default ({language})", "components.LanguageSelector.originalLanguageDefault": "All Languages", "components.Layout.LanguagePicker.displaylanguage": "Display Language", "components.Layout.SearchInput.searchPlaceholder": "Search Movies & TV", "components.Layout.Sidebar.dashboard": "Discover", + "components.Layout.Sidebar.issues": "Issues", "components.Layout.Sidebar.requests": "Requests", "components.Layout.Sidebar.settings": "Settings", "components.Layout.Sidebar.users": "Users", @@ -58,21 +135,26 @@ "components.Login.signinwithplex": "Use your Plex account", "components.Login.validationemailrequired": "You must provide a valid email address", "components.Login.validationpasswordrequired": "You must provide a password", + "components.ManageSlideOver.allseasonsmarkedavailable": "* All seasons will be marked as available.", + "components.ManageSlideOver.downloadstatus": "Download Status", + "components.ManageSlideOver.manageModalClearMedia": "Clear Media Data", + "components.ManageSlideOver.manageModalClearMediaWarning": "* This will irreversibly remove all data for this {mediaType}, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.", + "components.ManageSlideOver.manageModalNoRequests": "No requests.", + "components.ManageSlideOver.manageModalRequests": "Requests", + "components.ManageSlideOver.manageModalTitle": "Manage {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Mark as Available in 4K", + "components.ManageSlideOver.markavailable": "Mark as Available", + "components.ManageSlideOver.movie": "movie", + "components.ManageSlideOver.openarr": "Open {mediaType} in {arr}", + "components.ManageSlideOver.openarr4k": "Open {mediaType} in 4K {arr}", + "components.ManageSlideOver.tvshow": "series", "components.MediaSlider.ShowMoreCard.seemore": "See More", "components.MovieDetails.MovieCast.fullcast": "Full Cast", "components.MovieDetails.MovieCrew.fullcrew": "Full Crew", "components.MovieDetails.budget": "Budget", "components.MovieDetails.cast": "Cast", - "components.MovieDetails.downloadstatus": "Download Status", - "components.MovieDetails.manageModalClearMedia": "Clear Media Data", - "components.MovieDetails.manageModalClearMediaWarning": "* This will irreversibly remove all data for this movie, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.", - "components.MovieDetails.manageModalNoRequests": "No requests.", - "components.MovieDetails.manageModalRequests": "Requests", - "components.MovieDetails.manageModalTitle": "Manage Movie", "components.MovieDetails.mark4kavailable": "Mark as Available in 4K", "components.MovieDetails.markavailable": "Mark as Available", - "components.MovieDetails.openradarr": "Open Movie in Radarr", - "components.MovieDetails.openradarr4k": "Open Movie in 4K Radarr", "components.MovieDetails.originallanguage": "Original Language", "components.MovieDetails.originaltitle": "Original Title", "components.MovieDetails.overview": "Overview", @@ -90,6 +172,12 @@ "components.MovieDetails.studio": "{studioCount, plural, one {Studio} other {Studios}}", "components.MovieDetails.viewfullcrew": "View Full Crew", "components.MovieDetails.watchtrailer": "Watch Trailer", + "components.NotificationTypeSelector.issuecomment": "Issue Comment", + "components.NotificationTypeSelector.issuecommentDescription": "Send notifications when issues receive new comments.", + "components.NotificationTypeSelector.issuecreated": "Issue Created", + "components.NotificationTypeSelector.issuecreatedDescription": "Send notifications when new issues are created.", + "components.NotificationTypeSelector.issueresolved": "Issue Resolved", + "components.NotificationTypeSelector.issueresolvedDescription": "Send notifications when issues are resolved.", "components.NotificationTypeSelector.mediaAutoApproved": "Media Automatically Approved", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Send notifications when users submit new media requests which are automatically approved.", "components.NotificationTypeSelector.mediaapproved": "Media Approved", @@ -103,6 +191,8 @@ "components.NotificationTypeSelector.mediarequested": "Media Requested", "components.NotificationTypeSelector.mediarequestedDescription": "Send notifications when users submit new media requests which require approval.", "components.NotificationTypeSelector.notificationTypes": "Notification Types", + "components.NotificationTypeSelector.userissuecommentDescription": "Send notifications when your issue receives new comments.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Send notifications when your issues are resolved.", "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Get notified when other users submit new media requests which are automatically approved.", "components.NotificationTypeSelector.usermediaapprovedDescription": "Get notified when your media requests are approved.", "components.NotificationTypeSelector.usermediaavailableDescription": "Get notified when your media requests become available.", @@ -125,6 +215,10 @@ "components.PermissionEdit.autoapproveMoviesDescription": "Grant automatic approval for non-4K movie requests.", "components.PermissionEdit.autoapproveSeries": "Auto-Approve Series", "components.PermissionEdit.autoapproveSeriesDescription": "Grant automatic approval for non-4K series requests.", + "components.PermissionEdit.createissues": "Create Issues", + "components.PermissionEdit.createissuesDescription": "Grant permission to create new issues.", + "components.PermissionEdit.manageissues": "Manage Issues", + "components.PermissionEdit.manageissuesDescription": "Grant permission to manage Overseerr issues.", "components.PermissionEdit.managerequests": "Manage Requests", "components.PermissionEdit.managerequestsDescription": "Grant permission to manage Overseerr requests. All requests made by a user with this permission will be automatically approved.", "components.PermissionEdit.request": "Request", @@ -143,6 +237,8 @@ "components.PermissionEdit.settingsDescription": "Grant permission to modify Overseerr settings. A user must have this permission to grant it to others.", "components.PermissionEdit.users": "Manage Users", "components.PermissionEdit.usersDescription": "Grant permission to manage Overseerr users. Users with this permission cannot modify users with or grant the Admin privilege.", + "components.PermissionEdit.viewissues": "View Issues", + "components.PermissionEdit.viewissuesDescription": "Grant permission to view other users' issues.", "components.PermissionEdit.viewrequests": "View Requests", "components.PermissionEdit.viewrequestsDescription": "Grant permission to view other users' requests.", "components.PersonDetails.alsoknownas": "Also Known As: {names}", @@ -680,24 +776,13 @@ "components.StatusChacker.reloadOverseerr": "Reload", "components.TvDetails.TvCast.fullseriescast": "Full Series Cast", "components.TvDetails.TvCrew.fullseriescrew": "Full Series Crew", - "components.TvDetails.allseasonsmarkedavailable": "* All seasons will be marked as available.", "components.TvDetails.anime": "Anime", "components.TvDetails.cast": "Cast", - "components.TvDetails.downloadstatus": "Download Status", "components.TvDetails.episodeRuntime": "Episode Runtime", "components.TvDetails.episodeRuntimeMinutes": "{runtime} minutes", "components.TvDetails.firstAirDate": "First Air Date", - "components.TvDetails.manageModalClearMedia": "Clear Media Data", - "components.TvDetails.manageModalClearMediaWarning": "* This will irreversibly remove all data for this series, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.", - "components.TvDetails.manageModalNoRequests": "No requests.", - "components.TvDetails.manageModalRequests": "Requests", - "components.TvDetails.manageModalTitle": "Manage Series", - "components.TvDetails.mark4kavailable": "Mark as Available in 4K", - "components.TvDetails.markavailable": "Mark as Available", "components.TvDetails.network": "{networkCount, plural, one {Network} other {Networks}}", "components.TvDetails.nextAirDate": "Next Air Date", - "components.TvDetails.opensonarr": "Open Series in Sonarr", - "components.TvDetails.opensonarr4k": "Open Series in 4K Sonarr", "components.TvDetails.originallanguage": "Original Language", "components.TvDetails.originaltitle": "Original Title", "components.TvDetails.overview": "Overview", @@ -859,6 +944,7 @@ "i18n.next": "Next", "i18n.noresults": "No results.", "i18n.notrequested": "Not Requested", + "i18n.open": "Open", "i18n.partiallyavailable": "Partially Available", "i18n.pending": "Pending", "i18n.previous": "Previous", @@ -867,6 +953,7 @@ "i18n.request4k": "Request in 4K", "i18n.requested": "Requested", "i18n.requesting": "Requesting…", + "i18n.resolved": "Resolved", "i18n.resultsperpage": "Display {pageSize} results per page", "i18n.retry": "Retry", "i18n.retrying": "Retrying…", diff --git a/src/pages/issues/[issueId]/index.tsx b/src/pages/issues/[issueId]/index.tsx new file mode 100644 index 000000000..730bee6e7 --- /dev/null +++ b/src/pages/issues/[issueId]/index.tsx @@ -0,0 +1,9 @@ +import { NextPage } from 'next'; +import React from 'react'; +import IssueDetails from '../../../components/IssueDetails'; + +const IssuePage: NextPage = () => { + return ; +}; + +export default IssuePage; diff --git a/src/pages/issues/index.tsx b/src/pages/issues/index.tsx new file mode 100644 index 000000000..0168d888f --- /dev/null +++ b/src/pages/issues/index.tsx @@ -0,0 +1,9 @@ +import { NextPage } from 'next'; +import React from 'react'; +import IssueList from '../../components/IssueList'; + +const IssuePage: NextPage = () => { + return ; +}; + +export default IssuePage; diff --git a/src/styles/globals.css b/src/styles/globals.css index fec0c7127..228ec9c6f 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -2,457 +2,463 @@ @tailwind components; @tailwind utilities; -html { - min-height: calc(100% + env(safe-area-inset-top)); - padding: env(safe-area-inset-top) env(safe-area-inset-right) - env(safe-area-inset-bottom) env(safe-area-inset-left); -} - -body { - @apply bg-gray-900; -} - -.searchbar { - padding-top: env(safe-area-inset-top); - height: calc(4rem + env(safe-area-inset-top)); -} - -.sidebar { - @apply border-r border-gray-700; - padding-top: env(safe-area-inset-top); - padding-left: env(safe-area-inset-left); - background: linear-gradient(180deg, rgba(31, 41, 55, 1) 0%, #131928 100%); -} - -.slideover { - padding-top: calc(1.5rem + env(safe-area-inset-top)); - padding-bottom: 1.5rem; -} - -.sidebar-close-button { - top: env(safe-area-inset-top); -} - -.absolute-top-shift { - top: calc(-4rem - env(safe-area-inset-top)); -} - -.min-h-screen-shift { - min-height: calc(100vh + env(safe-area-inset-top)); -} - -.plex-button { - @apply flex justify-center w-full px-4 py-2 text-sm font-medium text-center text-white transition duration-150 ease-in-out bg-indigo-600 border border-transparent rounded-md disabled:opacity-50; - background-color: #cc7b19; -} - -.plex-button:hover { - background: #f19a30; -} - -ul.cards-vertical, -ul.cards-horizontal { - @apply grid gap-4; -} - -ul.cards-vertical { - grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr)); -} - -ul.cards-horizontal { - grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr)); -} - -.slider-header { - @apply relative flex mt-6 mb-4; -} - -.slider-title { - @apply inline-flex items-center text-xl font-bold leading-7 text-gray-300 sm:text-2xl sm:leading-9 sm:truncate; -} - -a.slider-title { - @apply transition duration-300 hover:text-white; -} - -a.slider-title svg { - @apply w-6 h-6 ml-2; -} - -.media-page { - @apply relative px-4 -mx-4 bg-center bg-cover; - margin-top: calc(-4rem - env(safe-area-inset-top)); - padding-top: calc(4rem + env(safe-area-inset-top)); -} - -.media-page-bg-image { - @apply absolute inset-0 w-full h-full; - z-index: -10; -} - -.media-header { - @apply flex flex-col items-center pt-4 xl:flex-row xl:items-end; -} - -.media-poster { - @apply w-32 overflow-hidden rounded shadow md:rounded-lg md:shadow-2xl md:w-44 xl:w-52 xl:mr-4; -} - -.media-status { - @apply mb-2 space-x-2; -} - -.media-title { - @apply flex flex-col flex-1 mt-4 text-center text-white xl:mr-4 xl:mt-0 xl:text-left; -} - -.media-title > h1 { - @apply text-2xl font-bold xl:text-4xl; -} - -h1 > .media-year { - @apply text-2xl; -} - -.media-attributes { - @apply mt-1 text-xs text-gray-300 sm:text-sm xl:text-base xl:mt-0; -} - -.media-attributes a { - @apply transition duration-300 hover:text-white hover:underline; -} - -.media-actions { - @apply relative flex flex-wrap items-center justify-center flex-shrink-0 mt-4 sm:justify-end sm:flex-nowrap xl:mt-0; -} - -.media-actions > * { - @apply mb-3 sm:mb-0; -} - -.media-overview { - @apply flex flex-col pt-8 pb-4 text-white lg:flex-row; -} - -.media-overview-left { - @apply flex-1 lg:mr-8; -} - -.tagline { - @apply mb-4 text-xl italic text-gray-400 lg:text-2xl; -} - -.media-overview h2 { - @apply text-xl font-bold text-gray-300 sm:text-2xl; -} - -.media-overview p { - @apply pt-2 text-sm text-gray-400 sm:text-base; -} - -ul.media-crew { - @apply grid grid-cols-2 gap-6 mt-6 sm:grid-cols-3; -} - -ul.media-crew > li { - @apply flex flex-col col-span-1 font-bold text-gray-300; -} - -a.crew-name, -.media-fact-value a, -.media-fact-value button { - @apply font-normal text-gray-400 transition duration-300 hover:underline hover:text-gray-100; -} - -.media-overview-right { - @apply w-full mt-8 lg:w-80 lg:mt-0; -} - -.media-facts { - @apply text-sm font-bold text-gray-300 bg-gray-900 border border-gray-700 rounded-lg shadow; -} - -.media-fact { - @apply flex justify-between px-4 py-2 border-b border-gray-700 last:border-b-0; -} - -.media-fact-value { - @apply ml-2 text-sm font-normal text-right text-gray-400; -} - -.media-ratings { - @apply flex items-center justify-center px-4 py-2 font-medium border-b border-gray-700 last:border-b-0; -} - -.media-rating { - @apply flex items-center mr-4 last:mr-0; -} - -.error-message { - @apply relative top-0 bottom-0 left-0 right-0 flex flex-col items-center justify-center h-screen text-center text-gray-300; -} - -.heading { - @apply text-2xl font-bold leading-8 text-gray-100; -} - -.description { - @apply max-w-4xl mt-1 text-sm leading-5 text-gray-400; -} - -img.avatar-sm { - @apply w-5 h-5 mr-1.5 rounded-full transition duration-300 scale-100 transform-gpu group-hover:scale-105; -} - -.card-field { - @apply flex items-center py-0.5 sm:py-1 text-sm truncate; -} - -.card-field-name { - @apply mr-2 font-bold; -} - -.card-field a { - @apply transition duration-300 hover:text-white hover:underline; -} - -.section { - @apply mt-6 mb-10 text-white; -} - -.form-row { - @apply max-w-6xl mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start; -} - -.form-input { - @apply text-sm text-white sm:col-span-2; -} - -.form-input-field { - @apply flex max-w-xl rounded-md shadow-sm; -} - -.actions { - @apply pt-5 mt-8 text-white border-t border-gray-700; -} - -label, -.group-label { - @apply block mb-1 text-sm font-bold leading-5 text-gray-400; -} - -label.checkbox-label { - @apply sm:mt-1; -} - -label.text-label { - @apply sm:mt-2; -} - -label a { - @apply text-gray-100 transition duration-300 hover:text-white hover:underline; -} - -.label-required { - @apply ml-1 text-red-500; -} - -.label-tip { - @apply block font-medium text-gray-500; -} - -button, -input, -select, -textarea { - @apply disabled:cursor-not-allowed; -} - -input[type='checkbox'] { - @apply w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md; -} - -input[type='text'], -input[type='password'], -select, -textarea { - @apply flex-1 block w-full min-w-0 text-white transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md sm:text-sm sm:leading-5; -} - -input.rounded-l-only, -select.rounded-l-only, -textarea.rounded-l-only { - @apply rounded-r-none; -} - -input.rounded-r-only, -select.rounded-r-only, -textarea.rounded-r-only { - @apply rounded-l-none; -} - -input.short { - @apply w-20; -} - -select.short { - @apply w-min; -} - -button > span { - @apply whitespace-nowrap; -} - -button.input-action { - @apply relative inline-flex items-center px-3 sm:px-3.5 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 hover:bg-indigo-500 active:bg-gray-100 active:text-gray-700 last:rounded-r-md; -} - -.button-md svg, -button.input-action svg, -.plex-button svg { - @apply w-5 h-5 ml-2 mr-2 first:ml-0 last:mr-0; -} - -.button-sm svg { - @apply w-4 h-4 ml-1.5 mr-1.5 first:ml-0 last:mr-0; -} - -.modal-icon { - @apply flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto text-white bg-gray-800 rounded-full ring-1 ring-gray-500 sm:mx-0 sm:h-10 sm:w-10; -} - -.modal-icon svg { - @apply w-6 h-6; -} - -svg.icon-md { - @apply w-5 h-5; -} - -svg.icon-sm { - @apply w-4 h-4; -} - -.protocol { - @apply inline-flex items-center px-3 text-gray-100 bg-gray-600 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm; -} - -.error { - @apply mt-2 text-sm text-red-500; -} - -.form-group { - @apply mt-6 text-white; -} - -.toast { - width: 360px; -} - -/* Used for animating height */ -.extra-max-height { - max-height: 100rem; -} - -.hide-scrollbar { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ -} - -.hide-scrollbar::-webkit-scrollbar { - display: none; -} - -/* Hide scrollbar for Chrome, Safari and Opera */ -.hide-scrollbar::-webkit-scrollbar { - display: none; -} - -/* Hide scrollbar for IE, Edge and Firefox */ -.hide-scrollbar { - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ -} - -code { - @apply px-2 py-1 bg-gray-800 rounded-md; -} - -input[type='search']::-webkit-search-cancel-button { - -webkit-appearance: none; -} - -.react-select-container { - @apply w-full; -} - -.react-select-container .react-select__control { - @apply text-white bg-gray-700 border border-gray-500 rounded-md hover:border-gray-500; -} - -.react-select-container-dark .react-select__control { - @apply bg-gray-800 border border-gray-700; -} - -.react-select-container .react-select__control--is-focused { - @apply text-white bg-gray-700 border border-gray-500 rounded-md shadow; -} - -.react-select-container-dark .react-select__control--is-focused { - @apply bg-gray-800 border-gray-600; -} - -.react-select-container .react-select__menu { - @apply text-gray-300 bg-gray-700; -} - -.react-select-container-dark .react-select__menu { - @apply bg-gray-800; -} - -.react-select-container .react-select__option--is-focused { - @apply text-white bg-gray-600; -} - -.react-select-container-dark .react-select__option--is-focused { - @apply bg-gray-700; -} - -.react-select-container .react-select__indicator-separator { - @apply bg-gray-500; -} - -.react-select-container .react-select__indicator { - @apply text-gray-500; -} - -.react-select-container .react-select__placeholder { - @apply text-gray-400; -} - -.react-select-container .react-select__multi-value { - @apply bg-gray-800 border border-gray-500 rounded-md; -} - -.react-select-container .react-select__multi-value__label { - @apply text-white; -} - -.react-select-container .react-select__multi-value__remove { - @apply cursor-pointer rounded-r-md hover:bg-red-700 hover:text-red-100; -} - -.react-select-container .react-select__input { - @apply text-base text-white border-none shadow-sm; -} - -.react-select-container .react-select__input input:focus { - @apply text-white border-none; - box-shadow: none; -} - -@media all and (display-mode: browser) { - .pwa-only { - @apply hidden; +@layer base { + html { + min-height: calc(100% + env(safe-area-inset-top)); + padding: env(safe-area-inset-top) env(safe-area-inset-right) + env(safe-area-inset-bottom) env(safe-area-inset-left); + } + + body { + @apply bg-gray-900; + } + + code { + @apply px-2 py-1 bg-gray-800 rounded-md; + } + + input[type='search']::-webkit-search-cancel-button { + -webkit-appearance: none; + } +} + +@layer components { + .searchbar { + padding-top: env(safe-area-inset-top); + height: calc(4rem + env(safe-area-inset-top)); + } + + .sidebar { + @apply border-r border-gray-700; + padding-top: env(safe-area-inset-top); + padding-left: env(safe-area-inset-left); + background: linear-gradient(180deg, rgba(31, 41, 55, 1) 0%, #131928 100%); + } + + .slideover { + padding-top: calc(1.5rem + env(safe-area-inset-top)); + padding-bottom: 1.5rem; + } + + .sidebar-close-button { + top: env(safe-area-inset-top); + } + + .plex-button { + @apply flex justify-center w-full px-4 py-2 text-sm font-medium text-center text-white transition duration-150 ease-in-out bg-indigo-600 border border-transparent rounded-md disabled:opacity-50; + background-color: #cc7b19; + } + + .plex-button:hover { + background: #f19a30; + } + + ul.cards-vertical, + ul.cards-horizontal { + @apply grid gap-4; + } + + ul.cards-vertical { + grid-template-columns: repeat(auto-fill, minmax(9.375rem, 1fr)); + } + + ul.cards-horizontal { + grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr)); + } + + .slider-header { + @apply relative flex mt-6 mb-4; + } + + .slider-title { + @apply inline-flex items-center text-xl font-bold leading-7 text-gray-300 sm:text-2xl sm:leading-9 sm:truncate; + } + + a.slider-title { + @apply transition duration-300 hover:text-white; + } + + a.slider-title svg { + @apply w-6 h-6 ml-2; + } + + .media-page { + @apply relative px-4 -mx-4 bg-center bg-cover; + margin-top: calc(-4rem - env(safe-area-inset-top)); + padding-top: calc(4rem + env(safe-area-inset-top)); + } + + .media-page-bg-image { + @apply absolute inset-0 w-full h-full; + z-index: -10; + } + + .media-header { + @apply flex flex-col items-center pt-4 xl:flex-row xl:items-end; + } + + .media-poster { + @apply w-32 overflow-hidden rounded shadow md:rounded-lg md:shadow-2xl md:w-44 xl:w-52 xl:mr-4; + } + + .media-status { + @apply mb-2 space-x-2; + } + + .media-title { + @apply flex flex-col flex-1 mt-4 text-center text-white xl:mr-4 xl:mt-0 xl:text-left; + } + + .media-title > h1 { + @apply text-2xl font-bold xl:text-4xl; + } + + h1 .media-year { + @apply text-2xl; + } + + .media-attributes { + @apply flex items-center mt-1 space-x-1 text-xs text-gray-300 sm:text-sm xl:text-base xl:mt-0; + } + + .media-attributes a { + @apply transition duration-300 hover:text-white hover:underline; + } + + .media-actions { + @apply relative flex flex-wrap items-center justify-center flex-shrink-0 mt-4 sm:justify-end sm:flex-nowrap xl:mt-0; + } + + .media-actions > * { + @apply mb-3 sm:mb-0; + } + + .media-overview { + @apply flex flex-col pt-8 pb-4 text-white lg:flex-row; + } + + .media-overview-left { + @apply flex-1 lg:mr-8; + } + + .tagline { + @apply mb-4 text-xl italic text-gray-400 lg:text-2xl; + } + + .media-overview h2 { + @apply text-xl font-bold text-gray-300 sm:text-2xl; + } + + .media-overview p { + @apply pt-2 text-sm text-gray-400 sm:text-base; + } + + ul.media-crew { + @apply grid grid-cols-2 gap-6 mt-6 sm:grid-cols-3; + } + + ul.media-crew > li { + @apply flex flex-col col-span-1 font-bold text-gray-300; + } + + a.crew-name, + .media-fact-value a, + .media-fact-value button { + @apply font-normal text-gray-400 transition duration-300 hover:underline hover:text-gray-100; + } + + .media-overview-right { + @apply w-full mt-8 lg:w-80 lg:mt-0; + } + + .media-facts { + @apply text-sm font-bold text-gray-300 bg-gray-900 border border-gray-700 rounded-lg shadow; + } + + .media-fact { + @apply flex justify-between px-4 py-2 border-b border-gray-700 last:border-b-0; + } + + .media-fact-value { + @apply ml-2 text-sm font-normal text-right text-gray-400; + } + + .media-ratings { + @apply flex items-center justify-center px-4 py-2 font-medium border-b border-gray-700 last:border-b-0; + } + + .media-rating { + @apply flex items-center mr-4 last:mr-0; + } + + .error-message { + @apply relative top-0 bottom-0 left-0 right-0 flex flex-col items-center justify-center h-screen text-center text-gray-300; + } + + .heading { + @apply text-2xl font-bold leading-8 text-gray-100; + } + + .description { + @apply max-w-4xl mt-1 text-sm leading-5 text-gray-400; + } + + img.avatar-sm { + @apply w-5 h-5 mr-1.5 rounded-full transition duration-300 scale-100 transform-gpu group-hover:scale-105; + } + + .card-field { + @apply flex items-center py-0.5 sm:py-1 text-sm truncate; + } + + .card-field-name { + @apply mr-2 font-bold; + } + + .card-field a { + @apply transition duration-300 hover:text-white hover:underline; + } + + .section { + @apply mt-6 mb-10 text-white; + } + + .form-row { + @apply max-w-6xl mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start; + } + + .form-input { + @apply text-sm text-white sm:col-span-2; + } + + .form-input-field { + @apply flex max-w-xl rounded-md shadow-sm; + } + + .actions { + @apply pt-5 mt-8 text-white border-t border-gray-700; + } + + label, + .group-label { + @apply block mb-1 text-sm font-bold leading-5 text-gray-400; + } + + label.checkbox-label { + @apply sm:mt-1; + } + + label.text-label { + @apply sm:mt-2; + } + + label a { + @apply text-gray-100 transition duration-300 hover:text-white hover:underline; + } + + .label-required { + @apply ml-1 text-red-500; + } + + .label-tip { + @apply block font-medium text-gray-500; + } + + button, + input, + select, + textarea { + @apply disabled:cursor-not-allowed; + } + + input[type='checkbox'] { + @apply w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md; + } + + input[type='text'], + input[type='password'], + select, + textarea { + @apply flex-1 block w-full min-w-0 text-white transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md sm:text-sm sm:leading-5; + } + + input.rounded-l-only, + select.rounded-l-only, + textarea.rounded-l-only { + @apply rounded-r-none; + } + + input.rounded-r-only, + select.rounded-r-only, + textarea.rounded-r-only { + @apply rounded-l-none; + } + + input.short { + @apply w-20; + } + + select.short { + @apply w-min; + } + + button > span { + @apply whitespace-nowrap; + } + + button.input-action { + @apply relative inline-flex items-center px-3 sm:px-3.5 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 hover:bg-indigo-500 active:bg-gray-100 active:text-gray-700 last:rounded-r-md; + } + + .button-md svg, + button.input-action svg, + .plex-button svg { + @apply w-5 h-5 ml-2 mr-2 first:ml-0 last:mr-0; + } + + .button-sm svg { + @apply w-4 h-4 ml-1.5 mr-1.5 first:ml-0 last:mr-0; + } + + .modal-icon { + @apply flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto text-white bg-gray-800 rounded-full ring-1 ring-gray-500 sm:mx-0 sm:h-10 sm:w-10; + } + + .modal-icon svg { + @apply w-6 h-6; + } + + svg.icon-md { + @apply w-5 h-5; + } + + svg.icon-sm { + @apply w-4 h-4; + } + + .protocol { + @apply inline-flex items-center px-3 text-gray-100 bg-gray-600 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm; + } + + .error { + @apply mt-2 text-sm text-red-500; + } + + .form-group { + @apply mt-6 text-white; + } + + .toast { + width: 360px; + } + + .react-select-container { + @apply w-full; + } + + .react-select-container .react-select__control { + @apply text-white bg-gray-700 border border-gray-500 rounded-md hover:border-gray-500; + } + + .react-select-container-dark .react-select__control { + @apply bg-gray-800 border border-gray-700; + } + + .react-select-container .react-select__control--is-focused { + @apply text-white bg-gray-700 border border-gray-500 rounded-md shadow; + } + + .react-select-container-dark .react-select__control--is-focused { + @apply bg-gray-800 border-gray-600; + } + + .react-select-container .react-select__menu { + @apply text-gray-300 bg-gray-700; + } + + .react-select-container-dark .react-select__menu { + @apply bg-gray-800; + } + + .react-select-container .react-select__option--is-focused { + @apply text-white bg-gray-600; + } + + .react-select-container-dark .react-select__option--is-focused { + @apply bg-gray-700; + } + + .react-select-container .react-select__indicator-separator { + @apply bg-gray-500; + } + + .react-select-container .react-select__indicator { + @apply text-gray-500; + } + + .react-select-container .react-select__placeholder { + @apply text-gray-400; + } + + .react-select-container .react-select__multi-value { + @apply bg-gray-800 border border-gray-500 rounded-md; + } + + .react-select-container .react-select__multi-value__label { + @apply text-white; + } + + .react-select-container .react-select__multi-value__remove { + @apply cursor-pointer rounded-r-md hover:bg-red-700 hover:text-red-100; + } + + .react-select-container .react-select__input { + @apply text-base text-white border-none shadow-sm; + } + + .react-select-container .react-select__input input:focus { + @apply text-white border-none; + box-shadow: none; + } +} + +@layer utilities { + .absolute-top-shift { + top: calc(-4rem - env(safe-area-inset-top)); + } + + .min-h-screen-shift { + min-height: calc(100vh + env(safe-area-inset-top)); + } + + /* Used for animating height */ + .extra-max-height { + max-height: 100rem; + } + + .hide-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + + .hide-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for Chrome, Safari and Opera */ + .hide-scrollbar::-webkit-scrollbar { + display: none; + } + + /* Hide scrollbar for IE, Edge and Firefox */ + .hide-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + } + + @media all and (display-mode: browser) { + .pwa-only { + @apply hidden; + } } } diff --git a/stylelint.config.js b/stylelint.config.js index 79a284598..7339dadef 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -10,6 +10,7 @@ module.exports = { 'variants', 'responsive', 'screen', + 'layer', ], }, ], From a35209c7390a3e7b8383e244bfc9ede591d152fb Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sun, 24 Oct 2021 11:42:52 -0400 Subject: [PATCH 084/238] chore(github): add stalebot-exempt labels (#2225) [skip ci] * chore(github): add stalebot-exempt labels * chore(github): also never stale priority:medium --- .github/stale.yml | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/.github/stale.yml b/.github/stale.yml index eeed081e3..a0c96e871 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,18 +1,44 @@ -# Number of days of inactivity before an issue becomes stale +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 60 -# Number of days of inactivity before a stale issue is closed + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. daysUntilClose: 7 -# Issues with these labels will never be considered stale + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - pinned - security - dependencies -# Label to use when marking an issue as stale + - never-stale + - priority:high + - priority:medium + +# Label to use when marking as stale staleLabel: stale -# Comment to post when marking an issue as stale. Set to `false` to disable + +# Comment to post when marking as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: false + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Comment to post when closing a stale Issue or Pull Request. +# closeComment: > +# Your comment here. + +# Limit to only `issues` or `pulls` +# only: issues + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +pulls: + markComment: > + This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. From ab20c21184639e1c7725f7cae96249c6fa157351 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sun, 24 Oct 2021 12:08:03 -0400 Subject: [PATCH 085/238] fix(lang): string edits (#2229) * fix(lang): string edits * fix: correct notif type ordering * fix(lang): a few more edits * fix(frontend): align & wrap media attributes properly * fix(lang): use consistent cache names --- server/lib/cache.ts | 4 +- .../IssueDetails/IssueDescription/index.tsx | 7 +- src/components/IssueDetails/index.tsx | 46 +++++++------ src/components/ManageSlideOver/index.tsx | 4 +- .../NotificationTypeSelector/index.tsx | 31 ++++++--- src/components/PermissionEdit/index.tsx | 41 +++++++----- src/i18n/locale/en.json | 65 +++++++++---------- src/styles/globals.css | 2 +- 8 files changed, 108 insertions(+), 92 deletions(-) diff --git a/server/lib/cache.ts b/server/lib/cache.ts index fa03783c8..7782a05a8 100644 --- a/server/lib/cache.ts +++ b/server/lib/cache.ts @@ -40,7 +40,7 @@ class Cache { class CacheManager { private availableCaches: Record = { - tmdb: new Cache('tmdb', 'TMDb API', { + tmdb: new Cache('tmdb', 'The Movie Database API', { stdTtl: 21600, checkPeriod: 60 * 30, }), @@ -54,7 +54,7 @@ class CacheManager { stdTtl: 21600, checkPeriod: 60 * 30, }), - plexguid: new Cache('plexguid', 'Plex GUID Cache', { + plexguid: new Cache('plexguid', 'Plex GUID', { stdTtl: 86400 * 7, // 1 week cache checkPeriod: 60 * 30, }), diff --git a/src/components/IssueDetails/IssueDescription/index.tsx b/src/components/IssueDetails/IssueDescription/index.tsx index ba550afbd..fea8b3b23 100644 --- a/src/components/IssueDetails/IssueDescription/index.tsx +++ b/src/components/IssueDetails/IssueDescription/index.tsx @@ -5,13 +5,12 @@ import React, { Fragment, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import ReactMarkdown from 'react-markdown'; import { Permission, useUser } from '../../../hooks/useUser'; +import globalMessages from '../../../i18n/globalMessages'; import Button from '../../Common/Button'; const messages = defineMessages({ description: 'Description', edit: 'Edit Description', - cancel: 'Cancel', - save: 'Save Changes', deleteissue: 'Delete Issue', }); @@ -125,10 +124,10 @@ const IssueDescription: React.FC = ({ type="button" onClick={() => setIsEditing(false)} > - {intl.formatMessage(messages.cancel)} + {intl.formatMessage(globalMessages.cancel)}
diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index 46ba759e8..9643d38e2 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -43,12 +43,13 @@ const messages = defineMessages({ reopenissue: 'Reopen Issue', reopenissueandcomment: 'Reopen with Comment', issuepagetitle: 'Issue', - openinradarr: 'Open in Radarr', - openinsonarr: 'Open in Sonarr', - toasteditdescriptionsuccess: 'Successfully edited the issue description.', - toasteditdescriptionfailed: 'Something went wrong editing the description.', - toaststatusupdated: 'Issue status updated.', - toaststatusupdatefailed: 'Something went wrong updating the issue status.', + openinarr: 'Open in {arr}', + toasteditdescriptionsuccess: 'Edited the issue description successfully!', + toasteditdescriptionfailed: + 'Something went wrong while editing the issue description.', + toaststatusupdated: 'Updated the issue status successfully!', + toaststatusupdatefailed: + 'Something went wrong while updating the issue status.', issuetype: 'Issue Type', mediatype: 'Media Type', lastupdated: 'Last Updated', @@ -62,8 +63,8 @@ const messages = defineMessages({ episode: 'Episode {episodeNumber}', deleteissue: 'Delete Issue', deleteissueconfirm: 'Are you sure you want to delete this issue?', - toastissuedeleted: 'Issue deleted succesfully.', - toastissuedeletefailed: 'Something went wrong deleting the issue.', + toastissuedeleted: 'Deleted the issue successfully!', + toastissuedeletefailed: 'Something went wrong while deleting the issue.', nocomments: 'No comments.', unknownissuetype: 'Unknown', }); @@ -172,12 +173,7 @@ const IssueDetails: React.FC = () => { height: 493, }} > - + { > - {intl.formatMessage( - issueData.media.mediaType === MediaType.MOVIE - ? messages.openinradarr - : messages.openinsonarr - )} + {intl.formatMessage(messages.openinarr, { + arr: + issueData.media.mediaType === MediaType.MOVIE + ? 'Radarr' + : 'Sonarr', + })} )} @@ -581,11 +578,12 @@ const IssueDetails: React.FC = () => { > - {intl.formatMessage( - issueData.media.mediaType === MediaType.MOVIE - ? messages.openinradarr - : messages.openinsonarr - )} + {intl.formatMessage(messages.openinarr, { + arr: + issueData.media.mediaType === MediaType.MOVIE + ? 'Radarr' + : 'Sonarr', + })} )} diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index 3773e4348..65704cc1c 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -27,8 +27,8 @@ const messages = defineMessages({ manageModalClearMedia: 'Clear Media Data', manageModalClearMediaWarning: '* This will irreversibly remove all data for this {mediaType}, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.', - openarr: 'Open {mediaType} in {arr}', - openarr4k: 'Open {mediaType} in 4K {arr}', + openarr: 'Open in {arr}', + openarr4k: 'Open in 4K {arr}', downloadstatus: 'Download Status', markavailable: 'Mark as Available', mark4kavailable: 'Mark as Available in 4K', diff --git a/src/components/NotificationTypeSelector/index.tsx b/src/components/NotificationTypeSelector/index.tsx index 37ecf9859..5fd774c4e 100644 --- a/src/components/NotificationTypeSelector/index.tsx +++ b/src/components/NotificationTypeSelector/index.tsx @@ -37,17 +37,19 @@ const messages = defineMessages({ 'Send notifications when media requests are declined.', usermediadeclinedDescription: 'Get notified when your media requests are declined.', - issuecreated: 'Issue Created', - issuecreatedDescription: 'Send notifications when new issues are created.', + issuecreated: 'Issue Reported', + issuecreatedDescription: 'Send notifications when issues are reported.', + userissuecreatedDescription: 'Get notified when other users report issues.', issuecomment: 'Issue Comment', issuecommentDescription: 'Send notifications when issues receive new comments.', userissuecommentDescription: - 'Send notifications when your issue receives new comments.', + 'Get notified when your issues receive new comments.', + adminissuecommentDescription: + 'Get notified when issues receive new comments.', issueresolved: 'Issue Resolved', issueresolvedDescription: 'Send notifications when issues are resolved.', - userissueresolvedDescription: - 'Send notifications when your issues are resolved.', + userissueresolvedDescription: 'Get notified when your issues are resolved.', }); export const hasNotificationType = ( @@ -99,7 +101,7 @@ export interface NotificationItem { name: string; description: string; value: Notification; - hasNotifyUser?: boolean; + hasNotifyUser: boolean; children?: NotificationItem[]; hidden?: boolean; } @@ -187,6 +189,7 @@ const NotificationTypeSelector: React.FC = ({ : messages.mediarequestedDescription ), value: Notification.MEDIA_PENDING, + hasNotifyUser: false, hidden: user && !hasPermission(Permission.MANAGE_REQUESTS), }, { @@ -198,6 +201,7 @@ const NotificationTypeSelector: React.FC = ({ : messages.mediaAutoApprovedDescription ), value: Notification.MEDIA_AUTO_APPROVED, + hasNotifyUser: false, hidden: user && !hasPermission(Permission.MANAGE_REQUESTS), }, { @@ -245,24 +249,33 @@ const NotificationTypeSelector: React.FC = ({ ), value: Notification.MEDIA_FAILED, hidden: user && !hasPermission(Permission.MANAGE_REQUESTS), + hasNotifyUser: false, }, { id: 'issue-created', name: intl.formatMessage(messages.issuecreated), - description: intl.formatMessage(messages.issuecreatedDescription), + description: intl.formatMessage( + user + ? messages.userissuecreatedDescription + : messages.issuecreatedDescription + ), value: Notification.ISSUE_CREATED, hidden: user && !hasPermission(Permission.MANAGE_ISSUES), + hasNotifyUser: false, }, { id: 'issue-comment', name: intl.formatMessage(messages.issuecomment), description: intl.formatMessage( user - ? messages.userissuecommentDescription + ? hasPermission(Permission.MANAGE_ISSUES) + ? messages.adminissuecommentDescription + : messages.userissuecommentDescription : messages.issuecommentDescription ), value: Notification.ISSUE_COMMENT, - hasNotifyUser: true, + hasNotifyUser: + !user || hasPermission(Permission.MANAGE_ISSUES) ? false : true, }, { id: 'issue-resolved', diff --git a/src/components/PermissionEdit/index.tsx b/src/components/PermissionEdit/index.tsx index b4b738250..504e615db 100644 --- a/src/components/PermissionEdit/index.tsx +++ b/src/components/PermissionEdit/index.tsx @@ -9,21 +9,24 @@ export const messages = defineMessages({ 'Full administrator access. Bypasses all other permission checks.', users: 'Manage Users', usersDescription: - 'Grant permission to manage Overseerr users. Users with this permission cannot modify users with or grant the Admin privilege.', + 'Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.', settings: 'Manage Settings', settingsDescription: - 'Grant permission to modify Overseerr settings. A user must have this permission to grant it to others.', + 'Grant permission to modify global settings. A user must have this permission to grant it to others.', managerequests: 'Manage Requests', managerequestsDescription: - 'Grant permission to manage Overseerr requests. All requests made by a user with this permission will be automatically approved.', + 'Grant permission to manage media requests. All requests made by a user with this permission will be automatically approved.', request: 'Request', - requestDescription: 'Grant permission to request non-4K media.', + requestDescription: 'Grant permission to submit requests for non-4K media.', requestMovies: 'Request Movies', - requestMoviesDescription: 'Grant permission to request non-4K movies.', + requestMoviesDescription: + 'Grant permission to submit requests for non-4K movies.', requestTv: 'Request Series', - requestTvDescription: 'Grant permission to request non-4K series.', + requestTvDescription: + 'Grant permission to submit requests for non-4K series.', autoapprove: 'Auto-Approve', - autoapproveDescription: 'Grant automatic approval for all non-4K requests.', + autoapproveDescription: + 'Grant automatic approval for all non-4K media requests.', autoapproveMovies: 'Auto-Approve Movies', autoapproveMoviesDescription: 'Grant automatic approval for non-4K movie requests.', @@ -31,7 +34,8 @@ export const messages = defineMessages({ autoapproveSeriesDescription: 'Grant automatic approval for non-4K series requests.', autoapprove4k: 'Auto-Approve 4K', - autoapprove4kDescription: 'Grant automatic approval for all 4K requests.', + autoapprove4kDescription: + 'Grant automatic approval for all 4K media requests.', autoapprove4kMovies: 'Auto-Approve 4K Movies', autoapprove4kMoviesDescription: 'Grant automatic approval for 4K movie requests.', @@ -39,22 +43,25 @@ export const messages = defineMessages({ autoapprove4kSeriesDescription: 'Grant automatic approval for 4K series requests.', request4k: 'Request 4K', - request4kDescription: 'Grant permission to request 4K media.', + request4kDescription: 'Grant permission to submit requests for 4K media.', request4kMovies: 'Request 4K Movies', - request4kMoviesDescription: 'Grant permission to request 4K movies.', + request4kMoviesDescription: + 'Grant permission to submit requests for 4K movies.', request4kTv: 'Request 4K Series', - request4kTvDescription: 'Grant permission to request 4K series.', + request4kTvDescription: 'Grant permission to submit requests for 4K series.', advancedrequest: 'Advanced Requests', advancedrequestDescription: - 'Grant permission to use advanced request options.', + 'Grant permission to modify advanced media request options.', viewrequests: 'View Requests', - viewrequestsDescription: "Grant permission to view other users' requests.", + viewrequestsDescription: + 'Grant permission to view media requests submitted by other users.', manageissues: 'Manage Issues', - manageissuesDescription: 'Grant permission to manage Overseerr issues.', - createissues: 'Create Issues', - createissuesDescription: 'Grant permission to create new issues.', + manageissuesDescription: 'Grant permission to manage media issues.', + createissues: 'Report Issues', + createissuesDescription: 'Grant permission to report media issues.', viewissues: 'View Issues', - viewissuesDescription: "Grant permission to view other users' issues.", + viewissuesDescription: + 'Grant permission to view media issues reported by other users.', }); interface PermissionEditProps { diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 6abd1ba33..286f2b174 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -38,11 +38,9 @@ "components.IssueDetails.IssueComment.postedby": "Posted by {username} {relativeTime}", "components.IssueDetails.IssueComment.postedbyedited": "Posted by {username} {relativeTime} (Edited)", "components.IssueDetails.IssueComment.validationComment": "You must provide a message", - "components.IssueDetails.IssueDescription.cancel": "Cancel", "components.IssueDetails.IssueDescription.deleteissue": "Delete Issue", "components.IssueDetails.IssueDescription.description": "Description", "components.IssueDetails.IssueDescription.edit": "Edit Description", - "components.IssueDetails.IssueDescription.save": "Save Changes", "components.IssueDetails.allepisodes": "All Episodes", "components.IssueDetails.allseasons": "All Seasons", "components.IssueDetails.closeissue": "Close Issue", @@ -58,8 +56,7 @@ "components.IssueDetails.mediatype": "Media Type", "components.IssueDetails.nocomments": "No comments.", "components.IssueDetails.openedby": "#{issueId} opened {relativeTime} by {username}", - "components.IssueDetails.openinradarr": "Open in Radarr", - "components.IssueDetails.openinsonarr": "Open in Sonarr", + "components.IssueDetails.openinarr": "Open in {arr}", "components.IssueDetails.problemepisode": "Affected Episode", "components.IssueDetails.problemseason": "Affected Season", "components.IssueDetails.reopenissue": "Reopen Issue", @@ -67,12 +64,12 @@ "components.IssueDetails.season": "Season {seasonNumber}", "components.IssueDetails.statusopen": "Open", "components.IssueDetails.statusresolved": "Resolved", - "components.IssueDetails.toasteditdescriptionfailed": "Something went wrong editing the description.", - "components.IssueDetails.toasteditdescriptionsuccess": "Successfully edited the issue description.", - "components.IssueDetails.toastissuedeleted": "Issue deleted succesfully.", - "components.IssueDetails.toastissuedeletefailed": "Something went wrong deleting the issue.", - "components.IssueDetails.toaststatusupdated": "Issue status updated.", - "components.IssueDetails.toaststatusupdatefailed": "Something went wrong updating the issue status.", + "components.IssueDetails.toasteditdescriptionfailed": "Something went wrong while editing the issue description.", + "components.IssueDetails.toasteditdescriptionsuccess": "Edited the issue description successfully!", + "components.IssueDetails.toastissuedeleted": "Deleted the issue successfully!", + "components.IssueDetails.toastissuedeletefailed": "Something went wrong while deleting the issue.", + "components.IssueDetails.toaststatusupdated": "Updated the issue status successfully!", + "components.IssueDetails.toaststatusupdatefailed": "Something went wrong while updating the issue status.", "components.IssueDetails.unknownissuetype": "Unknown", "components.IssueList.IssueItem.allepisodes": "All Episodes", "components.IssueList.IssueItem.allseasons": "All Seasons", @@ -145,8 +142,8 @@ "components.ManageSlideOver.mark4kavailable": "Mark as Available in 4K", "components.ManageSlideOver.markavailable": "Mark as Available", "components.ManageSlideOver.movie": "movie", - "components.ManageSlideOver.openarr": "Open {mediaType} in {arr}", - "components.ManageSlideOver.openarr4k": "Open {mediaType} in 4K {arr}", + "components.ManageSlideOver.openarr": "Open in {arr}", + "components.ManageSlideOver.openarr4k": "Open in 4K {arr}", "components.ManageSlideOver.tvshow": "series", "components.MediaSlider.ShowMoreCard.seemore": "See More", "components.MovieDetails.MovieCast.fullcast": "Full Cast", @@ -172,10 +169,11 @@ "components.MovieDetails.studio": "{studioCount, plural, one {Studio} other {Studios}}", "components.MovieDetails.viewfullcrew": "View Full Crew", "components.MovieDetails.watchtrailer": "Watch Trailer", + "components.NotificationTypeSelector.adminissuecommentDescription": "Get notified when issues receive new comments.", "components.NotificationTypeSelector.issuecomment": "Issue Comment", "components.NotificationTypeSelector.issuecommentDescription": "Send notifications when issues receive new comments.", - "components.NotificationTypeSelector.issuecreated": "Issue Created", - "components.NotificationTypeSelector.issuecreatedDescription": "Send notifications when new issues are created.", + "components.NotificationTypeSelector.issuecreated": "Issue Reported", + "components.NotificationTypeSelector.issuecreatedDescription": "Send notifications when issues are reported.", "components.NotificationTypeSelector.issueresolved": "Issue Resolved", "components.NotificationTypeSelector.issueresolvedDescription": "Send notifications when issues are resolved.", "components.NotificationTypeSelector.mediaAutoApproved": "Media Automatically Approved", @@ -191,8 +189,9 @@ "components.NotificationTypeSelector.mediarequested": "Media Requested", "components.NotificationTypeSelector.mediarequestedDescription": "Send notifications when users submit new media requests which require approval.", "components.NotificationTypeSelector.notificationTypes": "Notification Types", - "components.NotificationTypeSelector.userissuecommentDescription": "Send notifications when your issue receives new comments.", - "components.NotificationTypeSelector.userissueresolvedDescription": "Send notifications when your issues are resolved.", + "components.NotificationTypeSelector.userissuecommentDescription": "Get notified when your issues receive new comments.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Get notified when other users report issues.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Get notified when your issues are resolved.", "components.NotificationTypeSelector.usermediaAutoApprovedDescription": "Get notified when other users submit new media requests which are automatically approved.", "components.NotificationTypeSelector.usermediaapprovedDescription": "Get notified when your media requests are approved.", "components.NotificationTypeSelector.usermediaavailableDescription": "Get notified when your media requests become available.", @@ -202,45 +201,45 @@ "components.PermissionEdit.admin": "Admin", "components.PermissionEdit.adminDescription": "Full administrator access. Bypasses all other permission checks.", "components.PermissionEdit.advancedrequest": "Advanced Requests", - "components.PermissionEdit.advancedrequestDescription": "Grant permission to use advanced request options.", + "components.PermissionEdit.advancedrequestDescription": "Grant permission to modify advanced media request options.", "components.PermissionEdit.autoapprove": "Auto-Approve", "components.PermissionEdit.autoapprove4k": "Auto-Approve 4K", - "components.PermissionEdit.autoapprove4kDescription": "Grant automatic approval for all 4K requests.", + "components.PermissionEdit.autoapprove4kDescription": "Grant automatic approval for all 4K media requests.", "components.PermissionEdit.autoapprove4kMovies": "Auto-Approve 4K Movies", "components.PermissionEdit.autoapprove4kMoviesDescription": "Grant automatic approval for 4K movie requests.", "components.PermissionEdit.autoapprove4kSeries": "Auto-Approve 4K Series", "components.PermissionEdit.autoapprove4kSeriesDescription": "Grant automatic approval for 4K series requests.", - "components.PermissionEdit.autoapproveDescription": "Grant automatic approval for all non-4K requests.", + "components.PermissionEdit.autoapproveDescription": "Grant automatic approval for all non-4K media requests.", "components.PermissionEdit.autoapproveMovies": "Auto-Approve Movies", "components.PermissionEdit.autoapproveMoviesDescription": "Grant automatic approval for non-4K movie requests.", "components.PermissionEdit.autoapproveSeries": "Auto-Approve Series", "components.PermissionEdit.autoapproveSeriesDescription": "Grant automatic approval for non-4K series requests.", - "components.PermissionEdit.createissues": "Create Issues", - "components.PermissionEdit.createissuesDescription": "Grant permission to create new issues.", + "components.PermissionEdit.createissues": "Report Issues", + "components.PermissionEdit.createissuesDescription": "Grant permission to report media issues.", "components.PermissionEdit.manageissues": "Manage Issues", - "components.PermissionEdit.manageissuesDescription": "Grant permission to manage Overseerr issues.", + "components.PermissionEdit.manageissuesDescription": "Grant permission to manage media issues.", "components.PermissionEdit.managerequests": "Manage Requests", - "components.PermissionEdit.managerequestsDescription": "Grant permission to manage Overseerr requests. All requests made by a user with this permission will be automatically approved.", + "components.PermissionEdit.managerequestsDescription": "Grant permission to manage media requests. All requests made by a user with this permission will be automatically approved.", "components.PermissionEdit.request": "Request", "components.PermissionEdit.request4k": "Request 4K", - "components.PermissionEdit.request4kDescription": "Grant permission to request 4K media.", + "components.PermissionEdit.request4kDescription": "Grant permission to submit requests for 4K media.", "components.PermissionEdit.request4kMovies": "Request 4K Movies", - "components.PermissionEdit.request4kMoviesDescription": "Grant permission to request 4K movies.", + "components.PermissionEdit.request4kMoviesDescription": "Grant permission to submit requests for 4K movies.", "components.PermissionEdit.request4kTv": "Request 4K Series", - "components.PermissionEdit.request4kTvDescription": "Grant permission to request 4K series.", - "components.PermissionEdit.requestDescription": "Grant permission to request non-4K media.", + "components.PermissionEdit.request4kTvDescription": "Grant permission to submit requests for 4K series.", + "components.PermissionEdit.requestDescription": "Grant permission to submit requests for non-4K media.", "components.PermissionEdit.requestMovies": "Request Movies", - "components.PermissionEdit.requestMoviesDescription": "Grant permission to request non-4K movies.", + "components.PermissionEdit.requestMoviesDescription": "Grant permission to submit requests for non-4K movies.", "components.PermissionEdit.requestTv": "Request Series", - "components.PermissionEdit.requestTvDescription": "Grant permission to request non-4K series.", + "components.PermissionEdit.requestTvDescription": "Grant permission to submit requests for non-4K series.", "components.PermissionEdit.settings": "Manage Settings", - "components.PermissionEdit.settingsDescription": "Grant permission to modify Overseerr settings. A user must have this permission to grant it to others.", + "components.PermissionEdit.settingsDescription": "Grant permission to modify global settings. A user must have this permission to grant it to others.", "components.PermissionEdit.users": "Manage Users", - "components.PermissionEdit.usersDescription": "Grant permission to manage Overseerr users. Users with this permission cannot modify users with or grant the Admin privilege.", + "components.PermissionEdit.usersDescription": "Grant permission to manage users. Users with this permission cannot modify users with or grant the Admin privilege.", "components.PermissionEdit.viewissues": "View Issues", - "components.PermissionEdit.viewissuesDescription": "Grant permission to view other users' issues.", + "components.PermissionEdit.viewissuesDescription": "Grant permission to view media issues reported by other users.", "components.PermissionEdit.viewrequests": "View Requests", - "components.PermissionEdit.viewrequestsDescription": "Grant permission to view other users' requests.", + "components.PermissionEdit.viewrequestsDescription": "Grant permission to view media requests submitted by other users.", "components.PersonDetails.alsoknownas": "Also Known As: {names}", "components.PersonDetails.appearsin": "Appearances", "components.PersonDetails.ascharacter": "as {character}", diff --git a/src/styles/globals.css b/src/styles/globals.css index 228ec9c6f..c7a769a12 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -118,7 +118,7 @@ } .media-attributes { - @apply flex items-center mt-1 space-x-1 text-xs text-gray-300 sm:text-sm xl:text-base xl:mt-0; + @apply flex flex-wrap items-center justify-center mt-1 space-x-1 text-xs text-gray-300 sm:text-sm xl:text-base xl:mt-0 xl:justify-start; } .media-attributes a { From aeb7a48d72cec3fa2b857030aad3eaa0a457a896 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Mon, 25 Oct 2021 06:46:05 -0400 Subject: [PATCH 086/238] feat(notif): add Pushbullet and Pushover agents to user notification settings (#1740) * feat(notif): add Pushbullet and Pushover agents to user notification settings * docs(notif): add "hint" about user notifications to Pushbullet and Pushover pages * fix: regenerate DB migration --- .../notifications/pushbullet.md | 6 + .../using-overseerr/notifications/pushover.md | 6 + .../using-overseerr/notifications/telegram.md | 4 +- overseerr-api.yml | 9 + server/entity/UserSettings.ts | 9 + .../interfaces/api/userSettingsInterfaces.ts | 3 + server/lib/notifications/agents/pushbullet.ts | 178 ++++++++++---- server/lib/notifications/agents/pushover.ts | 189 +++++++++++---- server/lib/notifications/agents/telegram.ts | 78 +++--- server/lib/notifications/agents/webpush.ts | 10 +- ...63457-AddPushbulletPushoverUserSettings.ts | 33 +++ server/routes/user/usersettings.ts | 13 + .../Notifications/NotificationsTelegram.tsx | 4 +- .../UserNotificationsDiscord.tsx | 5 +- .../UserNotificationsEmail.tsx | 3 + .../UserNotificationsPushbullet.tsx | 172 +++++++++++++ .../UserNotificationsPushover.tsx | 228 ++++++++++++++++++ .../UserNotificationsTelegram.tsx | 5 +- .../UserNotificationsWebPush.tsx | 3 + .../UserNotificationSettings/index.tsx | 24 ++ src/i18n/locale/en.json | 13 + .../settings/notifications/pushbullet.tsx | 17 ++ .../settings/notifications/pushover.tsx | 17 ++ .../settings/notifications/pushbullet.tsx | 20 ++ .../settings/notifications/pushover.tsx | 20 ++ 25 files changed, 914 insertions(+), 155 deletions(-) create mode 100644 server/migration/1635079863457-AddPushbulletPushoverUserSettings.ts create mode 100644 src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsx create mode 100644 src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx create mode 100644 src/pages/profile/settings/notifications/pushbullet.tsx create mode 100644 src/pages/profile/settings/notifications/pushover.tsx create mode 100644 src/pages/users/[userId]/settings/notifications/pushbullet.tsx create mode 100644 src/pages/users/[userId]/settings/notifications/pushover.tsx diff --git a/docs/using-overseerr/notifications/pushbullet.md b/docs/using-overseerr/notifications/pushbullet.md index 45edcc3a0..6c6ba853e 100644 --- a/docs/using-overseerr/notifications/pushbullet.md +++ b/docs/using-overseerr/notifications/pushbullet.md @@ -1,5 +1,11 @@ # Pushbullet +{% hint style="info" %} +Users can optionally configure personal notifications in their user settings. + +User notifications are separate from system notifications, and the available notification types are dependent on user permissions. +{% endhint %} + ## Configuration ### Access Token diff --git a/docs/using-overseerr/notifications/pushover.md b/docs/using-overseerr/notifications/pushover.md index 55893dbad..cc09bfb69 100644 --- a/docs/using-overseerr/notifications/pushover.md +++ b/docs/using-overseerr/notifications/pushover.md @@ -1,5 +1,11 @@ # Pushover +{% hint style="info" %} +Users can optionally configure personal notifications in their user settings. + +User notifications are separate from system notifications, and the available notification types are dependent on user permissions. +{% endhint %} + ## Configuration ### Application/API Token diff --git a/docs/using-overseerr/notifications/telegram.md b/docs/using-overseerr/notifications/telegram.md index d0e6f6fcb..9bdb96dbc 100644 --- a/docs/using-overseerr/notifications/telegram.md +++ b/docs/using-overseerr/notifications/telegram.md @@ -1,7 +1,9 @@ # Telegram {% hint style="info" %} -Users can optionally configure their own notifications in their user settings. +Users can optionally configure personal notifications in their user settings. + +User notifications are separate from system notifications, and the available notification types are dependent on user permissions. {% endhint %} ## Configuration diff --git a/overseerr-api.yml b/overseerr-api.yml index 87d8061ea..00e1cf03a 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1630,6 +1630,15 @@ components: discordId: type: string nullable: true + pushbulletAccessToken: + type: string + nullable: true + pushoverApplicationToken: + type: string + nullable: true + pushoverUserKey: + type: string + nullable: true telegramEnabled: type: boolean telegramBotUsername: diff --git a/server/entity/UserSettings.ts b/server/entity/UserSettings.ts index 02f391112..08397b12f 100644 --- a/server/entity/UserSettings.ts +++ b/server/entity/UserSettings.ts @@ -42,6 +42,15 @@ export class UserSettings { @Column({ nullable: true }) public discordId?: string; + @Column({ nullable: true }) + public pushbulletAccessToken?: string; + + @Column({ nullable: true }) + public pushoverApplicationToken?: string; + + @Column({ nullable: true }) + public pushoverUserKey?: string; + @Column({ nullable: true }) public telegramChatId?: string; diff --git a/server/interfaces/api/userSettingsInterfaces.ts b/server/interfaces/api/userSettingsInterfaces.ts index 18e3c7aba..0f743efef 100644 --- a/server/interfaces/api/userSettingsInterfaces.ts +++ b/server/interfaces/api/userSettingsInterfaces.ts @@ -22,6 +22,9 @@ export interface UserSettingsNotificationsResponse { discordEnabled?: boolean; discordEnabledTypes?: number; discordId?: string; + pushbulletAccessToken?: string; + pushoverApplicationToken?: string; + pushoverUserKey?: string; telegramEnabled?: boolean; telegramBotUsername?: string; telegramChatId?: string; diff --git a/server/lib/notifications/agents/pushbullet.ts b/server/lib/notifications/agents/pushbullet.ts index 160eed87f..3684803f7 100644 --- a/server/lib/notifications/agents/pushbullet.ts +++ b/server/lib/notifications/agents/pushbullet.ts @@ -1,11 +1,19 @@ import axios from 'axios'; +import { getRepository } from 'typeorm'; import { hasNotificationType, Notification } from '..'; import { MediaType } from '../../../constants/media'; +import { User } from '../../../entity/User'; import logger from '../../../logger'; -import { getSettings, NotificationAgentPushbullet } from '../../settings'; +import { Permission } from '../../permissions'; +import { + getSettings, + NotificationAgentKey, + NotificationAgentPushbullet, +} from '../../settings'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; interface PushbulletPayload { + type: string; title: string; body: string; } @@ -25,22 +33,13 @@ class PushbulletAgent } public shouldSend(): boolean { - const settings = this.getSettings(); - - if (settings.enabled && settings.options.accessToken) { - return true; - } - - return false; + return true; } - private constructMessageDetails( + private getNotificationPayload( type: Notification, payload: NotificationPayload - ): { - title: string; - body: string; - } { + ): PushbulletPayload { let messageTitle = ''; let message = ''; @@ -126,6 +125,7 @@ class PushbulletAgent } return { + type: 'note', title: messageTitle, body: message, }; @@ -136,46 +136,132 @@ class PushbulletAgent payload: NotificationPayload ): Promise { const settings = this.getSettings(); + const endpoint = 'https://api.pushbullet.com/v2/pushes'; + const notificationPayload = this.getNotificationPayload(type, payload); - if (!hasNotificationType(type, settings.types ?? 0)) { - return true; - } - - logger.debug('Sending Pushbullet notification', { - label: 'Notifications', - type: Notification[type], - subject: payload.subject, - }); - - try { - const { title, body } = this.constructMessageDetails(type, payload); - - await axios.post( - 'https://api.pushbullet.com/v2/pushes', - { - type: 'note', - title: title, - body: body, - } as PushbulletPayload, - { - headers: { - 'Access-Token': settings.options.accessToken, - }, - } - ); - - return true; - } catch (e) { - logger.error('Error sending Pushbullet notification', { + // Send system notification + if ( + hasNotificationType(type, settings.types ?? 0) && + settings.enabled && + settings.options.accessToken + ) { + logger.debug('Sending Pushbullet notification', { label: 'Notifications', type: Notification[type], subject: payload.subject, - errorMessage: e.message, - response: e.response?.data, }); - return false; + try { + await axios.post(endpoint, notificationPayload, { + headers: { + 'Access-Token': settings.options.accessToken, + }, + }); + } catch (e) { + logger.error('Error sending Pushbullet notification', { + label: 'Notifications', + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } } + + if (payload.notifyUser) { + // Send notification to the user who submitted the request + if ( + payload.notifyUser.settings?.hasNotificationType( + NotificationAgentKey.PUSHBULLET, + type + ) && + payload.notifyUser.settings?.pushbulletAccessToken && + payload.notifyUser.settings.pushbulletAccessToken !== + settings.options.accessToken + ) { + logger.debug('Sending Pushbullet notification', { + label: 'Notifications', + recipient: payload.notifyUser.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await axios.post(endpoint, notificationPayload, { + headers: { + 'Access-Token': payload.notifyUser.settings.pushbulletAccessToken, + }, + }); + } catch (e) { + logger.error('Error sending Pushbullet notification', { + label: 'Notifications', + recipient: payload.notifyUser.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } + } + } else { + // Send notifications to all users with the Manage Requests permission + const userRepository = getRepository(User); + const users = await userRepository.find(); + + await Promise.all( + users + .filter( + (user) => + user.hasPermission(Permission.MANAGE_REQUESTS) && + user.settings?.hasNotificationType( + NotificationAgentKey.PUSHBULLET, + type + ) && + // Check if it's the user's own auto-approved request + (type !== Notification.MEDIA_AUTO_APPROVED || + user.id !== payload.request?.requestedBy.id) + ) + .map(async (user) => { + if ( + user.settings?.pushbulletAccessToken && + user.settings.pushbulletAccessToken !== + settings.options.accessToken + ) { + logger.debug('Sending Pushbullet notification', { + label: 'Notifications', + recipient: user.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await axios.post(endpoint, notificationPayload, { + headers: { + 'Access-Token': user.settings.pushbulletAccessToken, + }, + }); + } catch (e) { + logger.error('Error sending Pushbullet notification', { + label: 'Notifications', + recipient: user.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } + } + }) + ); + } + + return true; } } diff --git a/server/lib/notifications/agents/pushover.ts b/server/lib/notifications/agents/pushover.ts index b37b54461..e66158063 100644 --- a/server/lib/notifications/agents/pushover.ts +++ b/server/lib/notifications/agents/pushover.ts @@ -1,8 +1,15 @@ import axios from 'axios'; +import { getRepository } from 'typeorm'; import { hasNotificationType, Notification } from '..'; import { MediaType } from '../../../constants/media'; +import { User } from '../../../entity/User'; import logger from '../../../logger'; -import { getSettings, NotificationAgentPushover } from '../../settings'; +import { Permission } from '../../permissions'; +import { + getSettings, + NotificationAgentKey, + NotificationAgentPushover, +} from '../../settings'; import { BaseAgent, NotificationAgent, NotificationPayload } from './agent'; interface PushoverPayload { @@ -31,29 +38,13 @@ class PushoverAgent } public shouldSend(): boolean { - const settings = this.getSettings(); - - if ( - settings.enabled && - settings.options.accessToken && - settings.options.userToken - ) { - return true; - } - - return false; + return true; } - private constructMessageDetails( + private getNotificationPayload( type: Notification, payload: NotificationPayload - ): { - title: string; - message: string; - url: string | undefined; - url_title: string | undefined; - priority: number; - } { + ): Partial { const settings = getSettings(); let messageTitle = ''; let message = ''; @@ -155,6 +146,7 @@ class PushoverAgent url, url_title, priority, + html: 1, }; } @@ -163,45 +155,138 @@ class PushoverAgent payload: NotificationPayload ): Promise { const settings = this.getSettings(); + const endpoint = 'https://api.pushover.net/1/messages.json'; + const notificationPayload = this.getNotificationPayload(type, payload); - if (!hasNotificationType(type, settings.types ?? 0)) { - return true; - } - - logger.debug('Sending Pushover notification', { - label: 'Notifications', - type: Notification[type], - subject: payload.subject, - }); - try { - const endpoint = 'https://api.pushover.net/1/messages.json'; - - const { title, message, url, url_title, priority } = - this.constructMessageDetails(type, payload); - - await axios.post(endpoint, { - token: settings.options.accessToken, - user: settings.options.userToken, - title: title, - message: message, - url: url, - url_title: url_title, - priority: priority, - html: 1, - } as PushoverPayload); - - return true; - } catch (e) { - logger.error('Error sending Pushover notification', { + // Send system notification + if ( + hasNotificationType(type, settings.types ?? 0) && + settings.enabled && + settings.options.accessToken && + settings.options.userToken + ) { + logger.debug('Sending Pushover notification', { label: 'Notifications', type: Notification[type], subject: payload.subject, - errorMessage: e.message, - response: e.response?.data, }); - return false; + try { + await axios.post(endpoint, { + ...notificationPayload, + token: settings.options.accessToken, + user: settings.options.userToken, + } as PushoverPayload); + } catch (e) { + logger.error('Error sending Pushover notification', { + label: 'Notifications', + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } } + + if (payload.notifyUser) { + // Send notification to the user who submitted the request + if ( + payload.notifyUser.settings?.hasNotificationType( + NotificationAgentKey.PUSHOVER, + type + ) && + payload.notifyUser.settings?.pushoverApplicationToken && + payload.notifyUser.settings?.pushoverUserKey && + payload.notifyUser.settings.pushoverApplicationToken !== + settings.options.accessToken && + payload.notifyUser.settings?.pushoverUserKey !== + settings.options.userToken + ) { + logger.debug('Sending Pushover notification', { + label: 'Notifications', + recipient: payload.notifyUser.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await axios.post(endpoint, { + ...notificationPayload, + token: payload.notifyUser.settings.pushoverApplicationToken, + user: payload.notifyUser.settings.pushoverUserKey, + } as PushoverPayload); + } catch (e) { + logger.error('Error sending Pushover notification', { + label: 'Notifications', + recipient: payload.notifyUser.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } + } + } else { + // Send notifications to all users with the Manage Requests permission + const userRepository = getRepository(User); + const users = await userRepository.find(); + + await Promise.all( + users + .filter( + (user) => + user.hasPermission(Permission.MANAGE_REQUESTS) && + user.settings?.hasNotificationType( + NotificationAgentKey.PUSHOVER, + type + ) && + // Check if it's the user's own auto-approved request + (type !== Notification.MEDIA_AUTO_APPROVED || + user.id !== payload.request?.requestedBy.id) + ) + .map(async (user) => { + if ( + user.settings?.pushoverApplicationToken && + user.settings?.pushoverUserKey && + user.settings.pushoverApplicationToken !== + settings.options.accessToken && + user.settings.pushoverUserKey !== settings.options.userToken + ) { + logger.debug('Sending Pushover notification', { + label: 'Notifications', + recipient: user.displayName, + type: Notification[type], + subject: payload.subject, + }); + + try { + await axios.post(endpoint, { + ...notificationPayload, + token: user.settings.pushoverApplicationToken, + user: user.settings.pushoverUserKey, + } as PushoverPayload); + } catch (e) { + logger.error('Error sending Pushover notification', { + label: 'Notifications', + recipient: user.displayName, + type: Notification[type], + subject: payload.subject, + errorMessage: e.message, + response: e.response?.data, + }); + + return false; + } + } + }) + ); + } + + return true; } } diff --git a/server/lib/notifications/agents/telegram.ts b/server/lib/notifications/agents/telegram.ts index b63fbd62f..e71d29116 100644 --- a/server/lib/notifications/agents/telegram.ts +++ b/server/lib/notifications/agents/telegram.ts @@ -46,11 +46,7 @@ class TelegramAgent public shouldSend(): boolean { const settings = this.getSettings(); - if ( - settings.enabled && - settings.options.botAPI && - settings.options.chatId - ) { + if (settings.enabled && settings.options.botAPI) { return true; } @@ -61,12 +57,10 @@ class TelegramAgent return text ? text.replace(/[_*[\]()~>#+=|{}.!-]/gi, (x) => '\\' + x) : ''; } - private buildMessage( + private getNotificationPayload( type: Notification, - payload: NotificationPayload, - chatId: string, - sendSilently: boolean - ): TelegramMessagePayload | TelegramPhotoPayload { + payload: NotificationPayload + ): Partial { const settings = getSettings(); let message = ''; @@ -160,19 +154,15 @@ class TelegramAgent /* eslint-enable */ return payload.image - ? ({ + ? { photo: payload.image, caption: message, parse_mode: 'MarkdownV2', - chat_id: chatId, - disable_notification: !!sendSilently, - } as TelegramPhotoPayload) - : ({ + } + : { text: message, parse_mode: 'MarkdownV2', - chat_id: chatId, - disable_notification: !!sendSilently, - } as TelegramMessagePayload); + }; } public async send( @@ -180,13 +170,16 @@ class TelegramAgent payload: NotificationPayload ): Promise { const settings = this.getSettings(); - const endpoint = `${this.baseUrl}bot${settings.options.botAPI}/${ payload.image ? 'sendPhoto' : 'sendMessage' }`; + const notificationPayload = this.getNotificationPayload(type, payload); // Send system notification - if (hasNotificationType(type, settings.types ?? 0)) { + if ( + hasNotificationType(type, settings.types ?? 0) && + settings.options.chatId + ) { logger.debug('Sending Telegram notification', { label: 'Notifications', type: Notification[type], @@ -194,15 +187,11 @@ class TelegramAgent }); try { - await axios.post( - endpoint, - this.buildMessage( - type, - payload, - settings.options.chatId, - settings.options.sendSilently - ) - ); + await axios.post(endpoint, { + ...notificationPayload, + chat_id: settings.options.chatId, + disable_notification: !!settings.options.sendSilently, + } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { label: 'Notifications', @@ -224,7 +213,7 @@ class TelegramAgent type ) && payload.notifyUser.settings?.telegramChatId && - payload.notifyUser.settings?.telegramChatId !== settings.options.chatId + payload.notifyUser.settings.telegramChatId !== settings.options.chatId ) { logger.debug('Sending Telegram notification', { label: 'Notifications', @@ -234,15 +223,12 @@ class TelegramAgent }); try { - await axios.post( - endpoint, - this.buildMessage( - type, - payload, - payload.notifyUser.settings.telegramChatId, - !!payload.notifyUser.settings.telegramSendSilently - ) - ); + await axios.post(endpoint, { + ...notificationPayload, + chat_id: payload.notifyUser.settings.telegramChatId, + disable_notification: + !!payload.notifyUser.settings.telegramSendSilently, + } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { label: 'Notifications', @@ -287,15 +273,11 @@ class TelegramAgent }); try { - await axios.post( - endpoint, - this.buildMessage( - type, - payload, - user.settings.telegramChatId, - !!user.settings?.telegramSendSilently - ) - ); + await axios.post(endpoint, { + ...notificationPayload, + chat_id: user.settings.telegramChatId, + disable_notification: !!user.settings?.telegramSendSilently, + } as TelegramMessagePayload | TelegramPhotoPayload); } catch (e) { logger.error('Error sending Telegram notification', { label: 'Notifications', diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts index 624dab223..1ab03ba67 100644 --- a/server/lib/notifications/agents/webpush.ts +++ b/server/lib/notifications/agents/webpush.ts @@ -206,6 +206,11 @@ class WebPushAgent settings.vapidPrivate ); + const notificationPayload = Buffer.from( + JSON.stringify(this.getNotificationPayload(type, payload)), + 'utf-8' + ); + await Promise.all( pushSubs.map(async (sub) => { logger.debug('Sending web push notification', { @@ -224,10 +229,7 @@ class WebPushAgent p256dh: sub.p256dh, }, }, - Buffer.from( - JSON.stringify(this.getNotificationPayload(type, payload)), - 'utf-8' - ) + notificationPayload ); } catch (e) { logger.error( diff --git a/server/migration/1635079863457-AddPushbulletPushoverUserSettings.ts b/server/migration/1635079863457-AddPushbulletPushoverUserSettings.ts new file mode 100644 index 000000000..8934866fa --- /dev/null +++ b/server/migration/1635079863457-AddPushbulletPushoverUserSettings.ts @@ -0,0 +1,33 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddPushbulletPushoverUserSettings1635079863457 + implements MigrationInterface +{ + name = 'AddPushbulletPushoverUserSettings1635079863457'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "temporary_user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), "pushbulletAccessToken" varchar, "pushoverApplicationToken" varchar, "pushoverUserKey" varchar, CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_user_settings"("id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale") SELECT "id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale" FROM "user_settings"` + ); + await queryRunner.query(`DROP TABLE "user_settings"`); + await queryRunner.query( + `ALTER TABLE "temporary_user_settings" RENAME TO "user_settings"` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "user_settings" RENAME TO "temporary_user_settings"` + ); + await queryRunner.query( + `CREATE TABLE "user_settings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "notificationTypes" text, "discordId" varchar, "userId" integer, "region" varchar, "originalLanguage" varchar, "telegramChatId" varchar, "telegramSendSilently" boolean, "pgpKey" varchar, "locale" varchar NOT NULL DEFAULT (''), CONSTRAINT "UQ_986a2b6d3c05eb4091bb8066f78" UNIQUE ("userId"), CONSTRAINT "FK_986a2b6d3c05eb4091bb8066f78" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "user_settings"("id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale") SELECT "id", "notificationTypes", "discordId", "userId", "region", "originalLanguage", "telegramChatId", "telegramSendSilently", "pgpKey", "locale" FROM "temporary_user_settings"` + ); + await queryRunner.query(`DROP TABLE "temporary_user_settings"`); + } +} diff --git a/server/routes/user/usersettings.ts b/server/routes/user/usersettings.ts index 226dcae09..6558115a7 100644 --- a/server/routes/user/usersettings.ts +++ b/server/routes/user/usersettings.ts @@ -257,6 +257,9 @@ userSettingsRoutes.get<{ id: string }, UserSettingsNotificationsResponse>( ? settings?.discord.types : 0, discordId: user.settings?.discordId, + pushbulletAccessToken: user.settings?.pushbulletAccessToken, + pushoverApplicationToken: user.settings?.pushoverApplicationToken, + pushoverUserKey: user.settings?.pushoverUserKey, telegramEnabled: settings?.telegram.enabled, telegramBotUsername: settings?.telegram.options.botUsername, telegramChatId: user.settings?.telegramChatId, @@ -298,6 +301,9 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( user: req.user, pgpKey: req.body.pgpKey, discordId: req.body.discordId, + pushbulletAccessToken: req.body.pushbulletAccessToken, + pushoverApplicationToken: req.body.pushoverApplicationToken, + pushoverUserKey: req.body.pushoverUserKey, telegramChatId: req.body.telegramChatId, telegramSendSilently: req.body.telegramSendSilently, notificationTypes: req.body.notificationTypes, @@ -305,6 +311,10 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( } else { user.settings.pgpKey = req.body.pgpKey; user.settings.discordId = req.body.discordId; + user.settings.pushbulletAccessToken = req.body.pushbulletAccessToken; + user.settings.pushoverApplicationToken = + req.body.pushoverApplicationToken; + user.settings.pushoverUserKey = req.body.pushoverUserKey; user.settings.telegramChatId = req.body.telegramChatId; user.settings.telegramSendSilently = req.body.telegramSendSilently; user.settings.notificationTypes = Object.assign( @@ -319,6 +329,9 @@ userSettingsRoutes.post<{ id: string }, UserSettingsNotificationsResponse>( return res.status(200).json({ pgpKey: user.settings?.pgpKey, discordId: user.settings?.discordId, + pushbulletAccessToken: user.settings?.pushbulletAccessToken, + pushoverApplicationToken: user.settings?.pushoverApplicationToken, + pushoverUserKey: user.settings?.pushoverUserKey, telegramChatId: user.settings?.telegramChatId, telegramSendSilently: user?.settings?.telegramSendSilently, notificationTypes: user.settings.notificationTypes, diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx index bcb03df89..d76fdde33 100644 --- a/src/components/Settings/Notifications/NotificationsTelegram.tsx +++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx @@ -51,8 +51,8 @@ const NotificationsTelegram: React.FC = () => { otherwise: Yup.string().nullable(), }), chatId: Yup.string() - .when('enabled', { - is: true, + .when(['enabled', 'types'], { + is: (enabled: boolean, types: number) => enabled && !!types, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationChatIdRequired)), diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsx index 155c013b7..85d39ccfa 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsx @@ -35,7 +35,7 @@ const UserNotificationsDiscord: React.FC = () => { const UserNotificationsDiscordSchema = Yup.object().shape({ discordId: Yup.string() .when('types', { - is: (value: unknown) => !!value, + is: (types: number) => !!types, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationDiscordId)), @@ -63,6 +63,9 @@ const UserNotificationsDiscord: React.FC = () => { await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { pgpKey: data?.pgpKey, discordId: values.discordId, + pushbulletAccessToken: data?.pushbulletAccessToken, + pushoverApplicationToken: data?.pushoverApplicationToken, + pushoverUserKey: data?.pushoverUserKey, telegramChatId: data?.telegramChatId, telegramSendSilently: data?.telegramSendSilently, notificationTypes: { diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsx index 576bdf14e..a0132d5f1 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsx @@ -63,6 +63,9 @@ const UserEmailSettings: React.FC = () => { await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { pgpKey: values.pgpKey, discordId: data?.discordId, + pushbulletAccessToken: data?.pushbulletAccessToken, + pushoverApplicationToken: data?.pushoverApplicationToken, + pushoverUserKey: data?.pushoverUserKey, telegramChatId: data?.telegramChatId, telegramSendSilently: data?.telegramSendSilently, notificationTypes: { diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsx new file mode 100644 index 000000000..615b6132e --- /dev/null +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsx @@ -0,0 +1,172 @@ +import axios from 'axios'; +import { Form, Formik } from 'formik'; +import { useRouter } from 'next/router'; +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR from 'swr'; +import * as Yup from 'yup'; +import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces'; +import { useUser } from '../../../../hooks/useUser'; +import globalMessages from '../../../../i18n/globalMessages'; +import Button from '../../../Common/Button'; +import LoadingSpinner from '../../../Common/LoadingSpinner'; +import SensitiveInput from '../../../Common/SensitiveInput'; +import NotificationTypeSelector from '../../../NotificationTypeSelector'; + +const messages = defineMessages({ + pushbulletsettingssaved: + 'Pushbullet notification settings saved successfully!', + pushbulletsettingsfailed: 'Pushbullet notification settings failed to save.', + pushbulletAccessToken: 'Access Token', + pushbulletAccessTokenTip: + 'Create a token from your Account Settings', + validationPushbulletAccessToken: 'You must provide an access token', +}); + +const UserPushbulletSettings: React.FC = () => { + const intl = useIntl(); + const { addToast } = useToasts(); + const router = useRouter(); + const { user } = useUser({ id: Number(router.query.userId) }); + const { data, error, revalidate } = useSWR( + user ? `/api/v1/user/${user?.id}/settings/notifications` : null + ); + + const UserNotificationsPushbulletSchema = Yup.object().shape({ + pushbulletAccessToken: Yup.string().when('types', { + is: (types: number) => !!types, + then: Yup.string() + .nullable() + .required(intl.formatMessage(messages.validationPushbulletAccessToken)), + otherwise: Yup.string().nullable(), + }), + }); + + if (!data && !error) { + return ; + } + + return ( + { + try { + await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { + pgpKey: data?.pgpKey, + discordId: data?.discordId, + pushbulletAccessToken: values.pushbulletAccessToken, + pushoverApplicationToken: data?.pushoverApplicationToken, + pushoverUserKey: data?.pushoverUserKey, + telegramChatId: data?.telegramChatId, + telegramSendSilently: data?.telegramSendSilently, + notificationTypes: { + pushbullet: values.types, + }, + }); + addToast(intl.formatMessage(messages.pushbulletsettingssaved), { + appearance: 'success', + autoDismiss: true, + }); + } catch (e) { + addToast(intl.formatMessage(messages.pushbulletsettingsfailed), { + appearance: 'error', + autoDismiss: true, + }); + } finally { + revalidate(); + } + }} + > + {({ + errors, + touched, + isSubmitting, + isValid, + values, + setFieldValue, + setFieldTouched, + }) => { + return ( +
+
+ +
+
+ +
+ {errors.pushbulletAccessToken && + touched.pushbulletAccessToken && ( +
{errors.pushbulletAccessToken}
+ )} +
+
+ { + setFieldValue('types', newTypes); + setFieldTouched('types'); + }} + error={ + errors.types && touched.types + ? (errors.types as string) + : undefined + } + /> +
+
+ + + +
+
+ + ); + }} +
+ ); +}; + +export default UserPushbulletSettings; diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx new file mode 100644 index 000000000..0f88a795d --- /dev/null +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx @@ -0,0 +1,228 @@ +import axios from 'axios'; +import { Field, Form, Formik } from 'formik'; +import { useRouter } from 'next/router'; +import React from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR from 'swr'; +import * as Yup from 'yup'; +import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces'; +import { useUser } from '../../../../hooks/useUser'; +import globalMessages from '../../../../i18n/globalMessages'; +import Button from '../../../Common/Button'; +import LoadingSpinner from '../../../Common/LoadingSpinner'; +import NotificationTypeSelector from '../../../NotificationTypeSelector'; + +const messages = defineMessages({ + pushoversettingssaved: 'Pushover notification settings saved successfully!', + pushoversettingsfailed: 'Pushover notification settings failed to save.', + pushoverApplicationToken: 'Application API Token', + pushoverApplicationTokenTip: + 'Register an application for use with Overseerr', + pushoverUserKey: 'User or Group Key', + pushoverUserKeyTip: + 'Your 30-character user or group identifier', + validationPushoverApplicationToken: + 'You must provide a valid application token', + validationPushoverUserKey: 'You must provide a valid user or group key', +}); + +const UserPushoverSettings: React.FC = () => { + const intl = useIntl(); + const { addToast } = useToasts(); + const router = useRouter(); + const { user } = useUser({ id: Number(router.query.userId) }); + const { data, error, revalidate } = useSWR( + user ? `/api/v1/user/${user?.id}/settings/notifications` : null + ); + + const UserNotificationsPushoverSchema = Yup.object().shape({ + pushoverApplicationToken: Yup.string() + .when('types', { + is: (types: number) => !!types, + then: Yup.string() + .nullable() + .required( + intl.formatMessage(messages.validationPushoverApplicationToken) + ), + otherwise: Yup.string().nullable(), + }) + .matches( + /^[a-z\d]{30}$/i, + intl.formatMessage(messages.validationPushoverApplicationToken) + ), + pushoverUserKey: Yup.string() + .when('types', { + is: (types: number) => !!types, + then: Yup.string() + .nullable() + .required(intl.formatMessage(messages.validationPushoverUserKey)), + otherwise: Yup.string().nullable(), + }) + .matches( + /^[a-z\d]{30}$/i, + intl.formatMessage(messages.validationPushoverUserKey) + ), + }); + + if (!data && !error) { + return ; + } + + return ( + { + try { + await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { + pgpKey: data?.pgpKey, + discordId: data?.discordId, + pushbulletAccessToken: data?.pushbulletAccessToken, + pushoverApplicationToken: values.pushoverApplicationToken, + pushoverUserKey: values.pushoverUserKey, + telegramChatId: data?.telegramChatId, + telegramSendSilently: data?.telegramSendSilently, + notificationTypes: { + pushover: values.types, + }, + }); + addToast(intl.formatMessage(messages.pushoversettingssaved), { + appearance: 'success', + autoDismiss: true, + }); + } catch (e) { + addToast(intl.formatMessage(messages.pushoversettingsfailed), { + appearance: 'error', + autoDismiss: true, + }); + } finally { + revalidate(); + } + }} + > + {({ + errors, + touched, + isSubmitting, + isValid, + values, + setFieldValue, + setFieldTouched, + }) => { + return ( +
+
+ +
+
+ +
+ {errors.pushoverApplicationToken && + touched.pushoverApplicationToken && ( +
+ {errors.pushoverApplicationToken} +
+ )} +
+
+
+ +
+
+ +
+ {errors.pushoverUserKey && touched.pushoverUserKey && ( +
{errors.pushoverUserKey}
+ )} +
+
+ { + setFieldValue('types', newTypes); + setFieldTouched('types'); + }} + error={ + errors.types && touched.types + ? (errors.types as string) + : undefined + } + /> +
+
+ + + +
+
+ + ); + }} +
+ ); +}; + +export default UserPushoverSettings; diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx index b27e5afed..96adfdcf8 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx @@ -37,7 +37,7 @@ const UserTelegramSettings: React.FC = () => { const UserNotificationsTelegramSchema = Yup.object().shape({ telegramChatId: Yup.string() .when('types', { - is: (value: unknown) => !!value, + is: (types: number) => !!types, then: Yup.string() .nullable() .required(intl.formatMessage(messages.validationTelegramChatId)), @@ -67,6 +67,9 @@ const UserTelegramSettings: React.FC = () => { await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { pgpKey: data?.pgpKey, discordId: data?.discordId, + pushbulletAccessToken: data?.pushbulletAccessToken, + pushoverApplicationToken: data?.pushoverApplicationToken, + pushoverUserKey: data?.pushoverUserKey, telegramChatId: values.telegramChatId, telegramSendSilently: values.telegramSendSilently, notificationTypes: { diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx index d2e36810a..6cfb46532 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx @@ -44,6 +44,9 @@ const UserWebPushSettings: React.FC = () => { await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, { pgpKey: data?.pgpKey, discordId: data?.discordId, + pushbulletAccessToken: data?.pushbulletAccessToken, + pushoverApplicationToken: data?.pushoverApplicationToken, + pushoverUserKey: data?.pushoverUserKey, telegramChatId: data?.telegramChatId, telegramSendSilently: data?.telegramSendSilently, notificationTypes: { diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx index 0f58f7e7b..6f2cc64f1 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/index.tsx @@ -5,6 +5,8 @@ import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces'; import DiscordLogo from '../../../../assets/extlogos/discord.svg'; +import PushbulletLogo from '../../../../assets/extlogos/pushbullet.svg'; +import PushoverLogo from '../../../../assets/extlogos/pushover.svg'; import TelegramLogo from '../../../../assets/extlogos/telegram.svg'; import { useUser } from '../../../../hooks/useUser'; import globalMessages from '../../../../i18n/globalMessages'; @@ -64,6 +66,28 @@ const UserNotificationSettings: React.FC = ({ children }) => { route: '/settings/notifications/discord', regex: /\/settings\/notifications\/discord/, }, + { + text: 'Pushbullet', + content: ( + + + Pushbullet + + ), + route: '/settings/notifications/pushbullet', + regex: /\/settings\/notifications\/pushbullet/, + }, + { + text: 'Pushover', + content: ( + + + Pushover + + ), + route: '/settings/notifications/pushover', + regex: /\/settings\/notifications\/pushover/, + }, { text: 'Telegram', content: ( diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 286f2b174..3647fe7a5 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -874,6 +874,16 @@ "components.UserProfile.UserSettings.UserNotificationSettings.notificationsettings": "Notification Settings", "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKey": "PGP Public Key", "components.UserProfile.UserSettings.UserNotificationSettings.pgpPublicKeyTip": "Encrypt email messages using OpenPGP", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Access Token", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Create a token from your Account Settings", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Pushbullet notification settings failed to save.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet notification settings saved successfully!", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Application API Token", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Register an application for use with Overseerr", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "User or Group Key", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Your 30-character user or group identifier", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Pushover notification settings failed to save.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Pushover notification settings saved successfully!", "components.UserProfile.UserSettings.UserNotificationSettings.sendSilently": "Send Silently", "components.UserProfile.UserSettings.UserNotificationSettings.sendSilentlyDescription": "Send notifications with no sound", "components.UserProfile.UserSettings.UserNotificationSettings.telegramChatId": "Chat ID", @@ -882,6 +892,9 @@ "components.UserProfile.UserSettings.UserNotificationSettings.telegramsettingssaved": "Telegram notification settings saved successfully!", "components.UserProfile.UserSettings.UserNotificationSettings.validationDiscordId": "You must provide a valid user ID", "components.UserProfile.UserSettings.UserNotificationSettings.validationPgpPublicKey": "You must provide a valid PGP public key", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "You must provide an access token", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "You must provide a valid application token", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "You must provide a valid user or group key", "components.UserProfile.UserSettings.UserNotificationSettings.validationTelegramChatId": "You must provide a valid chat ID", "components.UserProfile.UserSettings.UserNotificationSettings.webpush": "Web Push", "components.UserProfile.UserSettings.UserNotificationSettings.webpushsettingsfailed": "Web push notification settings failed to save.", diff --git a/src/pages/profile/settings/notifications/pushbullet.tsx b/src/pages/profile/settings/notifications/pushbullet.tsx new file mode 100644 index 000000000..12afd9413 --- /dev/null +++ b/src/pages/profile/settings/notifications/pushbullet.tsx @@ -0,0 +1,17 @@ +import { NextPage } from 'next'; +import React from 'react'; +import UserSettings from '../../../../components/UserProfile/UserSettings'; +import UserNotificationSettings from '../../../../components/UserProfile/UserSettings/UserNotificationSettings'; +import UserNotificationsPushbullet from '../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet'; + +const NotificationsPage: NextPage = () => { + return ( + + + + + + ); +}; + +export default NotificationsPage; diff --git a/src/pages/profile/settings/notifications/pushover.tsx b/src/pages/profile/settings/notifications/pushover.tsx new file mode 100644 index 000000000..83b8d71db --- /dev/null +++ b/src/pages/profile/settings/notifications/pushover.tsx @@ -0,0 +1,17 @@ +import { NextPage } from 'next'; +import React from 'react'; +import UserSettings from '../../../../components/UserProfile/UserSettings'; +import UserNotificationSettings from '../../../../components/UserProfile/UserSettings/UserNotificationSettings'; +import UserNotificationsPushover from '../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover'; + +const NotificationsPage: NextPage = () => { + return ( + + + + + + ); +}; + +export default NotificationsPage; diff --git a/src/pages/users/[userId]/settings/notifications/pushbullet.tsx b/src/pages/users/[userId]/settings/notifications/pushbullet.tsx new file mode 100644 index 000000000..cd7ca10c8 --- /dev/null +++ b/src/pages/users/[userId]/settings/notifications/pushbullet.tsx @@ -0,0 +1,20 @@ +import { NextPage } from 'next'; +import React from 'react'; +import UserSettings from '../../../../../components/UserProfile/UserSettings'; +import UserNotificationSettings from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings'; +import UserNotificationsPushbullet from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet'; +import useRouteGuard from '../../../../../hooks/useRouteGuard'; +import { Permission } from '../../../../../hooks/useUser'; + +const NotificationsPage: NextPage = () => { + useRouteGuard(Permission.MANAGE_USERS); + return ( + + + + + + ); +}; + +export default NotificationsPage; diff --git a/src/pages/users/[userId]/settings/notifications/pushover.tsx b/src/pages/users/[userId]/settings/notifications/pushover.tsx new file mode 100644 index 000000000..b37a866f7 --- /dev/null +++ b/src/pages/users/[userId]/settings/notifications/pushover.tsx @@ -0,0 +1,20 @@ +import { NextPage } from 'next'; +import React from 'react'; +import UserSettings from '../../../../../components/UserProfile/UserSettings'; +import UserNotificationSettings from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings'; +import UserNotificationsPushover from '../../../../../components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover'; +import useRouteGuard from '../../../../../hooks/useRouteGuard'; +import { Permission } from '../../../../../hooks/useUser'; + +const NotificationsPage: NextPage = () => { + useRouteGuard(Permission.MANAGE_USERS); + return ( + + + + + + ); +}; + +export default NotificationsPage; From 216447121b686b6d01a31b95ec0c8eb005f6b103 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Mon, 25 Oct 2021 11:07:00 -0400 Subject: [PATCH 087/238] fix(frontend): use consistent formatting & strings (#2231) * fix(frontend): use consistent formatting & strings * fix(lang): remove duplicated status strings * fix(frontend): reduce height of items in request & issue lists * fix(frontend): issue description textarea label should be a label element * refactor: remove unnecessary reduce * fix: remove small avatar underneath issue comments * fix(frontend): don't hide Pushover app token tip --- .../IssueDetails/IssueComment/index.tsx | 32 ++- src/components/IssueDetails/index.tsx | 242 +++++++++++------- src/components/IssueList/IssueItem/index.tsx | 60 +++-- src/components/IssueList/index.tsx | 4 +- .../IssueModal/CreateIssueModal/index.tsx | 6 +- src/components/ManageSlideOver/index.tsx | 11 +- src/components/RequestCard/index.tsx | 2 +- .../RequestList/RequestItem/index.tsx | 12 +- .../UserNotificationsPushover.tsx | 41 +-- src/i18n/locale/en.json | 30 +-- src/styles/globals.css | 2 +- 11 files changed, 264 insertions(+), 178 deletions(-) diff --git a/src/components/IssueDetails/IssueComment/index.tsx b/src/components/IssueDetails/IssueComment/index.tsx index 603616da3..1b82ff6cf 100644 --- a/src/components/IssueDetails/IssueComment/index.tsx +++ b/src/components/IssueDetails/IssueComment/index.tsx @@ -3,6 +3,7 @@ import { ExclamationIcon } from '@heroicons/react/outline'; import { DotsVerticalIcon } from '@heroicons/react/solid'; import axios from 'axios'; import { Field, Form, Formik } from 'formik'; +import Link from 'next/link'; import React, { useState } from 'react'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import ReactMarkdown from 'react-markdown'; @@ -14,11 +15,11 @@ import Modal from '../../Common/Modal'; import Transition from '../../Transition'; const messages = defineMessages({ - postedby: 'Posted by {username} {relativeTime}', - postedbyedited: 'Posted by {username} {relativeTime} (Edited)', + postedby: 'Posted {relativeTime} by {username}', + postedbyedited: 'Posted {relativeTime} by {username} (Edited)', delete: 'Delete Comment', areyousuredelete: 'Are you sure you want to delete this comment?', - validationComment: 'You must provide a message', + validationComment: 'You must enter a message', edit: 'Edit Comment', }); @@ -86,11 +87,15 @@ const IssueComment: React.FC = ({ {intl.formatMessage(messages.areyousuredelete)}
- + + + + +
{(belongsToUser || hasPermission(Permission.MANAGE_ISSUES)) && ( @@ -221,7 +226,7 @@ const IssueComment: React.FC = ({
@@ -232,14 +237,15 @@ const IssueComment: React.FC = ({ : messages.postedby, { username: ( - - {comment.user.displayName} - + + {comment.user.displayName} + + ), relativeTime: ( {username}', + openedby: '#{issueId} opened {relativeTime} by {username}', closeissue: 'Close Issue', closeissueandcomment: 'Close with Comment', leavecomment: 'Comment', @@ -43,18 +43,18 @@ const messages = defineMessages({ reopenissue: 'Reopen Issue', reopenissueandcomment: 'Reopen with Comment', issuepagetitle: 'Issue', + playonplex: 'Play on Plex', + play4konplex: 'Play 4K on Plex', openinarr: 'Open in {arr}', - toasteditdescriptionsuccess: 'Edited the issue description successfully!', + openin4karr: 'Open in 4K {arr}', + toasteditdescriptionsuccess: 'Issue description edited successfully!', toasteditdescriptionfailed: 'Something went wrong while editing the issue description.', - toaststatusupdated: 'Updated the issue status successfully!', + toaststatusupdated: 'Issue status updated successfully!', toaststatusupdatefailed: 'Something went wrong while updating the issue status.', - issuetype: 'Issue Type', - mediatype: 'Media Type', + issuetype: 'Type', lastupdated: 'Last Updated', - statusopen: 'Open', - statusresolved: 'Resolved', problemseason: 'Affected Season', allseasons: 'All Seasons', season: 'Season {seasonNumber}', @@ -63,7 +63,7 @@ const messages = defineMessages({ episode: 'Episode {episodeNumber}', deleteissue: 'Delete Issue', deleteissueconfirm: 'Are you sure you want to delete this issue?', - toastissuedeleted: 'Deleted the issue successfully!', + toastissuedeleted: 'Issue deleted successfully!', toastissuedeletefailed: 'Something went wrong while deleting the issue.', nocomments: 'No comments.', unknownissuetype: 'Unknown', @@ -96,8 +96,6 @@ const IssueDetails: React.FC = () => { (opt) => opt.issueType === issueData?.issueType ); - const mediaType = issueData?.media.mediaType; - if (!data && !error) { return ; } @@ -212,7 +210,7 @@ const IssueDetails: React.FC = () => { />
)} -
+
{
{issueData.status === IssueStatus.OPEN && ( - {intl.formatMessage(messages.statusopen)} + {intl.formatMessage(globalMessages.open)} )} {issueData.status === IssueStatus.RESOLVED && ( - {intl.formatMessage(messages.statusresolved)} + {intl.formatMessage(globalMessages.resolved)} )}
@@ -259,27 +257,26 @@ const IssueDetails: React.FC = () => { {intl.formatMessage(messages.openedby, { issueId: issueData.id, - username: issueData.createdBy.displayName, - UserLink: function UserLink(msg) { - return ( -
- - - - - - - - {msg} - - -
- ); - }, + username: ( + + + + + {issueData.createdBy.displayName} + + + + ), relativeTime: ( { />
-
- {intl.formatMessage(messages.mediatype)} - - {intl.formatMessage( - mediaType === MediaType.MOVIE - ? globalMessages.movie - : globalMessages.tvshow - )} - -
{intl.formatMessage(messages.issuetype)} @@ -366,20 +353,66 @@ const IssueDetails: React.FC = () => {
- {hasPermission(Permission.MANAGE_ISSUES) && ( -
- {issueData?.media.serviceUrl && ( +
+ {issueData?.media.plexUrl && ( + + )} + {issueData?.media.serviceUrl && hasPermission(Permission.ADMIN) && ( + + )} + {issueData?.media.plexUrl4k && ( + + )} + {issueData?.media.serviceUrl4k && + hasPermission(Permission.ADMIN) && ( )} -
- )} +
@@ -513,16 +545,6 @@ const IssueDetails: React.FC = () => { )}
-
- {intl.formatMessage(messages.mediatype)} - - {intl.formatMessage( - mediaType === MediaType.MOVIE - ? globalMessages.movie - : globalMessages.tvshow - )} - -
{issueData.media.mediaType === MediaType.TV && ( <>
@@ -565,30 +587,74 @@ const IssueDetails: React.FC = () => {
- {hasPermission(Permission.MANAGE_ISSUES) && ( -
- {issueData?.media.serviceUrl && ( - - )} -
- )} +
+ {issueData?.media.plexUrl && ( + + )} + {issueData?.media.serviceUrl && hasPermission(Permission.ADMIN) && ( + + )} + {issueData?.media.plexUrl4k && ( + + )} + {issueData?.media.serviceUrl4k && hasPermission(Permission.ADMIN) && ( + + )} +
diff --git a/src/components/IssueList/IssueItem/index.tsx b/src/components/IssueList/IssueItem/index.tsx index 25cb758ac..8a93c2e64 100644 --- a/src/components/IssueList/IssueItem/index.tsx +++ b/src/components/IssueList/IssueItem/index.tsx @@ -18,11 +18,9 @@ import { issueOptions } from '../../IssueModal/constants'; const messages = defineMessages({ openeduserdate: '{date} by {user}', - allseasons: 'All Seasons', - season: 'Season {seasonNumber}', + seasons: '{seasonCount, plural, one {Season} other {Seasons}}', + episodes: '{episodeCount, plural, one {Episode} other {Episodes}}', problemepisode: 'Affected Episode', - allepisodes: 'All Episodes', - episode: 'Episode {episodeNumber}', issuetype: 'Type', issuestatus: 'Status', opened: 'Opened', @@ -55,7 +53,7 @@ const IssueItem: React.FC = ({ issue }) => { if (!title && !error) { return (
); @@ -69,30 +67,48 @@ const IssueItem: React.FC = ({ issue }) => { (opt) => opt.issueType === issue?.issueType ); - const problemSeasonEpisodeLine = []; + const problemSeasonEpisodeLine: React.ReactNode[] = []; if (!isMovie(title) && issue) { problemSeasonEpisodeLine.push( - issue.problemSeason > 0 - ? intl.formatMessage(messages.season, { - seasonNumber: issue.problemSeason, - }) - : intl.formatMessage(messages.allseasons) + <> + + {intl.formatMessage(messages.seasons, { + seasonCount: issue.problemSeason ? 1 : 0, + })} + + + + {issue.problemSeason > 0 + ? issue.problemSeason + : intl.formatMessage(globalMessages.all)} + + + ); if (issue.problemSeason > 0) { problemSeasonEpisodeLine.push( - issue.problemEpisode > 0 - ? intl.formatMessage(messages.episode, { - episodeNumber: issue.problemEpisode, - }) - : intl.formatMessage(messages.allepisodes) + <> + + {intl.formatMessage(messages.episodes, { + episodeCount: issue.problemEpisode ? 1 : 0, + })} + + + + {issue.problemEpisode > 0 + ? issue.problemEpisode + : intl.formatMessage(globalMessages.all)} + + + ); } } return ( -
+
{title.backdropPath && (
= ({ issue }) => { : `/tv/${issue.media.tmdbId}` } > - + = ({ issue }) => { {problemSeasonEpisodeLine.length > 0 && ( -
- {problemSeasonEpisodeLine.join(' | ')} +
+ {problemSeasonEpisodeLine.map((t, k) => ( + {t} + ))}
)}
@@ -212,7 +230,7 @@ const IssueItem: React.FC = ({ issue }) => { alt="" className="ml-1.5 avatar-sm" /> - + {issue.createdBy.displayName} diff --git a/src/components/IssueList/index.tsx b/src/components/IssueList/index.tsx index 8a2559a13..cabf9ad95 100644 --- a/src/components/IssueList/index.tsx +++ b/src/components/IssueList/index.tsx @@ -94,7 +94,7 @@ const IssueList: React.FC = () => { <>
-
Issues
+
{intl.formatMessage(messages.issues)}
@@ -157,7 +157,7 @@ const IssueList: React.FC = () => {
{data.results.map((issue) => { return ( -
+
); diff --git a/src/components/IssueModal/CreateIssueModal/index.tsx b/src/components/IssueModal/CreateIssueModal/index.tsx index 187fe0e54..2dd4ea8d8 100644 --- a/src/components/IssueModal/CreateIssueModal/index.tsx +++ b/src/components/IssueModal/CreateIssueModal/index.tsx @@ -278,10 +278,10 @@ const CreateIssueModal: React.FC = ({
- - {intl.formatMessage(messages.whatswrong)}{' '} + + - )} - + {isActiveUser && ( + + {({ active }) => ( + + )} + + )} {({ active }) => ( - )} - - - - {({ active }) => ( - - )} - + {belongsToUser && ( + + {({ active }) => ( + + )} + + )} + {(hasPermission(Permission.MANAGE_ISSUES) || + !commentCount) && ( + + {({ active }) => ( + + )} + + )}
diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index 57f6838f3..b0c065150 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -260,7 +260,7 @@ const IssueDetails: React.FC = () => { username: ( {
{ editFirstComment(newMessage); }} From 78a8091bcd29a7cf50cc7c493c28710389817adf Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sun, 31 Oct 2021 11:32:57 -0400 Subject: [PATCH 094/238] fix(frontend): setup page backdrops (#2251) --- src/components/Setup/index.tsx | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/components/Setup/index.tsx b/src/components/Setup/index.tsx index 9d6642299..c4a754dc4 100644 --- a/src/components/Setup/index.tsx +++ b/src/components/Setup/index.tsx @@ -2,7 +2,7 @@ import axios from 'axios'; import { useRouter } from 'next/router'; import React, { useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { mutate } from 'swr'; +import useSWR, { mutate } from 'swr'; import useLocale from '../../hooks/useLocale'; import AppDataWarning from '../AppDataWarning'; import Badge from '../Common/Badge'; @@ -51,18 +51,21 @@ const Setup: React.FC = () => { } }; + const { data: backdrops } = useSWR('/api/v1/backdrops', { + refreshInterval: 0, + refreshWhenHidden: false, + revalidateOnFocus: false, + }); + return (
`https://www.themoviedb.org/t/p/original${backdrop}` + ) ?? [] + } />
From 8c49309c35c31f7bcd0b84b0a307febc16842f68 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sun, 31 Oct 2021 16:39:08 +0100 Subject: [PATCH 095/238] feat(lang): translations update from Weblate (#2247) * feat(lang): translated using Weblate (German) Currently translated at 91.5% (904 of 987 strings) Co-authored-by: doob187 Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/de/ Translation: Overseerr/Overseerr Frontend * feat(lang): translated using Weblate (Swedish) Currently translated at 100.0% (987 of 987 strings) Co-authored-by: Hosted Weblate Co-authored-by: Shjosan Translate-URL: https://hosted.weblate.org/projects/overseerr/overseerr-frontend/sv/ Translation: Overseerr/Overseerr Frontend Co-authored-by: doob187 Co-authored-by: Shjosan --- src/i18n/locale/de.json | 60 +++++++++++++++++++++++++- src/i18n/locale/sv.json | 94 +++++++++++++++++++++++++++++++++++------ 2 files changed, 139 insertions(+), 15 deletions(-) diff --git a/src/i18n/locale/de.json b/src/i18n/locale/de.json index 056a2b0aa..e17e73c9a 100644 --- a/src/i18n/locale/de.json +++ b/src/i18n/locale/de.json @@ -862,5 +862,63 @@ "components.MovieDetails.showmore": "Mehr Anzeigen", "components.MovieDetails.streamingproviders": "Streamt derzeit auf", "components.TvDetails.streamingproviders": "Streamt derzeit auf", - "components.StatusBadge.status": "{status}" + "components.StatusBadge.status": "{status}", + "components.IssueDetails.reopenissueandcomment": "Mit Kommentar wieder öffnen", + "components.IssueModal.CreateIssueModal.allseasons": "Alle Staffeln", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Gibt es ein Problem mit {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Betroffene Episode", + "components.IssueModal.CreateIssueModal.problemseason": "Betroffene Staffel", + "components.IssueModal.CreateIssueModal.providedetail": "Geben Sie eine detaillierte Erklärung des Problems an.", + "components.IssueModal.CreateIssueModal.reportissue": "Ein Problem melden", + "components.IssueDetails.IssueComment.areyousuredelete": "Möchten Sie diesen Kommentar wirklich löschen?", + "components.IssueDetails.IssueComment.delete": "Kommentar löschen", + "components.IssueDetails.IssueComment.edit": "Kommentar bearbeiten", + "components.IssueDetails.IssueComment.postedby": "Gepostet {relativeTime} von {username}", + "components.IssueDetails.IssueComment.postedbyedited": "Gepostet {relativeTime} von {username} (Bearbeitet)", + "components.IssueDetails.IssueComment.validationComment": "Sie müssen eine Nachricht eingeben", + "components.IssueDetails.IssueDescription.deleteissue": "Problem löschen", + "components.IssueDetails.toasteditdescriptionsuccess": "Problembeschreibung erfolgreich bearbeitet!", + "components.IssueDetails.toastissuedeleted": "Problem erfolgreich gelöscht!", + "components.IssueDetails.toasteditdescriptionfailed": "Beim Bearbeiten der Problembeschreibung ist ein Fehler aufgetreten.", + "components.IssueDetails.IssueDescription.description": "Beschreibung", + "components.IssueDetails.IssueDescription.edit": "Beschreibung bearbeiten", + "components.IssueDetails.allepisodes": "Alle Folgen", + "components.IssueDetails.allseasons": "Alle Staffeln", + "components.IssueDetails.closeissue": "Problem schließen", + "components.IssueDetails.closeissueandcomment": "Schließen mit Kommentar", + "components.IssueDetails.comments": "Kommentare", + "components.IssueDetails.deleteissue": "Problem löschen", + "components.IssueDetails.deleteissueconfirm": "Möchten Sie dieses Problem wirklich löschen?", + "components.IssueDetails.episode": "Folge {episodeNumber}", + "components.IssueDetails.issuepagetitle": "Problem", + "components.IssueDetails.issuetype": "Typ", + "components.IssueDetails.lastupdated": "Letzte Aktualisierung", + "components.IssueDetails.leavecomment": "Kommentar", + "components.IssueDetails.openinarr": "In {arr} öffnen", + "components.IssueDetails.toastissuedeletefailed": "Beim Löschen des Problems ist ein Fehler aufgetreten.", + "components.IssueDetails.toaststatusupdatefailed": "Beim Aktualisieren des Problemstatus ist ein Fehler aufgetreten.", + "components.IssueDetails.unknownissuetype": "Unbekannt", + "components.IssueList.IssueItem.issuetype": "Typ", + "components.IssueList.IssueItem.openeduserdate": "{date} von {user}", + "components.IssueList.IssueItem.problemepisode": "Betroffene Episode", + "components.IssueList.IssueItem.unknownissuetype": "Unbekannt", + "components.IssueList.showallissues": "Alle Probleme anzeigen", + "components.IssueList.sortAdded": "Anforderungsdatum", + "components.IssueList.sortModified": "Zuletzt geändert", + "components.IssueDetails.nocomments": "Keine Kommentare.", + "components.IssueDetails.openedby": "#{issueId} geöffnet {relativeTime} von {username}", + "components.IssueDetails.openin4karr": "In {arr} 4K öffnen", + "components.IssueDetails.play4konplex": "Auf Plex in 4K abspielen", + "components.IssueDetails.playonplex": "Auf Plex abspielen", + "components.IssueDetails.problemepisode": "Betroffene Episode", + "components.IssueDetails.problemseason": "Betroffene Staffeln", + "components.IssueDetails.reopenissue": "Problem erneut öffnen", + "components.IssueDetails.season": "Staffel {seasonNumber}", + "components.IssueDetails.toaststatusupdated": "Ausgabestatus erfolgreich aktualisiert!", + "components.IssueList.IssueItem.issuestatus": "Status", + "components.IssueList.IssueItem.opened": "Geöffnet", + "components.IssueList.IssueItem.viewissue": "Problem anzeigen", + "components.IssueModal.CreateIssueModal.allepisodes": "Alle Folgen", + "components.IssueModal.CreateIssueModal.season": "Staffel {seasonNumber}", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Beim Senden des Problems ist ein Fehler aufgetreten." } diff --git a/src/i18n/locale/sv.json b/src/i18n/locale/sv.json index 80331ee93..c6afd4a95 100644 --- a/src/i18n/locale/sv.json +++ b/src/i18n/locale/sv.json @@ -305,19 +305,19 @@ "components.Settings.csrfProtection": "Aktivera CSRF-skydd", "components.UserList.userssaved": "Användarbehörigheter sparade!", "components.UserList.bulkedit": "Mass-redigering", - "components.PermissionEdit.usersDescription": "Bevilja behörighet att hantera Overseerr-användare. Användare med denna behörighet kan inte ändra användare eller bevilja administratörsbehörighet.", + "components.PermissionEdit.usersDescription": "Bevilja behörighet att hantera användare. Användare med denna behörighet kan inte ändra användare med eller bevilja administratörsbehörighet.", "components.PermissionEdit.users": "Hantera Användare", - "components.PermissionEdit.settingsDescription": "Bevilja behörighet att modifiera Overseerr-inställningar. En användare måste ha denna behörighet för att kunna ge den till andra.", + "components.PermissionEdit.settingsDescription": "Ge tillstånd att ändra globala inställningar. En användare måste ha denna behörighet för att ge den till andra.", "components.PermissionEdit.settings": "Hantera Inställningar", - "components.PermissionEdit.requestDescription": "Bevilja behörighet att begära media som inte är 4K.", - "components.PermissionEdit.request4kTvDescription": "Bevilja behörighet att begära 4K serier.", + "components.PermissionEdit.requestDescription": "Ge tillstånd att skicka förfrågningar för icke-4K-media.", + "components.PermissionEdit.request4kTvDescription": "Bevilja tillstånd att skicka förfrågningar för 4K-serien.", "components.PermissionEdit.request4kTv": "Begära 4K Serier", - "components.PermissionEdit.request4kMoviesDescription": "Bevilja behörighet att begära 4K filmer.", + "components.PermissionEdit.request4kMoviesDescription": "Ge tillstånd att skicka in förfrågningar om 4K-filmer.", "components.PermissionEdit.request4kMovies": "Begära 4K Filmer", - "components.PermissionEdit.request4kDescription": "Bevilja behörighet att begära 4K media.", + "components.PermissionEdit.request4kDescription": "Bevilja behörighet att skicka förfrågningar om 4K-media.", "components.PermissionEdit.request4k": "Begära 4K", "components.PermissionEdit.request": "Begära", - "components.PermissionEdit.managerequestsDescription": "Bevilja behörighet att hantera Overseerr-förfrågningar. Alla förfrågningar som görs av en användare med den här behörigheten kommer att godkännas.", + "components.PermissionEdit.managerequestsDescription": "Bevilja behörighet att hantera medieförfrågningar. Alla förfrågningar som görs av en användare med den här behörigheten kommer att godkännas.", "components.PermissionEdit.managerequests": "Hantera Förfrågningar", "components.PermissionEdit.adminDescription": "Fullständig administratörsbehörighet. Överskrider alla andra behörighetskontroller.", "components.PlexLoginButton.signinwithplex": "Logga in", @@ -360,9 +360,9 @@ "components.PermissionEdit.autoapproveSeries": "Auto-Godkänn Serier", "components.PermissionEdit.autoapproveMoviesDescription": "Bevilja automatiskt godkännande för icke-4K-filmförfrågningar.", "components.PermissionEdit.autoapproveMovies": "Auto-Godkänn Filmer", - "components.PermissionEdit.autoapproveDescription": "Bevilja automatiskt godkännande för alla icke-4K-förfrågningar.", + "components.PermissionEdit.autoapproveDescription": "Bevilja automatiskt godkännande för alla icke-4K-medieförfrågningar.", "components.PermissionEdit.autoapprove": "Auto-Godkänn", - "components.PermissionEdit.advancedrequestDescription": "Ge behörighet att använda avancerade inställningar vid en begäran.", + "components.PermissionEdit.advancedrequestDescription": "Ge behörighet att redigera avancerade inställningar vid en begäran.", "components.PermissionEdit.advancedrequest": "Avancerade Förfrågningar", "components.PermissionEdit.admin": "Admin", "components.NotificationTypeSelector.mediadeclinedDescription": "Skicka meddelanden när medieförfrågningar avvisas.", @@ -409,7 +409,7 @@ "components.Settings.SonarrModal.validationApplicationUrlTrailingSlash": "URL:n får inte avslutas med ett slash", "components.Settings.SonarrModal.validationApplicationUrl": "Du måste ange en giltig URL", "components.Settings.RadarrModal.validationApplicationUrlTrailingSlash": "URL:n får inte avslutas med ett slash", - "components.PermissionEdit.viewrequestsDescription": "Bevilja behörighet att visa andra användares förfrågningar.", + "components.PermissionEdit.viewrequestsDescription": "Ge tillstånd att se medieförfrågningar som skickats av andra användare.", "components.PermissionEdit.viewrequests": "Visa Förfrågningar", "components.RequestModal.AdvancedRequester.requestas": "Begär Som", "components.Setup.setup": "Installationsguide", @@ -574,7 +574,7 @@ "components.PermissionEdit.autoapprove4kSeries": "Godkänn automatiskt 4K-serier", "components.PermissionEdit.autoapprove4kMoviesDescription": "Bevilja automatiskt godkännande för 4K-filmförfrågningar.", "components.PermissionEdit.autoapprove4kMovies": "Godkänn automatiskt 4K-filmer", - "components.PermissionEdit.autoapprove4kDescription": "Bevilja automatiskt godkännande för alla 4K-förfrågningar.", + "components.PermissionEdit.autoapprove4kDescription": "Bevilja automatiskt godkännande för alla 4K-medieförfrågningar.", "components.PermissionEdit.autoapprove4k": "Automatiskt godkännande av 4K", "components.NotificationTypeSelector.mediaAutoApprovedDescription": "Skicka meddelanden när användare skickar in nya medieförfrågningar som godkänns automatiskt.", "components.NotificationTypeSelector.mediaAutoApproved": "Media Automatiskt Godkänd", @@ -808,9 +808,9 @@ "components.Settings.Notifications.NotificationsPushbullet.toastPushbulletTestSuccess": "Pushbullet testmeddelande skickat!", "components.Settings.SettingsUsers.newPlexLoginTip": "Tillåt Plex-användare att logga in utan att först importeras", "components.Settings.SettingsUsers.newPlexLogin": "Aktivera ny Plex-inloggning", - "components.PermissionEdit.requestTvDescription": "Bevilja tillstånd att begära serier som inte är 4K.", + "components.PermissionEdit.requestTvDescription": "Ge tillstånd att skicka förfrågningar för icke-4K-serier.", "components.PermissionEdit.requestTv": "Begär serie", - "components.PermissionEdit.requestMoviesDescription": "Ger tillstånd att begära filmer som inte är 4K.", + "components.PermissionEdit.requestMoviesDescription": "Ge tillåtelse att skicka in förfrågningar om icke-4K-filmer.", "components.PermissionEdit.requestMovies": "Begär filmer", "components.UserProfile.UserSettings.UserGeneralSettings.languageDefault": "Standard ({language})", "components.Settings.locale": "Visningsspråk", @@ -919,5 +919,71 @@ "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Avsnitt} other {Avsnitt}}", "components.IssueList.IssueItem.issuestatus": "Status", "components.IssueList.IssueItem.issuetype": "Typ", - "components.IssueList.IssueItem.problemepisode": "Påverkat avsnitt" + "components.IssueList.IssueItem.problemepisode": "Påverkat avsnitt", + "components.IssueModal.CreateIssueModal.toastSuccessCreate": "Problemrapport för {title} har skickats in!", + "components.ManageSlideOver.tvshow": "serie", + "components.ManageSlideOver.openarr4k": "Öppna i 4K {arr}", + "components.IssueModal.CreateIssueModal.reportissue": "Rapportera ett problem", + "components.IssueModal.CreateIssueModal.season": "Säsong {seasonNumber}", + "components.IssueModal.issueOther": "Övrigt", + "components.PermissionEdit.manageissuesDescription": "Bevilja behörighet att hantera medieproblem.", + "components.IssueModal.CreateIssueModal.toastFailedCreate": "Något gick fel när problemet skickades in.", + "components.IssueModal.CreateIssueModal.submitissue": "Skicka in problemet", + "components.ManageSlideOver.movie": "film", + "components.IssueModal.CreateIssueModal.whatswrong": "Vad är fel?", + "components.IssueModal.issueAudio": "Ljud", + "components.Layout.Sidebar.issues": "Problem", + "components.IssueModal.CreateIssueModal.validationMessageRequired": "Du måste ange en beskrivning", + "components.IssueModal.issueSubtitles": "Undertexter", + "components.IssueModal.issueVideo": "Video", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "Användar- eller gruppnyckel", + "components.ManageSlideOver.openarr": "Öppna i {arr}", + "components.ManageSlideOver.manageModalTitle": "Hantera {mediaType}", + "components.ManageSlideOver.mark4kavailable": "Markera som tillgängligt i 4K", + "components.NotificationTypeSelector.adminissuecommentDescription": "Få aviseringar när problem får nya kommentarer.", + "components.ManageSlideOver.manageModalRequests": "Förfrågningar", + "components.PermissionEdit.manageissues": "Hantera problem", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Inställningarna för pushover-meddelanden kunde inte sparas.", + "components.ManageSlideOver.markavailable": "Markera som tillgänglig", + "components.NotificationTypeSelector.issuecomment": "Problemkommentar", + "components.NotificationTypeSelector.issuecommentDescription": "Skicka aviseringar när problem får nya kommentarer.", + "components.PermissionEdit.createissuesDescription": "Bevilja behörighet att rapportera medieproblem.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Din 30-teckens användar- eller gruppidentifierare", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "Registrera en applikation för användning med {applicationTitle}", + "components.PermissionEdit.viewissuesDescription": "Ge tillstånd att se medieproblem som rapporterats av andra användare.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Api-token för program", + "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingssaved": "Inställningar för pushover-meddelanden har sparats!", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverApplicationToken": "Du måste tillhandahålla en giltig applikationstoken", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushoverUserKey": "Du måste ange en giltig användar- eller gruppnyckel", + "components.IssueModal.CreateIssueModal.problemseason": "Påverkad säsong", + "components.IssueModal.CreateIssueModal.toastviewissue": "Visa problem", + "components.NotificationTypeSelector.issuecreated": "Problem rappoterat", + "components.PermissionEdit.createissues": "Rapportera problem", + "components.PermissionEdit.viewissues": "Visa problem", + "components.ManageSlideOver.manageModalClearMediaWarning": "* Detta tar bort all data för denna {mediaType}, inklusive eventuella begäranden, på ett oåterkalleligt sätt. Om det här objektet finns i ditt Plex-bibliotek kommer medieinformationen att återskapas vid nästa genomsökning.", + "components.ManageSlideOver.manageModalNoRequests": "Inga förfrågningar.", + "components.NotificationTypeSelector.userissueresolvedDescription": "Få meddelande när dina problem blir lösta.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessToken": "Åtkomsttoken", + "components.UserProfile.UserSettings.UserNotificationSettings.validationPushbulletAccessToken": "Du måste tillhandahålla en åtkomsttoken", + "components.IssueList.sortAdded": "Datum för begäran", + "components.IssueList.sortModified": "Senast ändrad", + "components.IssueModal.CreateIssueModal.allepisodes": "Alla avsnitt", + "components.IssueModal.CreateIssueModal.allseasons": "Alla säsonger", + "components.IssueModal.CreateIssueModal.episode": "Avsnitt {episodeNumber}", + "components.IssueModal.CreateIssueModal.issomethingwrong": "Är det något problem med {title}?", + "components.IssueModal.CreateIssueModal.problemepisode": "Påverkat avsnitt", + "components.IssueModal.CreateIssueModal.providedetail": "Ge en detaljerad förklaring av problemet.", + "components.ManageSlideOver.allseasonsmarkedavailable": "* Alla säsonger kommer bli markerade som tillgängliga.", + "components.ManageSlideOver.downloadstatus": "Nerladdningsstatus", + "components.ManageSlideOver.manageModalClearMedia": "Rensa mediadata", + "components.NotificationTypeSelector.issuecreatedDescription": "Skicka aviseringar när problem rapporteras.", + "components.NotificationTypeSelector.issueresolved": "Problem löst", + "components.NotificationTypeSelector.issueresolvedDescription": "Skicka aviseringar när problem blir lösta.", + "components.NotificationTypeSelector.userissuecommentDescription": "Få meddelanden när dina problem får nya kommentarer.", + "components.NotificationTypeSelector.userissuecreatedDescription": "Få meddelanden när andra användare rapporterar problem.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletAccessTokenTip": "Skapa en token från dina kontoinställningar", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Inställningar för Pushbullet-aviseringar kunde inte sparas.", + "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Inställningar för Pushbullet-aviseringar har sparats!", + "i18n.open": "Öppna", + "i18n.resolved": "Löst" } From 3ec4a9c76e1f31bee5c8801b389721bf8e5884e0 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Sun, 31 Oct 2021 11:45:15 -0400 Subject: [PATCH 096/238] fix(frontend): more issues-related fixes (#2234) * fix(frontend): more issues-related fixes * fix: permission VIEW_ISSUES is also sufficient for viewing issues in slideover * fix(frontend): only display issue notif types user is eligible to receive * fix: don't display issues block in slideover if no open issues * fix: move year out of link in issue details header * fix: use 'view' global string for issue block button * fix: issue/request/user list sort options --- src/components/Common/Badge/index.tsx | 2 +- src/components/IssueBlock/index.tsx | 3 +- src/components/IssueDetails/index.tsx | 18 +++---- src/components/IssueList/IssueItem/index.tsx | 2 +- src/components/IssueList/index.tsx | 2 +- .../IssueModal/CreateIssueModal/index.tsx | 48 ++++++++++++++----- src/components/ManageSlideOver/index.tsx | 33 ++++++++----- src/components/MovieDetails/index.tsx | 39 ++++++++++----- .../NotificationTypeSelector/index.tsx | 10 ++++ src/components/RequestList/index.tsx | 2 +- src/components/TvDetails/index.tsx | 39 +++++++++------ src/components/UserList/index.tsx | 19 ++------ src/i18n/locale/en.json | 16 +++---- 13 files changed, 143 insertions(+), 90 deletions(-) diff --git a/src/components/Common/Badge/index.tsx b/src/components/Common/Badge/index.tsx index 3868e8a40..bc450d7f4 100644 --- a/src/components/Common/Badge/index.tsx +++ b/src/components/Common/Badge/index.tsx @@ -13,7 +13,7 @@ const Badge: React.FC = ({ children, }) => { const badgeStyle = [ - 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full', + 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap', ]; if (url) { diff --git a/src/components/IssueBlock/index.tsx b/src/components/IssueBlock/index.tsx index 7e8067c47..2d3cfb33e 100644 --- a/src/components/IssueBlock/index.tsx +++ b/src/components/IssueBlock/index.tsx @@ -8,6 +8,7 @@ import Link from 'next/link'; import React from 'react'; import { useIntl } from 'react-intl'; import type Issue from '../../../server/entity/Issue'; +import globalMessages from '../../i18n/globalMessages'; import Button from '../Common/Button'; import { issueOptions } from '../IssueModal/constants'; @@ -56,7 +57,7 @@ const IssueBlock: React.FC = ({ issue }) => {
diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index b0c065150..ebdbabd86 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -44,7 +44,7 @@ const messages = defineMessages({ reopenissueandcomment: 'Reopen with Comment', issuepagetitle: 'Issue', playonplex: 'Play on Plex', - play4konplex: 'Play 4K on Plex', + play4konplex: 'Play in 4K on Plex', openinarr: 'Open in {arr}', openin4karr: 'Open in 4K {arr}', toasteditdescriptionsuccess: 'Issue description edited successfully!', @@ -228,7 +228,7 @@ const IssueDetails: React.FC = () => {
{issueData.status === IssueStatus.OPEN && ( - + {intl.formatMessage(globalMessages.open)} )} @@ -244,15 +244,11 @@ const IssueDetails: React.FC = () => { issueData.media.mediaType === MediaType.MOVIE ? 'movie' : 'tv' }/${data.id}`} > - - {title}{' '} - {releaseYear && ( - - ({releaseYear.slice(0, 4)}) - - )} - - + {title} + {' '} + {releaseYear && ( + ({releaseYear.slice(0, 4)}) + )} {intl.formatMessage(messages.openedby, { diff --git a/src/components/IssueList/IssueItem/index.tsx b/src/components/IssueList/IssueItem/index.tsx index 8a93c2e64..267941fc2 100644 --- a/src/components/IssueList/IssueItem/index.tsx +++ b/src/components/IssueList/IssueItem/index.tsx @@ -183,7 +183,7 @@ const IssueItem: React.FC = ({ issue }) => { {intl.formatMessage(messages.issuestatus)} {issue.status === IssueStatus.OPEN ? ( - + {intl.formatMessage(globalMessages.open)} ) : ( diff --git a/src/components/IssueList/index.tsx b/src/components/IssueList/index.tsx index cabf9ad95..a78a762ca 100644 --- a/src/components/IssueList/index.tsx +++ b/src/components/IssueList/index.tsx @@ -19,7 +19,7 @@ import IssueItem from './IssueItem'; const messages = defineMessages({ issues: 'Issues', - sortAdded: 'Request Date', + sortAdded: 'Most Recent', sortModified: 'Last Modified', showallissues: 'Show All Issues', }); diff --git a/src/components/IssueModal/CreateIssueModal/index.tsx b/src/components/IssueModal/CreateIssueModal/index.tsx index 2dd4ea8d8..c2d75e243 100644 --- a/src/components/IssueModal/CreateIssueModal/index.tsx +++ b/src/components/IssueModal/CreateIssueModal/index.tsx @@ -9,9 +9,12 @@ import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; import * as Yup from 'yup'; +import { MediaStatus } from '../../../../server/constants/media'; import type Issue from '../../../../server/entity/Issue'; import { MovieDetails } from '../../../../server/models/Movie'; import { TvDetails } from '../../../../server/models/Tv'; +import useSettings from '../../../hooks/useSettings'; +import { Permission, useUser } from '../../../hooks/useUser'; import globalMessages from '../../../i18n/globalMessages'; import Button from '../../Common/Button'; import Modal from '../../Common/Modal'; @@ -21,7 +24,9 @@ const messages = defineMessages({ validationMessageRequired: 'You must provide a description', issomethingwrong: 'Is there a problem with {title}?', whatswrong: "What's wrong?", - providedetail: 'Provide a detailed explanation of the issue.', + providedetail: + 'Please provide a detailed explanation of the issue you encountered.', + extras: 'Extras', season: 'Season {seasonNumber}', episode: 'Episode {episodeNumber}', allseasons: 'All Seasons', @@ -56,6 +61,8 @@ const CreateIssueModal: React.FC = ({ tmdbId, }) => { const intl = useIntl(); + const settings = useSettings(); + const { hasPermission } = useUser(); const { addToast } = useToasts(); const { data, error } = useSWR( tmdbId ? `/api/v1/${mediaType}/${tmdbId}` : null @@ -65,6 +72,20 @@ const CreateIssueModal: React.FC = ({ return null; } + const availableSeasons = (data?.mediaInfo?.seasons ?? []) + .filter( + (season) => + season.status === MediaStatus.AVAILABLE || + season.status === MediaStatus.PARTIALLY_AVAILABLE || + (settings.currentSettings.series4kEnabled && + hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { + type: 'or', + }) && + (season.status4k === MediaStatus.AVAILABLE || + season.status4k === MediaStatus.PARTIALLY_AVAILABLE)) + ) + .map((season) => season.seasonNumber); + const CreateIssueModalSchema = Yup.object().shape({ message: Yup.string().required( intl.formatMessage(messages.validationMessageRequired) @@ -76,7 +97,7 @@ const CreateIssueModal: React.FC = ({ initialValues={{ selectedIssue: issueOptions[0], message: '', - problemSeason: 0, + problemSeason: availableSeasons.length === 1 ? availableSeasons[0] : 0, problemEpisode: 0, }} validationSchema={CreateIssueModalSchema} @@ -162,18 +183,23 @@ const CreateIssueModal: React.FC = ({ as="select" id="problemSeason" name="problemSeason" + disabled={availableSeasons.length === 1} > - - {data.seasons.map((season) => ( + {availableSeasons.length > 1 && ( + + )} + {availableSeasons.map((season) => ( ))} diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index 5f865766b..9b8898ef3 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -19,6 +19,7 @@ import RequestBlock from '../RequestBlock'; const messages = defineMessages({ manageModalTitle: 'Manage {mediaType}', + manageModalIssues: 'Open Issues', manageModalRequests: 'Requests', manageModalNoRequests: 'No requests.', manageModalClearMedia: 'Clear Media Data', @@ -77,6 +78,11 @@ const ManageSlideOver: React.FC< revalidate(); }; + const openIssues = + data.mediaInfo?.issues?.filter( + (issue) => issue.status === IssueStatus.OPEN + ) ?? []; + return ( )} - {(data.mediaInfo?.issues ?? []).length > 0 && ( - <> -

Open Issues

-
-
    - {data.mediaInfo?.issues - ?.filter((issue) => issue.status === IssueStatus.OPEN) - .map((issue) => ( + {hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], { + type: 'or', + }) && + openIssues.length > 0 && ( + <> +

    + {intl.formatMessage(messages.manageModalIssues)} +

    +
    +
      + {openIssues.map((issue) => (
    • ))} -
    -
    - - )} +
+
+ + )}

{intl.formatMessage(messages.manageModalRequests)}

diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index d7b0bbbbd..ee0ee8721 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -330,7 +330,14 @@ const MovieDetails: React.FC = ({ movie }) => { onUpdate={() => revalidate()} /> {(data.mediaInfo?.status === MediaStatus.AVAILABLE || - data.mediaInfo?.status4k === MediaStatus.AVAILABLE) && + (settings.currentSettings.movie4kEnabled && + hasPermission( + [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], + { + type: 'or', + } + ) && + data.mediaInfo?.status4k === MediaStatus.AVAILABLE)) && hasPermission( [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], { @@ -338,7 +345,7 @@ const MovieDetails: React.FC = ({ movie }) => { } ) && ( )}
diff --git a/src/components/NotificationTypeSelector/index.tsx b/src/components/NotificationTypeSelector/index.tsx index 5fd774c4e..bef9ed31b 100644 --- a/src/components/NotificationTypeSelector/index.tsx +++ b/src/components/NotificationTypeSelector/index.tsx @@ -274,6 +274,11 @@ const NotificationTypeSelector: React.FC = ({ : messages.issuecommentDescription ), value: Notification.ISSUE_COMMENT, + hidden: + user && + !hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), hasNotifyUser: !user || hasPermission(Permission.MANAGE_ISSUES) ? false : true, }, @@ -286,6 +291,11 @@ const NotificationTypeSelector: React.FC = ({ : messages.issueresolvedDescription ), value: Notification.ISSUE_RESOLVED, + hidden: + user && + !hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), hasNotifyUser: true, }, ]; diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index c74382282..7c1aa1545 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -22,7 +22,7 @@ import RequestItem from './RequestItem'; const messages = defineMessages({ requests: 'Requests', showallrequests: 'Show All Requests', - sortAdded: 'Request Date', + sortAdded: 'Most Recent', sortModified: 'Last Modified', }); diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 8ff39fb68..7365ba9a1 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -331,9 +331,14 @@ const TvDetails: React.FC = ({ tv }) => { is4kShowComplete={is4kComplete} /> {(data.mediaInfo?.status === MediaStatus.AVAILABLE || - data.mediaInfo?.status4k === MediaStatus.AVAILABLE || data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE || - data?.mediaInfo?.status4k === MediaStatus.PARTIALLY_AVAILABLE) && + (settings.currentSettings.series4kEnabled && + hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { + type: 'or', + }) && + (data.mediaInfo?.status4k === MediaStatus.AVAILABLE || + data?.mediaInfo?.status4k === + MediaStatus.PARTIALLY_AVAILABLE))) && hasPermission( [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], { @@ -341,7 +346,7 @@ const TvDetails: React.FC = ({ tv }) => { } ) && ( )}
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index 8b7fac7a9..a544c8f99 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -46,8 +46,7 @@ const messages = defineMessages({ totalrequests: 'Requests', accounttype: 'Type', role: 'Role', - created: 'Created', - lastupdated: 'Updated', + created: 'Joined', bulkedit: 'Bulk Edit', owner: 'Owner', admin: 'Admin', @@ -75,8 +74,7 @@ const messages = defineMessages({ autogeneratepassword: 'Automatically Generate Password', autogeneratepasswordTip: 'Email a server-generated password to the user', validationEmail: 'You must provide a valid email address', - sortCreated: 'Creation Date', - sortUpdated: 'Last Updated', + sortCreated: 'Join Date', sortDisplayName: 'Display Name', sortRequests: 'Request Count', localLoginDisabled: @@ -91,7 +89,7 @@ const UserList: React.FC = () => { const settings = useSettings(); const { addToast } = useToasts(); const { user: currentUser, hasPermission: currentHasPermission } = useUser(); - const [currentSort, setCurrentSort] = useState('created'); + const [currentSort, setCurrentSort] = useState('displayname'); const [currentPageSize, setCurrentPageSize] = useState(10); const page = router.query.page ? Number(router.query.page) : 1; @@ -522,9 +520,6 @@ const UserList: React.FC = () => { - @@ -556,7 +551,6 @@ const UserList: React.FC = () => { {intl.formatMessage(messages.accounttype)} {intl.formatMessage(messages.role)} {intl.formatMessage(messages.created)} - {intl.formatMessage(messages.lastupdated)} {(data.results ?? []).length > 1 && ( + + + + +
+
+ + ); + }} + + ); +}; + +export default NotificationsGotify; diff --git a/src/components/Settings/SettingsNotifications.tsx b/src/components/Settings/SettingsNotifications.tsx index 329ec679f..fda48aeae 100644 --- a/src/components/Settings/SettingsNotifications.tsx +++ b/src/components/Settings/SettingsNotifications.tsx @@ -2,6 +2,7 @@ import { CloudIcon, LightningBoltIcon, MailIcon } from '@heroicons/react/solid'; import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; import DiscordLogo from '../../assets/extlogos/discord.svg'; +import GotifyLogo from '../../assets/extlogos/gotify.svg'; import LunaSeaLogo from '../../assets/extlogos/lunasea.svg'; import PushbulletLogo from '../../assets/extlogos/pushbullet.svg'; import PushoverLogo from '../../assets/extlogos/pushover.svg'; @@ -58,6 +59,17 @@ const SettingsNotifications: React.FC = ({ children }) => { route: '/settings/notifications/discord', regex: /^\/settings\/notifications\/discord/, }, + { + text: 'Gotify', + content: ( + + + Gotify + + ), + route: '/settings/notifications/gotify', + regex: /^\/settings\/notifications\/gotify/, + }, { text: 'LunaSea', content: ( diff --git a/src/i18n/locale/en.json b/src/i18n/locale/en.json index 47f5f1c84..66f858953 100644 --- a/src/i18n/locale/en.json +++ b/src/i18n/locale/en.json @@ -365,6 +365,18 @@ "components.ResetPassword.validationpasswordrequired": "You must provide a password", "components.Search.search": "Search", "components.Search.searchresults": "Search Results", + "components.Settings.Notifications.NotificationsGotify.agentenabled": "Enable Agent", + "components.Settings.Notifications.NotificationsGotify.gotifysettingsfailed": "Gotify notification settings failed to save.", + "components.Settings.Notifications.NotificationsGotify.gotifysettingssaved": "Gotify notification settings saved successfully!", + "components.Settings.Notifications.NotificationsGotify.toastGotifyTestFailed": "Gotify test notification failed to send.", + "components.Settings.Notifications.NotificationsGotify.toastGotifyTestSending": "Sending Gotify test notification…", + "components.Settings.Notifications.NotificationsGotify.toastGotifyTestSuccess": "Gotify test notification sent!", + "components.Settings.Notifications.NotificationsGotify.token": "Application Token", + "components.Settings.Notifications.NotificationsGotify.url": "Server URL", + "components.Settings.Notifications.NotificationsGotify.validationTokenRequired": "You must provide a valid application token", + "components.Settings.Notifications.NotificationsGotify.validationTypes": "You must select at least one notification type", + "components.Settings.Notifications.NotificationsGotify.validationUrlRequired": "You must provide a valid URL", + "components.Settings.Notifications.NotificationsGotify.validationUrlTrailingSlash": "URL must not end in a trailing slash", "components.Settings.Notifications.NotificationsLunaSea.agentenabled": "Enable Agent", "components.Settings.Notifications.NotificationsLunaSea.profileName": "Profile Name", "components.Settings.Notifications.NotificationsLunaSea.profileNameTip": "Only required if not using the default profile", diff --git a/src/pages/settings/notifications/gotify.tsx b/src/pages/settings/notifications/gotify.tsx new file mode 100644 index 000000000..a47c9c288 --- /dev/null +++ b/src/pages/settings/notifications/gotify.tsx @@ -0,0 +1,20 @@ +import { NextPage } from 'next'; +import React from 'react'; +import NotificationsGotify from '../../../components/Settings/Notifications/NotificationsGotify'; +import SettingsLayout from '../../../components/Settings/SettingsLayout'; +import SettingsNotifications from '../../../components/Settings/SettingsNotifications'; +import useRouteGuard from '../../../hooks/useRouteGuard'; +import { Permission } from '../../../hooks/useUser'; + +const NotificationsPage: NextPage = () => { + useRouteGuard(Permission.MANAGE_SETTINGS); + return ( + + + + + + ); +}; + +export default NotificationsPage; From b31cdbf074d5dbecbbf6da135a9b686aea9e3c0e Mon Sep 17 00:00:00 2001 From: Danshil Kokil Mungur Date: Fri, 14 Jan 2022 11:52:10 +0400 Subject: [PATCH 144/238] feat(search): search by id (#2082) * feat(search): search by id This adds the ability to search by ID (starting with TMDb ID). Since there doesn't seem to be way of searching across movies, tv and persons, I have to search through all 3 and use the first one in the order: movie -> tv -> person Searching by ID is triggered using a 'prefix' just like in the *arrs. * fix: missed some refactoring * feat(search): use locale language * feat(search): search using imdb id * feat(search): search using tvdb id * fix: alias type import * fix: missed some refactoring * fix(search): account for id being a string * feat(search): account for movies/tvs/persons with the same id * feat(search): remove non-null assertion Co-authored-by: Ryan Cohen --- overseerr-api.yml | 4 +- server/api/themoviedb/index.ts | 6 +- server/api/themoviedb/interfaces.ts | 5 +- server/lib/search.ts | 169 +++++++++++++++++++++++++ server/models/Person.ts | 8 +- server/models/Search.ts | 54 ++++++++ server/routes/search.ts | 28 +++- server/utils/typeHelpers.ts | 17 ++- src/components/PersonDetails/index.tsx | 4 +- 9 files changed, 275 insertions(+), 20 deletions(-) create mode 100644 server/lib/search.ts diff --git a/overseerr-api.yml b/overseerr-api.yml index 211f029cd..e3fc90e32 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1326,7 +1326,7 @@ components: running: type: boolean example: false - PersonDetail: + PersonDetails: type: object properties: id: @@ -4871,7 +4871,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/PersonDetail' + $ref: '#/components/schemas/PersonDetails' /person/{personId}/combined_credits: get: diff --git a/server/api/themoviedb/index.ts b/server/api/themoviedb/index.ts index ddc180592..d565c35ae 100644 --- a/server/api/themoviedb/index.ts +++ b/server/api/themoviedb/index.ts @@ -10,7 +10,7 @@ import { TmdbMovieDetails, TmdbNetwork, TmdbPersonCombinedCredits, - TmdbPersonDetail, + TmdbPersonDetails, TmdbProductionCompany, TmdbRegion, TmdbSearchMovieResponse, @@ -122,9 +122,9 @@ class TheMovieDb extends ExternalAPI { }: { personId: number; language?: string; - }): Promise => { + }): Promise => { try { - const data = await this.get(`/person/${personId}`, { + const data = await this.get(`/person/${personId}`, { params: { language }, }); diff --git a/server/api/themoviedb/interfaces.ts b/server/api/themoviedb/interfaces.ts index 7892fe467..2282fe052 100644 --- a/server/api/themoviedb/interfaces.ts +++ b/server/api/themoviedb/interfaces.ts @@ -67,6 +67,7 @@ export interface TmdbUpcomingMoviesResponse extends TmdbPaginatedResponse { export interface TmdbExternalIdResponse { movie_results: TmdbMovieResult[]; tv_results: TmdbTvResult[]; + person_results: TmdbPersonResult[]; } export interface TmdbCreditCast { @@ -315,7 +316,7 @@ export interface TmdbKeyword { name: string; } -export interface TmdbPersonDetail { +export interface TmdbPersonDetails { id: number; name: string; birthday: string; @@ -324,7 +325,7 @@ export interface TmdbPersonDetail { also_known_as?: string[]; gender: number; biography: string; - popularity: string; + popularity: number; place_of_birth?: string; profile_path?: string; adult: boolean; diff --git a/server/lib/search.ts b/server/lib/search.ts new file mode 100644 index 000000000..b66079c83 --- /dev/null +++ b/server/lib/search.ts @@ -0,0 +1,169 @@ +import TheMovieDb from '../api/themoviedb'; +import { + TmdbMovieDetails, + TmdbMovieResult, + TmdbPersonDetails, + TmdbPersonResult, + TmdbSearchMultiResponse, + TmdbTvDetails, + TmdbTvResult, +} from '../api/themoviedb/interfaces'; +import { + mapMovieDetailsToResult, + mapPersonDetailsToResult, + mapTvDetailsToResult, +} from '../models/Search'; +import { isMovieDetails, isTvDetails } from '../utils/typeHelpers'; + +type SearchProviderId = 'TMDb' | 'IMDb' | 'TVDB'; + +interface SearchProvider { + id: SearchProviderId; + pattern: RegExp; + search: (id: string, language?: string) => Promise; +} + +const searchProviders: SearchProvider[] = []; + +export const findSearchProvider = ( + query: string +): SearchProvider | undefined => { + return searchProviders.find((provider) => provider.pattern.test(query)); +}; + +searchProviders.push({ + id: 'TMDb', + pattern: new RegExp(/(?<=tmdb:)\d+/), + search: async ( + id: string, + language?: string + ): Promise => { + const tmdb = new TheMovieDb(); + + const moviePromise = tmdb.getMovie({ movieId: parseInt(id), language }); + const tvShowPromise = tmdb.getTvShow({ tvId: parseInt(id), language }); + const personPromise = tmdb.getPerson({ personId: parseInt(id), language }); + + const responses = await Promise.allSettled([ + moviePromise, + tvShowPromise, + personPromise, + ]); + + const successfulResponses = responses.filter( + (r) => r.status === 'fulfilled' + ) as + | ( + | PromiseFulfilledResult + | PromiseFulfilledResult + | PromiseFulfilledResult + )[]; + + const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = []; + + if (successfulResponses.length) { + results.push( + ...successfulResponses.map((r) => { + if (isMovieDetails(r.value)) { + return mapMovieDetailsToResult(r.value); + } else if (isTvDetails(r.value)) { + return mapTvDetailsToResult(r.value); + } else { + return mapPersonDetailsToResult(r.value); + } + }) + ); + } + + return { + page: 1, + total_pages: 1, + total_results: results.length, + results, + }; + }, +}); + +searchProviders.push({ + id: 'IMDb', + pattern: new RegExp(/(?<=imdb:)(tt|nm)\d+/), + search: async ( + id: string, + language?: string + ): Promise => { + const tmdb = new TheMovieDb(); + + const responses = await tmdb.getByExternalId({ + externalId: id, + type: 'imdb', + language, + }); + + const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = []; + + // set the media_type here since searching by external id doesn't return it + results.push( + ...(responses.movie_results.map((movie) => ({ + ...movie, + media_type: 'movie', + })) as TmdbMovieResult[]), + ...(responses.tv_results.map((tv) => ({ + ...tv, + media_type: 'tv', + })) as TmdbTvResult[]), + ...(responses.person_results.map((person) => ({ + ...person, + media_type: 'person', + })) as TmdbPersonResult[]) + ); + + return { + page: 1, + total_pages: 1, + total_results: results.length, + results, + }; + }, +}); + +searchProviders.push({ + id: 'TVDB', + pattern: new RegExp(/(?<=tvdb:)\d+/), + search: async ( + id: string, + language?: string + ): Promise => { + const tmdb = new TheMovieDb(); + + const responses = await tmdb.getByExternalId({ + externalId: parseInt(id), + type: 'tvdb', + language, + }); + + const results: (TmdbMovieResult | TmdbTvResult | TmdbPersonResult)[] = []; + + // set the media_type here since searching by external id doesn't return it + results.push( + ...(responses.movie_results.map((movie) => ({ + ...movie, + media_type: 'movie', + })) as TmdbMovieResult[]), + ...(responses.tv_results.map((tv) => ({ + ...tv, + media_type: 'tv', + })) as TmdbTvResult[]), + ...(responses.person_results.map((person) => ({ + ...person, + media_type: 'person', + })) as TmdbPersonResult[]) + ); + + return { + page: 1, + total_pages: 1, + total_results: results.length, + results, + }; + }, +}); diff --git a/server/models/Person.ts b/server/models/Person.ts index 14925edb6..087ab1c7b 100644 --- a/server/models/Person.ts +++ b/server/models/Person.ts @@ -1,11 +1,11 @@ import type { TmdbPersonCreditCast, TmdbPersonCreditCrew, - TmdbPersonDetail, + TmdbPersonDetails, } from '../api/themoviedb/interfaces'; import Media from '../entity/Media'; -export interface PersonDetail { +export interface PersonDetails { id: number; name: string; birthday: string; @@ -14,7 +14,7 @@ export interface PersonDetail { alsoKnownAs?: string[]; gender: number; biography: string; - popularity: string; + popularity: number; placeOfBirth?: string; profilePath?: string; adult: boolean; @@ -62,7 +62,7 @@ export interface CombinedCredit { crew: PersonCreditCrew[]; } -export const mapPersonDetails = (person: TmdbPersonDetail): PersonDetail => ({ +export const mapPersonDetails = (person: TmdbPersonDetails): PersonDetails => ({ id: person.id, name: person.name, birthday: person.birthday, diff --git a/server/models/Search.ts b/server/models/Search.ts index 0dab4e587..73427a378 100644 --- a/server/models/Search.ts +++ b/server/models/Search.ts @@ -1,6 +1,9 @@ import type { + TmdbMovieDetails, TmdbMovieResult, + TmdbPersonDetails, TmdbPersonResult, + TmdbTvDetails, TmdbTvResult, } from '../api/themoviedb/interfaces'; import { MediaType as MainMediaType } from '../constants/media'; @@ -140,3 +143,54 @@ export const mapSearchResults = ( return mapPersonResult(result); } }); + +export const mapMovieDetailsToResult = ( + movieDetails: TmdbMovieDetails +): TmdbMovieResult => ({ + id: movieDetails.id, + media_type: 'movie', + adult: movieDetails.adult, + genre_ids: movieDetails.genres.map((genre) => genre.id), + original_language: movieDetails.original_language, + original_title: movieDetails.original_title, + overview: movieDetails.overview ?? '', + popularity: movieDetails.popularity, + release_date: movieDetails.release_date, + title: movieDetails.title, + video: movieDetails.video, + vote_average: movieDetails.vote_average, + vote_count: movieDetails.vote_count, + backdrop_path: movieDetails.backdrop_path, + poster_path: movieDetails.poster_path, +}); + +export const mapTvDetailsToResult = ( + tvDetails: TmdbTvDetails +): TmdbTvResult => ({ + id: tvDetails.id, + media_type: 'tv', + first_air_date: tvDetails.first_air_date, + genre_ids: tvDetails.genres.map((genre) => genre.id), + name: tvDetails.name, + origin_country: tvDetails.origin_country, + original_language: tvDetails.original_language, + original_name: tvDetails.original_name, + overview: tvDetails.overview, + popularity: tvDetails.popularity, + vote_average: tvDetails.vote_average, + vote_count: tvDetails.vote_count, + backdrop_path: tvDetails.backdrop_path, + poster_path: tvDetails.poster_path, +}); + +export const mapPersonDetailsToResult = ( + personDetails: TmdbPersonDetails +): TmdbPersonResult => ({ + id: personDetails.id, + media_type: 'person', + name: personDetails.name, + popularity: personDetails.popularity, + adult: personDetails.adult, + profile_path: personDetails.profile_path, + known_for: [], +}); diff --git a/server/routes/search.ts b/server/routes/search.ts index c843e78c3..466045d05 100644 --- a/server/routes/search.ts +++ b/server/routes/search.ts @@ -1,18 +1,34 @@ import { Router } from 'express'; import TheMovieDb from '../api/themoviedb'; +import { TmdbSearchMultiResponse } from '../api/themoviedb/interfaces'; import Media from '../entity/Media'; +import { findSearchProvider } from '../lib/search'; import { mapSearchResults } from '../models/Search'; const searchRoutes = Router(); searchRoutes.get('/', async (req, res) => { - const tmdb = new TheMovieDb(); + const queryString = req.query.query as string; + const searchProvider = findSearchProvider(queryString.toLowerCase()); + let results: TmdbSearchMultiResponse; - const results = await tmdb.searchMulti({ - query: req.query.query as string, - page: Number(req.query.page), - language: req.locale ?? (req.query.language as string), - }); + if (searchProvider) { + const [id] = queryString + .toLowerCase() + .match(searchProvider.pattern) as RegExpMatchArray; + results = await searchProvider.search( + id, + req.locale ?? (req.query.language as string) + ); + } else { + const tmdb = new TheMovieDb(); + + results = await tmdb.searchMulti({ + query: queryString, + page: Number(req.query.page), + language: req.locale ?? (req.query.language as string), + }); + } const media = await Media.getRelatedMedia( results.results.map((result) => result.id) diff --git a/server/utils/typeHelpers.ts b/server/utils/typeHelpers.ts index ca12ddf45..04070244b 100644 --- a/server/utils/typeHelpers.ts +++ b/server/utils/typeHelpers.ts @@ -1,7 +1,10 @@ import type { + TmdbMovieDetails, TmdbMovieResult, - TmdbTvResult, + TmdbPersonDetails, TmdbPersonResult, + TmdbTvDetails, + TmdbTvResult, } from '../api/themoviedb/interfaces'; export const isMovie = ( @@ -15,3 +18,15 @@ export const isPerson = ( ): person is TmdbPersonResult => { return (person as TmdbPersonResult).known_for !== undefined; }; + +export const isMovieDetails = ( + movie: TmdbMovieDetails | TmdbTvDetails | TmdbPersonDetails +): movie is TmdbMovieDetails => { + return (movie as TmdbMovieDetails).title !== undefined; +}; + +export const isTvDetails = ( + tv: TmdbMovieDetails | TmdbTvDetails | TmdbPersonDetails +): tv is TmdbTvDetails => { + return (tv as TmdbTvDetails).number_of_seasons !== undefined; +}; diff --git a/src/components/PersonDetails/index.tsx b/src/components/PersonDetails/index.tsx index 823914451..28aa0550d 100644 --- a/src/components/PersonDetails/index.tsx +++ b/src/components/PersonDetails/index.tsx @@ -5,7 +5,7 @@ import { defineMessages, useIntl } from 'react-intl'; import TruncateMarkup from 'react-truncate-markup'; import useSWR from 'swr'; import type { PersonCombinedCreditsResponse } from '../../../server/interfaces/api/personInterfaces'; -import type { PersonDetail } from '../../../server/models/Person'; +import type { PersonDetails as PersonDetailsType } from '../../../server/models/Person'; import Ellipsis from '../../assets/ellipsis.svg'; import globalMessages from '../../i18n/globalMessages'; import Error from '../../pages/_error'; @@ -27,7 +27,7 @@ const messages = defineMessages({ const PersonDetails: React.FC = () => { const intl = useIntl(); const router = useRouter(); - const { data, error } = useSWR( + const { data, error } = useSWR( `/api/v1/person/${router.query.personId}` ); const [showBio, setShowBio] = useState(false); From 256163971fa6b9ea7ecc838e789eba1c8934622f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Fri, 14 Jan 2022 16:53:29 +0900 Subject: [PATCH 145/238] docs: add schambers as a contributor for code (#2419) [skip ci] * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> --- .all-contributorsrc | 9 +++++++++ README.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 1c85e63f6..f19a6d133 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -620,6 +620,15 @@ "contributions": [ "translation" ] + }, + { + "login": "schambers", + "name": "Sean Chambers", + "avatar_url": "https://avatars.githubusercontent.com/u/31563?v=4", + "profile": "https://github.com/schambers", + "contributions": [ + "code" + ] } ], "badgeTemplate": "\"All-orange.svg\"/>", diff --git a/README.md b/README.md index 5e8e928ee..7136dd725 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Language grade: JavaScript GitHub -All Contributors +All Contributors

@@ -160,6 +160,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Shaaft

🌍
sr093906

🌍
Nackophilz

🌍 +
Sean Chambers

💻 From 9cb97db13ced5df2dc595cd9033470b1a0750093 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Fri, 14 Jan 2022 02:32:53 -0800 Subject: [PATCH 146/238] feat(plex): selective user import (#2188) * feat(api): allow importing of only selected Plex users * feat(frontend): modal for importing Plex users * feat: add alert if 'Enable New Plex Sign-In' setting is enabled * refactor: fetch all existing Plex users in a single DB query Co-authored-by: Ryan Cohen --- docs/using-overseerr/users/README.md | 4 +- overseerr-api.yml | 43 +++- server/api/plextv.ts | 2 +- server/interfaces/api/settingsInterfaces.ts | 1 + server/lib/settings.ts | 2 + server/routes/settings/index.ts | 54 +++- server/routes/user/index.ts | 3 +- src/components/Settings/SettingsServices.tsx | 4 +- src/components/UserList/PlexImportModal.tsx | 250 +++++++++++++++++++ src/components/UserList/index.tsx | 61 ++--- src/context/SettingsContext.tsx | 1 + src/i18n/globalMessages.ts | 2 + src/i18n/locale/en.json | 11 +- src/pages/_app.tsx | 1 + 14 files changed, 389 insertions(+), 50 deletions(-) create mode 100644 src/components/UserList/PlexImportModal.tsx diff --git a/docs/using-overseerr/users/README.md b/docs/using-overseerr/users/README.md index 275e469c0..139e935a9 100644 --- a/docs/using-overseerr/users/README.md +++ b/docs/using-overseerr/users/README.md @@ -8,9 +8,9 @@ The user account created during Overseerr setup is the "Owner" account, which ca There are currently two methods to add users to Overseerr: importing Plex users and creating "local users." All new users are created with the [default permissions](../settings/README.md#default-permissions) defined in **Settings → Users**. -### Importing Users from Plex +### Importing Plex Users -Clicking the **Import Users from Plex** button on the **User List** page will fetch the list of users with access to the Plex server from [plex.tv](https://www.plex.tv/), and add them to Overseerr automatically. +Clicking the **Import Plex Users** button on the **User List** page will fetch the list of users with access to the Plex server from [plex.tv](https://www.plex.tv/), and add them to Overseerr automatically. Importing Plex users is not required, however. Any user with access to the Plex server can log in to Overseerr even if they have not been imported, and will be assigned the configured [default permissions](../settings/README.md#default-permissions) upon their first login. diff --git a/overseerr-api.yml b/overseerr-api.yml index e3fc90e32..8ad5afa46 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1994,6 +1994,36 @@ paths: type: array items: $ref: '#/components/schemas/PlexDevice' + /settings/plex/users: + get: + summary: Get Plex users + description: | + Returns a list of Plex users in a JSON array. + + Requires the `MANAGE_USERS` permission. + tags: + - settings + - users + responses: + '200': + description: Plex users + content: + application/json: + schema: + type: array + items: + type: object + properties: + id: + type: string + title: + type: string + username: + type: string + email: + type: string + thumb: + type: string /settings/radarr: get: summary: Get Radarr settings @@ -3196,11 +3226,22 @@ paths: post: summary: Import all users from Plex description: | - Requests users from the Plex Server and creates a new user for each of them + Fetches and imports users from the Plex server. If a list of Plex IDs is provided in the request body, only the specified users will be imported. Otherwise, all users will be imported. Requires the `MANAGE_USERS` permission. tags: - users + requestBody: + required: false + content: + application/json: + schema: + type: object + properties: + plexIds: + type: array + items: + type: string responses: '201': description: A list of the newly created users diff --git a/server/api/plextv.ts b/server/api/plextv.ts index 9efcecc2b..1733a85a6 100644 --- a/server/api/plextv.ts +++ b/server/api/plextv.ts @@ -224,7 +224,7 @@ class PlexTvAPI { const users = friends.MediaContainer.User; - const user = users.find((u) => Number(u.$.id) === userId); + const user = users.find((u) => parseInt(u.$.id) === userId); if (!user) { throw new Error( diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts index 336bab0bd..2f5566352 100644 --- a/server/interfaces/api/settingsInterfaces.ts +++ b/server/interfaces/api/settingsInterfaces.ts @@ -35,6 +35,7 @@ export interface PublicSettingsResponse { enablePushRegistration: boolean; locale: string; emailEnabled: boolean; + newPlexLogin: boolean; } export interface CacheItem { diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 74d13e538..c500157cc 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -113,6 +113,7 @@ interface FullPublicSettings extends PublicSettings { enablePushRegistration: boolean; locale: string; emailEnabled: boolean; + newPlexLogin: boolean; } export interface NotificationAgentConfig { @@ -469,6 +470,7 @@ class Settings { enablePushRegistration: this.data.notifications.agents.webpush.enabled, locale: this.data.main.locale, emailEnabled: this.data.notifications.agents.email.enabled, + newPlexLogin: this.data.main.newPlexLogin, }; } diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index bad91eaca..c9908f4a4 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -1,7 +1,7 @@ import { Router } from 'express'; import rateLimit from 'express-rate-limit'; import fs from 'fs'; -import { merge, omit } from 'lodash'; +import { merge, omit, sortBy } from 'lodash'; import { rescheduleJob } from 'node-schedule'; import path from 'path'; import { getRepository } from 'typeorm'; @@ -225,6 +225,58 @@ settingsRoutes.post('/plex/sync', (req, res) => { return res.status(200).json(plexFullScanner.status()); }); +settingsRoutes.get( + '/plex/users', + isAuthenticated(Permission.MANAGE_USERS), + async (req, res) => { + const userRepository = getRepository(User); + const qb = userRepository.createQueryBuilder('user'); + + const admin = await userRepository.findOneOrFail({ + select: ['id', 'plexToken'], + order: { id: 'ASC' }, + }); + const plexApi = new PlexTvAPI(admin.plexToken ?? ''); + const plexUsers = (await plexApi.getUsers()).MediaContainer.User.map( + (user) => user.$ + ); + + const unimportedPlexUsers: { + id: string; + title: string; + username: string; + email: string; + thumb: string; + }[] = []; + + const existingUsers = await qb + .where('user.plexId IN (:...plexIds)', { + plexIds: plexUsers.map((plexUser) => plexUser.id), + }) + .orWhere('user.email IN (:...plexEmails)', { + plexEmails: plexUsers.map((plexUser) => plexUser.email.toLowerCase()), + }) + .getMany(); + + await Promise.all( + plexUsers.map(async (plexUser) => { + if ( + !existingUsers.find( + (user) => + user.plexId === parseInt(plexUser.id) || + user.email === plexUser.email.toLowerCase() + ) && + (await plexApi.checkUserAccess(parseInt(plexUser.id))) + ) { + unimportedPlexUsers.push(plexUser); + } + }) + ); + + return res.status(200).json(sortBy(unimportedPlexUsers, 'username')); + } +); + settingsRoutes.get( '/logs', rateLimit({ windowMs: 60 * 1000, max: 50 }), diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index e6fa09cdb..8352726b0 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -400,6 +400,7 @@ router.post( try { const settings = getSettings(); const userRepository = getRepository(User); + const body = req.body as { plexIds: string[] } | undefined; // taken from auth.ts const mainUser = await userRepository.findOneOrFail({ @@ -434,7 +435,7 @@ router.post( user.plexId = parseInt(account.id); } await userRepository.save(user); - } else { + } else if (!body || body.plexIds.includes(account.id)) { if (await mainPlexTv.checkUserAccess(parseInt(account.id))) { const newUser = new User({ plexUsername: account.username, diff --git a/src/components/Settings/SettingsServices.tsx b/src/components/Settings/SettingsServices.tsx index 377fda3e9..1ffbd4cfa 100644 --- a/src/components/Settings/SettingsServices.tsx +++ b/src/components/Settings/SettingsServices.tsx @@ -292,7 +292,7 @@ const SettingsServices: React.FC = () => { serverType: 'Radarr', strong: function strong(msg) { return ( - + {msg} ); @@ -382,7 +382,7 @@ const SettingsServices: React.FC = () => { serverType: 'Sonarr', strong: function strong(msg) { return ( - + {msg} ); diff --git a/src/components/UserList/PlexImportModal.tsx b/src/components/UserList/PlexImportModal.tsx new file mode 100644 index 000000000..7e9377931 --- /dev/null +++ b/src/components/UserList/PlexImportModal.tsx @@ -0,0 +1,250 @@ +import { InboxInIcon } from '@heroicons/react/solid'; +import axios from 'axios'; +import React, { useState } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; +import { useToasts } from 'react-toast-notifications'; +import useSWR from 'swr'; +import useSettings from '../../hooks/useSettings'; +import globalMessages from '../../i18n/globalMessages'; +import Alert from '../Common/Alert'; +import Modal from '../Common/Modal'; + +interface PlexImportProps { + onCancel?: () => void; + onComplete?: () => void; +} + +const messages = defineMessages({ + importfromplex: 'Import Plex Users', + importfromplexerror: 'Something went wrong while importing Plex users.', + importedfromplex: + '{userCount} {userCount, plural, one {user} other {users}} Plex users imported successfully!', + user: 'User', + nouserstoimport: 'There are no Plex users to import.', + newplexsigninenabled: + 'The Enable New Plex Sign-In setting is currently enabled. Plex users with library access do not need to be imported in order to sign in.', +}); + +const PlexImportModal: React.FC = ({ + onCancel, + onComplete, +}) => { + const intl = useIntl(); + const settings = useSettings(); + const { addToast } = useToasts(); + const [isImporting, setImporting] = useState(false); + const [selectedUsers, setSelectedUsers] = useState([]); + const { data, error } = useSWR< + { + id: string; + title: string; + username: string; + email: string; + thumb: string; + }[] + >(`/api/v1/settings/plex/users`, { + revalidateOnMount: true, + }); + + const importUsers = async () => { + setImporting(true); + + try { + const { data: createdUsers } = await axios.post( + '/api/v1/user/import-from-plex', + { plexIds: selectedUsers } + ); + + if (!createdUsers.length) { + throw new Error('No users were imported from Plex.'); + } + + addToast( + intl.formatMessage(messages.importedfromplex, { + userCount: createdUsers.length, + strong: function strong(msg) { + return {msg}; + }, + }), + { + autoDismiss: true, + appearance: 'success', + } + ); + + if (onComplete) { + onComplete(); + } + } catch (e) { + addToast(intl.formatMessage(messages.importfromplexerror), { + autoDismiss: true, + appearance: 'error', + }); + } finally { + setImporting(false); + } + }; + + const isSelectedUser = (plexId: string): boolean => + selectedUsers.includes(plexId); + + const isAllUsers = (): boolean => selectedUsers.length === data?.length; + + const toggleUser = (plexId: string): void => { + if (selectedUsers.includes(plexId)) { + setSelectedUsers((users) => users.filter((user) => user !== plexId)); + } else { + setSelectedUsers((users) => [...users, plexId]); + } + }; + + const toggleAllUsers = (): void => { + if (data && selectedUsers.length >= 0 && !isAllUsers()) { + setSelectedUsers(data.map((user) => user.id)); + } else { + setSelectedUsers([]); + } + }; + + return ( + } + onOk={() => { + importUsers(); + }} + okDisabled={isImporting || !selectedUsers.length} + okText={intl.formatMessage( + isImporting ? globalMessages.importing : globalMessages.import + )} + onCancel={onCancel} + > + {data?.length ? ( + <> + {settings.currentSettings.newPlexLogin && ( + {msg} + ); + }, + })} + type="info" + /> + )} +
+
+
+
+ + + + + + + + + {data?.map((user) => ( + + + + + ))} + +
+ toggleAllUsers()} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === 'Space') { + toggleAllUsers(); + } + }} + className="relative inline-flex items-center justify-center flex-shrink-0 w-10 h-5 pt-2 cursor-pointer focus:outline-none" + > + + + + + {intl.formatMessage(messages.user)} +
+ toggleUser(user.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === 'Space') { + toggleUser(user.id); + } + }} + className="relative inline-flex items-center justify-center flex-shrink-0 w-10 h-5 pt-2 cursor-pointer focus:outline-none" + > + + + + +
+ +
+
+ {user.username} +
+ {user.username && + user.username.toLowerCase() !== + user.email && ( +
+ {user.email} +
+ )} +
+
+
+
+
+
+
+ + ) : ( + + )} + + ); +}; + +export default PlexImportModal; diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index a544c8f99..fdf7b4b0a 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -33,15 +33,12 @@ import SensitiveInput from '../Common/SensitiveInput'; import Table from '../Common/Table'; import Transition from '../Transition'; import BulkEditModal from './BulkEditModal'; +import PlexImportModal from './PlexImportModal'; const messages = defineMessages({ users: 'Users', userlist: 'User List', - importfromplex: 'Import Users from Plex', - importfromplexerror: 'Something went wrong while importing users from Plex.', - importedfromplex: - '{userCount, plural, one {# new user} other {# new users}} imported from Plex successfully!', - nouserstoimport: 'No new users to import from Plex.', + importfromplex: 'Import Plex Users', user: 'User', totalrequests: 'Requests', accounttype: 'Type', @@ -103,7 +100,7 @@ const UserList: React.FC = () => { ); const [isDeleting, setDeleting] = useState(false); - const [isImporting, setImporting] = useState(false); + const [showImportModal, setShowImportModal] = useState(false); const [deleteModal, setDeleteModal] = useState<{ isOpen: boolean; user?: User; @@ -193,35 +190,6 @@ const UserList: React.FC = () => { } }; - const importFromPlex = async () => { - setImporting(true); - - try { - const { data: createdUsers } = await axios.post( - '/api/v1/user/import-from-plex' - ); - addToast( - createdUsers.length - ? intl.formatMessage(messages.importedfromplex, { - userCount: createdUsers.length, - }) - : intl.formatMessage(messages.nouserstoimport), - { - autoDismiss: true, - appearance: 'success', - } - ); - } catch (e) { - addToast(intl.formatMessage(messages.importfromplexerror), { - autoDismiss: true, - appearance: 'error', - }); - } finally { - revalidate(); - setImporting(false); - } - }; - if (!data && !error) { return ; } @@ -354,7 +322,7 @@ const UserList: React.FC = () => { title={intl.formatMessage(messages.localLoginDisabled, { strong: function strong(msg) { return ( - + {msg} ); @@ -481,6 +449,24 @@ const UserList: React.FC = () => { /> + + setShowImportModal(false)} + onComplete={() => { + setShowImportModal(false); + revalidate(); + }} + /> + +
{intl.formatMessage(messages.userlist)}
@@ -496,8 +482,7 @@ const UserList: React.FC = () => {
diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index f2a866669..20fbac0ba 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -271,13 +271,17 @@ const RequestCard: React.FC = ({ request, onTitleData }) => { {intl.formatMessage(globalMessages.status)} - {requestData.media[requestData.is4k ? 'status4k' : 'status'] === - MediaStatus.UNKNOWN || - requestData.status === MediaRequestStatus.DECLINED ? ( + {requestData.status === MediaRequestStatus.DECLINED ? ( - {requestData.status === MediaRequestStatus.DECLINED - ? intl.formatMessage(globalMessages.declined) - : intl.formatMessage(globalMessages.failed)} + {intl.formatMessage(globalMessages.declined)} + + ) : requestData.media[requestData.is4k ? 'status4k' : 'status'] === + MediaStatus.UNKNOWN ? ( + + {intl.formatMessage(globalMessages.failed)} ) : ( = ({ request, onTitleData }) => { ).length > 0 } is4k={requestData.is4k} + tmdbId={requestData.media.tmdbId} + mediaType={requestData.type} plexUrl={ - requestData.is4k - ? requestData.media.plexUrl4k - : requestData.media.plexUrl - } - serviceUrl={ - hasPermission(Permission.ADMIN) - ? requestData.is4k - ? requestData.media.serviceUrl4k - : requestData.media.serviceUrl - : undefined + requestData.media[requestData.is4k ? 'plexUrl4k' : 'plexUrl'] } /> )} diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index f3d9fb007..7a0619f21 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -272,13 +272,18 @@ const RequestItem: React.FC = ({ {intl.formatMessage(globalMessages.status)} - {requestData.media[requestData.is4k ? 'status4k' : 'status'] === - MediaStatus.UNKNOWN || - requestData.status === MediaRequestStatus.DECLINED ? ( + {requestData.status === MediaRequestStatus.DECLINED ? ( - {requestData.status === MediaRequestStatus.DECLINED - ? intl.formatMessage(globalMessages.declined) - : intl.formatMessage(globalMessages.failed)} + {intl.formatMessage(globalMessages.declined)} + + ) : requestData.media[ + requestData.is4k ? 'status4k' : 'status' + ] === MediaStatus.UNKNOWN ? ( + + {intl.formatMessage(globalMessages.failed)} ) : ( = ({ ).length > 0 } is4k={requestData.is4k} + tmdbId={requestData.media.tmdbId} + mediaType={requestData.type} plexUrl={ - requestData.is4k - ? requestData.media.plexUrl4k - : requestData.media.plexUrl - } - serviceUrl={ - hasPermission(Permission.ADMIN) - ? requestData.is4k - ? requestData.media.serviceUrl4k - : requestData.media.serviceUrl - : undefined + requestData.media[ + requestData.is4k ? 'plexUrl4k' : 'plexUrl' + ] } /> )} diff --git a/src/components/StatusBadge/index.tsx b/src/components/StatusBadge/index.tsx index 86c935bcc..cf02db003 100644 --- a/src/components/StatusBadge/index.tsx +++ b/src/components/StatusBadge/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { MediaStatus } from '../../../server/constants/media'; import Spinner from '../../assets/spinner.svg'; +import { Permission, useUser } from '../../hooks/useUser'; import globalMessages from '../../i18n/globalMessages'; import Badge from '../Common/Badge'; @@ -16,6 +17,8 @@ interface StatusBadgeProps { inProgress?: boolean; plexUrl?: string; serviceUrl?: string; + tmdbId?: number; + mediaType?: 'movie' | 'tv'; } const StatusBadge: React.FC = ({ @@ -24,13 +27,21 @@ const StatusBadge: React.FC = ({ inProgress = false, plexUrl, serviceUrl, + tmdbId, + mediaType, }) => { const intl = useIntl(); + const { hasPermission } = useUser(); + + const manageLink = + tmdbId && mediaType && hasPermission(Permission.MANAGE_REQUESTS) + ? `/${mediaType}/${tmdbId}?manage=1` + : undefined; switch (status) { case MediaStatus.AVAILABLE: return ( - +
{intl.formatMessage(is4k ? messages.status4k : messages.status, { @@ -44,7 +55,7 @@ const StatusBadge: React.FC = ({ case MediaStatus.PARTIALLY_AVAILABLE: return ( - +
{intl.formatMessage(is4k ? messages.status4k : messages.status, { @@ -58,7 +69,7 @@ const StatusBadge: React.FC = ({ case MediaStatus.PROCESSING: return ( - +
{intl.formatMessage(is4k ? messages.status4k : messages.status, { @@ -74,7 +85,7 @@ const StatusBadge: React.FC = ({ case MediaStatus.PENDING: return ( - + {intl.formatMessage(is4k ? messages.status4k : messages.status, { status: intl.formatMessage(globalMessages.pending), })} diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 99f4345b9..a1371c359 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -80,7 +80,9 @@ const TvDetails: React.FC = ({ tv }) => { const intl = useIntl(); const { locale } = useLocale(); const [showRequestModal, setShowRequestModal] = useState(false); - const [showManager, setShowManager] = useState(false); + const [showManager, setShowManager] = useState( + router.query.manage == '1' ? true : false + ); const [showIssueModal, setShowIssueModal] = useState(false); const { @@ -278,12 +280,9 @@ const TvDetails: React.FC = ({ tv }) => { 0} + tmdbId={data.mediaInfo?.tmdbId} + mediaType="tv" plexUrl={data.mediaInfo?.plexUrl} - serviceUrl={ - hasPermission(Permission.ADMIN) - ? data.mediaInfo?.serviceUrl - : undefined - } /> {settings.currentSettings.series4kEnabled && hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { @@ -295,12 +294,9 @@ const TvDetails: React.FC = ({ tv }) => { inProgress={ (data.mediaInfo?.downloadStatus4k ?? []).length > 0 } + tmdbId={data.mediaInfo?.tmdbId} + mediaType="tv" plexUrl={data.mediaInfo?.plexUrl4k} - serviceUrl={ - hasPermission(Permission.ADMIN) - ? data.mediaInfo?.serviceUrl4k - : undefined - } /> )}
From ce31bef8a125c5492f2a1cfef0dcf3d8a4e9ee11 Mon Sep 17 00:00:00 2001 From: Danshil Kokil Mungur Date: Thu, 20 Jan 2022 09:17:03 +0400 Subject: [PATCH 157/238] feat(logs): use separate json file to parse logs for log viewer (#2399) Co-authored-by: Ryan Cohen --- server/logger.ts | 20 ++++++++++-- server/routes/settings/index.ts | 56 +++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/server/logger.ts b/server/logger.ts index 824de630b..4f736e4ab 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -1,7 +1,7 @@ +import fs from 'fs'; +import path from 'path'; import * as winston from 'winston'; import 'winston-daily-rotate-file'; -import path from 'path'; -import fs from 'fs'; // Migrate away from old log const OLD_LOG_FILE = path.join(__dirname, '../config/logs/overseerr.log'); @@ -52,6 +52,22 @@ const logger = winston.createLogger({ createSymlink: true, symlinkName: 'overseerr.log', }), + new winston.transports.DailyRotateFile({ + filename: process.env.CONFIG_DIRECTORY + ? `${process.env.CONFIG_DIRECTORY}/logs/.machinelogs-%DATE%.json` + : path.join(__dirname, '../config/logs/.machinelogs-%DATE%.json'), + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '1d', + createSymlink: true, + symlinkName: '.machinelogs.json', + format: winston.format.combine( + winston.format.splat(), + winston.format.timestamp(), + winston.format.json() + ), + }), ], }); diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index c9908f4a4..c07232e47 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -303,38 +303,46 @@ settingsRoutes.get( } const logFile = process.env.CONFIG_DIRECTORY - ? `${process.env.CONFIG_DIRECTORY}/logs/overseerr.log` - : path.join(__dirname, '../../../config/logs/overseerr.log'); + ? `${process.env.CONFIG_DIRECTORY}/logs/.machinelogs.json` + : path.join(__dirname, '../../../config/logs/.machinelogs.json'); const logs: LogMessage[] = []; + const logMessageProperties = [ + 'timestamp', + 'level', + 'label', + 'message', + 'data', + ]; try { - fs.readFileSync(logFile) - .toString() - .split(/(?=\n\d{4}-\d{2})/g) + fs.readFileSync(logFile, 'utf-8') + .split('\n') .forEach((line) => { if (!line.length) return; - const jsonRegexp = new RegExp( - /[{[]{1}([,:{}[\]0-9.\-+Eaeflnr-u \n\r\t]|"[^"\n]*?")+[}\]]{1}/ - ); + const logMessage = JSON.parse(line); - const timestamp = line.match(new RegExp(/.{24}/)) || []; - const level = line.match(new RegExp(/(?<=.{24}\s\[).+?(?=\])/)) || []; - const label = - line.match(new RegExp(/(?<=.{24}\s\[.+\]\[).+?(?=\])/)) || []; - const message = - line.match(new RegExp(/(?<=\[.+\]:\s)[\s\S][^\r]+/)) || []; - const data = message[0].match(jsonRegexp) || []; - - if (level.length && filter.includes(level[0])) { - logs.push({ - timestamp: timestamp[0], - level: level[0], - label: label[0], - message: message[0].replace(jsonRegexp, ''), - data: data.length ? JSON.parse(data[0]) : undefined, - }); + if (!filter.includes(logMessage.level)) { + return; } + + if ( + !Object.keys(logMessage).every((key) => + logMessageProperties.includes(key) + ) + ) { + Object.keys(logMessage) + .filter((prop) => !logMessageProperties.includes(prop)) + .forEach((prop) => { + Object.assign(logMessage, { + data: { + [prop]: logMessage[prop], + }, + }); + }); + } + + logs.push(logMessage); }); const displayedLogs = logs.reverse().slice(skip, skip + pageSize); From 86dff12cdeef6dca92527dd31757a3a4c7f921bf Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Thu, 20 Jan 2022 01:48:35 -0500 Subject: [PATCH 158/238] fix(plex): user import (#2442) --- server/routes/settings/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index c07232e47..7a5938e33 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -239,7 +239,7 @@ settingsRoutes.get( const plexApi = new PlexTvAPI(admin.plexToken ?? ''); const plexUsers = (await plexApi.getUsers()).MediaContainer.User.map( (user) => user.$ - ); + ).filter((user) => user.email); const unimportedPlexUsers: { id: string; From 0842c233d0fc56d44824cad18749492cd52cbed5 Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Thu, 20 Jan 2022 05:36:59 -0500 Subject: [PATCH 159/238] feat: Tautulli integration (#2230) * feat: media/user watch history data via Tautulli * fix(frontend): only display slideover cog button if there is media to manage * fix(lang): tweak permission denied messages * refactor: reorder Media section in slideover * refactor: use new Tautulli stats API * fix(frontend): do not attempt to fetch data when user lacks req perms * fix: remove unneccessary get_user requests * feat(frontend): display user avatars * feat: add external URL setting * feat: add play counts for past week/month * fix(lang): tweak strings Co-authored-by: Ryan Cohen --- overseerr-api.yml | 136 +++- server/api/tautulli.ts | 228 +++++++ server/entity/Media.ts | 12 + server/interfaces/api/mediaInterfaces.ts | 16 + server/interfaces/api/userInterfaces.ts | 5 + server/lib/settings.ts | 19 + server/routes/media.ts | 113 +++- server/routes/settings/index.ts | 15 + server/routes/user/index.ts | 85 ++- src/components/IssueBlock/index.tsx | 20 +- src/components/ManageSlideOver/index.tsx | 579 ++++++++++++------ src/components/MovieDetails/index.tsx | 2 +- src/components/RequestBlock/index.tsx | 24 +- src/components/Settings/RadarrModal/index.tsx | 4 +- src/components/Settings/SettingsMain.tsx | 7 +- src/components/Settings/SettingsPlex.tsx | 288 ++++++++- src/components/TvDetails/index.tsx | 2 +- src/components/UserProfile/index.tsx | 63 +- src/i18n/locale/en.json | 33 +- 19 files changed, 1432 insertions(+), 219 deletions(-) create mode 100644 server/api/tautulli.ts diff --git a/overseerr-api.yml b/overseerr-api.yml index 429b31e77..7a87e8b83 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -165,6 +165,9 @@ components: port: type: number example: 32400 + useSsl: + type: boolean + nullable: true libraries: type: array readOnly: true @@ -172,6 +175,7 @@ components: $ref: '#/components/schemas/PlexLibrary' webAppUrl: type: string + nullable: true example: 'https://app.plex.tv/desktop' required: - name @@ -298,6 +302,26 @@ components: - provides - owned - connection + TautulliSettings: + type: object + properties: + hostname: + type: string + nullable: true + example: 'tautulli.example.com' + port: + type: number + nullable: true + example: 8181 + useSsl: + type: boolean + nullable: true + apiKey: + type: string + nullable: true + externalUrl: + type: string + nullable: true RadarrSettings: type: object properties: @@ -2024,6 +2048,37 @@ paths: type: string thumb: type: string + /settings/tautulli: + get: + summary: Get Tautulli settings + description: Retrieves current Tautulli settings. + tags: + - settings + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/TautulliSettings' + post: + summary: Update Tautulli settings + description: Updates Tautulli settings with the provided values. + tags: + - settings + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/TautulliSettings' + responses: + '200': + description: 'Values were successfully updated' + content: + application/json: + schema: + $ref: '#/components/schemas/TautulliSettings' /settings/radarr: get: summary: Get Radarr settings @@ -3643,6 +3698,35 @@ paths: permissions: type: number example: 2 + /user/{userId}/watch_data: + get: + summary: Get watch data + description: | + Returns play count, play duration, and recently watched media. + + Requires the `ADMIN` permission to fetch results for other users. + tags: + - users + parameters: + - in: path + name: userId + required: true + schema: + type: number + responses: + '200': + description: Users + content: + application/json: + schema: + type: object + properties: + recentlyWatched: + type: array + items: + $ref: '#/components/schemas/MediaInfo' + playCount: + type: number /search: get: summary: Search for movies, TV shows, or people @@ -4914,7 +4998,6 @@ paths: application/json: schema: $ref: '#/components/schemas/PersonDetails' - /person/{personId}/combined_credits: get: summary: Get combined credits @@ -5051,6 +5134,57 @@ paths: application/json: schema: $ref: '#/components/schemas/MediaInfo' + /media/{mediaId}/watch_data: + get: + summary: Get watch data + description: | + Returns play count, play duration, and users who have watched the media. + + Requires the `ADMIN` permission. + tags: + - media + parameters: + - in: path + name: mediaId + description: Media ID + required: true + example: '1' + schema: + type: string + responses: + '200': + description: Users + content: + application/json: + schema: + type: object + properties: + data: + type: object + properties: + playCount7Days: + type: number + playCount30Days: + type: number + playCount: + type: number + users: + type: array + items: + $ref: '#/components/schemas/User' + data4k: + type: object + properties: + playCount7Days: + type: number + playCount30Days: + type: number + playCount: + type: number + users: + type: array + items: + $ref: '#/components/schemas/User' /collection/{collectionId}: get: summary: Get collection details diff --git a/server/api/tautulli.ts b/server/api/tautulli.ts new file mode 100644 index 000000000..727c7d08c --- /dev/null +++ b/server/api/tautulli.ts @@ -0,0 +1,228 @@ +import axios, { AxiosInstance } from 'axios'; +import { User } from '../entity/User'; +import { TautulliSettings } from '../lib/settings'; +import logger from '../logger'; + +export interface TautulliHistoryRecord { + date: number; + duration: number; + friendly_name: string; + full_title: string; + grandparent_rating_key: number; + grandparent_title: string; + original_title: string; + group_count: number; + group_ids?: string; + guid: string; + ip_address: string; + live: number; + machine_id: string; + media_index: number; + media_type: string; + originally_available_at: string; + parent_media_index: number; + parent_rating_key: number; + parent_title: string; + paused_counter: number; + percent_complete: number; + platform: string; + product: string; + player: string; + rating_key: number; + reference_id?: number; + row_id?: number; + session_key?: string; + started: number; + state?: string; + stopped: number; + thumb: string; + title: string; + transcode_decision: string; + user: string; + user_id: number; + watched_status: number; + year: number; +} + +interface TautulliHistoryResponse { + response: { + result: string; + message?: string; + data: { + draw: number; + recordsTotal: number; + recordsFiltered: number; + total_duration: string; + filter_duration: string; + data: TautulliHistoryRecord[]; + }; + }; +} + +interface TautulliWatchStats { + query_days: number; + total_time: number; + total_plays: number; +} + +interface TautulliWatchStatsResponse { + response: { + result: string; + message?: string; + data: TautulliWatchStats[]; + }; +} + +interface TautulliWatchUser { + friendly_name: string; + user_id: number; + user_thumb: string; + username: string; + total_plays: number; + total_time: number; +} + +interface TautulliWatchUsersResponse { + response: { + result: string; + message?: string; + data: TautulliWatchUser[]; + }; +} + +class TautulliAPI { + private axios: AxiosInstance; + + constructor(settings: TautulliSettings) { + this.axios = axios.create({ + baseURL: `${settings.useSsl ? 'https' : 'http'}://${settings.hostname}:${ + settings.port + }${settings.urlBase ?? ''}`, + params: { apikey: settings.apiKey }, + }); + } + + public async getMediaWatchStats( + ratingKey: string + ): Promise { + try { + return ( + await this.axios.get('/api/v2', { + params: { + cmd: 'get_item_watch_time_stats', + rating_key: ratingKey, + grouping: 1, + }, + }) + ).data.response.data; + } catch (e) { + logger.error( + 'Something went wrong fetching media watch stats from Tautulli', + { + label: 'Tautulli API', + errorMessage: e.message, + ratingKey, + } + ); + throw new Error( + `[Tautulli] Failed to fetch media watch stats: ${e.message}` + ); + } + } + + public async getMediaWatchUsers( + ratingKey: string + ): Promise { + try { + return ( + await this.axios.get('/api/v2', { + params: { + cmd: 'get_item_user_stats', + rating_key: ratingKey, + grouping: 1, + }, + }) + ).data.response.data; + } catch (e) { + logger.error( + 'Something went wrong fetching media watch users from Tautulli', + { + label: 'Tautulli API', + errorMessage: e.message, + ratingKey, + } + ); + throw new Error( + `[Tautulli] Failed to fetch media watch users: ${e.message}` + ); + } + } + + public async getUserWatchStats(user: User): Promise { + try { + if (!user.plexId) { + throw new Error('User does not have an associated Plex ID'); + } + + return ( + await this.axios.get('/api/v2', { + params: { + cmd: 'get_user_watch_time_stats', + user_id: user.plexId, + query_days: 0, + grouping: 1, + }, + }) + ).data.response.data[0]; + } catch (e) { + logger.error( + 'Something went wrong fetching user watch stats from Tautulli', + { + label: 'Tautulli API', + errorMessage: e.message, + user: user.displayName, + } + ); + throw new Error( + `[Tautulli] Failed to fetch user watch stats: ${e.message}` + ); + } + } + + public async getUserWatchHistory( + user: User + ): Promise { + try { + if (!user.plexId) { + throw new Error('User does not have an associated Plex ID'); + } + + return ( + await this.axios.get('/api/v2', { + params: { + cmd: 'get_history', + grouping: 1, + order_column: 'date', + order_dir: 'desc', + user_id: user.plexId, + length: 100, + }, + }) + ).data.response.data.data; + } catch (e) { + logger.error( + 'Something went wrong fetching user watch history from Tautulli', + { + label: 'Tautulli API', + errorMessage: e.message, + user: user.displayName, + } + ); + throw new Error( + `[Tautulli] Failed to fetch user watch history: ${e.message}` + ); + } + } +} + +export default TautulliAPI; diff --git a/server/entity/Media.ts b/server/entity/Media.ts index 9cb8cd793..9d106d4f5 100644 --- a/server/entity/Media.ts +++ b/server/entity/Media.ts @@ -145,6 +145,9 @@ class Media { public plexUrl?: string; public plexUrl4k?: string; + public tautulliUrl?: string; + public tautulliUrl4k?: string; + constructor(init?: Partial) { Object.assign(this, init); } @@ -152,6 +155,7 @@ class Media { @AfterLoad() public setPlexUrls(): void { const { machineId, webAppUrl } = getSettings().plex; + const { externalUrl: tautulliUrl } = getSettings().tautulli; if (this.ratingKey) { this.plexUrl = `${ @@ -159,6 +163,10 @@ class Media { }#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${ this.ratingKey }`; + + if (tautulliUrl) { + this.tautulliUrl = `${tautulliUrl}/info?rating_key=${this.ratingKey}`; + } } if (this.ratingKey4k) { @@ -167,6 +175,10 @@ class Media { }#!/server/${machineId}/details?key=%2Flibrary%2Fmetadata%2F${ this.ratingKey4k }`; + + if (tautulliUrl) { + this.tautulliUrl4k = `${tautulliUrl}/info?rating_key=${this.ratingKey4k}`; + } } } diff --git a/server/interfaces/api/mediaInterfaces.ts b/server/interfaces/api/mediaInterfaces.ts index e530d2d2c..d17716d20 100644 --- a/server/interfaces/api/mediaInterfaces.ts +++ b/server/interfaces/api/mediaInterfaces.ts @@ -1,6 +1,22 @@ import type Media from '../../entity/Media'; +import { User } from '../../entity/User'; import { PaginatedResponse } from './common'; export interface MediaResultsResponse extends PaginatedResponse { results: Media[]; } + +export interface MediaWatchDataResponse { + data?: { + users: User[]; + playCount: number; + playCount7Days: number; + playCount30Days: number; + }; + data4k?: { + users: User[]; + playCount: number; + playCount7Days: number; + playCount30Days: number; + }; +} diff --git a/server/interfaces/api/userInterfaces.ts b/server/interfaces/api/userInterfaces.ts index facacd54c..e5f564826 100644 --- a/server/interfaces/api/userInterfaces.ts +++ b/server/interfaces/api/userInterfaces.ts @@ -1,3 +1,4 @@ +import Media from '../../entity/Media'; import { MediaRequest } from '../../entity/MediaRequest'; import type { User } from '../../entity/User'; import { PaginatedResponse } from './common'; @@ -22,3 +23,7 @@ export interface QuotaResponse { movie: QuotaStatus; tv: QuotaStatus; } +export interface UserWatchDataResponse { + recentlyWatched: Media[]; + playCount: number; +} diff --git a/server/lib/settings.ts b/server/lib/settings.ts index c500157cc..4c3b715cd 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -35,6 +35,15 @@ export interface PlexSettings { webAppUrl?: string; } +export interface TautulliSettings { + hostname?: string; + port?: number; + useSsl?: boolean; + urlBase?: string; + apiKey?: string; + externalUrl?: string; +} + export interface DVRSettings { id: number; name: string; @@ -244,6 +253,7 @@ interface AllSettings { vapidPrivate: string; main: MainSettings; plex: PlexSettings; + tautulli: TautulliSettings; radarr: RadarrSettings[]; sonarr: SonarrSettings[]; public: PublicSettings; @@ -290,6 +300,7 @@ class Settings { useSsl: false, libraries: [], }, + tautulli: {}, radarr: [], sonarr: [], public: { @@ -425,6 +436,14 @@ class Settings { this.data.plex = data; } + get tautulli(): TautulliSettings { + return this.data.tautulli; + } + + set tautulli(data: TautulliSettings) { + this.data.tautulli = data; + } + get radarr(): RadarrSettings[] { return this.data.radarr; } diff --git a/server/routes/media.ts b/server/routes/media.ts index 348197821..429b2010f 100644 --- a/server/routes/media.ts +++ b/server/routes/media.ts @@ -1,11 +1,17 @@ import { Router } from 'express'; -import { getRepository, FindOperator, FindOneOptions, In } from 'typeorm'; -import Media from '../entity/Media'; +import { FindOneOptions, FindOperator, getRepository, In } from 'typeorm'; +import TautulliAPI from '../api/tautulli'; import { MediaStatus, MediaType } from '../constants/media'; +import Media from '../entity/Media'; +import { User } from '../entity/User'; +import { + MediaResultsResponse, + MediaWatchDataResponse, +} from '../interfaces/api/mediaInterfaces'; +import { Permission } from '../lib/permissions'; +import { getSettings } from '../lib/settings'; import logger from '../logger'; import { isAuthenticated } from '../middleware/auth'; -import { Permission } from '../lib/permissions'; -import { MediaResultsResponse } from '../interfaces/api/mediaInterfaces'; const mediaRoutes = Router(); @@ -161,4 +167,103 @@ mediaRoutes.delete( } ); +mediaRoutes.get<{ id: string }, MediaWatchDataResponse>( + '/:id/watch_data', + isAuthenticated(Permission.ADMIN), + async (req, res, next) => { + const settings = getSettings().tautulli; + + if (!settings.hostname || !settings.port || !settings.apiKey) { + return next({ + status: 404, + message: 'Tautulli API not configured.', + }); + } + + const media = await getRepository(Media).findOne({ + where: { id: Number(req.params.id) }, + }); + + if (!media) { + return next({ status: 404, message: 'Media does not exist.' }); + } + + try { + const tautulli = new TautulliAPI(settings); + const userRepository = getRepository(User); + + const response: MediaWatchDataResponse = {}; + + if (media.ratingKey) { + const watchStats = await tautulli.getMediaWatchStats(media.ratingKey); + const watchUsers = await tautulli.getMediaWatchUsers(media.ratingKey); + + const users = await userRepository + .createQueryBuilder('user') + .where('user.plexId IN (:...plexIds)', { + plexIds: watchUsers.map((u) => u.user_id), + }) + .getMany(); + + const playCount = + watchStats.find((i) => i.query_days == 0)?.total_plays ?? 0; + + const playCount7Days = + watchStats.find((i) => i.query_days == 7)?.total_plays ?? 0; + + const playCount30Days = + watchStats.find((i) => i.query_days == 30)?.total_plays ?? 0; + + response.data = { + users: users, + playCount, + playCount7Days, + playCount30Days, + }; + } + + if (media.ratingKey4k) { + const watchStats4k = await tautulli.getMediaWatchStats( + media.ratingKey4k + ); + const watchUsers4k = await tautulli.getMediaWatchUsers( + media.ratingKey4k + ); + + const users = await userRepository + .createQueryBuilder('user') + .where('user.plexId IN (:...plexIds)', { + plexIds: watchUsers4k.map((u) => u.user_id), + }) + .getMany(); + + const playCount = + watchStats4k.find((i) => i.query_days == 0)?.total_plays ?? 0; + + const playCount7Days = + watchStats4k.find((i) => i.query_days == 7)?.total_plays ?? 0; + + const playCount30Days = + watchStats4k.find((i) => i.query_days == 30)?.total_plays ?? 0; + + response.data4k = { + users, + playCount, + playCount7Days, + playCount30Days, + }; + } + + return res.status(200).json(response); + } catch (e) { + logger.error('Something went wrong fetching media watch data', { + label: 'API', + errorMessage: e.message, + mediaId: req.params.id, + }); + next({ status: 500, message: 'Failed to fetch watch data.' }); + } + } +); + export default mediaRoutes; diff --git a/server/routes/settings/index.ts b/server/routes/settings/index.ts index 7a5938e33..eca38bc26 100644 --- a/server/routes/settings/index.ts +++ b/server/routes/settings/index.ts @@ -225,6 +225,21 @@ settingsRoutes.post('/plex/sync', (req, res) => { return res.status(200).json(plexFullScanner.status()); }); +settingsRoutes.get('/tautulli', (_req, res) => { + const settings = getSettings(); + + res.status(200).json(settings.tautulli); +}); + +settingsRoutes.post('/tautulli', async (req, res) => { + const settings = getSettings(); + + Object.assign(settings.tautulli, req.body); + settings.save(); + + return res.status(200).json(settings.tautulli); +}); + settingsRoutes.get( '/plex/users', isAuthenticated(Permission.MANAGE_USERS), diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index 8352726b0..9daa446ac 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -1,8 +1,11 @@ import { Router } from 'express'; import gravatarUrl from 'gravatar-url'; +import { uniqWith } from 'lodash'; import { getRepository, Not } from 'typeorm'; import PlexTvAPI from '../../api/plextv'; +import TautulliAPI from '../../api/tautulli'; import { UserType } from '../../constants/user'; +import Media from '../../entity/Media'; import { MediaRequest } from '../../entity/MediaRequest'; import { User } from '../../entity/User'; import { UserPushSubscription } from '../../entity/UserPushSubscription'; @@ -10,6 +13,7 @@ import { QuotaResponse, UserRequestsResponse, UserResultsResponse, + UserWatchDataResponse, } from '../../interfaces/api/userInterfaces'; import { hasPermission, Permission } from '../../lib/permissions'; import { getSettings } from '../../lib/settings'; @@ -475,7 +479,8 @@ router.get<{ id: string }, QuotaResponse>( ) { return next({ status: 403, - message: 'You do not have permission to access this endpoint.', + message: + "You do not have permission to view this user's request limits.", }); } @@ -492,4 +497,82 @@ router.get<{ id: string }, QuotaResponse>( } ); +router.get<{ id: string }, UserWatchDataResponse>( + '/:id/watch_data', + async (req, res, next) => { + if ( + Number(req.params.id) !== req.user?.id && + !req.user?.hasPermission(Permission.ADMIN) + ) { + return next({ + status: 403, + message: + "You do not have permission to view this user's recently watched media.", + }); + } + + const settings = getSettings().tautulli; + + if (!settings.hostname || !settings.port || !settings.apiKey) { + return next({ + status: 404, + message: 'Tautulli API not configured.', + }); + } + + try { + const mediaRepository = getRepository(Media); + const user = await getRepository(User).findOneOrFail({ + where: { id: Number(req.params.id) }, + select: ['id', 'plexId'], + }); + + const tautulli = new TautulliAPI(settings); + + const watchStats = await tautulli.getUserWatchStats(user); + const watchHistory = await tautulli.getUserWatchHistory(user); + + const media = ( + await Promise.all( + uniqWith(watchHistory, (recordA, recordB) => + recordA.grandparent_rating_key && recordB.grandparent_rating_key + ? recordA.grandparent_rating_key === + recordB.grandparent_rating_key + : recordA.parent_rating_key && recordB.parent_rating_key + ? recordA.parent_rating_key === recordB.parent_rating_key + : recordA.rating_key === recordB.rating_key + ) + .slice(0, 20) + .map( + async (record) => + await mediaRepository.findOne({ + where: { + ratingKey: + record.media_type === 'movie' + ? record.rating_key + : record.grandparent_rating_key, + }, + }) + ) + ) + ).filter((media) => !!media) as Media[]; + + return res.status(200).json({ + recentlyWatched: media, + playCount: watchStats.total_plays, + }); + } catch (e) { + logger.error('Something went wrong fetching user watch data', { + label: 'API', + errorMessage: e.message, + userId: req.params.id, + }); + next({ + status: 500, + message: 'Failed to fetch user watch data.', + }); + } + } +); + export default router; diff --git a/src/components/IssueBlock/index.tsx b/src/components/IssueBlock/index.tsx index 2d3cfb33e..318827814 100644 --- a/src/components/IssueBlock/index.tsx +++ b/src/components/IssueBlock/index.tsx @@ -8,7 +8,7 @@ import Link from 'next/link'; import React from 'react'; import { useIntl } from 'react-intl'; import type Issue from '../../../server/entity/Issue'; -import globalMessages from '../../i18n/globalMessages'; +import { useUser } from '../../hooks/useUser'; import Button from '../Common/Button'; import { issueOptions } from '../IssueModal/constants'; @@ -17,6 +17,7 @@ interface IssueBlockProps { } const IssueBlock: React.FC = ({ issue }) => { + const { user } = useUser(); const intl = useIntl(); const issueOption = issueOptions.find( (opt) => opt.issueType === issue.issueType @@ -27,7 +28,7 @@ const IssueBlock: React.FC = ({ issue }) => { } return ( -
+
@@ -39,7 +40,17 @@ const IssueBlock: React.FC = ({ issue }) => {
- {issue.createdBy.displayName} + + + {issue.createdBy.displayName} + +
@@ -55,9 +66,8 @@ const IssueBlock: React.FC = ({ issue }) => {
-
diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index 9b8898ef3..43064302b 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -1,10 +1,16 @@ -import { ServerIcon } from '@heroicons/react/outline'; +import { ServerIcon, ViewListIcon } from '@heroicons/react/outline'; import { CheckCircleIcon, DocumentRemoveIcon } from '@heroicons/react/solid'; import axios from 'axios'; +import Link from 'next/link'; import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; +import useSWR from 'swr'; import { IssueStatus } from '../../../server/constants/issue'; -import { MediaStatus } from '../../../server/constants/media'; +import { + MediaRequestStatus, + MediaStatus, +} from '../../../server/constants/media'; +import { MediaWatchDataResponse } from '../../../server/interfaces/api/mediaInterfaces'; import { MovieDetails } from '../../../server/models/Movie'; import { TvDetails } from '../../../server/models/Tv'; import useSettings from '../../hooks/useSettings'; @@ -21,17 +27,26 @@ const messages = defineMessages({ manageModalTitle: 'Manage {mediaType}', manageModalIssues: 'Open Issues', manageModalRequests: 'Requests', + manageModalMedia: 'Media', + manageModalMedia4k: '4K Media', + manageModalAdvanced: 'Advanced', manageModalNoRequests: 'No requests.', - manageModalClearMedia: 'Clear Media Data', + manageModalClearMedia: 'Clear Data', manageModalClearMediaWarning: '* This will irreversibly remove all data for this {mediaType}, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.', openarr: 'Open in {arr}', openarr4k: 'Open in 4K {arr}', - downloadstatus: 'Download Status', + downloadstatus: 'Downloads', markavailable: 'Mark as Available', mark4kavailable: 'Mark as Available in 4K', - allseasonsmarkedavailable: '* All seasons will be marked as available.', - // Recreated here for lowercase versions to go with the modal clear media warning + markallseasonsavailable: 'Mark All Seasons as Available', + markallseasons4kavailable: 'Mark All Seasons as Available in 4K', + opentautulli: 'Open in Tautulli', + plays: + '{playCount, number} {playCount, plural, one {play} other {plays}}', + pastdays: 'Past {days, number} Days', + alltime: 'All Time', + playedby: 'Played By', movie: 'movie', tvshow: 'series', }); @@ -60,29 +75,54 @@ interface ManageSlideOverTvProps extends ManageSlideOverProps { const ManageSlideOver: React.FC< ManageSlideOverMovieProps | ManageSlideOverTvProps > = ({ show, mediaType, onClose, data, revalidate }) => { - const { hasPermission } = useUser(); + const { user: currentUser, hasPermission } = useUser(); const intl = useIntl(); const settings = useSettings(); + const { data: watchData } = useSWR( + data.mediaInfo && hasPermission(Permission.ADMIN) + ? `/api/v1/media/${data.mediaInfo.id}/watch_data` + : null + ); const deleteMedia = async () => { - if (data?.mediaInfo?.id) { - await axios.delete(`/api/v1/media/${data?.mediaInfo?.id}`); + if (data.mediaInfo) { + await axios.delete(`/api/v1/media/${data.mediaInfo.id}`); revalidate(); } }; const markAvailable = async (is4k = false) => { - await axios.post(`/api/v1/media/${data?.mediaInfo?.id}/available`, { - is4k, - }); - revalidate(); + if (data.mediaInfo) { + await axios.post(`/api/v1/media/${data.mediaInfo?.id}/available`, { + is4k, + }); + revalidate(); + } }; + const requests = + data.mediaInfo?.requests?.filter( + (request) => request.status !== MediaRequestStatus.DECLINED + ) ?? []; + const openIssues = data.mediaInfo?.issues?.filter( (issue) => issue.status === IssueStatus.OPEN ) ?? []; + const styledPlayCount = (playCount: number): JSX.Element => { + return ( + <> + {intl.formatMessage(messages.plays, { + playCount, + strong: function strong(msg) { + return {msg}; + }, + })} + + ); + }; + return ( onClose()} subText={isMovie(data) ? data.title : data.name} > - {((data?.mediaInfo?.downloadStatus ?? []).length > 0 || - (data?.mediaInfo?.downloadStatus4k ?? []).length > 0) && ( - <> -

- {intl.formatMessage(messages.downloadstatus)} -

-
-
    - {data.mediaInfo?.downloadStatus?.map((status, index) => ( -
  • - -
  • - ))} - {data.mediaInfo?.downloadStatus4k?.map((status, index) => ( -
  • - -
  • - ))} -
-
- - )} - {data?.mediaInfo && - (data.mediaInfo.status !== MediaStatus.AVAILABLE || - (data.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.series4kEnabled)) && ( -
- {data?.mediaInfo && - data?.mediaInfo.status !== MediaStatus.AVAILABLE && ( -
- -
- )} - {data?.mediaInfo && - data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && - settings.currentSettings.series4kEnabled && ( -
- -
- )} - {mediaType === 'tv' && ( -
- {intl.formatMessage(messages.allseasonsmarkedavailable)} -
- )} -
- )} - {hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], { - type: 'or', - }) && - openIssues.length > 0 && ( - <> -

- {intl.formatMessage(messages.manageModalIssues)} +
+ {((data?.mediaInfo?.downloadStatus ?? []).length > 0 || + (data?.mediaInfo?.downloadStatus4k ?? []).length > 0) && ( +
+

+ {intl.formatMessage(messages.downloadstatus)}

-
+
    - {openIssues.map((issue) => ( + {data.mediaInfo?.downloadStatus?.map((status, index) => (
  • - + +
  • + ))} + {data.mediaInfo?.downloadStatus4k?.map((status, index) => ( +
  • +
  • ))}
- +
)} -

- {intl.formatMessage(messages.manageModalRequests)} -

-
-
    - {data.mediaInfo?.requests?.map((request) => ( -
  • - revalidate()} /> -
  • - ))} - {(data.mediaInfo?.requests ?? []).length === 0 && ( -
  • - {intl.formatMessage(messages.manageModalNoRequests)} -
  • + {hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], { + type: 'or', + }) && + openIssues.length > 0 && ( + <> +

    + {intl.formatMessage(messages.manageModalIssues)} +

    +
    +
      + {openIssues.map((issue) => ( +
    • + +
    • + ))} +
    +
    + )} -
-
- {hasPermission(Permission.ADMIN) && - (data?.mediaInfo?.serviceUrl || data?.mediaInfo?.serviceUrl4k) && ( -
- {data?.mediaInfo?.serviceUrl && ( - - - - )} - {data?.mediaInfo?.serviceUrl4k && ( - - - - )} + {requests.length > 0 && ( +
+

+ {intl.formatMessage(messages.manageModalRequests)} +

+
+
    + {requests.map((request) => ( +
  • + revalidate()} + /> +
  • + ))} +
+
)} - {data?.mediaInfo && ( -
- deleteMedia()} - confirmText={intl.formatMessage(globalMessages.areyousure)} - className="w-full" - > - - {intl.formatMessage(messages.manageModalClearMedia)} - -
- {intl.formatMessage(messages.manageModalClearMediaWarning, { - mediaType: intl.formatMessage( - mediaType === 'movie' ? messages.movie : messages.tvshow - ), - })} + {hasPermission(Permission.ADMIN) && + (data.mediaInfo?.serviceUrl || + data.mediaInfo?.tautulliUrl || + watchData?.data?.playCount) && ( +
+

+ {intl.formatMessage(messages.manageModalMedia)} +

+
+ {!!watchData?.data && ( +
+
+
+
+
+ {intl.formatMessage(messages.pastdays, { days: 7 })} +
+
+ {styledPlayCount(watchData.data.playCount7Days)} +
+
+
+
+ {intl.formatMessage(messages.pastdays, { + days: 30, + })} +
+
+ {styledPlayCount(watchData.data.playCount30Days)} +
+
+
+
+ {intl.formatMessage(messages.alltime)} +
+
+ {styledPlayCount(watchData.data.playCount)} +
+
+
+ {!!watchData.data.users.length && ( +
+ + {intl.formatMessage(messages.playedby)} + + + {watchData.data.users.map((user) => ( + + + {user.displayName} + + + ))} + +
+ )} +
+ {data.mediaInfo?.tautulliUrl && ( + + + + )} +
+ )} + {data?.mediaInfo?.serviceUrl && ( + + + + )} +
+
+ )} + {hasPermission(Permission.ADMIN) && + (data.mediaInfo?.serviceUrl4k || + data.mediaInfo?.tautulliUrl4k || + watchData?.data4k?.playCount) && ( +
+

+ {intl.formatMessage(messages.manageModalMedia4k)} +

+
+ {!!watchData?.data4k && ( +
+
+
+
+
+ {intl.formatMessage(messages.pastdays, { days: 7 })} +
+
+ {styledPlayCount(watchData.data4k.playCount7Days)} +
+
+
+
+ {intl.formatMessage(messages.pastdays, { + days: 30, + })} +
+
+ {styledPlayCount(watchData.data4k.playCount30Days)} +
+
+
+
+ {intl.formatMessage(messages.alltime)} +
+
+ {styledPlayCount(watchData.data4k.playCount)} +
+
+
+ {!!watchData.data4k.users.length && ( +
+ + {intl.formatMessage(messages.playedby)} + + + {watchData.data4k.users.map((user) => ( + + + {user.displayName} + + + ))} + +
+ )} +
+ {data.mediaInfo?.tautulliUrl4k && ( + + + + )} +
+ )} + {data?.mediaInfo?.serviceUrl4k && ( + + + + )} +
+
+ )} + {hasPermission(Permission.ADMIN) && data?.mediaInfo && ( +
+

+ {intl.formatMessage(messages.manageModalAdvanced)} +

+
+ {data?.mediaInfo.status !== MediaStatus.AVAILABLE && ( + + )} + {data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && + settings.currentSettings.series4kEnabled && ( + + )} +
+ deleteMedia()} + confirmText={intl.formatMessage(globalMessages.areyousure)} + className="w-full" + > + + + {intl.formatMessage(messages.manageModalClearMedia)} + + +
+ {intl.formatMessage(messages.manageModalClearMediaWarning, { + mediaType: intl.formatMessage( + mediaType === 'movie' ? messages.movie : messages.tvshow + ), + })} +
+
+
-
- )} + )} +
); }; diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index 980531666..b72fdc586 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -353,7 +353,7 @@ const MovieDetails: React.FC = ({ movie }) => { )} - {hasPermission(Permission.MANAGE_REQUESTS) && ( + {hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && ( + +
+
+ + ); + }} + + + )} ); }; diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index a1371c359..ee1b7a52e 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -354,7 +354,7 @@ const TvDetails: React.FC = ({ tv }) => { )} - {hasPermission(Permission.MANAGE_REQUESTS) && ( + {hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && (

{values.problemSeason > 0 && ( -
+