diff --git a/CHANGELOG.md b/CHANGELOG.md index 83f3090d6..d3a30c2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,84 @@ +# [4.31.0](https://github.com/Ombi-app/Ombi/compare/v4.30.0...v4.31.0) (2022-11-18) + + +### Features + +* **sonarr:** Added the ability to add default tags when sending to Sonarr ([#4803](https://github.com/Ombi-app/Ombi/issues/4803)) ([ecfbb8e](https://github.com/Ombi-app/Ombi/commit/ecfbb8eda91e1a90239dcf8be847afcc2394a78e)) + + + +# [4.30.0](https://github.com/Ombi-app/Ombi/compare/v4.29.3...v4.30.0) (2022-11-17) + + +### Features + +* **sonarr:** :sparkles: Add the username to a Sonarr tag when sent to Sonarr ([#4802](https://github.com/Ombi-app/Ombi/issues/4802)) ([1d5fabd](https://github.com/Ombi-app/Ombi/commit/1d5fabd317e3ce8f6dd31f06d15dc81277f39dbd)) + + + +## [4.29.3](https://github.com/Ombi-app/Ombi/compare/v4.29.2...v4.29.3) (2022-11-14) + + +### Bug Fixes + +* **notifications:** Fixed the Partially TV notifications going to the admin [#4797](https://github.com/Ombi-app/Ombi/issues/4797) ([#4799](https://github.com/Ombi-app/Ombi/issues/4799)) ([bcb3e7f](https://github.com/Ombi-app/Ombi/commit/bcb3e7f00380a4c4278f59dc55febf43e6d05d47)) +* Only log error messages from Microsoft ([#4787](https://github.com/Ombi-app/Ombi/issues/4787)) ([c614e0c](https://github.com/Ombi-app/Ombi/commit/c614e0ca5fe5023cbe7ced326145273cd75be85d)) + + + +## [4.29.2](https://github.com/Ombi-app/Ombi/compare/v4.29.1...v4.29.2) (2022-10-24) + + +### Bug Fixes + +* **plex:** Fixed an issue where sometimes the availability checker would throw an exception when checking episodes ([17ba202](https://github.com/Ombi-app/Ombi/commit/17ba2020ee0950c2c0e0e03fdb7835b579da75a9)) + + + +## [4.29.1](https://github.com/Ombi-app/Ombi/compare/v4.29.0...v4.29.1) (2022-10-22) + + +### Bug Fixes + +* Consistently reset loading flag when requesting movies on discover page. ([#4777](https://github.com/Ombi-app/Ombi/issues/4777)) ([a40ab5c](https://github.com/Ombi-app/Ombi/commit/a40ab5cddf769d4147696eca50c1610b466ab99b)) +* **sonarr:** :bug: Fixed an issue where the language list didn't correctly load for power users in the advanced options [#4782](https://github.com/Ombi-app/Ombi/issues/4782) ([2173670](https://github.com/Ombi-app/Ombi/commit/217367047d1568070dd507e54ad3fd2c68f05b88)) + + + +# [4.29.0](https://github.com/Ombi-app/Ombi/compare/v4.28.1...v4.29.0) (2022-10-19) + + +### Bug Fixes + +* Partially Available prevents further TV requests ([#4768](https://github.com/Ombi-app/Ombi/issues/4768)) ([#4779](https://github.com/Ombi-app/Ombi/issues/4779)) ([031e2b9](https://github.com/Ombi-app/Ombi/commit/031e2b9283b239827cabaca4e35f69f2f93a4d7b)) +* Unable to Delete Jellyfin Server ([#4705](https://github.com/Ombi-app/Ombi/issues/4705)) ([#4780](https://github.com/Ombi-app/Ombi/issues/4780)) ([76a0d0d](https://github.com/Ombi-app/Ombi/commit/76a0d0d26893bd480fea4735f77522ac6261a425)) + + +### Features + +* Provide a flag for missing users on Plex Server ([#4688](https://github.com/Ombi-app/Ombi/issues/4688)) ([#4778](https://github.com/Ombi-app/Ombi/issues/4778)) ([b4a14c2](https://github.com/Ombi-app/Ombi/commit/b4a14c2d28218409390e517b226130e3e84efee1)) + + + +## [4.28.1](https://github.com/Ombi-app/Ombi/compare/v4.28.0...v4.28.1) (2022-10-19) + + +### Bug Fixes + +* **plex:** :bug: Fixed not being able to enable watchlist requests in the Plex settings ([3e5158e](https://github.com/Ombi-app/Ombi/commit/3e5158ef9cda58ea2dd3be143f07aa5433691d79)) +* Reworked the version check ([#4719](https://github.com/Ombi-app/Ombi/issues/4719)) ([#4781](https://github.com/Ombi-app/Ombi/issues/4781)) ([55855c5](https://github.com/Ombi-app/Ombi/commit/55855c5adda3cd1c51b7fbd0c19b469fc813f98e)) + + + +# [4.28.0](https://github.com/Ombi-app/Ombi/compare/v4.27.8...v4.28.0) (2022-10-07) + + +### Features + +* **plex:** ✨ Added the ability to configure the watchlist to request the whole TV show rather than latest season ([#4774](https://github.com/Ombi-app/Ombi/issues/4774)) ([fa65712](https://github.com/Ombi-app/Ombi/commit/fa65712bd570fe8d5d21b8ca0abe182b84960017)) + + + ## [4.27.8](https://github.com/Ombi-app/Ombi/compare/v4.27.7...v4.27.8) (2022-10-07) @@ -303,106 +384,3 @@ -# [4.17.0](https://github.com/Ombi-app/Ombi/compare/v4.16.17...v4.17.0) (2022-04-25) - - -### Features - -* **discover:** Add original language filter ([ef7ec86](https://github.com/Ombi-app/Ombi/commit/ef7ec861d8aede2a4817752c990617f583805391)) - - - -## [4.16.17](https://github.com/Ombi-app/Ombi/compare/v4.16.16...v4.16.17) (2022-04-25) - - - -## [4.16.16](https://github.com/Ombi-app/Ombi/compare/v4.16.15...v4.16.16) (2022-04-25) - - -### Bug Fixes - -* **4616:** :bug: fixed mandatory fields ([d8f2260](https://github.com/Ombi-app/Ombi/commit/d8f2260c7ae3ed48386743b7adbd06e284487034)) - - - -## [4.16.15](https://github.com/Ombi-app/Ombi/compare/v4.16.14...v4.16.15) (2022-04-24) - - - -## [4.16.14](https://github.com/Ombi-app/Ombi/compare/v4.16.13...v4.16.14) (2022-04-19) - - - -## [4.16.13](https://github.com/Ombi-app/Ombi/compare/v4.16.12...v4.16.13) (2022-04-19) - - - -## [4.16.12](https://github.com/Ombi-app/Ombi/compare/v4.16.11...v4.16.12) (2022-04-19) - - - -## [4.16.11](https://github.com/Ombi-app/Ombi/compare/v4.16.10...v4.16.11) (2022-04-14) - - -### Bug Fixes - -* Set the default job for the watchlist import to hourly instead of daily ([75906af](https://github.com/Ombi-app/Ombi/commit/75906af0adee3e3c68d825c3aaa8f7b918461b1f)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0e8a64b](https://github.com/Ombi-app/Ombi/commit/0e8a64b8ca00d210fbe843ac2c3f6af218d80cbc)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([7b0ad61](https://github.com/Ombi-app/Ombi/commit/7b0ad61bfcff3986b33180dc64022cba7ea8eefb)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([4fc2c1f](https://github.com/Ombi-app/Ombi/commit/4fc2c1f24534085a783a3d5791f5533b68272153)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([76ab733](https://github.com/Ombi-app/Ombi/commit/76ab733b91791e4d93d184f3c7d0779c6a388695)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([06e4cef](https://github.com/Ombi-app/Ombi/commit/06e4cefa7b4e55b860da9a64f461f6ec8fa17367)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([c12d89d](https://github.com/Ombi-app/Ombi/commit/c12d89d6781a337520977ad285f8d08c93f434dd)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([bc0c2f6](https://github.com/Ombi-app/Ombi/commit/bc0c2f622e34fb5a2711039d9ed7aad34f982b15)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([e4b00e6](https://github.com/Ombi-app/Ombi/commit/e4b00e6b3468bd9389eeb02fc6ad7daf27abc3b3)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d1998d3](https://github.com/Ombi-app/Ombi/commit/d1998d326f999a38586d0a351a20c5448df95842)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([bee4ccb](https://github.com/Ombi-app/Ombi/commit/bee4ccb804594e7385b1fbdc9fe2ef5c42e0d21f)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([80233ed](https://github.com/Ombi-app/Ombi/commit/80233ed560cc976e83570d0655c3472f20171fb3)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([8a78adc](https://github.com/Ombi-app/Ombi/commit/8a78adc9bb62f277f2b213dcb3847ed6d0089fcb)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d04c60a](https://github.com/Ombi-app/Ombi/commit/d04c60aa5909b47ba6bffa6f66b03079cbd43521)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([92a785e](https://github.com/Ombi-app/Ombi/commit/92a785e736fa4b72a45270da2d0f4661df433078)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([634982d](https://github.com/Ombi-app/Ombi/commit/634982df2661cefab5ea9f5163fe04a005cc0171)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([b404baa](https://github.com/Ombi-app/Ombi/commit/b404baad6d0aeaa1561701e0db8db4e78613a364)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d14f11e](https://github.com/Ombi-app/Ombi/commit/d14f11e0eb20ab0a68e765ee77968b3b3e54e995)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([7cf64f9](https://github.com/Ombi-app/Ombi/commit/7cf64f909d78908edaabeffb8a39a7d02e73fe7e)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0c9e1ec](https://github.com/Ombi-app/Ombi/commit/0c9e1ec090827080cc8f7393e5e91456ff37d691)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([3b0b730](https://github.com/Ombi-app/Ombi/commit/3b0b730cb02efe24f6d4026e5fdb20d37e495119)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([6ed1a03](https://github.com/Ombi-app/Ombi/commit/6ed1a03b7ff4077f09ea9e13394b18b0d138f4c3)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([2941acd](https://github.com/Ombi-app/Ombi/commit/2941acd3b2ec74a5e6aeea275ab5a39d2653f37f)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([c075a1a](https://github.com/Ombi-app/Ombi/commit/c075a1a66784d975eaf60f2dfbbcbe048f2f63d7)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([76bd81c](https://github.com/Ombi-app/Ombi/commit/76bd81c3ca55a98c6ec944a838dc01294a6193a6)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0d38275](https://github.com/Ombi-app/Ombi/commit/0d3827507e002bcf58f673e97ffcc3bd25dcf337)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([5c99601](https://github.com/Ombi-app/Ombi/commit/5c99601b07aec1a65d0186a4c4327440811e64c6)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([01546a0](https://github.com/Ombi-app/Ombi/commit/01546a0f7f86379528b486463246ef9bdfb9033e)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d7fea78](https://github.com/Ombi-app/Ombi/commit/d7fea7843aaaab7ddff8dc31ca6d2a9117471dcc)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([1a6b95d](https://github.com/Ombi-app/Ombi/commit/1a6b95d45c220310213b8d811272a63f0f6ff42b)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([fa10174](https://github.com/Ombi-app/Ombi/commit/fa1017422c4efd4b0897871bd3c671151774d7c3)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0c31e62](https://github.com/Ombi-app/Ombi/commit/0c31e628df376aac6d56ae67c7c705a9a4a7c080)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([6399643](https://github.com/Ombi-app/Ombi/commit/63996437a02fe10ffae6822ffa15369bec0a6b36)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([5826e2d](https://github.com/Ombi-app/Ombi/commit/5826e2d9a1c3f1210a87fa270dc0c81bac32944a)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d434514](https://github.com/Ombi-app/Ombi/commit/d43451405be489254d7cdc7755d5f516a1e495a5)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0b9596d](https://github.com/Ombi-app/Ombi/commit/0b9596d807178f5e071113ec0347868ec7f0960b)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([8c4c0b2](https://github.com/Ombi-app/Ombi/commit/8c4c0b262978c1303767af360d802c4b4c2b4d24)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([289ab77](https://github.com/Ombi-app/Ombi/commit/289ab77b0e04aae235b6f6cebc86e0a8d1f0cf2b)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([30e3417](https://github.com/Ombi-app/Ombi/commit/30e3417285a4eed18d429d7776f0e74096e834c0)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([6c0a5da](https://github.com/Ombi-app/Ombi/commit/6c0a5dadd4b8f37760252eb0fe7f88908f55506d)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d5bf969](https://github.com/Ombi-app/Ombi/commit/d5bf9692ce1fc0ccfe7beca6dd200c78be177bdc)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([8a9e7ea](https://github.com/Ombi-app/Ombi/commit/8a9e7ea588aefbcd73ed82625887e3614e1703ea)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([01047a3](https://github.com/Ombi-app/Ombi/commit/01047a3fd67153f3ff16f860d2c7b50213e8d9b2)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([698a23f](https://github.com/Ombi-app/Ombi/commit/698a23fb83f323cdd1dd57cb49803079d44214a7)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([24eb842](https://github.com/Ombi-app/Ombi/commit/24eb842fc4424f7bcc3ec2949d7f5472492e96f6)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([ac8b16a](https://github.com/Ombi-app/Ombi/commit/ac8b16a3051ad71dbd54a8973c7dd847b564a515)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([f428ce6](https://github.com/Ombi-app/Ombi/commit/f428ce6a700c081437703839bc84d2f2b1138bcc)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([94b16df](https://github.com/Ombi-app/Ombi/commit/94b16dfe09bf1d2cd6286777d74eb5d4496abbbb)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([4881775](https://github.com/Ombi-app/Ombi/commit/4881775eda69a8f136ce0d8fbbf970e3d0406dc9)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([8297db9](https://github.com/Ombi-app/Ombi/commit/8297db91e85da308bde6fb09ad78347dee063630)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d1152ab](https://github.com/Ombi-app/Ombi/commit/d1152ab7674243daa528c524c0cdc87d81ad49c9)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([eb2788b](https://github.com/Ombi-app/Ombi/commit/eb2788b761b55c487a59a049427ca08f6c10e836)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([21a794c](https://github.com/Ombi-app/Ombi/commit/21a794cbc0a5fa735ca0347c8f7f1ac04a487fbc)) - - - -## [4.10.2](https://github.com/Ombi-app/Ombi/compare/v4.10.1...v4.10.2) (2022-01-22) - - - diff --git a/README.md b/README.md index e54604cb4..098c7f6d0 100644 --- a/README.md +++ b/README.md @@ -257,6 +257,13 @@ Here are some of the features Ombi has: Patrick Collins + + + xweskingx +
+ Wesley King +
+ chriscpritchard @@ -277,15 +284,15 @@ Here are some of the features Ombi has:
Stephen Panzer
- + + aptalca
Aptalca
- - + dr3am37 @@ -320,15 +327,15 @@ Here are some of the features Ombi has:
Andrew Metzger
- + + au5ton
Austin Jackson
- - + D34DC3N73R @@ -363,15 +370,15 @@ Here are some of the features Ombi has:
Jack Steel
- + + jpeters
Jeffrey Peters
- - + MariusSchiffer @@ -406,15 +413,15 @@ Here are some of the features Ombi has:
Javier Pastor
- + + AbeKline
Abe Kline
- - + XanderStrike @@ -449,15 +456,15 @@ Here are some of the features Ombi has:
Calvin
- + + origamirobot
Chris Lees
- - + cdemi @@ -492,15 +499,15 @@ Here are some of the features Ombi has:
David Torosyan
- + + onedr0p
Devin Buhl
- - + elisspace @@ -535,15 +542,15 @@ Here are some of the features Ombi has:
Igor Borges
- + + ImgBotApp
Imgbot
- - + JPyke3 @@ -578,15 +585,15 @@ Here are some of the features Ombi has:
Jon Bloom
- + + jonocairns
Jono Cairns
- - + krisklosterman @@ -621,15 +628,15 @@ Here are some of the features Ombi has:
Madeleine Schönemann
- + + marleypowell
Marley
- - + mattmattmatt @@ -664,15 +671,15 @@ Here are some of the features Ombi has:
Nathan Miller
- + + cqxmzz
Qiming Chen
- - + randallbruder @@ -707,15 +714,15 @@ Here are some of the features Ombi has:
Shoghi
- + + Teifun2
Teifun2
- - + thomasvt1 @@ -750,15 +757,15 @@ Here are some of the features Ombi has:
Travis Bybee
- + + Xirg
Xirg
- - + bazhip @@ -793,15 +800,15 @@ Here are some of the features Ombi has:
Dorian ALKOUM
- + + echel0n
Echel0n
- - + m4tta @@ -836,15 +843,15 @@ Here are some of the features Ombi has:
Sirmarv
- + + tdorsey
Tdorsey
- - + thegame3202 diff --git a/src/Ombi.Api.Sonarr/ISonarrV3Api.cs b/src/Ombi.Api.Sonarr/ISonarrV3Api.cs index 1d3ea3468..da7b1ae91 100644 --- a/src/Ombi.Api.Sonarr/ISonarrV3Api.cs +++ b/src/Ombi.Api.Sonarr/ISonarrV3Api.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Ombi.Api.Sonarr.Models; -using System.Net.Http; using Ombi.Api.Sonarr.Models.V3; namespace Ombi.Api.Sonarr @@ -9,5 +8,7 @@ namespace Ombi.Api.Sonarr public interface ISonarrV3Api : ISonarrApi { Task> LanguageProfiles(string apiKey, string baseUrl); + Task CreateTag(string apiKey, string baseUrl, string tagName); + Task GetTag(int tagId, string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Sonarr/Models/NewSeries.cs b/src/Ombi.Api.Sonarr/Models/NewSeries.cs index d6f721b0b..9d4a894af 100644 --- a/src/Ombi.Api.Sonarr/Models/NewSeries.cs +++ b/src/Ombi.Api.Sonarr/Models/NewSeries.cs @@ -26,6 +26,7 @@ namespace Ombi.Api.Sonarr.Models public string seriesType { get; set; } public int id { get; set; } public List images { get; set; } + public List tags { get; set; } // V3 Property public int languageProfileId { get; set; } diff --git a/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs b/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs index 3ade006d5..7c9b80f5c 100644 --- a/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs +++ b/src/Ombi.Api.Sonarr/Models/SonarrSeries.cs @@ -40,7 +40,7 @@ namespace Ombi.Api.Sonarr.Models public string titleSlug { get; set; } public string certification { get; set; } public string[] genres { get; set; } - public object[] tags { get; set; } + public List tags { get; set; } public DateTime added { get; set; } public Ratings ratings { get; set; } public int qualityProfileId { get; set; } diff --git a/src/Ombi.Api.Sonarr/SonarrV3Api.cs b/src/Ombi.Api.Sonarr/SonarrV3Api.cs index 71c230f35..dbc87eee1 100644 --- a/src/Ombi.Api.Sonarr/SonarrV3Api.cs +++ b/src/Ombi.Api.Sonarr/SonarrV3Api.cs @@ -11,7 +11,6 @@ namespace Ombi.Api.Sonarr { public SonarrV3Api(IApi api) : base(api) { - } protected override string ApiBaseUrl => "/api/v3/"; @@ -30,5 +29,22 @@ namespace Ombi.Api.Sonarr request.AddHeader("X-Api-Key", apiKey); return await Api.Request>(request); } + + public Task CreateTag(string apiKey, string baseUrl, string tagName) + { + var request = new Request($"{ApiBaseUrl}tag", baseUrl, HttpMethod.Post); + request.AddHeader("X-Api-Key", apiKey); + request.AddJsonBody(new { Label = tagName }); + + return Api.Request(request); + } + + public Task GetTag(int tagId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiBaseUrl}tag/{tagId}", baseUrl, HttpMethod.Get); + request.AddHeader("X-Api-Key", apiKey); + + return Api.Request(request); + } } } diff --git a/src/Ombi.Core/Models/TesterResultModel.cs b/src/Ombi.Core/Models/TesterResultModel.cs index f23e14d43..563fa5cb2 100644 --- a/src/Ombi.Core/Models/TesterResultModel.cs +++ b/src/Ombi.Core/Models/TesterResultModel.cs @@ -3,6 +3,7 @@ public class TesterResultModel { public bool IsValid { get; set; } + public string Version { get; set; } public string ExpectedSubDir { get; set; } } } diff --git a/src/Ombi.Core/Senders/SonarrSendOptions.cs b/src/Ombi.Core/Senders/SonarrSendOptions.cs new file mode 100644 index 000000000..1bad4604e --- /dev/null +++ b/src/Ombi.Core/Senders/SonarrSendOptions.cs @@ -0,0 +1,10 @@ +using Ombi.Api.Sonarr.Models; +using System.Collections.Generic; + +namespace Ombi.Core.Senders +{ + internal class SonarrSendOptions + { + public List Tags { get; set; } = new List(); + } +} diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 1daa1bf0f..dda7d305d 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Microsoft.VisualBasic; using Ombi.Api.DogNzb; using Ombi.Api.DogNzb.Models; using Ombi.Api.SickRage; @@ -155,11 +157,13 @@ namespace Ombi.Core.Senders { return null; } + var options = new SonarrSendOptions(); int qualityToUse; var languageProfileId = s.LanguageProfile; string rootFolderPath; string seriesType; + int? tagToUse = null; var profiles = await UserQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId); @@ -190,6 +194,7 @@ namespace Ombi.Core.Senders } } seriesType = "anime"; + tagToUse = s.AnimeTag; } else { @@ -209,6 +214,7 @@ namespace Ombi.Core.Senders } } seriesType = "standard"; + tagToUse = s.Tag; } // Overrides on the request take priority @@ -240,6 +246,16 @@ namespace Ombi.Core.Senders try { + if (tagToUse.HasValue) + { + options.Tags.Add(tagToUse.Value); + } + if (s.SendUserTags) + { + var userTag = await GetOrCreateTag(model, s); + options.Tags.Add(userTag.id); + } + // Does the series actually exist? var allSeries = await SonarrApi.GetSeries(s.ApiKey, s.FullUri); var existingSeries = allSeries.FirstOrDefault(x => x.tvdbId == model.ParentRequest.TvDbId); @@ -265,10 +281,10 @@ namespace Ombi.Core.Senders ignoreEpisodesWithoutFiles = false, // We want all missing searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly. }, - languageProfileId = languageProfileId - }; + languageProfileId = languageProfileId, + tags = options.Tags + }; - // Montitor the correct seasons, // If we have that season in the model then it's monitored! @@ -280,11 +296,11 @@ namespace Ombi.Core.Senders throw new Exception(string.Join(',', result.ErrorMessages)); } existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri); - await SendToSonarr(model, existingSeries, s); + await SendToSonarr(model, existingSeries, s, options); } else { - await SendToSonarr(model, existingSeries, s); + await SendToSonarr(model, existingSeries, s, options); } return new NewSeries @@ -303,7 +319,30 @@ namespace Ombi.Core.Senders } } - private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s) + private async Task GetOrCreateTag(ChildRequests model, SonarrSettings s) + { + var tagName = model.RequestedUser.UserName; + // Does tag exist? + + var allTags = await SonarrV3Api.GetTags(s.ApiKey, s.FullUri); + var existingTag = allTags.FirstOrDefault(x => x.label.Equals(tagName, StringComparison.InvariantCultureIgnoreCase)); + existingTag ??= await SonarrV3Api.CreateTag(s.ApiKey, s.FullUri, tagName); + + return existingTag; + } + + private async Task GetTag(int tagId, SonarrSettings s) + { + var tag = await SonarrV3Api.GetTag(tagId, s.ApiKey, s.FullUri); + if (tag == null) + { + Logger.LogError($"Tag ID {tagId} does not exist in sonarr. Please update the settings"); + return null; + } + return tag; + } + + private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s, SonarrSendOptions options) { // Check to ensure we have the all the seasons, ensure the Sonarr metadata has grabbed all the data Season existingSeason = null; @@ -321,15 +360,27 @@ namespace Ombi.Core.Senders } } - var episodesToUpdate = new List(); - // Ok, now let's sort out the episodes. + // Does the show have the correct tags we are expecting + if (options.Tags.Any()) + { + result.tags ??= options.Tags; + var tagsToAdd = options.Tags.Except(result.tags); + + if (tagsToAdd.Any()) + { + result.tags.AddRange(tagsToAdd); + } + result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); + } if (model.SeriesType == SeriesType.Anime) { result.seriesType = "anime"; - await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); + result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); } + var episodesToUpdate = new List(); + // Ok, now let's sort out the episodes. var sonarrEpisodes = await SonarrApi.GetEpisodes(result.id, s.ApiKey, s.FullUri); var sonarrEpList = sonarrEpisodes.ToList() ?? new List(); while (!sonarrEpList.Any()) diff --git a/src/Ombi.I18n/Resources/Texts.ca.resx b/src/Ombi.I18n/Resources/Texts.ca.resx new file mode 100644 index 000000000..11f80dd71 --- /dev/null +++ b/src/Ombi.I18n/Resources/Texts.ca.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nous Àlbums + + + Noves Pel·lícules + + + Sèries Noves + + + Gèneres: + + + Tipus: + + + Temporada: + + + Episodis: + + + Desenvolupat per + + + Cancel·la la subscripció + + + Àlbum + + + Pel·lícula + + + Sèries de TV + + \ No newline at end of file diff --git a/src/Ombi.Notifications/Agents/MobileNotification.cs b/src/Ombi.Notifications/Agents/MobileNotification.cs index 0d21f39a5..36a5c6fb6 100644 --- a/src/Ombi.Notifications/Agents/MobileNotification.cs +++ b/src/Ombi.Notifications/Agents/MobileNotification.cs @@ -63,7 +63,7 @@ namespace Ombi.Notifications.Agents // Get admin devices var playerIds = await GetPrivilegedUsersPlayerIds(); - await Send(playerIds, notification, settings, model, true); + await Send(playerIds, notification); } protected override async Task NewIssue(NotificationOptions model, MobileNotificationSettings settings) @@ -83,7 +83,7 @@ namespace Ombi.Notifications.Agents // Get admin devices var playerIds = await GetAdmins(); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } protected override async Task IssueComment(NotificationOptions model, MobileNotificationSettings settings) @@ -107,13 +107,13 @@ namespace Ombi.Notifications.Agents model.Substitutes.TryGetValue("IssueId", out var issueId); // Send to user var playerIds = await GetUsersForIssue(model, int.Parse(issueId), NotificationType.IssueComment); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } else { // Send to admin var playerIds = await GetAdmins(); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } } } @@ -136,7 +136,7 @@ namespace Ombi.Notifications.Agents // Send to user var playerIds = await GetUsers(model, NotificationType.IssueResolved); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } @@ -158,7 +158,7 @@ namespace Ombi.Notifications.Agents // Get admin devices var playerIds = await GetAdmins(); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } protected override async Task RequestDeclined(NotificationOptions model, MobileNotificationSettings settings) @@ -179,7 +179,7 @@ namespace Ombi.Notifications.Agents // Send to user var playerIds = await GetUsers(model, NotificationType.RequestDeclined); await AddSubscribedUsers(playerIds); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } protected override async Task RequestApproved(NotificationOptions model, MobileNotificationSettings settings) @@ -201,7 +201,7 @@ namespace Ombi.Notifications.Agents var playerIds = await GetUsers(model, NotificationType.RequestApproved); await AddSubscribedUsers(playerIds); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } protected override async Task AvailableRequest(NotificationOptions model, MobileNotificationSettings settings) @@ -225,7 +225,7 @@ namespace Ombi.Notifications.Agents var playerIds = await GetUsers(model, NotificationType.RequestAvailable); await AddSubscribedUsers(playerIds); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } private static Dictionary GetNotificationData(NotificationMessageContent parsed, NotificationType type) @@ -240,7 +240,7 @@ namespace Ombi.Notifications.Agents throw new NotImplementedException(); } - protected async Task Send(List playerIds, NotificationMessage model, MobileNotificationSettings settings, NotificationOptions requestModel, bool isAdminNotification = false) + protected async Task Send(List playerIds, NotificationMessage model) { if (playerIds == null || !playerIds.Any()) { @@ -276,7 +276,7 @@ namespace Ombi.Notifications.Agents } var playerIds = user.NotificationUserIds.Select(x => x.PlayerId).ToList(); - await Send(playerIds, notification, settings, model); + await Send(playerIds, notification); } private async Task> GetAdmins() @@ -382,13 +382,15 @@ namespace Ombi.Notifications.Agents var notification = new NotificationMessage { Message = parsed.Message, - Subject = "New Request", + Subject = "Request Partially Available", Data = GetNotificationData(parsed, NotificationType.PartiallyAvailable) }; - // Get admin devices - var playerIds = await GetAdmins(); - await Send(playerIds, notification, settings, model, true); + + var playerIds = await GetUsers(model, NotificationType.PartiallyAvailable); + + await AddSubscribedUsers(playerIds); + await Send(playerIds, notification); } } } \ No newline at end of file diff --git a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs index 4e48c7d94..babd85bc5 100644 --- a/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs +++ b/src/Ombi.Schedule.Tests/PlexAvailabilityCheckerTests.cs @@ -176,7 +176,7 @@ namespace Ombi.Schedule.Tests Series = new PlexServerContent { TheMovieDbId = 33.ToString(), - Title = "Test" + Title = "abc" }, EpisodeNumber = 1, SeasonNumber = 2, @@ -226,7 +226,7 @@ namespace Ombi.Schedule.Tests { Series = new PlexServerContent { - Title = "UNITTEST", + Title = "UnitTest", ImdbId = "invlaid", }, EpisodeNumber = 1, diff --git a/src/Ombi.Schedule.Tests/PlexUserImporterTests.cs b/src/Ombi.Schedule.Tests/PlexUserImporterTests.cs index ae29f1ddf..62d058d19 100644 --- a/src/Ombi.Schedule.Tests/PlexUserImporterTests.cs +++ b/src/Ombi.Schedule.Tests/PlexUserImporterTests.cs @@ -324,5 +324,84 @@ namespace Ombi.Schedule.Tests _mocker.Verify(x => x.UpdateAsync(It.Is(x => x.ProviderUserId == "PLEX_ID" && x.Email == "email" && x.UserName == "user")), Times.Once); } + + + [Test] + public async Task Import_Cleanup_Missing_Plex_Users() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings + { + ImportPlexAdmin = true, + ImportPlexUsers = true, + DefaultRoles = new List + { + OmbiRoles.RequestMovie + }, + CleanupPlexUsers = true, + }); + _mocker.Setup>(x => x.GetUsers(It.IsAny())).ReturnsAsync(new PlexFriends + { + User = new UserFriends[] + { + } + }); + _mocker.Setup>(x => x.GetAccount(It.IsAny())).ReturnsAsync(new PlexAccount + { + user = new User + { + email = "email", + authentication_token = "user_token", + title = "user_title", + username = "user_username", + id = "user_id", + } + }); + + _mocker.Setup>(x => x.CreateAsync(It.Is(x => x.UserName == "user_username" && x.Email == "email" && x.ProviderUserId == "user_id" && x.UserType == UserType.PlexUser))) + .ReturnsAsync(IdentityResult.Success); + _mocker.Setup>(x => x.AddToRoleAsync(It.Is(x => x.UserName == "user_username"), It.Is(x => x == OmbiRoles.Admin))) + .ReturnsAsync(IdentityResult.Success); + + await _subject.Execute(null); + + _mocker.Verify(x => x.DeleteAsync(It.Is(x => x.ProviderUserId == "PLEX_ID" && x.Email == "dupe" && x.UserName == "plex")), Times.Once); + } + + [Test] + public async Task Import_Cleanup_Missing_Plex_Admin() + { + _mocker.Setup, Task>(x => x.GetSettingsAsync()) + .ReturnsAsync(new UserManagementSettings + { + ImportPlexAdmin = true, + ImportPlexUsers = false, + DefaultRoles = new List + { + OmbiRoles.RequestMovie + }, + CleanupPlexUsers = true, + }); + _mocker.Setup>(x => x.GetAccount(It.IsAny())).ReturnsAsync(new PlexAccount + { + user = new User + { + email = "diff_email", + authentication_token = "user_token", + title = "user_title", + username = "diff_username", + id = "diff_user_id", + } + }); + + _mocker.Setup>(x => x.CreateAsync(It.Is(x => x.UserName == "diff_username" && x.Email == "diff_email" && x.ProviderUserId == "diff_user_id" && x.UserType == UserType.PlexUser))) + .ReturnsAsync(IdentityResult.Success); + _mocker.Setup>(x => x.AddToRoleAsync(It.Is(x => x.UserName == "diff_username"), It.Is(x => x == OmbiRoles.Admin))) + .ReturnsAsync(IdentityResult.Success); + + await _subject.Execute(null); + + _mocker.Verify(x => x.DeleteAsync(It.Is(x => x.ProviderUserId == "PLEX_ID" && x.Email == "dupe" && x.UserName == "plex")), Times.Once); + } } } diff --git a/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs b/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs index 5e724b6f8..9537710a3 100644 --- a/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs +++ b/src/Ombi.Schedule.Tests/PlexWatchlistImportTests.cs @@ -345,7 +345,6 @@ namespace Ombi.Schedule.Tests [Test] public async Task TvRequestFromWatchList_AlreadyRequested() { - _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true }); _mocker.Setup>(x => x.GetWatchlist(It.IsAny(), It.IsAny())).ReturnsAsync(new PlexWatchlistContainer { @@ -540,5 +539,56 @@ namespace Ombi.Schedule.Tests _mocker.Verify>(x => x.Add(It.IsAny()), Times.Never); _mocker.Verify>(x => x.GetAll(), Times.Once); } + + + [Test] + public async Task TvRequestFromWatchList_RequestAllSeasons() + { + + _mocker.Setup, Task>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true, MonitorAll = true }); + _mocker.Setup>(x => x.GetWatchlist(It.IsAny(), It.IsAny())).ReturnsAsync(new PlexWatchlistContainer + { + MediaContainer = new PlexWatchlist + { + Metadata = new List + { + new Metadata + { + type = "show", + ratingKey = "abc" + } + } + } + }); + _mocker.Setup>(x => x.GetWatchlistMetadata("abc", It.IsAny(), It.IsAny())) + .ReturnsAsync(new PlexWatchlistMetadataContainer + { + MediaContainer = new PlexWatchlistMetadata + { + Metadata = new WatchlistMetadata[] + { + new WatchlistMetadata + { + Guid = new List + { + new PlexGuids + { + Id = "tmdb://123" + } + } + } + } + + } + }); + _mocker.Setup>(x => x.RequestTvShow(It.IsAny())) + .ReturnsAsync(new RequestEngineResult { RequestId = 1 }); + await _subject.Execute(_context.Object); + _mocker.Verify(x => x.RequestTvShow(It.Is(x => x.TheMovieDbId == 123 && x.LatestSeason == false && x.RequestAll == true)), Times.Once); + _mocker.Verify(x => x.GetWatchlistMetadata("abc", It.IsAny(), It.IsAny()), Times.Once); + _mocker.Verify(x => x.SetUser(It.Is(x => x.Id == "abc")), Times.Once); + _mocker.Verify>(x => x.GetAll(), Times.Once); + _mocker.Verify>(x => x.Add(It.IsAny()), Times.Once); + } } } diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs index 8172db205..112998a82 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexAvailabilityChecker.cs @@ -101,7 +101,7 @@ namespace Ombi.Schedule.Jobs.Plex { // Let's try and match the series by name seriesEpisodes = plexEpisodes.Where(x => - x.Series.Title.Equals(child.Title, StringComparison.InvariantCultureIgnoreCase)); + x.Series.Title == child.Title); } await ProcessTvShow(seriesEpisodes, child); diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index 388686548..eb31a5c8f 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -312,7 +312,7 @@ namespace Ombi.Schedule.Jobs.Plex { break; } - if (quality.Equals(existing.Quality)) + if (quality == null || quality.Equals(existing.Quality)) { // We got it continue; diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs index 4cbf73d60..3b931b586 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexUserImporter.cs @@ -56,6 +56,8 @@ namespace Ombi.Schedule.Jobs.Plex await _notification.SendNotificationToAdmins("Plex User Importer Started"); var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.PlexUser).ToListAsync(); + List newOrUpdatedUsers = new List(); + foreach (var server in settings.Servers) { if (string.IsNullOrEmpty(server.PlexAuthToken)) @@ -63,23 +65,46 @@ namespace Ombi.Schedule.Jobs.Plex continue; } + if (userManagementSettings.ImportPlexAdmin) { - await ImportAdmin(userManagementSettings, server, allUsers); + OmbiUser newOrUpdatedAdmin = await ImportAdmin(userManagementSettings, server, allUsers); + if (newOrUpdatedAdmin != null) + { + newOrUpdatedUsers.Add(newOrUpdatedAdmin); + } } if (userManagementSettings.ImportPlexUsers) { - await ImportPlexUsers(userManagementSettings, allUsers, server); + newOrUpdatedUsers.AddRange(await ImportPlexUsers(userManagementSettings, allUsers, server)); } } + if (userManagementSettings.CleanupPlexUsers) + { + // Refresh users from updates + allUsers = await _userManager.Users.Where(x => x.UserType == UserType.PlexUser) + .ToListAsync(); + var missingUsers = allUsers + .Where(x => !newOrUpdatedUsers.Contains(x)); + foreach (var ombiUser in missingUsers) + { + _log.LogInformation("Deleting user {0} not found in Plex Server.", ombiUser.UserName); + await _userManager.DeleteAsync(ombiUser); + } + } + + await _notification.SendNotificationToAdmins("Plex User Importer Finished"); } - private async Task ImportPlexUsers(UserManagementSettings userManagementSettings, List allUsers, PlexServers server) + private async Task> ImportPlexUsers(UserManagementSettings userManagementSettings, + List allUsers, PlexServers server) { var users = await _api.GetUsers(server.PlexAuthToken); + List newOrUpdatedUsers = new List(); + foreach (var plexUser in users.User) { // Check if we should import this user @@ -129,19 +154,21 @@ namespace Ombi.Schedule.Jobs.Plex { continue; } + // Get the new user object to avoid any concurrency failures + var dbUser = + await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == newUser.UserName); if (userManagementSettings.DefaultRoles.Any()) { - // Get the new user object to avoid any concurrency failures - var dbUser = - await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == newUser.UserName); foreach (var defaultRole in userManagementSettings.DefaultRoles) { await _userManager.AddToRoleAsync(dbUser, defaultRole); } } + newOrUpdatedUsers.Add(dbUser); } else { + newOrUpdatedUsers.Add(existingPlexUser); // Do we need to update this user? existingPlexUser.Email = plexUser.Email; existingPlexUser.UserName = plexUser.Username; @@ -149,9 +176,12 @@ namespace Ombi.Schedule.Jobs.Plex await _userManager.UpdateAsync(existingPlexUser); } } + + return newOrUpdatedUsers; } - private async Task ImportAdmin(UserManagementSettings settings, PlexServers server, List allUsers) + private async Task ImportAdmin(UserManagementSettings settings, PlexServers server, + List allUsers) { var plexAdmin = (await _api.GetAccount(server.PlexAuthToken)).user; @@ -166,7 +196,7 @@ namespace Ombi.Schedule.Jobs.Plex adminUserFromDb.UserName = plexAdmin.username; adminUserFromDb.ProviderUserId = plexAdmin.id; await _userManager.UpdateAsync(adminUserFromDb); - return; + return adminUserFromDb; } // Ensure we don't have a user with the same username @@ -174,7 +204,7 @@ namespace Ombi.Schedule.Jobs.Plex if (await _userManager.Users.AnyAsync(x => x.NormalizedUserName == normalUsername)) { _log.LogWarning($"Cannot add user {plexAdmin.username} because their username is already in Ombi, skipping this user"); - return; + return null; } var newUser = new OmbiUser @@ -190,11 +220,12 @@ namespace Ombi.Schedule.Jobs.Plex var result = await _userManager.CreateAsync(newUser); if (!LogResult(result)) { - return; + return null; } var roleResult = await _userManager.AddToRoleAsync(newUser, OmbiRoles.Admin); LogResult(roleResult); + return newUser; } private bool LogResult(IdentityResult result) diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs b/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs index 3891723b7..326cf35f9 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexWatchlistImport.cs @@ -125,7 +125,7 @@ namespace Ombi.Schedule.Jobs.Plex switch (item.type) { case "show": - await ProcessShow(int.Parse(providerIds.TheMovieDb), user); + await ProcessShow(int.Parse(providerIds.TheMovieDb), user, settings.MonitorAll); break; case "movie": await ProcessMovie(int.Parse(providerIds.TheMovieDb), user); @@ -165,10 +165,16 @@ namespace Ombi.Schedule.Jobs.Plex } } - private async Task ProcessShow(int theMovieDbId, OmbiUser user) + private async Task ProcessShow(int theMovieDbId, OmbiUser user, bool requestAll) { _tvRequestEngine.SetUser(user); - var response = await _tvRequestEngine.RequestTvShow(new TvRequestViewModelV2 { LatestSeason = true, TheMovieDbId = theMovieDbId, Source = RequestSource.PlexWatchlist }); + var requestModel = new TvRequestViewModelV2 { LatestSeason = true, TheMovieDbId = theMovieDbId, Source = RequestSource.PlexWatchlist }; + if (requestAll) + { + requestModel.RequestAll = true; + requestModel.LatestSeason = false; + } + var response = await _tvRequestEngine.RequestTvShow(requestModel); if (response.IsError) { if (response.ErrorCode == ErrorCode.AlreadyRequested) diff --git a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs index 0368984dd..4c29d32a1 100644 --- a/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs +++ b/src/Ombi.Schedule/Processor/ChangeLogProcessor.cs @@ -40,6 +40,8 @@ namespace Ombi.Schedule.Processor private UpdateModel TransformUpdate(Release release) { + Version updateVersion = Version.Parse(release.Version.TrimStart('v')); + Version currentVersion = Version.Parse(AssemblyHelper.GetRuntimeVersion()); var newUpdate = new UpdateModel { UpdateVersionString = release.Version, @@ -47,7 +49,7 @@ namespace Ombi.Schedule.Processor UpdateDate = DateTime.Now, ChangeLogs = release.Description, Downloads = new List(), - UpdateAvailable = release.Version != "v" + AssemblyHelper.GetRuntimeVersion() + UpdateAvailable = updateVersion > currentVersion }; foreach (var dl in release.Downloads) diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index 5917e2592..6edfe0351 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -8,6 +8,7 @@ namespace Ombi.Core.Settings.Models.External { public bool Enable { get; set; } public bool EnableWatchlistImport { get; set; } + public bool MonitorAll { get; set; } /// /// This is the ClientId for OAuth /// diff --git a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs index c367cb48e..8e0b37524 100644 --- a/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/SonarrSettings.cs @@ -17,9 +17,13 @@ public string QualityProfileAnime { get; set; } public string RootPathAnime { get; set; } + public int? AnimeTag { get; set; } + public int? Tag { get; set; } public bool AddOnly { get; set; } public int LanguageProfile { get; set; } public int LanguageProfileAnime { get; set; } public bool ScanForAvailability { get; set; } + + public bool SendUserTags { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs b/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs index 3547812ee..8f04d67da 100644 --- a/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs +++ b/src/Ombi.Settings/Settings/Models/UserManagementSettings.cs @@ -7,6 +7,7 @@ namespace Ombi.Settings.Settings.Models { public bool ImportPlexAdmin { get; set; } public bool ImportPlexUsers { get; set; } + public bool CleanupPlexUsers { get; set; } public bool ImportEmbyUsers { get; set; } public bool ImportJellyfinUsers { get; set; } public int MovieRequestLimit { get; set; } diff --git a/src/Ombi/.vscode/settings.json b/src/Ombi/.vscode/settings.json index d0eaf2fe6..6274dd334 100644 --- a/src/Ombi/.vscode/settings.json +++ b/src/Ombi/.vscode/settings.json @@ -23,7 +23,8 @@ "availability-rules", "details", "requests", - "sonarr" + "sonarr", + "plex" ], "rpc.enabled": true } diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts index d5557c0b5..c7348e91c 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts @@ -8,7 +8,7 @@ import { IDiscoverCardResult } from "../../interfaces"; import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; import { ISearchTvResultV2 } from "../../../interfaces/ISearchTvResultV2"; import { MatDialog } from "@angular/material/dialog"; -import { RequestType } from "../../../interfaces"; +import { IMovieRequestModel, RequestType } from "../../../interfaces"; import { TranslateService } from "@ngx-translate/core"; @Component({ @@ -130,45 +130,75 @@ export class DiscoverCardComponent implements OnInit { event.preventDefault(); this.loading = true; switch (this.result.type) { - case RequestType.tvShow: - const dia = this.dialog.open(EpisodeRequestComponent, { width: "700px", data: { series: this.tvSearchResult, isAdmin: this.isAdmin }, panelClass: 'modal-panel' }); - dia.afterClosed().subscribe(x => this.loading = false); - return; - case RequestType.movie: - if (this.isAdmin) { - const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.result.id, }, panelClass: 'modal-panel' }); - dialog.afterClosed().subscribe((result) => { - if (result) { - this.requestService.requestMovie({ theMovieDbId: +this.result.id, - languageCode: this.translate.currentLang, - qualityPathOverride: result.radarrPathId, - requestOnBehalf: result.username?.id, - rootFolderOverride: result.radarrFolderId, - is4KRequest: is4k }).subscribe(x => { - if (x.result) { - this.result.requested = true; - this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.result.title }), "Ok"); - } else { - this.messageService.sendRequestEngineResultError(x); - } - }); - } - }); - } else { - this.requestService.requestMovie({ theMovieDbId: +this.result.id, languageCode: this.translate.currentLang, requestOnBehalf: null, qualityPathOverride: null, rootFolderOverride: null, is4KRequest: is4k }).subscribe(x => { - if (x.result) { - this.result.requested = true; - this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.result.title }), "Ok"); - } else { - this.messageService.sendRequestEngineResultError(x); - } - this.loading = false; - }); - return; + case RequestType.tvShow: + const dialog = this.dialog.open(EpisodeRequestComponent, { + width: "700px", + data: { series: this.tvSearchResult, isAdmin: this.isAdmin }, + panelClass: "modal-panel", + }); + dialog.afterClosed().subscribe(() => (this.loading = false)); + break; + case RequestType.movie: + const movieRequest: IMovieRequestModel = { + theMovieDbId: +this.result.id, + languageCode: this.translate.currentLang, + requestOnBehalf: null, + qualityPathOverride: null, + rootFolderOverride: null, + is4KRequest: is4k, + }; + + if (!this.isAdmin) { + this.requestMovie(movieRequest); + break; } + + const adminRequestDialog = this.dialog.open( + AdminRequestDialogComponent, + { + width: "700px", + data: { type: RequestType.movie, id: this.result.id }, + panelClass: "modal-panel", + } + ); + adminRequestDialog.afterClosed().subscribe((result) => { + if (!result) { + this.loading = false; + return; + } + + movieRequest.requestOnBehalf = result.username?.id; + movieRequest.qualityPathOverride = result.radarrPathId; + movieRequest.rootFolderOverride = result.radarrFolderId; + this.requestMovie(movieRequest); + }); + break; } } + private requestMovie(movieRequest: IMovieRequestModel) { + this.requestService.requestMovie(movieRequest).subscribe({ + next: (response) => { + if (response.result) { + this.result.requested = true; + const message = this.translate.instant( + "Requests.RequestAddedSuccessfully", + { title: this.result.title } + ); + this.messageService.send(message, "Ok"); + } else { + this.messageService.sendRequestEngineResultError(response); + } + + this.loading = false; + }, + error: (error) => { + this.messageService.sendRequestEngineResultError(error); + this.loading = false; + }, + }); + } + public onImageError(event: any) { const originalSrc = event.target.src; diff --git a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html index 0750a12c0..ca5360532 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/collections/discover-collections.component.html @@ -16,7 +16,7 @@
- +
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index 1c66fff92..cd7956fdd 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -113,6 +113,7 @@ export interface IPublicInfo { export interface IPlexSettings extends ISettings { enable: boolean; enableWatchlistImport: boolean; + monitorAll: boolean; servers: IPlexServer[]; } @@ -145,6 +146,9 @@ export interface ISonarrSettings extends IExternalSettings { languageProfile: number; languageProfileAnime: number; scanForAvailability: boolean; + sendUserTags: boolean; + tag: number | null; + animeTag: number | null; } export interface IRadarrSettings extends IExternalSettings { @@ -252,6 +256,7 @@ export interface ICustomPage extends ISettings { export interface IUserManagementSettings extends ISettings { importPlexUsers: boolean; + cleanupPlexUsers: boolean; importPlexAdmin: boolean; importEmbyUsers: boolean; importJellyfinUsers: boolean; diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISonarr.ts b/src/Ombi/ClientApp/src/app/interfaces/ISonarr.ts index ffe9e9b6d..bd43e671b 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISonarr.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISonarr.ts @@ -12,3 +12,8 @@ export interface ILanguageProfiles { name: string; id: number; } + +export interface ITag { + label: string; + id: number; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/interfaces/ITester.ts b/src/Ombi/ClientApp/src/app/interfaces/ITester.ts index c61292be4..b5615aae7 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ITester.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ITester.ts @@ -1,4 +1,5 @@ export interface ITesterResult { isValid: boolean; + version?: string; expectedSubDir?: string; } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html index b893af45c..8d270417d 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.html @@ -57,11 +57,11 @@
- - diff --git a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts index 473417d53..edd531a0b 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/tv/tv-details.component.ts @@ -126,9 +126,10 @@ export class TvDetailsComponent implements OnInit { } } - public allEpisodesRequested(): boolean { - return this.tv.seasonRequests.every(e => e.episodes.every(x => x.approved || x.requested)); + public allEpisodesRequestedOrAvailable(): boolean { + return this.tv.seasonRequests.every(e => e.episodes.every(x => x.available || x.approved || x.requested)); } + private checkPoster() { if (this.tv.images.original == null) { this.tv.images.original = "../../../images/default_movie_poster.png"; diff --git a/src/Ombi/ClientApp/src/app/services/applications/sonarr.service.ts b/src/Ombi/ClientApp/src/app/services/applications/sonarr.service.ts index 13a2076d4..ae41badf0 100644 --- a/src/Ombi/ClientApp/src/app/services/applications/sonarr.service.ts +++ b/src/Ombi/ClientApp/src/app/services/applications/sonarr.service.ts @@ -4,7 +4,7 @@ import { Injectable, Inject } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { ISonarrSettings } from "../../interfaces"; +import { ISonarrSettings, ITag } from "../../interfaces"; import { ILanguageProfiles, ISonarrProfile, ISonarrRootFolder } from "../../interfaces"; import { ServiceHelpers } from "../service.helpers"; @@ -36,6 +36,10 @@ export class SonarrService extends ServiceHelpers { return this.http.get(`${this.url}/v3/languageprofiles/`, {headers: this.headers}); } + public getTags(settings: ISonarrSettings): Observable { + return this.http.post(`${this.url}/tags/`, JSON.stringify(settings), {headers: this.headers}); + } + public isEnabled(): Promise { return this.http.get(`${this.url}/enabled/`, { headers: this.headers }).toPromise(); } diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html index 6787fb4c9..11d19c5ca 100644 --- a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.html @@ -10,11 +10,11 @@
- Enable + Enable
- +
@@ -56,7 +56,7 @@
-
+
Base URL @@ -125,7 +125,7 @@ - +
diff --git a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts index 3e48f6020..f9e22c976 100644 --- a/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/emby/emby.component.ts @@ -72,6 +72,7 @@ export class EmbyComponent implements OnInit { if (index > -1) { this.settings.servers.splice(index, 1); this.selected.setValue(this.settings.servers.length - 1); + this.toggle(); } } diff --git a/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.html b/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.html index 6b3ebfdbc..d49507f75 100644 --- a/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.html +++ b/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.html @@ -10,13 +10,13 @@
- Enable + Enable
- +
diff --git a/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.ts b/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.ts index 20fd9f68d..4f6ca6d2a 100644 --- a/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/jellyfin/jellyfin.component.ts @@ -73,6 +73,7 @@ export class JellyfinComponent implements OnInit { if (index > -1) { this.settings.servers.splice(index, 1); this.selected.setValue(this.settings.servers.length - 1); + this.toggle(); } } diff --git a/src/Ombi/ClientApp/src/app/settings/plex/components/form-field/plex-form-field.component.ts b/src/Ombi/ClientApp/src/app/settings/plex/components/form-field/plex-form-field.component.ts index 46518b0d0..0e2f518c5 100644 --- a/src/Ombi/ClientApp/src/app/settings/plex/components/form-field/plex-form-field.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/plex/components/form-field/plex-form-field.component.ts @@ -2,6 +2,11 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; @Component({ selector: "settings-plex-form-field", + styles: [` + .margin { + margin: 10px; + } + `], template: `
@@ -16,7 +21,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; - +
diff --git a/src/Ombi/ClientApp/src/app/settings/plex/plex.component.html b/src/Ombi/ClientApp/src/app/settings/plex/plex.component.html index d1cc4d06e..8bd1629e3 100644 --- a/src/Ombi/ClientApp/src/app/settings/plex/plex.component.html +++ b/src/Ombi/ClientApp/src/app/settings/plex/plex.component.html @@ -10,7 +10,7 @@
- + When a Plex User adds something to their watchlist in Plex, it will turn up in Ombi as a Request if enabled. This only applies to users that are logging in with their Plex Account
Request limits if set are all still applied
diff --git a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html index b6c9cb7cd..cc4ce8d72 100644 --- a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html +++ b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.html @@ -16,6 +16,10 @@
Scan for Availability
+
+ Add the user as a tag +
This will add the username of the requesting user as a tag in Sonarr. If the tag doesn't exist, Ombi will create it.
+
@@ -53,14 +57,17 @@
- +
-
-
- -
-
+
+
+ +
+
+
+
+
Quality Profiles @@ -72,7 +79,7 @@
-
+
Quality Profiles (Anime) @@ -84,14 +91,18 @@
+
-
-
-
+
+
+
Default Root Folders @@ -103,7 +114,7 @@
-
+
Default Root Folders (Anime) @@ -114,21 +125,58 @@
+
-
- -
- -
-
-
+
+
+
+
+
+
+
- Language Profiles + Default Tag + + {{tag.label}} + + +
+
+
+
+ + Anime Tags + + {{tag.label}} + + +
+
+
+ +
+ + + +
+
+
+
+
+
+
+
+
+ +
+ + Language Profiles {{lang.name}} @@ -137,19 +185,20 @@
-
+
- Language Profiles Anime + Anime {{lang.name}} A Language Profile Anime is required
-
+
+
@@ -166,18 +215,19 @@
-
-
- -
-
- +
+
+
+ +
+
+
diff --git a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts index 2095028a2..fcd5bc47e 100644 --- a/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts +++ b/src/Ombi/ClientApp/src/app/settings/sonarr/sonarr.component.ts @@ -1,7 +1,8 @@ import { Component, OnInit } from "@angular/core"; import { UntypedFormBuilder, FormControl, UntypedFormGroup, Validators } from "@angular/forms"; +import { finalize, map } from "rxjs"; -import { ILanguageProfiles, ISonarrProfile, ISonarrRootFolder } from "../../interfaces"; +import { ILanguageProfiles, ISonarrProfile, ISonarrRootFolder, ITag } from "../../interfaces"; import { ISonarrSettings } from "../../interfaces"; import { SonarrService } from "../../services"; @@ -21,14 +22,20 @@ export class SonarrComponent implements OnInit { public rootFoldersAnime: ISonarrRootFolder[]; public languageProfiles: ILanguageProfiles[]; public languageProfilesAnime: ILanguageProfiles[]; + + public tags: ITag[]; + public animeTags: ITag[]; + public selectedRootFolder: ISonarrRootFolder; public selectedQuality: ISonarrProfile; public selectedLanguageProfiles: ILanguageProfiles; public profilesRunning: boolean; public rootFoldersRunning: boolean; + public tagsRunning: boolean; public langRunning: boolean; public form: UntypedFormGroup; public advanced = false; + public sonarrVersion: string; formErrors: any; constructor(private settingsService: SettingsService, @@ -72,11 +79,29 @@ export class SonarrComponent implements OnInit { port: [x.port, [Validators.required]], addOnly: [x.addOnly], seasonFolders: [x.seasonFolders], - languageProfile: [x.languageProfile, [Validators.required, validateProfile]], + languageProfile: [x.languageProfile], languageProfileAnime: [x.languageProfileAnime], scanForAvailability: [x.scanForAvailability], + sendUserTags: [x.sendUserTags], + tag: [x.tag], + animeTag: [x.animeTag] }); + this.rootFolders = []; + this.qualities = []; + this.languageProfiles = []; + this.tags = []; + this.animeTags = []; + + if (x.enabled && this.form.valid) { + this.testerService.sonarrTest(x).subscribe(result => { + this.sonarrVersion = result.version[0]; + if (this.sonarrVersion === '3') { + this.form.controls.languageProfile.addValidators([Validators.required, validateProfile]); + } + }); + } + if (x.qualityProfile) { this.getProfiles(this.form); } @@ -86,6 +111,9 @@ export class SonarrComponent implements OnInit { if (x.languageProfile) { this.getLanguageProfiles(this.form); } + if (x.tag || x.animeTag) { + this.getTags(this.form); + } this.formErrors ={ apiKey: {}, @@ -96,12 +124,12 @@ export class SonarrComponent implements OnInit { }; this.onFormValuesChanged(); }); - this.rootFolders = []; - this.qualities = []; - this.languageProfiles = []; + this.rootFolders.push({ path: "Please Select", id: -1 }); this.qualities.push({ name: "Please Select", id: -1 }); this.languageProfiles.push({ name: "Please Select", id: -1 }); + this.animeTags.push({label: "None", id: -1}); + this.tags.push({label: "None", id: -1}); } public getProfiles(form: UntypedFormGroup) { @@ -141,14 +169,27 @@ export class SonarrComponent implements OnInit { }); } + public getTags(form: UntypedFormGroup) { + this.tagsRunning = true; + this.sonarrService.getTags(form.value).pipe( + finalize(() => { + this.tagsRunning = false; + this.animeTags.unshift({ label: "None", id: -1 }); + this.tags.unshift({ label: "None", id: -1 }); + this.notificationService.success("Successfully retrieved the Tags"); + }), + map(result => { + this.tags = result; + this.tags.forEach(val => this.animeTags.push(Object.assign({}, val))); + }) + ).subscribe() + } + public test(form: UntypedFormGroup) { - if (form.invalid) { - this.notificationService.error("Please check your entered values"); - return; - } const settings = form.value; this.testerService.sonarrTest(settings).subscribe(result => { if (result.isValid) { + this.sonarrVersion = result.version[0]; this.notificationService.success("Successfully connected to Sonarr!"); } else if (result.expectedSubDir) { this.notificationService.error("Your Sonarr Base URL must be set to " + result.expectedSubDir); @@ -178,6 +219,12 @@ export class SonarrComponent implements OnInit { this.notificationService.error("Please check your entered values"); } } + if (form.controls.animeTag.value == -1) { + form.controls.animeTag.setValue(null); + } + if (form.controls.tag.value == -1) { + form.controls.tag.setValue(null); + } this.settingsService.saveSonarr(form.value) .subscribe(x => { @@ -189,6 +236,7 @@ export class SonarrComponent implements OnInit { }); } } + function validateProfile(qualityProfile): { [key: string]:boolean } | null { if (qualityProfile.value !== undefined && (isNaN(qualityProfile.value) || qualityProfile.value == -1)) { diff --git a/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html index 266c725cb..7512294df 100644 --- a/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/src/app/settings/usermanagement/usermanagement.component.html @@ -17,6 +17,11 @@
Import Plex Admin
+ +
+ + Cleanup Plex Users +

Plex Users excluded from Import

@@ -121,7 +126,7 @@
-
+
diff --git a/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts index 9104c4dac..5ee3e586f 100644 --- a/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts +++ b/src/Ombi/ClientApp/src/app/shared/admin-request-dialog/admin-request-dialog.component.ts @@ -65,11 +65,9 @@ export class AdminRequestDialogComponent implements OnInit { if (this.data.type === RequestType.tvShow) { this.sonarrEnabled = await this.sonarrService.isEnabled(); if (this.sonarrEnabled) { - this.settingsService.getSonarr().subscribe((settings: ISonarrSettings) => { - this.sonarrService.getV3LanguageProfiles(settings).subscribe((profiles: ILanguageProfiles[]) => { - this.sonarrLanguageProfiles = profiles; - }) - }); + this.sonarrService.getV3LanguageProfilesWithoutSettings().subscribe((profiles: ILanguageProfiles[]) => { + this.sonarrLanguageProfiles = profiles; + }) this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => { this.sonarrProfiles = c; }); diff --git a/src/Ombi/Controllers/V1/External/TesterController.cs b/src/Ombi/Controllers/V1/External/TesterController.cs index bedf207b1..79a008322 100644 --- a/src/Ombi/Controllers/V1/External/TesterController.cs +++ b/src/Ombi/Controllers/V1/External/TesterController.cs @@ -46,7 +46,7 @@ namespace Ombi.Controllers.V1.External /// public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN, IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm, - IPlexApi plex, IEmbyApiFactory emby, IRadarrV3Api radarr, ISonarrApi sonarr, ILogger log, IEmailProvider provider, + IPlexApi plex, IEmbyApiFactory emby, IRadarrV3Api radarr, ISonarrV3Api sonarr, ILogger log, IEmailProvider provider, ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, ILegacyMobileNotification mobileNotification, ILidarrApi lidarrApi, IGotifyNotification gotifyNotification, IWhatsAppApi whatsAppApi, OmbiUserManager um, IWebhookNotification webhookNotification, IJellyfinApi jellyfinApi, IPrincipal user) @@ -90,7 +90,7 @@ namespace Ombi.Controllers.V1.External private IPlexApi PlexApi { get; } private IRadarrV3Api RadarrApi { get; } private IEmbyApiFactory EmbyApi { get; } - private ISonarrApi SonarrApi { get; } + private ISonarrV3Api SonarrApi { get; } private ICouchPotatoApi CouchPotatoApi { get; } private ILogger Log { get; } private IEmailProvider EmailProvider { get; } @@ -415,6 +415,7 @@ namespace Ombi.Controllers.V1.External return new TesterResultModel { IsValid = result.urlBase == settings.SubDir || string.IsNullOrEmpty(result.urlBase) && string.IsNullOrEmpty(settings.SubDir), + Version = result.version, ExpectedSubDir = result.urlBase }; } diff --git a/src/Ombi/appsettings.json b/src/Ombi/appsettings.json index 3de3dabad..84475ed2d 100644 --- a/src/Ombi/appsettings.json +++ b/src/Ombi/appsettings.json @@ -4,7 +4,7 @@ "LogLevel": { "Default": "Warning", "System": "Warning", - "Microsoft": "Warning", + "Microsoft": "Error", "Hangfire": "None", "System.Net.Http.HttpClient.health-checks": "Warning", "HealthChecks": "Warning" diff --git a/src/Ombi/wwwroot/translations/ca.json b/src/Ombi/wwwroot/translations/ca.json new file mode 100644 index 000000000..57fcc6b49 --- /dev/null +++ b/src/Ombi/wwwroot/translations/ca.json @@ -0,0 +1,459 @@ +{ + "Login": { + "SignInButton": "Inicia sessió", + "UsernamePlaceholder": "Nom d'usuari", + "PasswordPlaceholder": "Contrasenya", + "RememberMe": "Recorda'm", + "SignInWith": "Inicia la sessió amb {{appName}}", + "SignInWithPlex": "Inicia la sessió amb Plex", + "ForgottenPassword": "Has oblidat la contrasenya?", + "Errors": { + "IncorrectCredentials": "Nom d'usuari o contrasenya incorrecta" + } + }, + "Common": { + "ContinueButton": "Continua", + "Available": "Disponible", + "Available4K": "Disponible en 4K", + "Approved": "Aprovat", + "Approve4K": "Aprovat en 4K", + "Pending": "Pendent", + "PartiallyAvailable": "Parcialment disponible", + "Monitored": "En seguiment", + "NotAvailable": "No disponible", + "ProcessingRequest": "Processant sol·licitud", + "ProcessingRequest4K": "Processant sol·licitud en 4K", + "PendingApproval": "Pendent d'aprovació", + "PendingApproval4K": "Pendent d'aprovació en 4K", + "RequestDenied": "Sol·licitud denegada", + "RequestDenied4K": "Sol·licitud en 4K denegada", + "NotRequested": "No sol·licitat", + "NotRequested4K": "No sol·licitat en 4K", + "Requested": "Sol·licitada", + "Requested4K": "Sol·licitada en 4K", + "Search": "Cerca", + "Request": "Sol·licitud", + "Request4K": "Sol·licitud en 4K", + "Denied": "Denegat", + "Approve": "Aprova", + "PartlyAvailable": "Parcialment disponible", + "ViewDetails": "Visualitza els detalls", + "Errors": { + "Validation": "Comproveu les dades inserides" + }, + "Cancel": "Cancel·la", + "Submit": "Envia", + "Update": "Actualitza", + "tvShow": "Sèries de TV", + "movie": "Pel·lícula", + "album": "Àlbum" + }, + "PasswordReset": { + "EmailAddressPlaceholder": "Adreça electrònica", + "ResetPasswordButton": "Restableix la contrasenya" + }, + "LandingPage": { + "OnlineHeading": "En línia", + "OnlineParagraph": "El servidor està fora de línia", + "PartiallyOnlineHeading": "Parcialment en línia", + "PartiallyOnlineParagraph": "El servidor de mitjans està parcialment en línia.", + "MultipleServersUnavailable": "Hi ha {{serversUnavailable}} de {{totalServers}} servidors fora de línia.", + "SingleServerUnavailable": "Hi ha {{serversUnavailable}} de {{totalServers}} servidors fora de línia.", + "OfflineHeading": "Fora de línia", + "OfflineParagraph": "El servidor de mitjans està fora de línia.", + "CheckPageForUpdates": "Consulteu aquesta pàgina per obtenir actualitzacions contínues del lloc." + }, + "ErrorPages": { + "NotFound": "No s'ha trobat la pàgina", + "SomethingWentWrong": "Alguna cosa no ha anat bé!" + }, + "NavigationBar": { + "Discover": "Descobriu", + "Search": "Cerca", + "Requests": "Sol·licituds", + "UserManagement": "Usuaris", + "Issues": "Incidències", + "Vote": "Vota", + "Donate": "Feu un donatiu!", + "DonateLibraryMaintainer": "Feu un donatiu al desenvolupador de la biblioteca", + "DonateTooltip": "Així és com convenço a la meva dona para a què em deixi passar el meu temps lliure desenvolupant Ombi ;)", + "UpdateAvailableTooltip": "Actualització disponible!", + "Settings": "Configuració", + "Welcome": "Benvingut/da, {{username}}", + "UpdateDetails": "Edita les dades d'usuari", + "Logout": "Tanca sessió", + "OpenMobileApp": "Obre l'aplicació mòbil", + "RecentlyAdded": "Afegit recentment", + "ChangeTheme": "Canvia el tema", + "Calendar": "Calendari", + "UserPreferences": "Configuració", + "FeatureSuggestion": "Suggeriu noves característiques", + "FeatureSuggestionTooltip": "Tens una gran idea nova? Suggereix-ho aquí!", + "Filter": { + "Movies": "Pel·lícules", + "TvShows": "Sèries de TV", + "Music": "Música", + "People": "Persones" + }, + "MorningWelcome": "Bon dia!", + "AfternoonWelcome": "Bon vespre!", + "EveningWelcome": "Bona nit!" + }, + "Search": { + "Title": "Cerca", + "Paragraph": "Voleu veure alguna cosa que no està disponible actualment? Cap problema, només cal que ho cerqueu i ho sol·liciteu!", + "MoviesTab": "Pel·lícules", + "TvTab": "Sèries de TV", + "MusicTab": "Música", + "AdvancedSearch": "Podeu emplenar qualsevol dels següents apartats per descobrir nous mitjans. Tots els resultats estan ordenats per popularitat", + "AdvancedSearchHeader": "Cerca avançada", + "Suggestions": "Suggeriments", + "NoResults": "Ho sent, no s'han trobat resultats!", + "DigitalDate": "Versió digital: {{date}}", + "TheatricalRelease": "En cines: {{date}}", + "ViewOnPlex": "Reproduïu a Plex", + "ViewOnEmby": "Reproduïu a Emby", + "ViewOnJellyfin": "Reproduïu a Jellyfin", + "RequestAdded": "La sol·licitud per {{title}} s'ha afegit amb èxit", + "Similar": "Semblant", + "Refine": "Filtres", + "SearchBarPlaceholder": "Premeu aquí per cercar", + "Movies": { + "PopularMovies": "Pel·lícules populars", + "UpcomingMovies": "Properes pel·lícules", + "TopRatedMovies": "Pel·lícules millor valorades", + "NowPlayingMovies": "Pel·lícules en cartellera", + "HomePage": "Pàgina d'inici", + "Trailer": "Tràiler" + }, + "TvShows": { + "Popular": "Popular", + "Trending": "Tendències", + "MostWatched": "Més vistes", + "MostAnticipated": "Més esperades", + "Results": "Resultats", + "AirDate": "Data d'emissió:", + "AllSeasons": "Totes les temporades", + "FirstSeason": "Primera temporada", + "LatestSeason": "Última temporada", + "Select": "Selecciona...", + "SubmitRequest": "Envieu una sol·licitud", + "Season": "Temporada {{seasonNumber}}", + "SelectAllInSeason": "Selecciona-ho tot en la temporada {{seasonNumber}}" + }, + "AdvancedSearchInstructions": "Si us plau, escolliu el tipus de mitjà que esteu cercant:", + "YearOfRelease": "Any de llançament", + "SearchGenre": "Cerca per gènere", + "SearchKeyword": "Cerca per paraules clau", + "SearchProvider": "Proveïdor de cerca", + "KeywordSearchingDisclaimer": "Tingueu en compte que la cerca de paraules clau és poc confiable a causa de les dades inconsistents a TheMovieDb" + }, + "Requests": { + "Title": "Sol·licituds", + "Paragraph": "A continuació podeu veure la vostra i totes les altres sol·licituds, així com el seu estat de descàrrega i aprovació.", + "MoviesTab": "Pel·lícules", + "ArtistName": "Artista", + "AlbumName": "Nom de l'àlbum", + "TvTab": "Sèries de TV", + "MusicTab": "Música", + "RequestedBy": "Sol·licitat per", + "Status": "Estat", + "RequestStatus": "Estat de la sol·licitud", + "Denied": " Denegat:", + "TheatricalRelease": "En cines: {{date}}", + "ReleaseDate": "Llançament: {{date}}", + "TheatricalReleaseSort": "En cines", + "DigitalRelease": "Versió digital: {{date}}", + "RequestDate": "Data de la sol·licitud", + "QualityOverride": "Sobreescriure qualitat:", + "RootFolderOverride": "Sobreescriure carpeta arrel:", + "ChangeRootFolder": "Carpeta arrel", + "ChangeQualityProfile": "Perfil de qualitat", + "MarkUnavailable": "Marca com a disponible", + "MarkUnavailable4K": "Marca com a no disponible en 4K", + "MarkAvailable": "Marca com a disponible", + "MarkAvailable4K": "Marca com a disponible en 4K", + "Remove": "Suprimeix", + "Deny": "Denega-ho", + "Deny4K": "Denega-ho en 4K", + "Has4KRequest": "Té sol·licitud en 4K", + "DenyReason": "Raó de denegació", + "DeniedReason": "Motiu de denegació", + "Season": "Temporada", + "GridTitle": "Títol", + "AirDate": "Data d'emissió", + "GridStatus": "Estat", + "ReportIssue": "Notifiqueu una incidència", + "Filter": "Filtra", + "Sort": "Ordena", + "SeasonNumberHeading": "Temporada: {seasonNumber}", + "SortTitleAsc": "Títol ▲", + "SortTitleDesc": "Títol ▼", + "SortRequestDateAsc": "Data de sol·licitud ▲", + "SortRequestDateDesc": "Data de sol·licitud ▼", + "SortStatusAsc": "Estat ▲", + "SortStatusDesc": "Estat ▼", + "Remaining": { + "Quota": "{{remaining}}/{{total}} sol·licituds restants", + "NextDays": "S'afegirà una altra sol·licitud d'aquí a {{time}} dies", + "NextHours": "S'afegirà una altra sol·licitud d'aquí a {{time}} hores", + "NextMinutes": "S'afegirà una altra sol·licitud d'aquí a {{time}} minuts", + "NextMinute": "S'afegirà una altra sol·licitud d'aquí a {{time}} minut" + }, + "AllRequests": "Totes les sol·licituds", + "PendingRequests": "Sol·licituds pendents", + "ProcessingRequests": "Processant sol·licituds", + "AvailableRequests": "Sol·licituds disponibles", + "DeniedRequests": "Sol·licituds denegades", + "RequestsToDisplay": "Sol·licituds a mostrar", + "RequestsTitle": "Títol", + "Details": "Detalls", + "Options": "Opcions", + "RequestPanel": { + "Delete": "Suprimeix la sol·licitud", + "Approve": "Aprova la sol·licitud", + "Deny": "Denega la sol·licitud", + "Approve4K": "Aprova la sol·licitud en 4K", + "Deny4K": "Denega la sol·licitud en 4K", + "ChangeAvailability": "Marca com a disponible", + "Deleted": "Els elements seleccionats s'han suprimit correctament", + "Approved": "Els elements seleccionats s'han aprovat correctament", + "Denied": "Els elements seleccionats s'han denegat correctament" + }, + "SuccessfullyApproved": "Aprovat correctament", + "SuccessfullyDeleted": "La sol·licitud s'ha suprimit correctament", + "NowAvailable": "La sol·licitud ja està disponible", + "NowUnavailable": "La sol·licitud ja no està disponible", + "SuccessfullyReprocessed": "La sol·licitud s'ha tornat a processar correctament", + "DeniedRequest": "Sol·licitud denegada", + "RequestCollection": "Sol·licita col·lecció", + "CollectionSuccesfullyAdded": "La col·lecció {{name}} s'ha afegit correctament!", + "NeedToSelectEpisodes": "Heu de seleccionar algun episodi!", + "RequestAddedSuccessfully": "La sol·licitud de {{title}} s'ha afegit correctament", + "ErrorCodes": { + "AlreadyRequested": "Ja s'ha sol·licitat", + "EpisodesAlreadyRequested": "Ja hi ha capítols sol·licitats d'aquesta sèrie", + "NoPermissionsOnBehalf": "No teniu els permisos correctes per fer sol·licituds en nom d'altres usuaris!", + "NoPermissions": "No teniu els permisos correctes!", + "RequestDoesNotExist": "La sol·licitud no existeix", + "ChildRequestDoesNotExist": "La sol·licitud no existeix", + "NoPermissionsRequestMovie": "No teniu permisos per sol·licitar una pel·lícula", + "NoPermissionsRequestTV": "No teniu permisos per sol·licitar una sèrie de televisió", + "NoPermissionsRequestAlbum": "No teniu permisos per sol·licitar un àlbum", + "MovieRequestQuotaExceeded": "Heu superat la vostra quota de sol·licitud de pel·lícules!", + "TvRequestQuotaExceeded": "Heu superat la vostra quota de sol·licitud d'episodis!", + "AlbumRequestQuotaExceeded": "Heu superat la vostra quota de sol·licitud d'àlbums!" + }, + "Notify": "Notifica'm", + "RemoveNotification": "Elimina les notificacions", + "SuccessfulNotify": "Ara se us notificarà pel títol {{title}}", + "SuccessfulUnNotify": "Ja no se us notificarà pel títol {{title}}", + "CouldntNotify": "No s'ha pogut notificar pel títol {{title}}" + }, + "Issues": { + "Title": "Incidències", + "IssuesForTitle": "Incidència per {{title}}", + "PendingTitle": "Incidències pendents", + "InProgressTitle": "Incidències en procés", + "ResolvedTitle": "Incidències resoltes", + "ColumnTitle": "Títol", + "Count": "Recompte", + "Category": "Categoria", + "Status": "Estat", + "Details": "Detalls", + "Description": "Descripció", + "NoComments": "Sense comentaris!", + "MarkInProgress": "Marca en progrés", + "MarkResolved": "Marca com resolt", + "SendMessageButton": "Envia", + "Subject": "Assumpte", + "Comments": "Comentaris", + "WriteMessagePlaceholder": "Escriviu aquí el missatge...", + "ReportedBy": "Notificat per", + "IssueDialog": { + "Title": "Notifiqueu una incidència", + "DescriptionPlaceholder": "Si us plau, descriviu la incidència", + "TitlePlaceholder": "Títol curt de la incidència", + "SelectCategory": "Selecciona categoria", + "IssueCreated": "S'ha creat la incidència" + }, + "Outstanding": "Hi ha incidències pendents", + "ResolvedDate": "Data de resolució", + "CreatedDate": "Creat", + "MarkedAsResolved": "Aquesta incidència ha sigut marcada com resolta!", + "MarkedAsInProgress": "Aquesta incidència ha sigut marcada com en procés!", + "Delete": "Esborra la incidència", + "DeletedIssue": "S'ha esborrat la incidència", + "Chat": "Xat", + "EnterYourMessage": "Introduïu el vostre missatge", + "Requested": "Sol·licitat", + "UserOnDate": "{{user}} a {{date}}" + }, + "Filter": { + "ClearFilter": "Neteja el filtre", + "FilterHeaderAvailability": "Disponibilitat", + "FilterHeaderRequestStatus": "Estat", + "Approved": "Aprovat", + "PendingApproval": "Pendent d'aprovació", + "WatchProviders": "Proveïdors de contingut", + "Keywords": "Etiquetes" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} restants", + "MovieRemaining": "Pel·lícules: {{remaining}}/{{total}} restants", + "MusicRemaining": "Música: {{remaining}}/{{total}} restants", + "TvDue": "TV: {{date}}", + "MovieDue": "Pel·lícula: {{date}}", + "MusicDue": "Música: {{date}}" + }, + "Votes": { + "CompletedVotesTab": "Votat", + "VotesTab": "Vots necessaris" + }, + "MediaDetails": { + "Denied": "Denegat", + "Denied4K": "Denegat en 4K", + "Trailers": "Tràilers", + "RecommendationsTitle": "Recomanacions", + "SimilarTitle": "Semblant", + "VideosTitle": "Vídeos", + "AlbumsTitle": "Àlbums", + "RequestAllAlbums": "Sol·licita tots els àlbums", + "ClearSelection": "Esborra selecció", + "RequestSelectedAlbums": "Sol·licita àlbums seleccionats", + "ViewCollection": "Mostra col·lecció", + "NotEnoughInfo": "Malauradament, encara no hi ha prou informació sobre aquesta sèrie!", + "AdvancedOptions": "Opcions avançades", + "AutoApproveOptions": "Podeu configurar la sol·licitud aquí, un cop sol·licitada, s'enviarà a la vostra aplicació DVR i s'aprovarà automàticament! Tingueu en compte que això és opcional, només cal que premeu \"Sol·licita\" per ometre'l!", + "AutoApproveOptionsTv": "Podeu configurar la sol·licitud aquí, un cop sol·licitada, s'enviarà a la vostra aplicació DVR i s'aprovarà automàticament! Si la sol·licitud ja està a Sonarr, no canviarà la carpeta arrel ni el perfil de qualitat configurat! Tingueu en compte que això és opcional, només cal que premeu \"Sol·licita\" per ometre'l!", + "AutoApproveOptionsTvShort": "Podeu configurar la sol·licitud aquí, un cop sol·licitada, s'enviarà a la vostra aplicació DVR! Si la sol·licitud ja està a Sonarr, no canviarà la carpeta arrel ni el perfil de qualitat configurat! Tingueu en compte que això és opcional, només cal que premeu \"Sol·licita\" per ometre'l!", + "QualityProfilesSelect": "Seleccioneu un perfil de qualitat", + "RootFolderSelect": "Seleccioneu una carpeta arrel", + "LanguageProfileSelect": "Seleccioneu un perfil d'idioma", + "Status": "Estat:", + "StatusValues": { + "Rumored": "Es rumoreja", + "Planned": "Planificat", + "In Production": "En producció", + "Post Production": "En postproducció", + "Released": "En cines", + "Running": "En emissió", + "Returning Series": "Sèrie que torna a emetre's", + "Ended": "Finalitzada", + "Canceled": "Cancel·lada" + }, + "Seasons": "Temporades:", + "Episodes": "Episodis:", + "Availability": "Disponibilitat:", + "RequestStatus": "Estat de la sol·licitud:", + "Quality": "Qualitat:", + "RootFolderOverride": "Sobreescriure carpeta arrel:", + "QualityOverride": "Sobreescriure qualitat:", + "Network": "Plataforma:", + "GenresLabel": "Gèneres:", + "Genres": "Gèneres", + "FirstAired": "Primera emissió:", + "TheatricalRelease": "Llançament:", + "DigitalRelease": "Estrena digital:", + "Votes": "Vots:", + "Runtime": "Durada:", + "Minutes": "{{runtime}} minuts", + "Revenue": "Ingressos:", + "Budget": "Pressupost:", + "Keywords": "Paraules clau/Etiquetes:", + "Casts": { + "CastTitle": "Repartiment" + }, + "Crews": { + "CrewTitle": "Equip" + }, + "EpisodeSelector": { + "AllSeasonsTooltip": "Això demanarà cada temporada per a aquesta sèrie", + "FirstSeasonTooltip": "Això sols demanarà la primera temporada per a aquesta sèrie", + "LatestSeasonTooltip": "Això sols demanarà l'última temporada per a aquesta sèrie", + "NoEpisodes": "Malauradament, encara no hi ha dades d'episodi per a aquesta sèrie!", + "SeasonNumber": "Temporada {{number}}" + }, + "SonarrConfiguration": "Configuració de Sonarr", + "RadarrConfiguration": "Configuració de Radarr", + "RequestOnBehalf": "Sol·liciteu en nom de", + "PleaseSelectUser": "Si us plau, seleccioneu un usuari", + "StreamingOn": "Emès en:", + "RequestedBy": "Sol·licitat per:", + "OnDate": "A:", + "RequestedByOn": "Sol·licitat per {{user}} el {{date}}", + "RequestDate": "Data de sol·licitud:", + "DeniedReason": "Motiu de denegació:", + "ReProcessRequest": "Torna a processar la sol·licitud", + "ReProcessRequest4K": "Torna a processar la sol·licitud en 4K", + "Music": { + "Type": "Tipus:", + "Country": "País:", + "StartDate": "Data d'inici:", + "EndDate": "Data de finalització:" + }, + "RequestSource": "Font:" + }, + "Discovery": { + "PopularTab": "Popular", + "TrendingTab": "Tendències", + "UpcomingTab": "Pròximament", + "SeasonalTab": "Per temporada", + "RecentlyRequestedTab": "Sol·licituds recents", + "Movies": "Pel·lícules", + "Combined": "Combinat", + "Tv": "TV", + "CardDetails": { + "Availability": "Disponibilitat", + "Studio": "Estudi", + "Network": "Plataforma", + "UnknownNetwork": "Desconegut", + "RequestStatus": "Estat de la sol·licitud", + "Director": "Direcció", + "InCinemas": "En cines", + "FirstAired": "Emès per primera vegada", + "Writer": "Guionistes", + "ExecProducer": "Producció executiva" + }, + "NoSearch": "Ho sentim, no hi ha coincidències a la cerca!" + }, + "UserPreferences": { + "Welcome": "Benvingut/da {{username}}!", + "OmbiLanguage": "Idioma", + "DarkMode": "Mode fosc", + "Updated": "S’ha actualitzat correctament", + "StreamingCountry": "País d'emissió", + "StreamingCountryDescription": "Aquest és el codi de país per al qual mostrarem la informació de transmissió. Si sou a Espanya, seleccioneu ES i obtindreu informació de l'emissió relacionada amb Espanya.", + "LanguageDescription": "Aquest és l'idioma en que es mostrarà la interfície d'Ombi.", + "MobileQRCode": "Codi QR mòbil", + "LegacyApp": "Inicia l'aplicació antiga", + "NoQrCode": "Poseu-vos en contacte amb el vostre administrador per activar els codis QR", + "UserType": "Tipus d'usuari:", + "ChangeDetails": "Modifica les teves dades", + "NeedCurrentPassword": "És necessari la teva contrasenya actual per a confirmar els canvis", + "CurrentPassword": "Contrasenya actual", + "EmailAddress": "Adreça de correu", + "NewPassword": "Nova contrasenya", + "NewPasswordConfirm": "Confirmeu la nova contrasenya", + "Security": "Seguretat", + "Profile": "Perfil", + "UpdatedYourInformation": "S'ha actualitzat la vostra informació", + "Unsubscribed": "La subscripció s'ha cancel·lat!" + }, + "UserTypeLabel": { + "1": "Usuari local", + "2": "Usuari de Plex", + "3": "Usuari d'Emby", + "4": "Usuari connectat d'Emby", + "5": "Usuari de Jellyfin" + }, + "Paginator": { + "itemsPerPageLabel": "Elements per pàgina:", + "nextPageLabel": "Pàgina següent", + "previousPageLabel": "Pàgina anterior", + "firstPageLabel": "Primera pàgina", + "lastPageLabel": "Última pàgina", + "rangePageLabel1": "0 de {{length}}", + "rangePageLabel2": "{{startIndex}} – {{endIndex}} de {{length}}" + } +} diff --git a/src/Ombi/wwwroot/translations/nl.json b/src/Ombi/wwwroot/translations/nl.json index 17f0baae4..097292cf1 100644 --- a/src/Ombi/wwwroot/translations/nl.json +++ b/src/Ombi/wwwroot/translations/nl.json @@ -14,21 +14,21 @@ "Common": { "ContinueButton": "Doorgaan", "Available": "Beschikbaar", - "Available4K": "Available 4K", + "Available4K": "Beschikbaar", "Approved": "Goedgekeurd", - "Approve4K": "Approve 4K", + "Approve4K": "Goedgekeurd 4K", "Pending": "In afwachting", "PartiallyAvailable": "Deels Beschikbaar", "Monitored": "Gemonitord", "NotAvailable": "Niet Beschikbaar", "ProcessingRequest": "Verzoek wordt verwerkt", - "ProcessingRequest4K": "Processing Request 4K", + "ProcessingRequest4K": "Verzoeken in behandeling 4K", "PendingApproval": "Wacht op goedkeuring", - "PendingApproval4K": "Pending Approval 4K", + "PendingApproval4K": "Wacht op goedkeuring 4K", "RequestDenied": "Verzoek geweigerd", - "RequestDenied4K": "Request Denied 4K", + "RequestDenied4K": "Verzoek geweigerd 4K", "NotRequested": "Niet verzocht", - "NotRequested4K": "Not Requested 4K", + "NotRequested4K": "Niet aangevraagd 4K", "Requested": "Aangevraagd", "Requested4K": "Requested 4K", "Search": "Zoeken", diff --git a/src/Ombi/wwwroot/translations/zh.json b/src/Ombi/wwwroot/translations/zh.json index 449c2c7f9..54878b499 100644 --- a/src/Ombi/wwwroot/translations/zh.json +++ b/src/Ombi/wwwroot/translations/zh.json @@ -365,7 +365,7 @@ "CastTitle": "演员" }, "Crews": { - "CrewTitle": "Crew" + "CrewTitle": "工作人员" }, "EpisodeSelector": { "AllSeasonsTooltip": "请求这个节目的每一季", diff --git a/version.json b/version.json index 242dd69ad..b39ebdbf5 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "4.27.8" + "version": "4.31.0" } \ No newline at end of file