mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-22 06:13:22 -07:00
Merge branch 'develop' into settings-wip
This commit is contained in:
commit
4c6346091a
47 changed files with 1313 additions and 301 deletions
184
CHANGELOG.md
184
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)
|
||||
|
||||
|
||||
|
||||
|
|
91
README.md
91
README.md
|
@ -257,6 +257,13 @@ Here are some of the features Ombi has:
|
|||
<sub><b>Patrick Collins</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/xweskingx">
|
||||
<img src="https://avatars.githubusercontent.com/u/6268446?v=4" width="50;" alt="xweskingx"/>
|
||||
<br />
|
||||
<sub><b>Wesley King</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/chriscpritchard">
|
||||
<img src="https://avatars.githubusercontent.com/u/1839074?v=4" width="50;" alt="chriscpritchard"/>
|
||||
|
@ -277,15 +284,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Stephen Panzer</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/aptalca">
|
||||
<img src="https://avatars.githubusercontent.com/u/541623?v=4" width="50;" alt="aptalca"/>
|
||||
<br />
|
||||
<sub><b>Aptalca</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/dr3am37">
|
||||
<img src="https://avatars.githubusercontent.com/u/91037083?v=4" width="50;" alt="dr3am37"/>
|
||||
|
@ -320,15 +327,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Andrew Metzger</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/au5ton">
|
||||
<img src="https://avatars.githubusercontent.com/u/4109551?v=4" width="50;" alt="au5ton"/>
|
||||
<br />
|
||||
<sub><b>Austin Jackson</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/D34DC3N73R">
|
||||
<img src="https://avatars.githubusercontent.com/u/9123670?v=4" width="50;" alt="D34DC3N73R"/>
|
||||
|
@ -363,15 +370,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Jack Steel</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/jpeters">
|
||||
<img src="https://avatars.githubusercontent.com/u/167401?v=4" width="50;" alt="jpeters"/>
|
||||
<br />
|
||||
<sub><b>Jeffrey Peters</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/MariusSchiffer">
|
||||
<img src="https://avatars.githubusercontent.com/u/183124?v=4" width="50;" alt="MariusSchiffer"/>
|
||||
|
@ -406,15 +413,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Javier Pastor</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/AbeKline">
|
||||
<img src="https://avatars.githubusercontent.com/u/8125653?v=4" width="50;" alt="AbeKline"/>
|
||||
<br />
|
||||
<sub><b>Abe Kline</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/XanderStrike">
|
||||
<img src="https://avatars.githubusercontent.com/u/1565303?v=4" width="50;" alt="XanderStrike"/>
|
||||
|
@ -449,15 +456,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Calvin</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/origamirobot">
|
||||
<img src="https://avatars.githubusercontent.com/u/1346803?v=4" width="50;" alt="origamirobot"/>
|
||||
<br />
|
||||
<sub><b>Chris Lees</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/cdemi">
|
||||
<img src="https://avatars.githubusercontent.com/u/8025435?v=4" width="50;" alt="cdemi"/>
|
||||
|
@ -492,15 +499,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>David Torosyan</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/onedr0p">
|
||||
<img src="https://avatars.githubusercontent.com/u/213795?v=4" width="50;" alt="onedr0p"/>
|
||||
<br />
|
||||
<sub><b>Devin Buhl</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/elisspace">
|
||||
<img src="https://avatars.githubusercontent.com/u/18365129?v=4" width="50;" alt="elisspace"/>
|
||||
|
@ -535,15 +542,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Igor Borges</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/ImgBotApp">
|
||||
<img src="https://avatars.githubusercontent.com/u/31427850?v=4" width="50;" alt="ImgBotApp"/>
|
||||
<br />
|
||||
<sub><b>Imgbot</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/JPyke3">
|
||||
<img src="https://avatars.githubusercontent.com/u/13283054?v=4" width="50;" alt="JPyke3"/>
|
||||
|
@ -578,15 +585,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Jon Bloom</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/jonocairns">
|
||||
<img src="https://avatars.githubusercontent.com/u/182836?v=4" width="50;" alt="jonocairns"/>
|
||||
<br />
|
||||
<sub><b>Jono Cairns</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/krisklosterman">
|
||||
<img src="https://avatars.githubusercontent.com/u/7139579?v=4" width="50;" alt="krisklosterman"/>
|
||||
|
@ -621,15 +628,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Madeleine Schönemann</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/marleypowell">
|
||||
<img src="https://avatars.githubusercontent.com/u/55280588?v=4" width="50;" alt="marleypowell"/>
|
||||
<br />
|
||||
<sub><b>Marley</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/mattmattmatt">
|
||||
<img src="https://avatars.githubusercontent.com/u/927830?v=4" width="50;" alt="mattmattmatt"/>
|
||||
|
@ -664,15 +671,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Nathan Miller</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/cqxmzz">
|
||||
<img src="https://avatars.githubusercontent.com/u/3071863?v=4" width="50;" alt="cqxmzz"/>
|
||||
<br />
|
||||
<sub><b>Qiming Chen</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/randallbruder">
|
||||
<img src="https://avatars.githubusercontent.com/u/6447487?v=4" width="50;" alt="randallbruder"/>
|
||||
|
@ -707,15 +714,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Shoghi</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Teifun2">
|
||||
<img src="https://avatars.githubusercontent.com/u/7461832?v=4" width="50;" alt="Teifun2"/>
|
||||
<br />
|
||||
<sub><b>Teifun2</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/thomasvt1">
|
||||
<img src="https://avatars.githubusercontent.com/u/2271011?v=4" width="50;" alt="thomasvt1"/>
|
||||
|
@ -750,15 +757,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Travis Bybee</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/Xirg">
|
||||
<img src="https://avatars.githubusercontent.com/u/6020502?v=4" width="50;" alt="Xirg"/>
|
||||
<br />
|
||||
<sub><b>Xirg</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/bazhip">
|
||||
<img src="https://avatars.githubusercontent.com/u/10350445?v=4" width="50;" alt="bazhip"/>
|
||||
|
@ -793,15 +800,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Dorian ALKOUM</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/echel0n">
|
||||
<img src="https://avatars.githubusercontent.com/u/1128022?v=4" width="50;" alt="echel0n"/>
|
||||
<br />
|
||||
<sub><b>Echel0n</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/m4tta">
|
||||
<img src="https://avatars.githubusercontent.com/u/427218?v=4" width="50;" alt="m4tta"/>
|
||||
|
@ -836,15 +843,15 @@ Here are some of the features Ombi has:
|
|||
<br />
|
||||
<sub><b>Sirmarv</b></sub>
|
||||
</a>
|
||||
</td>
|
||||
</td></tr>
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/tdorsey">
|
||||
<img src="https://avatars.githubusercontent.com/u/1218404?v=4" width="50;" alt="tdorsey"/>
|
||||
<br />
|
||||
<sub><b>Tdorsey</b></sub>
|
||||
</a>
|
||||
</td></tr>
|
||||
<tr>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/thegame3202">
|
||||
<img src="https://avatars.githubusercontent.com/u/22148848?v=4" width="50;" alt="thegame3202"/>
|
||||
|
|
|
@ -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<IEnumerable<LanguageProfiles>> LanguageProfiles(string apiKey, string baseUrl);
|
||||
Task<Tag> CreateTag(string apiKey, string baseUrl, string tagName);
|
||||
Task<Tag> GetTag(int tagId, string apiKey, string baseUrl);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ namespace Ombi.Api.Sonarr.Models
|
|||
public string seriesType { get; set; }
|
||||
public int id { get; set; }
|
||||
public List<SonarrImage> images { get; set; }
|
||||
public List<int> tags { get; set; }
|
||||
|
||||
// V3 Property
|
||||
public int languageProfileId { get; set; }
|
||||
|
|
|
@ -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<int> tags { get; set; }
|
||||
public DateTime added { get; set; }
|
||||
public Ratings ratings { get; set; }
|
||||
public int qualityProfileId { get; set; }
|
||||
|
|
|
@ -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<List<SonarrProfile>>(request);
|
||||
}
|
||||
|
||||
public Task<Tag> 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<Tag>(request);
|
||||
}
|
||||
|
||||
public Task<Tag> 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<Tag>(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
public class TesterResultModel
|
||||
{
|
||||
public bool IsValid { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string ExpectedSubDir { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
10
src/Ombi.Core/Senders/SonarrSendOptions.cs
Normal file
10
src/Ombi.Core/Senders/SonarrSendOptions.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using Ombi.Api.Sonarr.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Core.Senders
|
||||
{
|
||||
internal class SonarrSendOptions
|
||||
{
|
||||
public List<int> Tags { get; set; } = new List<int>();
|
||||
}
|
||||
}
|
|
@ -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,11 +281,11 @@ 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!
|
||||
var seasonsToAdd = GetSeasonsToCreate(model);
|
||||
|
@ -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<Tag> 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<Tag> 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<Episode>();
|
||||
// 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<Episode>();
|
||||
// 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<Episode>();
|
||||
while (!sonarrEpList.Any())
|
||||
|
|
156
src/Ombi.I18n/Resources/Texts.ca.resx
Normal file
156
src/Ombi.I18n/Resources/Texts.ca.resx
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="NewAlbums" xml:space="preserve">
|
||||
<value>Nous Àlbums</value>
|
||||
</data>
|
||||
<data name="NewMovies" xml:space="preserve">
|
||||
<value>Noves Pel·lícules</value>
|
||||
</data>
|
||||
<data name="NewTV" xml:space="preserve">
|
||||
<value>Sèries Noves</value>
|
||||
</data>
|
||||
<data name="GenresLabel" xml:space="preserve">
|
||||
<value>Gèneres:</value>
|
||||
</data>
|
||||
<data name="AlbumTypeLabel" xml:space="preserve">
|
||||
<value>Tipus:</value>
|
||||
</data>
|
||||
<data name="SeasonLabel" xml:space="preserve">
|
||||
<value>Temporada:</value>
|
||||
</data>
|
||||
<data name="EpisodesLabel" xml:space="preserve">
|
||||
<value>Episodis:</value>
|
||||
</data>
|
||||
<data name="PoweredBy" xml:space="preserve">
|
||||
<value>Desenvolupat per</value>
|
||||
</data>
|
||||
<data name="Unsubscribe" xml:space="preserve">
|
||||
<value>Cancel·la la subscripció</value>
|
||||
</data>
|
||||
<data name="Album" xml:space="preserve">
|
||||
<value>Àlbum</value>
|
||||
</data>
|
||||
<data name="Movie" xml:space="preserve">
|
||||
<value>Pel·lícula</value>
|
||||
</data>
|
||||
<data name="TvShow" xml:space="preserve">
|
||||
<value>Sèries de TV</value>
|
||||
</data>
|
||||
</root>
|
|
@ -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<string,string> GetNotificationData(NotificationMessageContent parsed, NotificationType type)
|
||||
|
@ -240,7 +240,7 @@ namespace Ombi.Notifications.Agents
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected async Task Send(List<string> playerIds, NotificationMessage model, MobileNotificationSettings settings, NotificationOptions requestModel, bool isAdminNotification = false)
|
||||
protected async Task Send(List<string> 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<List<string>> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -324,5 +324,84 @@ namespace Ombi.Schedule.Tests
|
|||
|
||||
_mocker.Verify<OmbiUserManager>(x => x.UpdateAsync(It.Is<OmbiUser>(x => x.ProviderUserId == "PLEX_ID" && x.Email == "email" && x.UserName == "user")), Times.Once);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task Import_Cleanup_Missing_Plex_Users()
|
||||
{
|
||||
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||
.ReturnsAsync(new UserManagementSettings
|
||||
{
|
||||
ImportPlexAdmin = true,
|
||||
ImportPlexUsers = true,
|
||||
DefaultRoles = new List<string>
|
||||
{
|
||||
OmbiRoles.RequestMovie
|
||||
},
|
||||
CleanupPlexUsers = true,
|
||||
});
|
||||
_mocker.Setup<IPlexApi, Task<PlexFriends>>(x => x.GetUsers(It.IsAny<string>())).ReturnsAsync(new PlexFriends
|
||||
{
|
||||
User = new UserFriends[]
|
||||
{
|
||||
}
|
||||
});
|
||||
_mocker.Setup<IPlexApi, Task<PlexAccount>>(x => x.GetAccount(It.IsAny<string>())).ReturnsAsync(new PlexAccount
|
||||
{
|
||||
user = new User
|
||||
{
|
||||
email = "email",
|
||||
authentication_token = "user_token",
|
||||
title = "user_title",
|
||||
username = "user_username",
|
||||
id = "user_id",
|
||||
}
|
||||
});
|
||||
|
||||
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.CreateAsync(It.Is<OmbiUser>(x => x.UserName == "user_username" && x.Email == "email" && x.ProviderUserId == "user_id" && x.UserType == UserType.PlexUser)))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.AddToRoleAsync(It.Is<OmbiUser>(x => x.UserName == "user_username"), It.Is<string>(x => x == OmbiRoles.Admin)))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
await _subject.Execute(null);
|
||||
|
||||
_mocker.Verify<OmbiUserManager>(x => x.DeleteAsync(It.Is<OmbiUser>(x => x.ProviderUserId == "PLEX_ID" && x.Email == "dupe" && x.UserName == "plex")), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Import_Cleanup_Missing_Plex_Admin()
|
||||
{
|
||||
_mocker.Setup<ISettingsService<UserManagementSettings>, Task<UserManagementSettings>>(x => x.GetSettingsAsync())
|
||||
.ReturnsAsync(new UserManagementSettings
|
||||
{
|
||||
ImportPlexAdmin = true,
|
||||
ImportPlexUsers = false,
|
||||
DefaultRoles = new List<string>
|
||||
{
|
||||
OmbiRoles.RequestMovie
|
||||
},
|
||||
CleanupPlexUsers = true,
|
||||
});
|
||||
_mocker.Setup<IPlexApi, Task<PlexAccount>>(x => x.GetAccount(It.IsAny<string>())).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<OmbiUserManager, Task<IdentityResult>>(x => x.CreateAsync(It.Is<OmbiUser>(x => x.UserName == "diff_username" && x.Email == "diff_email" && x.ProviderUserId == "diff_user_id" && x.UserType == UserType.PlexUser)))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
_mocker.Setup<OmbiUserManager, Task<IdentityResult>>(x => x.AddToRoleAsync(It.Is<OmbiUser>(x => x.UserName == "diff_username"), It.Is<string>(x => x == OmbiRoles.Admin)))
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
await _subject.Execute(null);
|
||||
|
||||
_mocker.Verify<OmbiUserManager>(x => x.DeleteAsync(It.Is<OmbiUser>(x => x.ProviderUserId == "PLEX_ID" && x.Email == "dupe" && x.UserName == "plex")), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -345,7 +345,6 @@ namespace Ombi.Schedule.Tests
|
|||
[Test]
|
||||
public async Task TvRequestFromWatchList_AlreadyRequested()
|
||||
{
|
||||
|
||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||
{
|
||||
|
@ -540,5 +539,56 @@ namespace Ombi.Schedule.Tests
|
|||
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Never);
|
||||
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Once);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task TvRequestFromWatchList_RequestAllSeasons()
|
||||
{
|
||||
|
||||
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true, MonitorAll = true });
|
||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistContainer>>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(new PlexWatchlistContainer
|
||||
{
|
||||
MediaContainer = new PlexWatchlist
|
||||
{
|
||||
Metadata = new List<Metadata>
|
||||
{
|
||||
new Metadata
|
||||
{
|
||||
type = "show",
|
||||
ratingKey = "abc"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
_mocker.Setup<IPlexApi, Task<PlexWatchlistMetadataContainer>>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new PlexWatchlistMetadataContainer
|
||||
{
|
||||
MediaContainer = new PlexWatchlistMetadata
|
||||
{
|
||||
Metadata = new WatchlistMetadata[]
|
||||
{
|
||||
new WatchlistMetadata
|
||||
{
|
||||
Guid = new List<PlexGuids>
|
||||
{
|
||||
new PlexGuids
|
||||
{
|
||||
Id = "tmdb://123"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
_mocker.Setup<ITvRequestEngine, Task<RequestEngineResult>>(x => x.RequestTvShow(It.IsAny<TvRequestViewModelV2>()))
|
||||
.ReturnsAsync(new RequestEngineResult { RequestId = 1 });
|
||||
await _subject.Execute(_context.Object);
|
||||
_mocker.Verify<ITvRequestEngine>(x => x.RequestTvShow(It.Is<TvRequestViewModelV2>(x => x.TheMovieDbId == 123 && x.LatestSeason == false && x.RequestAll == true)), Times.Once);
|
||||
_mocker.Verify<IPlexApi>(x => x.GetWatchlistMetadata("abc", It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
_mocker.Verify<ITvRequestEngine>(x => x.SetUser(It.Is<OmbiUser>(x => x.Id == "abc")), Times.Once);
|
||||
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.GetAll(), Times.Once);
|
||||
_mocker.Verify<IExternalRepository<PlexWatchlistHistory>>(x => x.Add(It.IsAny<PlexWatchlistHistory>()), Times.Once);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<OmbiUser> newOrUpdatedUsers = new List<OmbiUser>();
|
||||
|
||||
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<OmbiUser> allUsers, PlexServers server)
|
||||
private async Task<List<OmbiUser>> ImportPlexUsers(UserManagementSettings userManagementSettings,
|
||||
List<OmbiUser> allUsers, PlexServers server)
|
||||
{
|
||||
var users = await _api.GetUsers(server.PlexAuthToken);
|
||||
|
||||
List<OmbiUser> newOrUpdatedUsers = new List<OmbiUser>();
|
||||
|
||||
foreach (var plexUser in users.User)
|
||||
{
|
||||
// Check if we should import this user
|
||||
|
@ -129,19 +154,21 @@ namespace Ombi.Schedule.Jobs.Plex
|
|||
{
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
if (userManagementSettings.DefaultRoles.Any())
|
||||
{
|
||||
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<OmbiUser> allUsers)
|
||||
private async Task<OmbiUser> ImportAdmin(UserManagementSettings settings, PlexServers server,
|
||||
List<OmbiUser> 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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<Downloads>(),
|
||||
UpdateAvailable = release.Version != "v" + AssemblyHelper.GetRuntimeVersion()
|
||||
UpdateAvailable = updateVersion > currentVersion
|
||||
};
|
||||
|
||||
foreach (var dl in release.Downloads)
|
||||
|
|
|
@ -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; }
|
||||
/// <summary>
|
||||
/// This is the ClientId for OAuth
|
||||
/// </summary>
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
|
|
3
src/Ombi/.vscode/settings.json
vendored
3
src/Ombi/.vscode/settings.json
vendored
|
@ -23,7 +23,8 @@
|
|||
"availability-rules",
|
||||
"details",
|
||||
"requests",
|
||||
"sonarr"
|
||||
"sonarr",
|
||||
"plex"
|
||||
],
|
||||
"rpc.enabled": true
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
@ -131,44 +131,74 @@ export class DiscoverCardComponent implements OnInit {
|
|||
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;
|
||||
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:
|
||||
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,
|
||||
const movieRequest: IMovieRequestModel = {
|
||||
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);
|
||||
requestOnBehalf: null,
|
||||
qualityPathOverride: null,
|
||||
rootFolderOverride: null,
|
||||
is4KRequest: is4k,
|
||||
};
|
||||
|
||||
if (!this.isAdmin) {
|
||||
this.requestMovie(movieRequest);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} 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);
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</div>
|
||||
<div *ngIf="discoverResults" class="row full-height">
|
||||
<div class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults">
|
||||
<discover-card [isAdmin]="isAdmins" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
|
||||
<discover-card [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -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;
|
||||
|
|
|
@ -12,3 +12,8 @@ export interface ILanguageProfiles {
|
|||
name: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ITag {
|
||||
label: string;
|
||||
id: number;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
export interface ITesterResult {
|
||||
isValid: boolean;
|
||||
version?: string;
|
||||
expectedSubDir?: string;
|
||||
}
|
||||
|
|
|
@ -57,11 +57,11 @@
|
|||
<i class="far fa-play-circle fa-2x"></i>
|
||||
</a>
|
||||
</ng-container>
|
||||
<button *ngIf="!tv.fullyAvailable && !allEpisodesRequested()" mat-raised-button id="requestBtn" class="btn-spacing" color="primary"
|
||||
<button *ngIf="(!tv.fullyAvailable || (tv.fullyAvailable && tv.partlyAvailable)) && !allEpisodesRequestedOrAvailable()" mat-raised-button id="requestBtn" class="btn-spacing" color="primary"
|
||||
(click)="request()"><i class="fas fa-plus"></i>
|
||||
{{ 'Common.Request' | translate }}</button>
|
||||
|
||||
<button *ngIf="!tv.denied && allEpisodesRequested()" mat-raised-button class="btn-spacing" color="warn" [disabled]>
|
||||
<button *ngIf="!tv.denied && allEpisodesRequestedOrAvailable()" mat-raised-button class="btn-spacing" color="warn" [disabled]>
|
||||
<i class="fas fa-check"></i>
|
||||
{{ 'Common.Requested' | translate }}</button>
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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<ILanguageProfiles[]>(`${this.url}/v3/languageprofiles/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getTags(settings: ISonarrSettings): Observable<ITag[]> {
|
||||
return this.http.post<ITag[]>(`${this.url}/tags/`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
||||
public isEnabled(): Promise<boolean> {
|
||||
return this.http.get<boolean>(`${this.url}/enabled/`, { headers: this.headers }).toPromise();
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-6 col-6 col-sm-6">
|
||||
<div class="md-form-field">
|
||||
<mat-slide-toggle [(ngModel)]="settings.enable" [checked]="settings.enable">Enable
|
||||
<mat-slide-toggle [(ngModel)]="settings.enable" (change)="toggle()" [checked]="settings.enable">Enable
|
||||
</mat-slide-toggle>
|
||||
</div> </div>
|
||||
</div>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-6 col-6 col-sm-6">
|
||||
<div class="md-form-field">
|
||||
<mat-slide-toggle [(ngModel)]="settings.enable" [checked]="settings.enable">Enable
|
||||
<mat-slide-toggle [(ngModel)]="settings.enable" (change)="toggle()" [checked]="settings.enable">Enable
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,11 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
|
|||
|
||||
@Component({
|
||||
selector: "settings-plex-form-field",
|
||||
styles: [`
|
||||
.margin {
|
||||
margin: 10px;
|
||||
}
|
||||
`],
|
||||
template: `
|
||||
<div class="row">
|
||||
<div class="col-2 align-self-center">
|
||||
|
@ -16,7 +21,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
|
|||
<input matInput placeholder={{placeholder}} [attr.type]="type" id="{{id}}" name="{{id}}" [ngModel]="value" (ngModelChange)="change($event)" value="{{value}}">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-slide-toggle *ngIf="type === 'checkbox'" id="{{id}}" [ngModel]="value" (ngModelChange)="change($event)" [checked]="value"></mat-slide-toggle>
|
||||
<mat-slide-toggle class="margin" *ngIf="type === 'checkbox'" id="{{id}}" [ngModel]="value" (ngModelChange)="change($event)" [checked]="value"></mat-slide-toggle>
|
||||
|
||||
<ng-content select="[below]"></ng-content>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
<settings-plex-form-field [label]="'Enable'" [type]="'checkbox'" [id]="'enable'" [(value)]="settings.enable"></settings-plex-form-field>
|
||||
|
||||
<settings-plex-form-field [label]="'Enable User Watchlist Requests'" [type]="'checkbox'" [id]="'enable'" [(value)]="settings.enableWatchlistImport">
|
||||
<settings-plex-form-field [label]="'Enable User Watchlist Requests'" [type]="'checkbox'" [id]="'enableWatchlistImport'" [(value)]="settings.enableWatchlistImport">
|
||||
<small bottom>When a Plex User adds something to their watchlist in Plex, it will turn up in Ombi as a Request if enabled. This <b>only</b> applies to users that are logging in with their Plex Account
|
||||
<br>Request limits if set are all still applied
|
||||
</small>
|
||||
|
|
|
@ -16,6 +16,10 @@
|
|||
<div class="md-form-field">
|
||||
<mat-slide-toggle formControlName="scanForAvailability">Scan for Availability</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="md-form-field">
|
||||
<mat-slide-toggle formControlName="sendUserTags" id="sendUserTags">Add the user as a tag</mat-slide-toggle>
|
||||
<small><br>This will add the username of the requesting user as a tag in Sonarr. If the tag doesn't exist, Ombi will create it.</small>
|
||||
</div>
|
||||
<div class="md-form-field" style="margin-top:1em;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,14 +57,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-md-5 col-4 col-sm-12">
|
||||
<label for="username" class="control-label"><h3>Sonarr Interface</h3></label>
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<div id="profiles">
|
||||
<div class="md-form-field" style="display:inline;">
|
||||
<button mat-raised-button id="profiles" (click)="getProfiles(form)" class="mat-stroked-button">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button mat-raised-button id="profiles" type="button" (click)="getProfiles(form)" class="mat-stroked-button">
|
||||
Load Qualities <span *ngIf="profilesRunning" class="fas fa-spinner fa-spin"></span></button>
|
||||
<div class="md-form-field" style="margin-top:1em;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="profiles" class="col-md-6">
|
||||
<div class="md-form-field" style="display:contents;">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Quality Profiles</mat-label>
|
||||
|
@ -72,7 +79,7 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div id="qualityProfileAnime">
|
||||
<div id="qualityProfileAnime" class="col-md-6">
|
||||
<div class="md-form-field" style="display:contents;">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Quality Profiles (Anime)</mat-label>
|
||||
|
@ -84,14 +91,18 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<div id="rootFolders">
|
||||
<div class="md-form-field" style="display:inline">
|
||||
<button mat-raised-button id="rootFolder" (click)="getRootFolders(form)" class="mat-stroked-button">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button mat-raised-button id="rootFolder" type="button" (click)="getRootFolders(form)" class="mat-stroked-button">
|
||||
Load Folders <span *ngIf="rootFoldersRunning" class="fas fa-spinner fa-spin"></span></button><div class="md-form-field" style="margin-top:1em;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="rootFolders" class="col-md-6">
|
||||
<div class="md-form-field" style="display:contents;">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Default Root Folders</mat-label>
|
||||
|
@ -103,7 +114,7 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div id="rootFoldersAnime">
|
||||
<div id="rootFoldersAnime" class="col-md-6">
|
||||
<div class="md-form-field" style="display:contents;">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Default Root Folders (Anime)</mat-label>
|
||||
|
@ -114,18 +125,55 @@
|
|||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
<div class="form-group col-md-12">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button mat-raised-button id="rootFolder" type="button" (click)="getTags(form)" class="mat-stroked-button">
|
||||
Load Tags <span *ngIf="tagsRunning" class="fas fa-spinner fa-spin"></span></button><div class="md-form-field" style="margin-top:1em;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="tag" class="col-md-6">
|
||||
<div class="md-form-field" style="display:contents;">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Default Tag</mat-label>
|
||||
<mat-select formControlName="tag">
|
||||
<mat-option *ngFor="let tag of tags" [value]="tag.id">{{tag.label}} </mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div id="animeTag" class="col-md-6">
|
||||
<div class="md-form-field" style="display:contents;">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Anime Tags</mat-label>
|
||||
<mat-select formControlName="animeTag">
|
||||
<mat-option *ngFor="let tag of animeTags" [value]="tag.id">{{tag.label}} </mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
<div class="form-group col-md-12" *ngIf="sonarrVersion === '3'">
|
||||
<label for="select" class="control-label">Language Profiles
|
||||
<i *ngIf="form.get('languageProfile').hasError('required')" class="fas fa-exclamation-circle error-text" pTooltip="A Language Profile is required"></i>
|
||||
</label>
|
||||
<div id="langaugeProfile">
|
||||
|
||||
|
||||
<div class="md-form-field" style="display:inline">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button type="button" mat-raised-button (click)="getLanguageProfiles(form)" class="mat-stroked-button">Load
|
||||
Languages <span *ngIf="langRunning" class="fas fa-spinner fa-spin"> </span></button><div class="md-form-field" style="margin-top:1em;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="langaugeProfile" class="col-md-6">
|
||||
|
||||
<div class="md-form-field" style="display:contents;">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Language Profiles</mat-label>
|
||||
|
@ -137,16 +185,17 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
<div id="langaugeProfileAnime">
|
||||
<div id="langaugeProfileAnime" class="col-md-6">
|
||||
<div class="md-form-field" style="display:contents;">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label>Language Profiles Anime</mat-label>
|
||||
<mat-label>Anime</mat-label>
|
||||
<mat-select formControlName="languageProfileAnime">
|
||||
<mat-option *ngFor="let lang of languageProfiles" [value]="lang.id">{{lang.name}}</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>A Language Profile Anime is required</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -166,11 +215,6 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="form-group col-md-7">
|
||||
<div>
|
||||
<button mat-raised-button type="submit" class="mat-stroked-button accent mat-accent">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-7">
|
||||
<div>
|
||||
|
@ -178,6 +222,12 @@
|
|||
<span id="spinner"> </span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group col-md-7">
|
||||
<div>
|
||||
<button mat-raised-button type="submit" class="mat-stroked-button accent mat-accent">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -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 test(form: UntypedFormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
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) {
|
||||
const settings = <ISonarrSettings> 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)) {
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
<div class="form-group">
|
||||
<mat-slide-toggle id="importAdmin" [(ngModel)]="settings.importPlexAdmin">Import Plex Admin</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div *ngIf="settings.importPlexUsers || settings.importPlexAdmin">
|
||||
<mat-slide-toggle id="cleanupPlexUsers" [(ngModel)]="settings.cleanupPlexUsers">
|
||||
Cleanup Plex Users</mat-slide-toggle>
|
||||
</div>
|
||||
<div *ngIf="plexUsers">
|
||||
<p>Plex Users excluded from Import</p>
|
||||
|
||||
|
|
|
@ -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.sonarrService.getV3LanguageProfilesWithoutSettings().subscribe((profiles: ILanguageProfiles[]) => {
|
||||
this.sonarrLanguageProfiles = profiles;
|
||||
})
|
||||
});
|
||||
this.sonarrService.getQualityProfilesWithoutSettings().subscribe(c => {
|
||||
this.sonarrProfiles = c;
|
||||
});
|
||||
|
|
|
@ -46,7 +46,7 @@ namespace Ombi.Controllers.V1.External
|
|||
/// </summary>
|
||||
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<TesterController> log, IEmailProvider provider,
|
||||
IPlexApi plex, IEmbyApiFactory emby, IRadarrV3Api radarr, ISonarrV3Api sonarr, ILogger<TesterController> 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<TesterController> 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
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"System": "Warning",
|
||||
"Microsoft": "Warning",
|
||||
"Microsoft": "Error",
|
||||
"Hangfire": "None",
|
||||
"System.Net.Http.HttpClient.health-checks": "Warning",
|
||||
"HealthChecks": "Warning"
|
||||
|
|
459
src/Ombi/wwwroot/translations/ca.json
Normal file
459
src/Ombi/wwwroot/translations/ca.json
Normal file
|
@ -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}}"
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -365,7 +365,7 @@
|
|||
"CastTitle": "演员"
|
||||
},
|
||||
"Crews": {
|
||||
"CrewTitle": "Crew"
|
||||
"CrewTitle": "工作人员"
|
||||
},
|
||||
"EpisodeSelector": {
|
||||
"AllSeasonsTooltip": "请求这个节目的每一季",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"version": "4.27.8"
|
||||
"version": "4.31.0"
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue