* Update pr.yml

[skip ci]

* Update label.yml

[skip ci]

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.19.0

* refactor(newsletter): Clarify very rare cases where newsletter doesn't publish a series

* refactor(newsletter): Clarify very rare cases where newsletter doesn't publish movie

* chore(release): 🚀 v4.19.1

* feat(discover): Show more relevant shows in upcoming TV

* chore(release): 🚀 v4.20.0

* fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes

* perf(sync): Emby+Jellyfin - use a more reliable filter to missing items

* fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes [skip ci]

* fix: added media type tag to media type text (#4638)

[skip ci]

* fix(sickrage): Fixed issue with incorrect handling of SiCKRAGE episode results returned during episode status changes, now expects array of objects from data path if present (#4648)

[skip ci]

* fix: Missing Poster broken link fix (#4637)

[skip ci]

* 🌐 Translations Update (#4622)

[skip ci]

* Update launch.json (#4650)

[skip ci]

* fix: Improve Swagger documentation (#4652)

* Upgrade Swashbuckle dependency

* Document /token response

* Add support for Newtonsoft annotations in Swagger

* Remove unecessary ActionResult [skip ci]

* fix(API): Fix pagination in some edge cases (#4649)

[skip ci]

* 🌐 Translations Update (#4655)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(discover): Carousel touch not working when scrolling page and recommendations and similar movie navigation (#4633)

* fixed touch not working on carousels

* fixed touch not working

* Movie details component fixes

Fixed recommendations and similar not changing the data on the component by calling the init function again on param change

Moved the ngif results > 0 to the mat-expansion panel to avoid rendering  the entire element if it doesn't have any results instead of having an empty panel.

* removed unused line, added scroll to top on init

* updated recommendation refresh implementation

Changed the implementation to use the router instead in order to reload the component instead of just reloading the data.

This implementation makes sure the component gets destroyed on navigation eliminating any memory leaks, reloading CSS in case of having animations on page load and generally a continuation of the experience you get when you browse into a movie from the discover page.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.20.1 [skip ci]

* fix: 🐛 Fixed the Request on Behalf of having blanks (#4667)

* chore(release): 🚀 v4.20.2 [skip ci]

* fix(plex): 🐛 Fixed an issue with the Plex Sync

* chore(release): 🚀 v4.20.3 [skip ci]

* fix (technical): Improved some of the date time parsing handling

* fix: fixed build

* chore(release): 🚀 v4.20.4 [skip ci]

* feat: Upgrade to Angular14 (#4668)

* refactor: 🔥 removed angular-bootstrap-md dependancy

* chore: update tsconfig

* yeah

* ng14 upgrade

* refactor: migration changes

* fix: fixed CLI

* test: Fixed automation

* chore: 👥 Updated Contributors [skip ci]

* perf: stop populating obsolete subscribe fields (#4625)

* chore(release): 🚀 v4.21.0 [skip ci]

* fix(images): Retry images with a backoff when we get a Too Many requests from TheMovieDb #4685

* chore(release): 🚀 v4.21.1 [skip ci]

* 🌐 Translations Update (#4683)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix: Landing and Login page improvements (#4690)

* chore(release): 🚀 v4.21.2 [skip ci]

* feat(discover):  Added infinite scroll on advanced search results

* feat(discover):  Added infinite scroll on advanced search results

* chore(release): 🚀 v4.22.0 [skip ci]

* 🌐 Translations Update (#4694)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(discover): 🐛 Created new Image component to handle 429's from TMDB (#4698) and fixed #4635 (#4699)

* chore(release): 🚀 v4.22.1 [skip ci]

* fix: fixed an issue where I broke images for some users

* chore(release): 🚀 v4.22.2 [skip ci]

* ci(Mergify): configuration update (#4701)

Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci]

* fix: Override Sonarr V3 Profiles endpoint (#4678)

* Override Sonarr V3 Profiles endpoint [skip ci]

* fix(4K) :4K request fixes (#4702)

* GetRequestsByStatus wasn't implementing the MovieRequests object correctly for 4K quality requests with the ProcessingRequest status.

* Fixed 4K requests not getting automatically approved if the user has the "Auto Approve Movie" role flag enabled.

* Fixed "Request Date" values for the "left-panel-details" div class. Previously when the movie was exclusively 4K (regular request was absent), then "Request Date" equaled DateTime.MinValue (January 1, 0001).

* Fixed "Request Status" evaluation in the "left-panel-details" div class. Now it shows the appropriate status instead of an empty spot. "Request Status" displays both regular and 4K statuses at the same time if needed. Added a comma to the end of the "RequestStatus" label to maintain design consistency with the other labels. Also added a "Denied Reason" element for 4K  requests.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.22.3 [skip ci]

* chore: Storybook (#4700)

[skip ci]

* chore: Translations

[skip ci]

* 🌐 Translations Update (#4704)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci] (#4713)

* build: Run automation tests in docker (#4715)

[skip ci]

* fix: fixed trakt image not loading when base url present (#4711)

[skip ci]

* fix: 🐛 Fixed missing externals (#4712)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.22.4 [skip ci]

* test: fixed automationt tests [skip ci]

* fix: Log Microsoft warnings to log file (#4723)

[skip ci]

* feat:  Recently Requested on Discover Page (#4387)

* chore(release): 🚀 v4.23.0 [skip ci]

* fix: Localize recently requested on discover page (#4729)

[skip ci]

* 🌐 Translations Update (#4731)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* Fix: Ombi.Api.Lidarr: Remove unused fields from ArtistAdd (#4727)

When an artist is not found in Lidarr as part of requesting an album,
MusicSender will make a POST request against the /api/v1/artist endpoint
to add such artist.

Not all fields defined in ArtistAdd are initialized, and those
uninitialized will be `null` values in the JSON body of the request, as
shown in this intercepted request:

```
{
    "addOptions": {
        "AlbumsToMonitor": [
            "e5c48b66-44ef-3685-ad53-45dbcd7294c0"
        ],
        "monitor": 6,
        "monitored": true,
        "searchForMissingAlbums": false
    },
    "added": "2022-08-10T06:49:32.4374278+00:00",
    "albumFolder": true,
    "artistName": "Manolo García",
    "cleanName": "manologarcía",
    "disambiguation": null,
    "discogsId": 0,
    "ended": false,
    "foreignArtistId": "1c8309da-9789-40bf-b9c2-e20064263820",
    "images": [],
    "links": [],
    "metadataProfileId": 1,
    "monitored": true,
    "overview": null,
    "qualityProfileId": 3,
    "ratings": null,
    "remotePoster": null,
    "rootFolderPath": "/media/music/",
    "sortName": null,
    "statistics": null,
    "status": null,
    "tadbId": 0,
    "tags": null
}
```

This request will fail and Lidarr will return a 400 BadRequest error
with the following message:

```
2022-08-10 01:45:52.458 +00:00 [Error] StatusCode: BadRequest, Reason: Bad Request, RequestUri: http://lidarr:8686/api/v1/artist
2022-08-10 01:45:52.459 +00:00 [Debug] {
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-52e01b322a05d7c6633eca2488ef2a5c-06345b3bb8c4bb6c-00",
  "errors": {
    "$.status": [
      "The JSON value could not be converted to NzbDrone.Core.Music.ArtistStatusType. Path: $.status | LineNumber: 0 | BytePositionInLine: 14."
    ]
  }
}
```

Removing all the `null` fields from the JSON body fixes the problem and
correctly adds the artist to Lidarr.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.23.1 [skip ci]

* fix: Fix conflicting property name for Swagger (#4733)

* chore(release): 🚀 v4.23.2 [skip ci]

* feat: add crew on movie page (#4722)

* add crew on movie page

* order by director, add default image and fix click

Co-authored-by: tidusjar <tidusjar@gmail.com>

* 🌐 Translations Update (#4736)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* chore(release): 🚀 v4.24.0 [skip ci]

* feat: Watchlist history errors(#4741)

[skip ci]

* fix: fixed stats controller (#4742)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.25.0 [skip ci]

* fix(webhook): Remove added trailing slash from webhook URL #4710

* chore(release): 🚀 v4.25.1 [skip ci]

* feat(notifications): Add more curly variables for partially available notification

* feat: Add more curly variables for partially available notification

* test: Fix newly added test

* chore(release): 🚀 v4.26.0 [skip ci]

* feat: Recently requested improvements (#4755)

* feat(discover):  Admins can now approve the Recently Requested list

* feat(discover):  Images for the recently requested area are now loading faster and just better all around

* test:  Added automation for the new feature

* chore(release): 🚀 v4.27.0 [skip ci]

* fix(plex): stop the plex sync from deleting episodes when we can't find the plex key

* chore(release): 🚀 v4.27.1 [skip ci]

* refactor: Encapsulate common TV availability checker logic (#4753)

[skip ci]

* fix(sonarr): 🐛 Cleaned up and removed Sonarr v3 option, sonarr v3 is now the default. This allows us to get ready for the upcoming Sonarr v4 (#4764)

* chore(release): 🚀 v4.27.2 [skip ci]

* 🌐 Translations Update (#4739)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(availability): 🐛 Fixed a issue with the availability checker after the previous update. Added full test coverage around that area

* chore(release): 🚀 v4.27.3 [skip ci]

* test: 🧪 added full test coverage to the plex availability checker, also fixed a small few bugs in there at the same time

* chore(release): 🚀 v4.27.4 [skip ci]

* fix(importer): 🐛 Allow you to only import Plex Admins without the Plex Users

* chore(release): 🚀 v4.27.5 [skip ci]

* fix(notifications): Fixed the error when sending multiple test notifications. Added more logging when Discord complains the message is invalid

* chore(release): 🚀 v4.27.6 [skip ci]

* fix: Fixes default image for recently requested items. (#4767)

* chore(release): 🚀 v4.27.7 [skip ci]

* refactor: Upgrades nuget packages. Removes deprecated packages. Fixes build warnings. (#4769)

* Upgrades nuget packages. Removes deprecated packages. Fixes build warnings.

* Fixes the last few build warnings.

* chore(release): 🚀 v4.27.8 [skip ci]

* refactor: Rework the Plex Settings Page (#4772)

[skip ci]

* feat(plex):  Added the ability to configure the watchlist to request the whole TV show rather than latest season (#4774)

* chore(release): 🚀 v4.28.0 [skip ci]

* 🌐 Translations Update (#4771)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* 🌐 Translations Update (#4775)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix: Reworked the version check (#4719) (#4781)

[skip ci]

* fix(plex): 🐛 Fixed not being able to enable watchlist requests in the Plex settings

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.28.1 [skip ci]

* feat: Provide a flag for missing users on Plex Server (#4688) (#4778)

[skip ci]

* fix: Unable to Delete Jellyfin Server (#4705) (#4780)

[skip ci]

* fix: Partially Available prevents further TV requests (#4768) (#4779)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.29.0 [skip ci]

* fix: Consistently reset loading flag when requesting movies on discover page. (#4777)

[skip ci]

* fix(sonarr): 🐛 Fixed an issue where the language list didn't correctly load for power users in the advanced options #4782

* chore(release): 🚀 v4.29.1 [skip ci]

* fix(plex): Fixed an issue where sometimes the availability checker would throw an exception when checking episodes

* chore: fixed tests

* chore(release): 🚀 v4.29.2 [skip ci]

* fix: Only log error messages from Microsoft (#4787)

[skip ci]

* 🌐 Translations Update (#4784)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(notifications): Fixed the Partially TV notifications going to the admin #4797 (#4799)

* chore(release): 🚀 v4.29.3 [skip ci]

* feat(sonarr):  Add the username to a Sonarr tag when sent to Sonarr (#4802)

* chore(release): 🚀 v4.30.0 [skip ci]

* feat(sonarr): Added the ability to add default tags when sending to Sonarr (#4803)

* chore(release): 🚀 v4.31.0 [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci] (#4801)

* feat(plex): Rework the Plex Settings page (#4805)

* chore(release): 🚀 v4.32.0 [skip ci]

* fix(plex): 🐛 Fixed the issue where you couldn't add a new server on a fresh setup after the settings page rework

* chore(release): 🚀 v4.32.1 [skip ci]

* fix(sonarr): 🐛 Sonarr V4 should work now (#4810)

* fix(sonarr): 🐛 Sonarr V4 should work now

Auto detect the sonarr version and adjust the UI depending on V3 or V4 (Lang profiles)

* fix: Fixed the load error

* chore(release): 🚀 v4.32.2 [skip ci]

* fix(sonarr): V4 actually works this time around

* chore(release): 🚀 v4.32.3 [skip ci]

* feat: Angular 15 and Dependency upgrades (#4818)

* chore(release): 🚀 v4.33.0 [skip ci]

* fix(plex): Added the watchlist request whole show back into the settings

* chore: undid

* fixed (#4833)

* chore(release): 🚀 v4.33.1 [skip ci]

* chore: add logo [skip ci]

* feat: Radarr tags (#4815)

* chore(release): 🚀 v4.34.0 [skip ci]

* fix(plex-watchlist): Lookup the ID from different sources when Plex doesn't contain the metadata (#4843)

* chore(release): 🚀 v4.34.1 [skip ci]

* feat: Add the option for header authentication to create users (#4841)

* feat: allow SSO to create new users automatically

* feat: apply default user settings to SSO users

* feat: add warnings to header auth toggles

* chore(release): 🚀 v4.35.0 [skip ci]

* fix(plex-watchlist): Index out of bounds error

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.35.1 [skip ci]

* fix(database): Just some tweaks, shouldn't notice any difference, maybe a less error in the log

* chore(release): 🚀 v4.35.2 [skip ci]

* fix(#4847): Invalid Discord request fixed, also fixed an issue where App Only users would not show as logged in on the user management page (#4848)

* chore(release): 🚀 v4.35.3 [skip ci]

* bug(#4854): 🐛 Fixed the Recently Requested showing requests when it should be hidden

* fix(discover): 🐛 Fixed the default poster not taking into account the base url in some scenarios #4845

* fix(Hide music from navbar and request list when not enabled): 🐛

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.35.4 [skip ci]

* fix(radarr-settings): 🐛 Fixed a typo

* chore(release): 🚀 v4.35.5 [skip ci]

* fix: Fixed the issue where the login page is still present after logging in with oauth

* chore(release): 🚀 v4.35.6 [skip ci]

* fix(wizard): 🐛 Stop access to the wizard when you have already setup ombi (#4866)

* chore(release): 🚀 v4.35.7 [skip ci]

* fix(plex-oauth): 🐛 Fixed an issue where using OAuth you could log in as a Ombi Local user #4835

* chore(release): 🚀 v4.35.8 [skip ci]

* chore: 👥 Updated Contributors [skip ci]

* fixed bad merge

* chore(release): 🚀 v4.35.9 [skip ci]

* Update .gitignore

* Fixed automation

* fix(sonarr): 🐛 Improved the error handling in the sonarr settings page in the UI

This should hopefully prevent some odd situations where the settings are in a odd state #4877

* chore: update deps

* chore: more deps

* bump

* chore(release): 🚀 v4.35.12 [skip ci]

* fix(sonarr): 🐛 Added some more error handling and information around testing sonarr

#4877

* chore(release): 🚀 v4.35.13 [skip ci]

* fix: Some minor tweaks to the movie info panel (#4883)

* fix: Hide denied reason label if there is no value

* fix: Movie would show as pending approval when denied

* chore(release): 🚀 v4.35.14 [skip ci]

* fix(sonarr): 🐛 Stop the sonarr version endpoint from breaking when Sonarr is down #4895

* chore(release): 🚀 v4.35.15 [skip ci]

* fix: Support duplicates in Emby/JF collections (#4902)

Support same movie that belongs in different collections
in Emby or Jellyfin

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.35.16 [skip ci]

* fix(discover): Fix denied requests displayed as approved (#4901)

* fix: Fix denied movie shown as 'processing request' in details view (#4900)

* chore(release): 🚀 v4.35.17 [skip ci]

* fix(#4906): 🐛 Fixed an issue with power users and permissions

* chore(release): 🚀 v4.35.18 [skip ci]

* fix(radarr): Fixed an issue where the radarr sync would break

* chore(release): 🚀 v4.35.19 [skip ci]

* feat(discover): Add deny option to recently requested (#4907)

* chore(release): 🚀 v4.36.0 [skip ci]

* fix(healthchecks): Removed redundant ping check

* chore(release): 🚀 v4.36.1 [skip ci]

* feat: Search by genre

[skip-ci]

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.37.0 [skip ci]

* fix(discover): 🚸 Improved the new Genre buttons, it now includes TV results

* chore: 👥 Updated Contributors [skip ci]

* fix: Cron Validation (#4842)

* Add Cron Next Time Validation

The cron job can't be created if the year is more than 100 years in the future.
Getting the next valid time will return null if this is the case.

* add next cron validation to api
* add next cron validation to job settings page

* Add Missing Import

* chore: 👥 Updated Contributors [skip ci]

* 🌐 Translations Update (#4806)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(lidarr): Change monitor to Existing to properly add artist #3597

Discussed and tested manually in https://github.com/Lidarr/Lidarr/issues/3597#issuecomment-1530804055

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.37.1 [skip ci]

* fix(jellyfin): Fixed an issue where the sync could stop working. Removed unused properties so the deseralization no longer fails

* chore(release): 🚀 v4.37.2 [skip ci]

* fix: Show the ApiAlias in the requests-list

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.37.3 [skip ci]

* feat(emby): Show watched status for Movie requests

* First step towards played sync

* Change TMDB id format to integer

This will better integrate with TMDB id type in the request model

* Display played state in the requests list

* Fix played status filter

* Run played sync job after content sync instead of on its own

* Add a toggle to activate played sync

* Hoovering

* FIx played sync job not being triggered

* Expose played state according to hide requests setting

* Fix tests

* Fix tests for real

* Add MySql migrations

[skip ci]

* fix: remove sort header

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.38.0 [skip ci]

* fix(notificaitons): Add the RequestedByAlias field to the Notification Message

* chore: 👥 Updated Contributors [skip ci]

* 🌐 Translations Update (#4921)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(API): Allow RequestOnBehalf rights if requested from the API (#4919)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.38.1 [skip ci]

* Merge pull request from GHSA-28j3-84m7-gpjp

* chore(release): 🚀 v4.38.2 [skip ci]

* Develop master (#4930)

* Update (#4871)

* Remove dead code

* Localize TV requests messages on TV details page

* Transform buttons with link into anchors

* Sonarr sync: stop using seasonpass API

* chore(release): 🚀 v4.16.13

* Fix requests when 4k available and 4k disabled

Fixes #4610

* chore(release): 🚀 v4.16.14

* Hide subscribe button when request is available

* Hide subscribe button when request is denied

* Add Title to Partially Available Message

If the Title of the show is not menitoned it can be unclear what Episodes are now available.

* Better error message when test email fails due to missing recipient

* feat(discover): Add original language filter

* chore(release): 🚀 v4.16.15

* fix(4616): 🐛 fixed mandatory fields

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.16.16

* added test results into the PR pipeline

* chore(release): 🚀 v4.16.17

* Add information about cache refresh

* Update pr.yml

[skip ci]

* Update pr.yml

[skip ci]

* Update pr.yml

[skip ci]

* chore(release): 🚀 v4.17.0

* feat(discover): Add new trending source experimental feature

* fix(settings): Allow toggling features when there are more than one

* fix(discover): Fix new trending feature detection

* fix(discover): Fix cache mix up

* refactor(discover): Move movie trending feature toggle to backend

* feat(discover): Default trending source to new logic

* chore(release): 🚀 v4.18.0

* feat(sync): Detect reidentified movies in Emby and Jellyfin

* feat(sync): Detect reidentified series in Emby and Jellyfin

* Fix sync log criticity

* Update pr.yml

[skip ci]

* Update label.yml

[skip ci]

* Fix formatting

* Update pr.yml

[skip ci]

* Update label.yml

[skip ci]

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.19.0

* refactor(newsletter): Clarify very rare cases where newsletter doesn't publish a series

* refactor(newsletter): Clarify very rare cases where newsletter doesn't publish movie

* chore(release): 🚀 v4.19.1

* feat(discover): Show more relevant shows in upcoming TV

* chore(release): 🚀 v4.20.0

* fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes

* perf(sync): Emby+Jellyfin - use a more reliable filter to missing items

* fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes [skip ci]

* fix: added media type tag to media type text (#4638)

[skip ci]

* fix(sickrage): Fixed issue with incorrect handling of SiCKRAGE episode results returned during episode status changes, now expects array of objects from data path if present (#4648)

[skip ci]

* fix: Missing Poster broken link fix (#4637)

[skip ci]

* 🌐 Translations Update (#4622)

[skip ci]

* Update launch.json (#4650)

[skip ci]

* fix: Improve Swagger documentation (#4652)

* Upgrade Swashbuckle dependency

* Document /token response

* Add support for Newtonsoft annotations in Swagger

* Remove unecessary ActionResult [skip ci]

* fix(API): Fix pagination in some edge cases (#4649)

[skip ci]

* 🌐 Translations Update (#4655)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(discover): Carousel touch not working when scrolling page and recommendations and similar movie navigation (#4633)

* fixed touch not working on carousels

* fixed touch not working

* Movie details component fixes

Fixed recommendations and similar not changing the data on the component by calling the init function again on param change

Moved the ngif results > 0 to the mat-expansion panel to avoid rendering  the entire element if it doesn't have any results instead of having an empty panel.

* removed unused line, added scroll to top on init

* updated recommendation refresh implementation

Changed the implementation to use the router instead in order to reload the component instead of just reloading the data.

This implementation makes sure the component gets destroyed on navigation eliminating any memory leaks, reloading CSS in case of having animations on page load and generally a continuation of the experience you get when you browse into a movie from the discover page.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.20.1 [skip ci]

* fix: 🐛 Fixed the Request on Behalf of having blanks (#4667)

* chore(release): 🚀 v4.20.2 [skip ci]

* fix(plex): 🐛 Fixed an issue with the Plex Sync

* chore(release): 🚀 v4.20.3 [skip ci]

* fix (technical): Improved some of the date time parsing handling

* fix: fixed build

* chore(release): 🚀 v4.20.4 [skip ci]

* feat: Upgrade to Angular14 (#4668)

* refactor: 🔥 removed angular-bootstrap-md dependancy

* chore: update tsconfig

* yeah

* ng14 upgrade

* refactor: migration changes

* fix: fixed CLI

* test: Fixed automation

* chore: 👥 Updated Contributors [skip ci]

* perf: stop populating obsolete subscribe fields (#4625)

* chore(release): 🚀 v4.21.0 [skip ci]

* fix(images): Retry images with a backoff when we get a Too Many requests from TheMovieDb #4685

* chore(release): 🚀 v4.21.1 [skip ci]

* 🌐 Translations Update (#4683)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix: Landing and Login page improvements (#4690)

* chore(release): 🚀 v4.21.2 [skip ci]

* feat(discover):  Added infinite scroll on advanced search results

* feat(discover):  Added infinite scroll on advanced search results

* chore(release): 🚀 v4.22.0 [skip ci]

* 🌐 Translations Update (#4694)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(discover): 🐛 Created new Image component to handle 429's from TMDB (#4698) and fixed #4635 (#4699)

* chore(release): 🚀 v4.22.1 [skip ci]

* fix: fixed an issue where I broke images for some users

* chore(release): 🚀 v4.22.2 [skip ci]

* ci(Mergify): configuration update (#4701)

Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci]

* fix: Override Sonarr V3 Profiles endpoint (#4678)

* Override Sonarr V3 Profiles endpoint [skip ci]

* fix(4K) :4K request fixes (#4702)

* GetRequestsByStatus wasn't implementing the MovieRequests object correctly for 4K quality requests with the ProcessingRequest status.

* Fixed 4K requests not getting automatically approved if the user has the "Auto Approve Movie" role flag enabled.

* Fixed "Request Date" values for the "left-panel-details" div class. Previously when the movie was exclusively 4K (regular request was absent), then "Request Date" equaled DateTime.MinValue (January 1, 0001).

* Fixed "Request Status" evaluation in the "left-panel-details" div class. Now it shows the appropriate status instead of an empty spot. "Request Status" displays both regular and 4K statuses at the same time if needed. Added a comma to the end of the "RequestStatus" label to maintain design consistency with the other labels. Also added a "Denied Reason" element for 4K  requests.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.22.3 [skip ci]

* chore: Storybook (#4700)

[skip ci]

* chore: Translations

[skip ci]

* 🌐 Translations Update (#4704)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci] (#4713)

* build: Run automation tests in docker (#4715)

[skip ci]

* fix: fixed trakt image not loading when base url present (#4711)

[skip ci]

* fix: 🐛 Fixed missing externals (#4712)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.22.4 [skip ci]

* test: fixed automationt tests [skip ci]

* fix: Log Microsoft warnings to log file (#4723)

[skip ci]

* feat:  Recently Requested on Discover Page (#4387)

* chore(release): 🚀 v4.23.0 [skip ci]

* fix: Localize recently requested on discover page (#4729)

[skip ci]

* 🌐 Translations Update (#4731)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* Fix: Ombi.Api.Lidarr: Remove unused fields from ArtistAdd (#4727)

When an artist is not found in Lidarr as part of requesting an album,
MusicSender will make a POST request against the /api/v1/artist endpoint
to add such artist.

Not all fields defined in ArtistAdd are initialized, and those
uninitialized will be `null` values in the JSON body of the request, as
shown in this intercepted request:

```
{
    "addOptions": {
        "AlbumsToMonitor": [
            "e5c48b66-44ef-3685-ad53-45dbcd7294c0"
        ],
        "monitor": 6,
        "monitored": true,
        "searchForMissingAlbums": false
    },
    "added": "2022-08-10T06:49:32.4374278+00:00",
    "albumFolder": true,
    "artistName": "Manolo García",
    "cleanName": "manologarcía",
    "disambiguation": null,
    "discogsId": 0,
    "ended": false,
    "foreignArtistId": "1c8309da-9789-40bf-b9c2-e20064263820",
    "images": [],
    "links": [],
    "metadataProfileId": 1,
    "monitored": true,
    "overview": null,
    "qualityProfileId": 3,
    "ratings": null,
    "remotePoster": null,
    "rootFolderPath": "/media/music/",
    "sortName": null,
    "statistics": null,
    "status": null,
    "tadbId": 0,
    "tags": null
}
```

This request will fail and Lidarr will return a 400 BadRequest error
with the following message:

```
2022-08-10 01:45:52.458 +00:00 [Error] StatusCode: BadRequest, Reason: Bad Request, RequestUri: http://lidarr:8686/api/v1/artist
2022-08-10 01:45:52.459 +00:00 [Debug] {
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-52e01b322a05d7c6633eca2488ef2a5c-06345b3bb8c4bb6c-00",
  "errors": {
    "$.status": [
      "The JSON value could not be converted to NzbDrone.Core.Music.ArtistStatusType. Path: $.status | LineNumber: 0 | BytePositionInLine: 14."
    ]
  }
}
```

Removing all the `null` fields from the JSON body fixes the problem and
correctly adds the artist to Lidarr.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.23.1 [skip ci]

* fix: Fix conflicting property name for Swagger (#4733)

* chore(release): 🚀 v4.23.2 [skip ci]

* feat: add crew on movie page (#4722)

* add crew on movie page

* order by director, add default image and fix click

Co-authored-by: tidusjar <tidusjar@gmail.com>

* 🌐 Translations Update (#4736)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* chore(release): 🚀 v4.24.0 [skip ci]

* feat: Watchlist history errors(#4741)

[skip ci]

* fix: fixed stats controller (#4742)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.25.0 [skip ci]

* fix(webhook): Remove added trailing slash from webhook URL #4710

* chore(release): 🚀 v4.25.1 [skip ci]

* feat(notifications): Add more curly variables for partially available notification

* feat: Add more curly variables for partially available notification

* test: Fix newly added test

* chore(release): 🚀 v4.26.0 [skip ci]

* feat: Recently requested improvements (#4755)

* feat(discover):  Admins can now approve the Recently Requested list

* feat(discover):  Images for the recently requested area are now loading faster and just better all around

* test:  Added automation for the new feature

* chore(release): 🚀 v4.27.0 [skip ci]

* fix(plex): stop the plex sync from deleting episodes when we can't find the plex key

* chore(release): 🚀 v4.27.1 [skip ci]

* refactor: Encapsulate common TV availability checker logic (#4753)

[skip ci]

* fix(sonarr): 🐛 Cleaned up and removed Sonarr v3 option, sonarr v3 is now the default. This allows us to get ready for the upcoming Sonarr v4 (#4764)

* chore(release): 🚀 v4.27.2 [skip ci]

* 🌐 Translations Update (#4739)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(availability): 🐛 Fixed a issue with the availability checker after the previous update. Added full test coverage around that area

* chore(release): 🚀 v4.27.3 [skip ci]

* test: 🧪 added full test coverage to the plex availability checker, also fixed a small few bugs in there at the same time

* chore(release): 🚀 v4.27.4 [skip ci]

* fix(importer): 🐛 Allow you to only import Plex Admins without the Plex Users

* chore(release): 🚀 v4.27.5 [skip ci]

* fix(notifications): Fixed the error when sending multiple test notifications. Added more logging when Discord complains the message is invalid

* chore(release): 🚀 v4.27.6 [skip ci]

* fix: Fixes default image for recently requested items. (#4767)

* chore(release): 🚀 v4.27.7 [skip ci]

* refactor: Upgrades nuget packages. Removes deprecated packages. Fixes build warnings. (#4769)

* Upgrades nuget packages. Removes deprecated packages. Fixes build warnings.

* Fixes the last few build warnings.

* chore(release): 🚀 v4.27.8 [skip ci]

* refactor: Rework the Plex Settings Page (#4772)

[skip ci]

* feat(plex):  Added the ability to configure the watchlist to request the whole TV show rather than latest season (#4774)

* chore(release): 🚀 v4.28.0 [skip ci]

* 🌐 Translations Update (#4771)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* 🌐 Translations Update (#4775)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix: Reworked the version check (#4719) (#4781)

[skip ci]

* fix(plex): 🐛 Fixed not being able to enable watchlist requests in the Plex settings

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.28.1 [skip ci]

* feat: Provide a flag for missing users on Plex Server (#4688) (#4778)

[skip ci]

* fix: Unable to Delete Jellyfin Server (#4705) (#4780)

[skip ci]

* fix: Partially Available prevents further TV requests (#4768) (#4779)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.29.0 [skip ci]

* fix: Consistently reset loading flag when requesting movies on discover page. (#4777)

[skip ci]

* fix(sonarr): 🐛 Fixed an issue where the language list didn't correctly load for power users in the advanced options #4782

* chore(release): 🚀 v4.29.1 [skip ci]

* fix(plex): Fixed an issue where sometimes the availability checker would throw an exception when checking episodes

* chore: fixed tests

* chore(release): 🚀 v4.29.2 [skip ci]

* fix: Only log error messages from Microsoft (#4787)

[skip ci]

* 🌐 Translations Update (#4784)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(notifications): Fixed the Partially TV notifications going to the admin #4797 (#4799)

* chore(release): 🚀 v4.29.3 [skip ci]

* feat(sonarr):  Add the username to a Sonarr tag when sent to Sonarr (#4802)

* chore(release): 🚀 v4.30.0 [skip ci]

* feat(sonarr): Added the ability to add default tags when sending to Sonarr (#4803)

* chore(release): 🚀 v4.31.0 [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci] (#4801)

* feat(plex): Rework the Plex Settings page (#4805)

* chore(release): 🚀 v4.32.0 [skip ci]

* fix(plex): 🐛 Fixed the issue where you couldn't add a new server on a fresh setup after the settings page rework

* chore(release): 🚀 v4.32.1 [skip ci]

* fix(sonarr): 🐛 Sonarr V4 should work now (#4810)

* fix(sonarr): 🐛 Sonarr V4 should work now

Auto detect the sonarr version and adjust the UI depending on V3 or V4 (Lang profiles)

* fix: Fixed the load error

* chore(release): 🚀 v4.32.2 [skip ci]

* fix(sonarr): V4 actually works this time around

* chore(release): 🚀 v4.32.3 [skip ci]

* feat: Angular 15 and Dependency upgrades (#4818)

* chore(release): 🚀 v4.33.0 [skip ci]

* fix(plex): Added the watchlist request whole show back into the settings

* chore: undid

* fixed (#4833)

* chore(release): 🚀 v4.33.1 [skip ci]

* chore: add logo [skip ci]

* feat: Radarr tags (#4815)

* chore(release): 🚀 v4.34.0 [skip ci]

* fix(plex-watchlist): Lookup the ID from different sources when Plex doesn't contain the metadata (#4843)

* chore(release): 🚀 v4.34.1 [skip ci]

* feat: Add the option for header authentication to create users (#4841)

* feat: allow SSO to create new users automatically

* feat: apply default user settings to SSO users

* feat: add warnings to header auth toggles

* chore(release): 🚀 v4.35.0 [skip ci]

* fix(plex-watchlist): Index out of bounds error

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.35.1 [skip ci]

* fix(database): Just some tweaks, shouldn't notice any difference, maybe a less error in the log

* chore(release): 🚀 v4.35.2 [skip ci]

* fix(#4847): Invalid Discord request fixed, also fixed an issue where App Only users would not show as logged in on the user management page (#4848)

* chore(release): 🚀 v4.35.3 [skip ci]

* bug(#4854): 🐛 Fixed the Recently Requested showing requests when it should be hidden

* fix(discover): 🐛 Fixed the default poster not taking into account the base url in some scenarios #4845

* fix(Hide music from navbar and request list when not enabled): 🐛

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.35.4 [skip ci]

* fix(radarr-settings): 🐛 Fixed a typo

* chore(release): 🚀 v4.35.5 [skip ci]

* fix: Fixed the issue where the login page is still present after logging in with oauth

* chore(release): 🚀 v4.35.6 [skip ci]

* fix(wizard): 🐛 Stop access to the wizard when you have already setup ombi (#4866)

* chore(release): 🚀 v4.35.7 [skip ci]

* fix(plex-oauth): 🐛 Fixed an issue where using OAuth you could log in as a Ombi Local user #4835

* chore(release): 🚀 v4.35.8 [skip ci]

* chore: 👥 Updated Contributors [skip ci]

* fixed bad merge

* chore(release): 🚀 v4.35.9 [skip ci]

* Update .gitignore

* Fixed automation

---------

Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci]
Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com>
Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com>
Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com>
Co-authored-by: echel0n <echel0n@sickrage.ca>
Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com>
Co-authored-by: Igor Borges <igor@borges.dev>
Co-authored-by: Lucane <Lucane@users.noreply.github.com>
Co-authored-by: mkgeeky <github@mkgeeky.xyz>
Co-authored-by: Miguel A Vico Moya <mvicomoya@gmail.com>
Co-authored-by: Hadrien <26697460+ketsapiwiq@users.noreply.github.com>
Co-authored-by: Victor Usoltsev <bernarden@users.noreply.github.com>
Co-authored-by: Wesley King <kingwe92@gmail.com>
Co-authored-by: Lea <me@janderedev.xyz>

* chore(release): 🚀 v4.35.10 [skip ci]

---------

Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci]
Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com>
Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com>
Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com>
Co-authored-by: echel0n <echel0n@sickrage.ca>
Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com>
Co-authored-by: Igor Borges <igor@borges.dev>
Co-authored-by: Lucane <Lucane@users.noreply.github.com>
Co-authored-by: mkgeeky <github@mkgeeky.xyz>
Co-authored-by: Miguel A Vico Moya <mvicomoya@gmail.com>
Co-authored-by: Hadrien <26697460+ketsapiwiq@users.noreply.github.com>
Co-authored-by: Victor Usoltsev <bernarden@users.noreply.github.com>
Co-authored-by: Wesley King <kingwe92@gmail.com>
Co-authored-by: Lea <me@janderedev.xyz>

* Develop master (#4931) [skip ci]

* Update (#4871)

* Remove dead code

* Localize TV requests messages on TV details page

* Transform buttons with link into anchors

* Sonarr sync: stop using seasonpass API

* chore(release): 🚀 v4.16.13

* Fix requests when 4k available and 4k disabled

Fixes #4610

* chore(release): 🚀 v4.16.14

* Hide subscribe button when request is available

* Hide subscribe button when request is denied

* Add Title to Partially Available Message

If the Title of the show is not menitoned it can be unclear what Episodes are now available.

* Better error message when test email fails due to missing recipient

* feat(discover): Add original language filter

* chore(release): 🚀 v4.16.15

* fix(4616): 🐛 fixed mandatory fields

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.16.16

* added test results into the PR pipeline

* chore(release): 🚀 v4.16.17

* Add information about cache refresh

* Update pr.yml

[skip ci]

* Update pr.yml

[skip ci]

* Update pr.yml

[skip ci]

* chore(release): 🚀 v4.17.0

* feat(discover): Add new trending source experimental feature

* fix(settings): Allow toggling features when there are more than one

* fix(discover): Fix new trending feature detection

* fix(discover): Fix cache mix up

* refactor(discover): Move movie trending feature toggle to backend

* feat(discover): Default trending source to new logic

* chore(release): 🚀 v4.18.0

* feat(sync): Detect reidentified movies in Emby and Jellyfin

* feat(sync): Detect reidentified series in Emby and Jellyfin

* Fix sync log criticity

* Update pr.yml

[skip ci]

* Update label.yml

[skip ci]

* Fix formatting

* Update pr.yml

[skip ci]

* Update label.yml

[skip ci]

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.19.0

* refactor(newsletter): Clarify very rare cases where newsletter doesn't publish a series

* refactor(newsletter): Clarify very rare cases where newsletter doesn't publish movie

* chore(release): 🚀 v4.19.1

* feat(discover): Show more relevant shows in upcoming TV

* chore(release): 🚀 v4.20.0

* fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes

* perf(sync): Emby+Jellyfin - use a more reliable filter to missing items

* fix(sync): Emby+Jellyfin - sync multi-episode files of 3+ episodes [skip ci]

* fix: added media type tag to media type text (#4638)

[skip ci]

* fix(sickrage): Fixed issue with incorrect handling of SiCKRAGE episode results returned during episode status changes, now expects array of objects from data path if present (#4648)

[skip ci]

* fix: Missing Poster broken link fix (#4637)

[skip ci]

* 🌐 Translations Update (#4622)

[skip ci]

* Update launch.json (#4650)

[skip ci]

* fix: Improve Swagger documentation (#4652)

* Upgrade Swashbuckle dependency

* Document /token response

* Add support for Newtonsoft annotations in Swagger

* Remove unecessary ActionResult [skip ci]

* fix(API): Fix pagination in some edge cases (#4649)

[skip ci]

* 🌐 Translations Update (#4655)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(discover): Carousel touch not working when scrolling page and recommendations and similar movie navigation (#4633)

* fixed touch not working on carousels

* fixed touch not working

* Movie details component fixes

Fixed recommendations and similar not changing the data on the component by calling the init function again on param change

Moved the ngif results > 0 to the mat-expansion panel to avoid rendering  the entire element if it doesn't have any results instead of having an empty panel.

* removed unused line, added scroll to top on init

* updated recommendation refresh implementation

Changed the implementation to use the router instead in order to reload the component instead of just reloading the data.

This implementation makes sure the component gets destroyed on navigation eliminating any memory leaks, reloading CSS in case of having animations on page load and generally a continuation of the experience you get when you browse into a movie from the discover page.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.20.1 [skip ci]

* fix: 🐛 Fixed the Request on Behalf of having blanks (#4667)

* chore(release): 🚀 v4.20.2 [skip ci]

* fix(plex): 🐛 Fixed an issue with the Plex Sync

* chore(release): 🚀 v4.20.3 [skip ci]

* fix (technical): Improved some of the date time parsing handling

* fix: fixed build

* chore(release): 🚀 v4.20.4 [skip ci]

* feat: Upgrade to Angular14 (#4668)

* refactor: 🔥 removed angular-bootstrap-md dependancy

* chore: update tsconfig

* yeah

* ng14 upgrade

* refactor: migration changes

* fix: fixed CLI

* test: Fixed automation

* chore: 👥 Updated Contributors [skip ci]

* perf: stop populating obsolete subscribe fields (#4625)

* chore(release): 🚀 v4.21.0 [skip ci]

* fix(images): Retry images with a backoff when we get a Too Many requests from TheMovieDb #4685

* chore(release): 🚀 v4.21.1 [skip ci]

* 🌐 Translations Update (#4683)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix: Landing and Login page improvements (#4690)

* chore(release): 🚀 v4.21.2 [skip ci]

* feat(discover):  Added infinite scroll on advanced search results

* feat(discover):  Added infinite scroll on advanced search results

* chore(release): 🚀 v4.22.0 [skip ci]

* 🌐 Translations Update (#4694)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(discover): 🐛 Created new Image component to handle 429's from TMDB (#4698) and fixed #4635 (#4699)

* chore(release): 🚀 v4.22.1 [skip ci]

* fix: fixed an issue where I broke images for some users

* chore(release): 🚀 v4.22.2 [skip ci]

* ci(Mergify): configuration update (#4701)

Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci]

* fix: Override Sonarr V3 Profiles endpoint (#4678)

* Override Sonarr V3 Profiles endpoint [skip ci]

* fix(4K) :4K request fixes (#4702)

* GetRequestsByStatus wasn't implementing the MovieRequests object correctly for 4K quality requests with the ProcessingRequest status.

* Fixed 4K requests not getting automatically approved if the user has the "Auto Approve Movie" role flag enabled.

* Fixed "Request Date" values for the "left-panel-details" div class. Previously when the movie was exclusively 4K (regular request was absent), then "Request Date" equaled DateTime.MinValue (January 1, 0001).

* Fixed "Request Status" evaluation in the "left-panel-details" div class. Now it shows the appropriate status instead of an empty spot. "Request Status" displays both regular and 4K statuses at the same time if needed. Added a comma to the end of the "RequestStatus" label to maintain design consistency with the other labels. Also added a "Denied Reason" element for 4K  requests.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.22.3 [skip ci]

* chore: Storybook (#4700)

[skip ci]

* chore: Translations

[skip ci]

* 🌐 Translations Update (#4704)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci] (#4713)

* build: Run automation tests in docker (#4715)

[skip ci]

* fix: fixed trakt image not loading when base url present (#4711)

[skip ci]

* fix: 🐛 Fixed missing externals (#4712)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.22.4 [skip ci]

* test: fixed automationt tests [skip ci]

* fix: Log Microsoft warnings to log file (#4723)

[skip ci]

* feat:  Recently Requested on Discover Page (#4387)

* chore(release): 🚀 v4.23.0 [skip ci]

* fix: Localize recently requested on discover page (#4729)

[skip ci]

* 🌐 Translations Update (#4731)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* Fix: Ombi.Api.Lidarr: Remove unused fields from ArtistAdd (#4727)

When an artist is not found in Lidarr as part of requesting an album,
MusicSender will make a POST request against the /api/v1/artist endpoint
to add such artist.

Not all fields defined in ArtistAdd are initialized, and those
uninitialized will be `null` values in the JSON body of the request, as
shown in this intercepted request:

```
{
    "addOptions": {
        "AlbumsToMonitor": [
            "e5c48b66-44ef-3685-ad53-45dbcd7294c0"
        ],
        "monitor": 6,
        "monitored": true,
        "searchForMissingAlbums": false
    },
    "added": "2022-08-10T06:49:32.4374278+00:00",
    "albumFolder": true,
    "artistName": "Manolo García",
    "cleanName": "manologarcía",
    "disambiguation": null,
    "discogsId": 0,
    "ended": false,
    "foreignArtistId": "1c8309da-9789-40bf-b9c2-e20064263820",
    "images": [],
    "links": [],
    "metadataProfileId": 1,
    "monitored": true,
    "overview": null,
    "qualityProfileId": 3,
    "ratings": null,
    "remotePoster": null,
    "rootFolderPath": "/media/music/",
    "sortName": null,
    "statistics": null,
    "status": null,
    "tadbId": 0,
    "tags": null
}
```

This request will fail and Lidarr will return a 400 BadRequest error
with the following message:

```
2022-08-10 01:45:52.458 +00:00 [Error] StatusCode: BadRequest, Reason: Bad Request, RequestUri: http://lidarr:8686/api/v1/artist
2022-08-10 01:45:52.459 +00:00 [Debug] {
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-52e01b322a05d7c6633eca2488ef2a5c-06345b3bb8c4bb6c-00",
  "errors": {
    "$.status": [
      "The JSON value could not be converted to NzbDrone.Core.Music.ArtistStatusType. Path: $.status | LineNumber: 0 | BytePositionInLine: 14."
    ]
  }
}
```

Removing all the `null` fields from the JSON body fixes the problem and
correctly adds the artist to Lidarr.

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.23.1 [skip ci]

* fix: Fix conflicting property name for Swagger (#4733)

* chore(release): 🚀 v4.23.2 [skip ci]

* feat: add crew on movie page (#4722)

* add crew on movie page

* order by director, add default image and fix click

Co-authored-by: tidusjar <tidusjar@gmail.com>

* 🌐 Translations Update (#4736)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* chore(release): 🚀 v4.24.0 [skip ci]

* feat: Watchlist history errors(#4741)

[skip ci]

* fix: fixed stats controller (#4742)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.25.0 [skip ci]

* fix(webhook): Remove added trailing slash from webhook URL #4710

* chore(release): 🚀 v4.25.1 [skip ci]

* feat(notifications): Add more curly variables for partially available notification

* feat: Add more curly variables for partially available notification

* test: Fix newly added test

* chore(release): 🚀 v4.26.0 [skip ci]

* feat: Recently requested improvements (#4755)

* feat(discover):  Admins can now approve the Recently Requested list

* feat(discover):  Images for the recently requested area are now loading faster and just better all around

* test:  Added automation for the new feature

* chore(release): 🚀 v4.27.0 [skip ci]

* fix(plex): stop the plex sync from deleting episodes when we can't find the plex key

* chore(release): 🚀 v4.27.1 [skip ci]

* refactor: Encapsulate common TV availability checker logic (#4753)

[skip ci]

* fix(sonarr): 🐛 Cleaned up and removed Sonarr v3 option, sonarr v3 is now the default. This allows us to get ready for the upcoming Sonarr v4 (#4764)

* chore(release): 🚀 v4.27.2 [skip ci]

* 🌐 Translations Update (#4739)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(availability): 🐛 Fixed a issue with the availability checker after the previous update. Added full test coverage around that area

* chore(release): 🚀 v4.27.3 [skip ci]

* test: 🧪 added full test coverage to the plex availability checker, also fixed a small few bugs in there at the same time

* chore(release): 🚀 v4.27.4 [skip ci]

* fix(importer): 🐛 Allow you to only import Plex Admins without the Plex Users

* chore(release): 🚀 v4.27.5 [skip ci]

* fix(notifications): Fixed the error when sending multiple test notifications. Added more logging when Discord complains the message is invalid

* chore(release): 🚀 v4.27.6 [skip ci]

* fix: Fixes default image for recently requested items. (#4767)

* chore(release): 🚀 v4.27.7 [skip ci]

* refactor: Upgrades nuget packages. Removes deprecated packages. Fixes build warnings. (#4769)

* Upgrades nuget packages. Removes deprecated packages. Fixes build warnings.

* Fixes the last few build warnings.

* chore(release): 🚀 v4.27.8 [skip ci]

* refactor: Rework the Plex Settings Page (#4772)

[skip ci]

* feat(plex):  Added the ability to configure the watchlist to request the whole TV show rather than latest season (#4774)

* chore(release): 🚀 v4.28.0 [skip ci]

* 🌐 Translations Update (#4771)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* 🌐 Translations Update (#4775)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix: Reworked the version check (#4719) (#4781)

[skip ci]

* fix(plex): 🐛 Fixed not being able to enable watchlist requests in the Plex settings

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.28.1 [skip ci]

* feat: Provide a flag for missing users on Plex Server (#4688) (#4778)

[skip ci]

* fix: Unable to Delete Jellyfin Server (#4705) (#4780)

[skip ci]

* fix: Partially Available prevents further TV requests (#4768) (#4779)

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.29.0 [skip ci]

* fix: Consistently reset loading flag when requesting movies on discover page. (#4777)

[skip ci]

* fix(sonarr): 🐛 Fixed an issue where the language list didn't correctly load for power users in the advanced options #4782

* chore(release): 🚀 v4.29.1 [skip ci]

* fix(plex): Fixed an issue where sometimes the availability checker would throw an exception when checking episodes

* chore: fixed tests

* chore(release): 🚀 v4.29.2 [skip ci]

* fix: Only log error messages from Microsoft (#4787)

[skip ci]

* 🌐 Translations Update (#4784)

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci]

* fix(notifications): Fixed the Partially TV notifications going to the admin #4797 (#4799)

* chore(release): 🚀 v4.29.3 [skip ci]

* feat(sonarr):  Add the username to a Sonarr tag when sent to Sonarr (#4802)

* chore(release): 🚀 v4.30.0 [skip ci]

* feat(sonarr): Added the ability to add default tags when sending to Sonarr (#4803)

* chore(release): 🚀 v4.31.0 [skip ci]

* fix(translations): 🌐 New translations from Crowdin [skip ci] (#4801)

* feat(plex): Rework the Plex Settings page (#4805)

* chore(release): 🚀 v4.32.0 [skip ci]

* fix(plex): 🐛 Fixed the issue where you couldn't add a new server on a fresh setup after the settings page rework

* chore(release): 🚀 v4.32.1 [skip ci]

* fix(sonarr): 🐛 Sonarr V4 should work now (#4810)

* fix(sonarr): 🐛 Sonarr V4 should work now

Auto detect the sonarr version and adjust the UI depending on V3 or V4 (Lang profiles)

* fix: Fixed the load error

* chore(release): 🚀 v4.32.2 [skip ci]

* fix(sonarr): V4 actually works this time around

* chore(release): 🚀 v4.32.3 [skip ci]

* feat: Angular 15 and Dependency upgrades (#4818)

* chore(release): 🚀 v4.33.0 [skip ci]

* fix(plex): Added the watchlist request whole show back into the settings

* chore: undid

* fixed (#4833)

* chore(release): 🚀 v4.33.1 [skip ci]

* chore: add logo [skip ci]

* feat: Radarr tags (#4815)

* chore(release): 🚀 v4.34.0 [skip ci]

* fix(plex-watchlist): Lookup the ID from different sources when Plex doesn't contain the metadata (#4843)

* chore(release): 🚀 v4.34.1 [skip ci]

* feat: Add the option for header authentication to create users (#4841)

* feat: allow SSO to create new users automatically

* feat: apply default user settings to SSO users

* feat: add warnings to header auth toggles

* chore(release): 🚀 v4.35.0 [skip ci]

* fix(plex-watchlist): Index out of bounds error

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.35.1 [skip ci]

* fix(database): Just some tweaks, shouldn't notice any difference, maybe a less error in the log

* chore(release): 🚀 v4.35.2 [skip ci]

* fix(#4847): Invalid Discord request fixed, also fixed an issue where App Only users would not show as logged in on the user management page (#4848)

* chore(release): 🚀 v4.35.3 [skip ci]

* bug(#4854): 🐛 Fixed the Recently Requested showing requests when it should be hidden

* fix(discover): 🐛 Fixed the default poster not taking into account the base url in some scenarios #4845

* fix(Hide music from navbar and request list when not enabled): 🐛

* chore: 👥 Updated Contributors [skip ci]

* chore(release): 🚀 v4.35.4 [skip ci]

* fix(radarr-settings): 🐛 Fixed a typo

* chore(release): 🚀 v4.35.5 [skip ci]

* fix: Fixed the issue where the login page is still present after logging in with oauth

* chore(release): 🚀 v4.35.6 [skip ci]

* fix(wizard): 🐛 Stop access to the wizard when you have already setup ombi (#4866)

* chore(release): 🚀 v4.35.7 [skip ci]

* fix(plex-oauth): 🐛 Fixed an issue where using OAuth you could log in as a Ombi Local user #4835

* chore(release): 🚀 v4.35.8 [skip ci]

* chore: 👥 Updated Contributors [skip ci]

* fixed bad merge

* chore(release): 🚀 v4.35.9 [skip ci]

* Update .gitignore

* Fixed automation

---------

Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci]
Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com>
Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com>
Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com>
Co-authored-by: echel0n <echel0n@sickrage.ca>
Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com>
Co-authored-by: Igor Borges <igor@borges.dev>
Co-authored-by: Lucane <Lucane@users.noreply.github.com>
Co-authored-by: mkgeeky <github@mkgeeky.xyz>
Co-authored-by: Miguel A Vico Moya <mvicomoya@gmail.com>
Co-authored-by: Hadrien <26697460+ketsapiwiq@users.noreply.github.com>
Co-authored-by: Victor Usoltsev <bernarden@users.noreply.github.com>
Co-authored-by: Wesley King <kingwe92@gmail.com>
Co-authored-by: Lea <me@janderedev.xyz>

* chore(release): 🚀 v4.35.10 [skip ci]

---------

Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci]
Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com>
Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com>
Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com>
Co-authored-by: echel0n <echel0n@sickrage.ca>
Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com>
Co-authored-by: Igor Borges <igor@borges.dev>
Co-authored-by: Lucane <Lucane@users.noreply.github.com>
Co-authored-by: mkgeeky <github@mkgeeky.xyz>
Co-authored-by: Miguel A Vico Moya <mvicomoya@gmail.com>
Co-authored-by: Hadrien <26697460+ketsapiwiq@users.noreply.github.com>
Co-authored-by: Victor Usoltsev <bernarden@users.noreply.github.com>
Co-authored-by: Wesley King <kingwe92@gmail.com>
Co-authored-by: Lea <me@janderedev.xyz>

* fix(emby): Fix Emby played sync running a full sync during recently added sync (#4932)

* feat: Hide watched status when request is not available (#4934)

* chore(release): 🚀 v4.39.0 [skip ci]

* ignore flaky test

---------

Signed-off-by: Jamie <tidusjar@gmail.com> [skip ci]
Co-authored-by: contrib-readme-bot <contrib-readme-action@noreply.com>
Co-authored-by: Conventional Changelog Action <conventional.changelog.action@github.com>
Co-authored-by: sephrat <34862846+sephrat@users.noreply.github.com>
Co-authored-by: dr3amer <91037083+dr3am37@users.noreply.github.com>
Co-authored-by: echel0n <echel0n@sickrage.ca>
Co-authored-by: Marley <55280588+marleypowell@users.noreply.github.com>
Co-authored-by: Igor Borges <igor@borges.dev>
Co-authored-by: Lucane <Lucane@users.noreply.github.com>
Co-authored-by: mkgeeky <github@mkgeeky.xyz>
Co-authored-by: Miguel A Vico Moya <mvicomoya@gmail.com>
Co-authored-by: Hadrien <26697460+ketsapiwiq@users.noreply.github.com>
Co-authored-by: Victor Usoltsev <bernarden@users.noreply.github.com>
Co-authored-by: Wesley King <kingwe92@gmail.com>
Co-authored-by: Lea <me@janderedev.xyz>
Co-authored-by: ryan-c44 <54028283+ryan-c44@users.noreply.github.com>
Co-authored-by: Alexander Russell <ajex94@gmail.com>
Co-authored-by: Grygon <647846+Grygon@users.noreply.github.com>
Co-authored-by: phildups7 <60622768+phildups7@users.noreply.github.com>
Co-authored-by: Teifun2 <Teifun2@users.noreply.github.com>
pull/4938/head^2
Jamie 1 year ago committed by GitHub
parent 12a90f1eba
commit e297f863a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,345 +1,375 @@
## [4.35.11](https://github.com/Ombi-app/Ombi/compare/v4.35.10...v4.35.11) (2023-05-17) # [4.39.0](https://github.com/Ombi-app/Ombi/compare/v4.38.2...v4.39.0) (2023-05-17)
### Bug Fixes
## [4.35.10](https://github.com/Ombi-app/Ombi/compare/v4.22.5...v4.35.10) (2023-02-25) * **emby:** Fix Emby played sync running a full sync during recently added sync ([#4932](https://github.com/Ombi-app/Ombi/issues/4932)) ([9424586](https://github.com/Ombi-app/Ombi/commit/9424586e9c1b622b6475aeb8ee3cf4a8f346da6e))
### Features
## [4.22.5](https://github.com/Ombi-app/Ombi/compare/v4.16.12...v4.22.5) (2022-08-05) * Hide watched status when request is not available ([#4934](https://github.com/Ombi-app/Ombi/issues/4934)) ([82c7f1c](https://github.com/Ombi-app/Ombi/commit/82c7f1c44fd7c87d57cc2b0c34a10fcda7628f4e))
## [4.16.12](https://github.com/Ombi-app/Ombi/compare/v4.16.11...v4.16.12) (2022-04-19) ## [4.38.2](https://github.com/Ombi-app/Ombi/compare/v4.38.1...v4.38.2) (2023-05-17)
## [4.16.11](https://github.com/Ombi-app/Ombi/compare/v4.16.10...v4.16.11) (2022-04-14) ## [4.38.1](https://github.com/Ombi-app/Ombi/compare/v4.38.0...v4.38.1) (2023-05-09)
### Bug Fixes ### Bug Fixes
* Set the default job for the watchlist import to hourly instead of daily ([75906af](https://github.com/Ombi-app/Ombi/commit/75906af0adee3e3c68d825c3aaa8f7b918461b1f)) * **API:** Allow RequestOnBehalf rights if requested from the API ([#4919](https://github.com/Ombi-app/Ombi/issues/4919)) ([bb6dedd](https://github.com/Ombi-app/Ombi/commit/bb6deddfaecb3d6c7c3c6970414444b619bb9106))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([0e8a64b](https://github.com/Ombi-app/Ombi/commit/0e8a64b8ca00d210fbe843ac2c3f6af218d80cbc)) * **notificaitons:** Add the RequestedByAlias field to the Notification Message ([7e9c8be](https://github.com/Ombi-app/Ombi/commit/7e9c8bec6b02bb4e11f8db50394e493d4dd07723))
* **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)) # [4.38.0](https://github.com/Ombi-app/Ombi/compare/v4.37.3...v4.38.0) (2023-05-07)
* **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)
## [4.16.10](https://github.com/Ombi-app/Ombi/compare/v4.16.9...v4.16.10) (2022-04-13)
## [4.16.9](https://github.com/Ombi-app/Ombi/compare/v4.16.8...v4.16.9) (2022-04-13)
### Bug Fixes ### Bug Fixes
* **plex-watchlist:** Only request the latest season when importing from the watchlist ([77a47ff](https://github.com/Ombi-app/Ombi/commit/77a47ff157c6c5feafe3f2a29a3fcba8df4fdfef)) * remove sort header ([969bc7b](https://github.com/Ombi-app/Ombi/commit/969bc7bb25ea900ab9199509b079b36843e5bd6f))
### Features
## [4.16.8](https://github.com/Ombi-app/Ombi/compare/v4.16.7...v4.16.8) (2022-04-13) * **emby:** Show watched status for Movie requests ([9cfb10b](https://github.com/Ombi-app/Ombi/commit/9cfb10bb1ee69067a6f47bd2c8a72d4e6834350e))
## [4.37.3](https://github.com/Ombi-app/Ombi/compare/v4.37.2...v4.37.3) (2023-05-07)
### Bug Fixes ### Bug Fixes
* **availability:** Fixed an issue where we wouldn't mark a available 4k movie as available (when 4K request feature is disabled) ([b492699](https://github.com/Ombi-app/Ombi/commit/b49269961d4830a530e3054976a47f519524948b)) * Show the ApiAlias in the requests-list ([9ff624c](https://github.com/Ombi-app/Ombi/commit/9ff624ce4646815b239fbb8327117947f0a90e4b))
## [4.16.7](https://github.com/Ombi-app/Ombi/compare/v4.16.6...v4.16.7) (2022-04-12) ## [4.37.2](https://github.com/Ombi-app/Ombi/compare/v4.37.1...v4.37.2) (2023-05-03)
### Bug Fixes
## [4.16.6](https://github.com/Ombi-app/Ombi/compare/v4.16.5...v4.16.6) (2022-04-11) * **jellyfin:** Fixed an issue where the sync could stop working. Removed unused properties so the deseralization no longer fails ([0e5e0ad](https://github.com/Ombi-app/Ombi/commit/0e5e0adf862701d0f672beff14ec0aa75e4b5220))
## [4.16.5](https://github.com/Ombi-app/Ombi/compare/v4.16.4...v4.16.5) (2022-04-08) ## [4.37.1](https://github.com/Ombi-app/Ombi/compare/v4.37.0...v4.37.1) (2023-05-02)
### Bug Fixes ### Bug Fixes
* **watchlist:** actually fixed it this time... ([d962a32](https://github.com/Ombi-app/Ombi/commit/d962a3211eca29520662ddce962676e3aea17ec5)) * Cron Validation ([#4842](https://github.com/Ombi-app/Ombi/issues/4842)) ([97cc42f](https://github.com/Ombi-app/Ombi/commit/97cc42ffa8672e7d0d0996b5fbda7f7fe699da2d))
* **discover:** :children_crossing: Improved the new Genre buttons, it now includes TV results ([b087d60](https://github.com/Ombi-app/Ombi/commit/b087d606ff36565208e564f8856903f2a4098db5))
* **lidarr:** Change monitor to Existing to properly add artist [#3597](https://github.com/Ombi-app/Ombi/issues/3597) ([506f607](https://github.com/Ombi-app/Ombi/commit/506f60773bf1031d0be51ccd34289b855a04ea40)), closes [/github.com/Lidarr/Lidarr/issues/3597#issuecomment-1530804055](https://github.com//github.com/Lidarr/Lidarr/issues/3597/issues/issuecomment-1530804055)
## [4.16.4](https://github.com/Ombi-app/Ombi/compare/v4.16.3...v4.16.4) (2022-04-08) # [4.37.0](https://github.com/Ombi-app/Ombi/compare/v4.36.1...v4.37.0) (2023-04-24)
### Features
## [4.16.3](https://github.com/Ombi-app/Ombi/compare/v4.16.2...v4.16.3) (2022-04-08) * Search by genre ([1837419](https://github.com/Ombi-app/Ombi/commit/18374198f9f2462ba85c5781b0fcc05892728b21))
## [4.36.1](https://github.com/Ombi-app/Ombi/compare/v4.36.0...v4.36.1) (2023-04-20)
### Bug Fixes ### Bug Fixes
* **plex-watchlist:** :bug: Fixed the issue where the watchlist didn't work for users logging in via OAuth ([6398f6a](https://github.com/Ombi-app/Ombi/commit/6398f6a4f7755281ebeac537e3ff623df5cfa0f3)) * **healthchecks:** Removed redundant ping check ([1751305](https://github.com/Ombi-app/Ombi/commit/1751305064176d2c0135f867773ccc46b03915ec))
# [4.36.0](https://github.com/Ombi-app/Ombi/compare/v4.35.19...v4.36.0) (2023-04-20)
### Features
* **discover:** Add deny option to recently requested ([#4907](https://github.com/Ombi-app/Ombi/issues/4907)) ([78f340e](https://github.com/Ombi-app/Ombi/commit/78f340ee5f309c55690497170897533801957668))
## [4.16.2](https://github.com/Ombi-app/Ombi/compare/v4.16.1...v4.16.2) (2022-04-07) ## [4.35.19](https://github.com/Ombi-app/Ombi/compare/v4.35.18...v4.35.19) (2023-04-20)
### Bug Fixes ### Bug Fixes
* **wizard:** Fixed an issue when using Plex OAuth it could fail setting up ([b743cf4](https://github.com/Ombi-app/Ombi/commit/b743cf4fafa7341ad1b163276f006d7ab0e9dcff)) * **radarr:** Fixed an issue where the radarr sync would break ([de4baad](https://github.com/Ombi-app/Ombi/commit/de4baade9f87248d77106ff1a313a498870f4fb3))
## [4.16.1](https://github.com/Ombi-app/Ombi/compare/v4.16.0...v4.16.1) (2022-04-07) ## [4.35.18](https://github.com/Ombi-app/Ombi/compare/v4.35.17...v4.35.18) (2023-04-15)
### Bug Fixes
* **#4906:** :bug: Fixed an issue with power users and permissions ([80884bc](https://github.com/Ombi-app/Ombi/commit/80884bcd725c329867c278ad235cd4096cd4fe7a))
## [4.35.17](https://github.com/Ombi-app/Ombi/compare/v4.35.16...v4.35.17) (2023-04-15)
# [4.16.0](https://github.com/Ombi-app/Ombi/compare/v4.15.6...v4.16.0) (2022-04-07)
### Bug Fixes
* **discover:** Fix denied requests displayed as approved ([#4901](https://github.com/Ombi-app/Ombi/issues/4901)) ([1e87f20](https://github.com/Ombi-app/Ombi/commit/1e87f2010491b0f3fdda70d2b19d9afd94438df7))
* Fix denied movie shown as 'processing request' in details view ([#4900](https://github.com/Ombi-app/Ombi/issues/4900)) ([0069bfd](https://github.com/Ombi-app/Ombi/commit/0069bfdf54e0785bad45c832ca052f19fd4b940b))
## [4.15.6](https://github.com/Ombi-app/Ombi/compare/v4.15.5...v4.15.6) (2022-04-07) ## [4.35.16](https://github.com/Ombi-app/Ombi/compare/v4.35.15...v4.35.16) (2023-04-13)
### Bug Fixes ### Bug Fixes
* **radarr:** Fixed an issue where we couldn't sync radarr content [#4577](https://github.com/Ombi-app/Ombi/issues/4577) ([a5355a3](https://github.com/Ombi-app/Ombi/commit/a5355a3023e6900c4dd1b0da4722d7596c03907f)) * Support duplicates in Emby/JF collections ([#4902](https://github.com/Ombi-app/Ombi/issues/4902)) ([141f96d](https://github.com/Ombi-app/Ombi/commit/141f96da5e45d5b3fa5f496806b102e473da6607))
## [4.35.15](https://github.com/Ombi-app/Ombi/compare/v4.35.14...v4.35.15) (2023-04-06)
## [4.15.5](https://github.com/Ombi-app/Ombi/compare/v4.15.4...v4.15.5) (2022-04-06) ### Bug Fixes
* **sonarr:** :bug: Stop the sonarr version endpoint from breaking when Sonarr is down [#4895](https://github.com/Ombi-app/Ombi/issues/4895) ([7bb8bec](https://github.com/Ombi-app/Ombi/commit/7bb8becfb140ef6012356752a71d53b5b404e482))
## [4.15.4](https://github.com/Ombi-app/Ombi/compare/v4.15.3...v4.15.4) (2022-03-29) ## [4.35.14](https://github.com/Ombi-app/Ombi/compare/v4.35.13...v4.35.14) (2023-04-06)
### Bug Fixes
## [4.15.3](https://github.com/Ombi-app/Ombi/compare/v4.15.2...v4.15.3) (2022-03-24) * Some minor tweaks to the movie info panel ([#4883](https://github.com/Ombi-app/Ombi/issues/4883)) ([1244487](https://github.com/Ombi-app/Ombi/commit/12444871df2f7602200f73971fce962f06b4a80b))
## [4.15.2](https://github.com/Ombi-app/Ombi/compare/v4.15.1...v4.15.2) (2022-03-23) ## [4.35.13](https://github.com/Ombi-app/Ombi/compare/v4.35.12...v4.35.13) (2023-03-28)
### Bug Fixes ### Bug Fixes
* **metadata:** improved the metadata job to also lookup the media in Plex to see if it has any more uptodate metadata ([83d1a15](https://github.com/Ombi-app/Ombi/commit/83d1a15cc9d0ee91be73bd91c4672cf1bcf2728a)) * **sonarr:** :bug: Added some more error handling and information around testing sonarr ([bd2c2d3](https://github.com/Ombi-app/Ombi/commit/bd2c2d3901e239393010fd582b207f1571fb4b7e)), closes [#4877](https://github.com/Ombi-app/Ombi/issues/4877)
## [4.15.1](https://github.com/Ombi-app/Ombi/compare/v4.15.0...v4.15.1) (2022-03-18) ## [4.35.12](https://github.com/Ombi-app/Ombi/compare/v4.35.9...v4.35.12) (2023-03-25)
### Bug Fixes ### Bug Fixes
* **mediaserver:** fixed an issue where we were not detecting available content correctly [#4542](https://github.com/Ombi-app/Ombi/issues/4542) ([9cdd6f4](https://github.com/Ombi-app/Ombi/commit/9cdd6f41cdab8825a984905c089611409c53c753)) * **sonarr:** :bug: Improved the error handling in the sonarr settings page in the UI ([fcd78fe](https://github.com/Ombi-app/Ombi/commit/fcd78fee619d10ec7d78e8c8ec6c3ac4b0a361a1)), closes [#4877](https://github.com/Ombi-app/Ombi/issues/4877)
## [4.35.9](https://github.com/Ombi-app/Ombi/compare/v4.35.8...v4.35.9) (2023-02-24)
# [4.15.0](https://github.com/Ombi-app/Ombi/compare/v4.14.4...v4.15.0) (2022-03-17)
## [4.22.5](https://github.com/Ombi-app/Ombi/compare/v4.22.4...v4.22.5) (2022-08-05)
## [4.35.8](https://github.com/Ombi-app/Ombi/compare/v4.35.7...v4.35.8) (2023-02-17)
### Bug Fixes ### Bug Fixes
* **jellyfin:** :bug: Fixed an issue where Jellyfin content was showing the Play on Emby button ([18b167d](https://github.com/Ombi-app/Ombi/commit/18b167d16a3d682b5060ee36dedbbb069bef09de)), closes [#4542](https://github.com/Ombi-app/Ombi/issues/4542) * **plex-oauth:** 🐛 Fixed an issue where using OAuth you could log in as a Ombi Local user [#4835](https://github.com/Ombi-app/Ombi/issues/4835) ([4098da3](https://github.com/Ombi-app/Ombi/commit/4098da305aaea9dae9a552644268a4fed7204cfe))
## [4.14.4](https://github.com/Ombi-app/Ombi/compare/v4.14.3...v4.14.4) (2022-03-10) ## [4.35.7](https://github.com/Ombi-app/Ombi/compare/v4.35.6...v4.35.7) (2023-02-10)
### Bug Fixes ### Bug Fixes
* :bug: Fixed the Request On Behalf autocomplete not filtering correctly ([a8ba2f3](https://github.com/Ombi-app/Ombi/commit/a8ba2f3544a1c01c57f217c4036a277ab0e67a09)), closes [#4539](https://github.com/Ombi-app/Ombi/issues/4539) * **wizard:** :bug: Stop access to the wizard when you have already setup ombi ([#4866](https://github.com/Ombi-app/Ombi/issues/4866)) ([353de98](https://github.com/Ombi-app/Ombi/commit/353de981a462e1753288d225ec4644a44a62d2bc))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([356c742](https://github.com/Ombi-app/Ombi/commit/356c7424e0ce8c1c5063b04bc6ed9b809f214d65))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([6fcaecf](https://github.com/Ombi-app/Ombi/commit/6fcaecf80b766f2d43ac7082d74364238e1638b7))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([132f4d4](https://github.com/Ombi-app/Ombi/commit/132f4d4e609b7fb7e37f38ee2f395926e2911abe))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([f292006](https://github.com/Ombi-app/Ombi/commit/f292006a08894a8d0ba899c8c6e9fe863e558dda))
## [4.14.3](https://github.com/Ombi-app/Ombi/compare/v4.14.2...v4.14.3) (2022-03-06) ## [4.35.6](https://github.com/Ombi-app/Ombi/compare/v4.35.5...v4.35.6) (2023-01-31)
### Bug Fixes ### Bug Fixes
* **availability:** :bug: Fixed an issue where with 4k content, we could repeat notifications ([f9ebc1c](https://github.com/Ombi-app/Ombi/commit/f9ebc1cc2e13c7cd335121cd86295b10eda529ba)) * Fixed the issue where the login page is still present after logging in with oauth ([aca4ee3](https://github.com/Ombi-app/Ombi/commit/aca4ee37915a28200e5233be3dc711ccc4a5aee9))
## [4.14.2](https://github.com/Ombi-app/Ombi/compare/v4.14.1...v4.14.2) (2022-03-05) ## [4.35.5](https://github.com/Ombi-app/Ombi/compare/v4.35.4...v4.35.5) (2023-01-24)
### Bug Fixes ### Bug Fixes
* **Sonarr:** :bug: Fixed an issue where some seasons were not being monitored correctly in sonarr ([60cfd41](https://github.com/Ombi-app/Ombi/commit/60cfd41f68e9006555c1a419dcff1aaa24b3e09f)), closes [#4506](https://github.com/Ombi-app/Ombi/issues/4506) * **radarr-settings:** 🐛 Fixed a typo ([4a50a00](https://github.com/Ombi-app/Ombi/commit/4a50a00d4729d99f4359874b9af4dbc58a0c220b))
## [4.14.1](https://github.com/Ombi-app/Ombi/compare/v4.14.0...v4.14.1) (2022-03-03) ## [4.35.4](https://github.com/Ombi-app/Ombi/compare/v4.35.3...v4.35.4) (2023-01-22)
### Bug Fixes
# [4.14.0](https://github.com/Ombi-app/Ombi/compare/v4.13.2...v4.14.0) (2022-03-02) * **discover:** :bug: Fixed the default poster not taking into account the base url in some scenarios [#4845](https://github.com/Ombi-app/Ombi/issues/4845) ([8eda250](https://github.com/Ombi-app/Ombi/commit/8eda250367953183daec03ccb5cdf9fe94275b27))
* **Hide music from navbar and request list when not enabled:** :bug: ([5123a76](https://github.com/Ombi-app/Ombi/commit/5123a76954e9f81d58c05e31afc7a29aec19cb7a))
## [4.13.2](https://github.com/Ombi-app/Ombi/compare/v4.13.1...v4.13.2) (2022-03-01) ## [4.35.3](https://github.com/Ombi-app/Ombi/compare/v4.35.2...v4.35.3) (2023-01-13)
### Bug Fixes ### Bug Fixes
* **requests:** :bug: Fixed an issue where you couldn't approve movies from the request list ([1611ef9](https://github.com/Ombi-app/Ombi/commit/1611ef9198befbb7a4db50a4f0953e50f29a788f)) * **#4847:** Invalid Discord request fixed, also fixed an issue where App Only users would not show as logged in on the user management page ([#4848](https://github.com/Ombi-app/Ombi/issues/4848)) ([f229d88](https://github.com/Ombi-app/Ombi/commit/f229d88bd744bc5253b5d3db69ae5ef22d014230))
## [4.13.1](https://github.com/Ombi-app/Ombi/compare/v4.13.0...v4.13.1) (2022-03-01) ## [4.35.2](https://github.com/Ombi-app/Ombi/compare/v4.35.1...v4.35.2) (2023-01-08)
### Bug Fixes ### Bug Fixes
* **details:** :bug: Fixed the missing Play on Media server button for 4k content [#4529](https://github.com/Ombi-app/Ombi/issues/4529) ([68600f3](https://github.com/Ombi-app/Ombi/commit/68600f3b45376e12dd2ef263d81ca4040c84cbca)) * **database:** Just some tweaks, shouldn't notice any difference, maybe a less error in the log ([67fb992](https://github.com/Ombi-app/Ombi/commit/67fb9921c0c025025286eb6c0a9d09fd01b18465))
* **discover:** :bug: Fixed the issue where there was an option on the discover to request 4k shows (that's not supported currently) ([dcfd688](https://github.com/Ombi-app/Ombi/commit/dcfd688c8d2337e55fa9c6c33b7c3e80fc560cda))
* **requests:** :bug: Fixed the issue where we could no longer approve TV Requests from the requests list ([19fe4e3](https://github.com/Ombi-app/Ombi/commit/19fe4e342efe5578c26ab8ba7ee2f2e64bbc9418))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([#4526](https://github.com/Ombi-app/Ombi/issues/4526)) ([7e9f54f](https://github.com/Ombi-app/Ombi/commit/7e9f54fc80a09c938184e6be40ce5f49ce9673ef))
# [4.13.0](https://github.com/Ombi-app/Ombi/compare/v4.12.7...v4.13.0) (2022-02-25) ## [4.35.1](https://github.com/Ombi-app/Ombi/compare/v4.35.0...v4.35.1) (2023-01-06)
### Bug Fixes ### Bug Fixes
* **4k:** Hide 'Has 4K Request' column list if 4k feature is disabled ([#4521](https://github.com/Ombi-app/Ombi/issues/4521)) ([a9a6067](https://github.com/Ombi-app/Ombi/commit/a9a60678e74d22fa7ba34051a2645db86b600b4a)) * **plex-watchlist:** Index out of bounds error ([8cd556e](https://github.com/Ombi-app/Ombi/commit/8cd556e268931596b9c1d1ae0ce533bfaaf330f4))
* **issues:** Fix label ID in chatbox page ([#4520](https://github.com/Ombi-app/Ombi/issues/4520)) ([76882ad](https://github.com/Ombi-app/Ombi/commit/76882adf231f92e1cdd396239933c13467c112b3))
* **localisation:** Localize request types in notifications ([#4516](https://github.com/Ombi-app/Ombi/issues/4516)) ([e09435d](https://github.com/Ombi-app/Ombi/commit/e09435da455b12fc429f129372de31e0654da797))
* **notifications:** Remove generic admin email in favour of admins' email ([#4519](https://github.com/Ombi-app/Ombi/issues/4519)) ([b90fc5f](https://github.com/Ombi-app/Ombi/commit/b90fc5fea771a83e6cf576c71a307066efd59ea4))
* **tv:** Display TV show as requested if all episodes are requested ([#4518](https://github.com/Ombi-app/Ombi/issues/4518)) ([2ed8c48](https://github.com/Ombi-app/Ombi/commit/2ed8c48d128a69f0d144c5d332286dbf3b0bdf28)) # [4.35.0](https://github.com/Ombi-app/Ombi/compare/v4.34.1...v4.35.0) (2023-01-04)
### Features ### Features
* **email-notifications:** Add a link to Ombi details page in email notifications ([#4517](https://github.com/Ombi-app/Ombi/issues/4517)) ([a3e97b3](https://github.com/Ombi-app/Ombi/commit/a3e97b31e2298d95e7deebd71268095b8ed5e9dc)) * Add the option for header authentication to create users ([#4841](https://github.com/Ombi-app/Ombi/issues/4841)) ([e6c9ce5](https://github.com/Ombi-app/Ombi/commit/e6c9ce5ad0056608ecda8273fb8124ed292e2942))
* **media-details:** Add Trakt to social icons ([#4522](https://github.com/Ombi-app/Ombi/issues/4522)) ([d6ae79c](https://github.com/Ombi-app/Ombi/commit/d6ae79ce9eddbd5b7b888ab1b9f7e342d9d9ff9e))
## [4.34.1](https://github.com/Ombi-app/Ombi/compare/v4.34.0...v4.34.1) (2023-01-04)
### Bug Fixes
* **plex-watchlist:** Lookup the ID from different sources when Plex doesn't contain the metadata ([#4843](https://github.com/Ombi-app/Ombi/issues/4843)) ([a2cc23b](https://github.com/Ombi-app/Ombi/commit/a2cc23b351c4a568c44e6c855f94db9f71ad084a))
## [4.12.7](https://github.com/Ombi-app/Ombi/compare/v4.12.6...v4.12.7) (2022-02-23) # [4.34.0](https://github.com/Ombi-app/Ombi/compare/v4.33.1...v4.34.0) (2023-01-04)
### Features
* Radarr tags ([#4815](https://github.com/Ombi-app/Ombi/issues/4815)) ([6fa5064](https://github.com/Ombi-app/Ombi/commit/6fa506491fe867cdeef9df79991ae49319d71c3d))
## [4.12.6](https://github.com/Ombi-app/Ombi/compare/v4.12.5...v4.12.6) (2022-02-22)
## [4.33.1](https://github.com/Ombi-app/Ombi/compare/v4.33.0...v4.33.1) (2022-12-22)
### Bug Fixes ### Bug Fixes
* **emby/jellyfin:** :bug: Fixed another issue where we were not correctly displaying the correct status' for movies ([5c0556e](https://github.com/Ombi-app/Ombi/commit/5c0556e6f44b8997a611f3a4d8e9e4e05d08bd13)) * **plex:** Added the watchlist request whole show back into the settings ([10701c4](https://github.com/Ombi-app/Ombi/commit/10701c4a0b6190eebb75c5d8b18224f3d0bc8502))
* **mediaserver:** fixed some more issues in the media server sync and availability checks ([f3ea979](https://github.com/Ombi-app/Ombi/commit/f3ea979b8bd77842780ce8e6928b16237dd779cf))
# [4.33.0](https://github.com/Ombi-app/Ombi/compare/v4.32.3...v4.33.0) (2022-12-01)
### Features
* Angular 15 and Dependency upgrades ([#4818](https://github.com/Ombi-app/Ombi/issues/4818)) ([4816acf](https://github.com/Ombi-app/Ombi/commit/4816acf6f94443d23ebef6091d4cfcbca580f9ca))
## [4.12.5](https://github.com/Ombi-app/Ombi/compare/v4.12.4...v4.12.5) (2022-02-21) ## [4.32.3](https://github.com/Ombi-app/Ombi/compare/v4.32.2...v4.32.3) (2022-11-24)
### Bug Fixes ### Bug Fixes
* **emby:** :bug: Fixed the emby content sync [#4513](https://github.com/Ombi-app/Ombi/issues/4513) ([2927504](https://github.com/Ombi-app/Ombi/commit/2927504f0e0b4e7251e69b44e0e30c7ec9519980)) * **sonarr:** V4 actually works this time around ([f62e70f](https://github.com/Ombi-app/Ombi/commit/f62e70fc493c7971da5e4508ce10522f5df0bbf7))
* **emby:** :bug: Fixed the emby content sync [#4513](https://github.com/Ombi-app/Ombi/issues/4513) ([bd441cb](https://github.com/Ombi-app/Ombi/commit/bd441cb54fd77d6befb03fae321dc36c29f0de2e))
## [4.12.4](https://github.com/Ombi-app/Ombi/compare/v4.12.3...v4.12.4) (2022-02-17) ## [4.32.2](https://github.com/Ombi-app/Ombi/compare/v4.32.1...v4.32.2) (2022-11-23)
### Bug Fixes
## [4.12.3](https://github.com/Ombi-app/Ombi/compare/v4.12.2...v4.12.3) (2022-02-16) * **sonarr:** :bug: Sonarr V4 should work now ([#4810](https://github.com/Ombi-app/Ombi/issues/4810)) ([37655af](https://github.com/Ombi-app/Ombi/commit/37655aff9d3d133b42f5664bc9445d6571e966d6))
## [4.12.2](https://github.com/Ombi-app/Ombi/compare/v4.12.1...v4.12.2) (2022-02-16) ## [4.32.1](https://github.com/Ombi-app/Ombi/compare/v4.32.0...v4.32.1) (2022-11-21)
### Bug Fixes ### Bug Fixes
* **requests:** :bug: Fixed the approve 4k option on the requests list not working as expected ([c0189da](https://github.com/Ombi-app/Ombi/commit/c0189dad478ea375beda61ba3bee3f029a39b8e5)) * **plex:** :bug: Fixed the issue where you couldn't add a new server on a fresh setup after the settings page rework ([187b18d](https://github.com/Ombi-app/Ombi/commit/187b18d5c01f6a13831e4a410b5d7c349e27d847))
## [4.12.1](https://github.com/Ombi-app/Ombi/compare/v4.12.0...v4.12.1) (2022-02-16) # [4.32.0](https://github.com/Ombi-app/Ombi/compare/v4.31.0...v4.32.0) (2022-11-18)
### Bug Fixes ### Bug Fixes
* **requests:** :bug: Fixed the issue where Approving a 4K Request wouldn't send it to the correct 4K radarr instance ([87cb990](https://github.com/Ombi-app/Ombi/commit/87cb9903db30e1dead25ee8c5ea34305eb084a03)), closes [#4509](https://github.com/Ombi-app/Ombi/issues/4509) * **translations:** 🌐 New translations from Crowdin [skip ci] ([#4801](https://github.com/Ombi-app/Ombi/issues/4801)) ([4692003](https://github.com/Ombi-app/Ombi/commit/46920032baed04675b2ffbe1700afdc0740a4ac4))
### Features
* **plex:** Rework the Plex Settings page ([#4805](https://github.com/Ombi-app/Ombi/issues/4805)) ([1b8c47f](https://github.com/Ombi-app/Ombi/commit/1b8c47f3163f618851d4904732cb07015e1e93ff))
# [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.12.0](https://github.com/Ombi-app/Ombi/compare/v4.11.8...v4.12.0) (2022-02-14) # [4.30.0](https://github.com/Ombi-app/Ombi/compare/v4.29.3...v4.30.0) (2022-11-17)
### Features ### Features
* **radarr:** 4K Requests and Radarr 4K support ([ba88848](https://github.com/Ombi-app/Ombi/commit/ba88848866b0a9dedb1e79b55c4d81a0fd453843)) * **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.11.8](https://github.com/Ombi-app/Ombi/compare/v4.11.7...v4.11.8) (2022-02-13) ## [4.29.2](https://github.com/Ombi-app/Ombi/compare/v4.29.1...v4.29.2) (2022-10-24)
### Bug Fixes ### Bug Fixes
* **settings:** :bug: Fixed an issue where we were not displaying the excluded keyworks correctly in the TheMovieDbSettings page ([d3b3316](https://github.com/Ombi-app/Ombi/commit/d3b3316cbac18356b2f6b0912a3deb2c183e6534)) * **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))

@ -85,13 +85,6 @@ Here are some of the features Ombi has:
<sub><b>Twan Ariens</b></sub> <sub><b>Twan Ariens</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/Drewster727">
<img src="https://avatars.githubusercontent.com/u/4528753?v=4" width="50;" alt="Drewster727"/>
<br />
<sub><b>Drew</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/sephrat"> <a href="https://github.com/sephrat">
<img src="https://avatars.githubusercontent.com/u/34862846?v=4" width="50;" alt="sephrat"/> <img src="https://avatars.githubusercontent.com/u/34862846?v=4" width="50;" alt="sephrat"/>
@ -112,15 +105,15 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Magikarp Lvl 4</b></sub> <sub><b>Magikarp Lvl 4</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/MrTopCat"> <a href="https://github.com/MrTopCat">
<img src="https://avatars.githubusercontent.com/u/774415?v=4" width="50;" alt="MrTopCat"/> <img src="https://avatars.githubusercontent.com/u/774415?v=4" width="50;" alt="MrTopCat"/>
<br /> <br />
<sub><b>James Carty</b></sub> <sub><b>James Carty</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/smcpeck"> <a href="https://github.com/smcpeck">
<img src="https://avatars.githubusercontent.com/u/8724583?v=4" width="50;" alt="smcpeck"/> <img src="https://avatars.githubusercontent.com/u/8724583?v=4" width="50;" alt="smcpeck"/>
@ -155,15 +148,15 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Joshua M. Boniface</b></sub> <sub><b>Joshua M. Boniface</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/bruvv"> <a href="https://github.com/bruvv">
<img src="https://avatars.githubusercontent.com/u/3063928?v=4" width="50;" alt="bruvv"/> <img src="https://avatars.githubusercontent.com/u/3063928?v=4" width="50;" alt="bruvv"/>
<br /> <br />
<sub><b>Bruvv</b></sub> <sub><b>Bruvv</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/louis-lau"> <a href="https://github.com/louis-lau">
<img src="https://avatars.githubusercontent.com/u/1346804?v=4" width="50;" alt="louis-lau"/> <img src="https://avatars.githubusercontent.com/u/1346804?v=4" width="50;" alt="louis-lau"/>
@ -198,15 +191,15 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Jim MacKenize</b></sub> <sub><b>Jim MacKenize</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/Unimatrix0"> <a href="https://github.com/Unimatrix0">
<img src="https://avatars.githubusercontent.com/u/357984?v=4" width="50;" alt="Unimatrix0"/> <img src="https://avatars.githubusercontent.com/u/357984?v=4" width="50;" alt="Unimatrix0"/>
<br /> <br />
<sub><b>Avi</b></sub> <sub><b>Avi</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/kitzin"> <a href="https://github.com/kitzin">
<img src="https://avatars.githubusercontent.com/u/3277321?v=4" width="50;" alt="kitzin"/> <img src="https://avatars.githubusercontent.com/u/3277321?v=4" width="50;" alt="kitzin"/>
@ -241,15 +234,15 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Francesco Servida</b></sub> <sub><b>Francesco Servida</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/Patricol"> <a href="https://github.com/Patricol">
<img src="https://avatars.githubusercontent.com/u/13428020?v=4" width="50;" alt="Patricol"/> <img src="https://avatars.githubusercontent.com/u/13428020?v=4" width="50;" alt="Patricol"/>
<br /> <br />
<sub><b>Patrick Collins</b></sub> <sub><b>Patrick Collins</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/xweskingx"> <a href="https://github.com/xweskingx">
<img src="https://avatars.githubusercontent.com/u/6268446?v=4" width="50;" alt="xweskingx"/> <img src="https://avatars.githubusercontent.com/u/6268446?v=4" width="50;" alt="xweskingx"/>
@ -284,15 +277,15 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Aptalca</b></sub> <sub><b>Aptalca</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/dr3am37"> <a href="https://github.com/dr3am37">
<img src="https://avatars.githubusercontent.com/u/91037083?v=4" width="50;" alt="dr3am37"/> <img src="https://avatars.githubusercontent.com/u/91037083?v=4" width="50;" alt="dr3am37"/>
<br /> <br />
<sub><b>Dr3amer</b></sub> <sub><b>Dr3amer</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/mhann"> <a href="https://github.com/mhann">
<img src="https://avatars.githubusercontent.com/u/17162399?v=4" width="50;" alt="mhann"/> <img src="https://avatars.githubusercontent.com/u/17162399?v=4" width="50;" alt="mhann"/>
@ -307,6 +300,13 @@ Here are some of the features Ombi has:
<sub><b>Ombi-bot</b></sub> <sub><b>Ombi-bot</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/phildups7">
<img src="https://avatars.githubusercontent.com/u/60622768?v=4" width="50;" alt="phildups7"/>
<br />
<sub><b>Phildups7</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/snyk-bot"> <a href="https://github.com/snyk-bot">
<img src="https://avatars.githubusercontent.com/u/19733683?v=4" width="50;" alt="snyk-bot"/> <img src="https://avatars.githubusercontent.com/u/19733683?v=4" width="50;" alt="snyk-bot"/>
@ -415,6 +415,13 @@ Here are some of the features Ombi has:
</a> </a>
</td></tr> </td></tr>
<tr> <tr>
<td align="center">
<a href="https://github.com/aj3x">
<img src="https://avatars.githubusercontent.com/u/15078358?v=4" width="50;" alt="aj3x"/>
<br />
<sub><b>Alexander Russell</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/XanderStrike"> <a href="https://github.com/XanderStrike">
<img src="https://avatars.githubusercontent.com/u/1565303?v=4" width="50;" alt="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 /> <br />
<sub><b>Calvin</b></sub> <sub><b>Calvin</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/origamirobot"> <a href="https://github.com/origamirobot">
<img src="https://avatars.githubusercontent.com/u/1346803?v=4" width="50;" alt="origamirobot"/> <img src="https://avatars.githubusercontent.com/u/1346803?v=4" width="50;" alt="origamirobot"/>
<br /> <br />
<sub><b>Chris Lees</b></sub> <sub><b>Chris Lees</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/cdemi"> <a href="https://github.com/cdemi">
<img src="https://avatars.githubusercontent.com/u/8025435?v=4" width="50;" alt="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 /> <br />
<sub><b>David Torosyan</b></sub> <sub><b>David Torosyan</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/onedr0p"> <a href="https://github.com/onedr0p">
<img src="https://avatars.githubusercontent.com/u/213795?v=4" width="50;" alt="onedr0p"/> <img src="https://avatars.githubusercontent.com/u/213795?v=4" width="50;" alt="onedr0p"/>
<br /> <br />
<sub><b>Devin Buhl</b></sub> <sub><b>Devin Buhl</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/elisspace"> <a href="https://github.com/elisspace">
<img src="https://avatars.githubusercontent.com/u/18365129?v=4" width="50;" alt="elisspace"/> <img src="https://avatars.githubusercontent.com/u/18365129?v=4" width="50;" alt="elisspace"/>
@ -515,6 +522,13 @@ Here are some of the features Ombi has:
<sub><b>Fish2</b></sub> <sub><b>Fish2</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/Grygon">
<img src="https://avatars.githubusercontent.com/u/647846?v=4" width="50;" alt="Grygon"/>
<br />
<sub><b>Grygon</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/ketsapiwiq"> <a href="https://github.com/ketsapiwiq">
<img src="https://avatars.githubusercontent.com/u/26697460?v=4" width="50;" alt="ketsapiwiq"/> <img src="https://avatars.githubusercontent.com/u/26697460?v=4" width="50;" alt="ketsapiwiq"/>
@ -528,7 +542,8 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Haries Ramdhani</b></sub> <sub><b>Haries Ramdhani</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/comigor"> <a href="https://github.com/comigor">
<img src="https://avatars.githubusercontent.com/u/735858?v=4" width="50;" alt="comigor"/> <img src="https://avatars.githubusercontent.com/u/735858?v=4" width="50;" alt="comigor"/>
@ -542,8 +557,7 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Imgbot</b></sub> <sub><b>Imgbot</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/JPyke3"> <a href="https://github.com/JPyke3">
<img src="https://avatars.githubusercontent.com/u/13283054?v=4" width="50;" alt="JPyke3"/> <img src="https://avatars.githubusercontent.com/u/13283054?v=4" width="50;" alt="JPyke3"/>
@ -571,7 +585,8 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Joe Harvey</b></sub> <sub><b>Joe Harvey</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/jonbloom"> <a href="https://github.com/jonbloom">
<img src="https://avatars.githubusercontent.com/u/492819?v=4" width="50;" alt="jonbloom"/> <img src="https://avatars.githubusercontent.com/u/492819?v=4" width="50;" alt="jonbloom"/>
@ -585,8 +600,7 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Jono Cairns</b></sub> <sub><b>Jono Cairns</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/krisklosterman"> <a href="https://github.com/krisklosterman">
<img src="https://avatars.githubusercontent.com/u/7139579?v=4" width="50;" alt="krisklosterman"/> <img src="https://avatars.githubusercontent.com/u/7139579?v=4" width="50;" alt="krisklosterman"/>
@ -614,7 +628,8 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Lightkeeper</b></sub> <sub><b>Lightkeeper</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/Lucane"> <a href="https://github.com/Lucane">
<img src="https://avatars.githubusercontent.com/u/7999446?v=4" width="50;" alt="Lucane"/> <img src="https://avatars.githubusercontent.com/u/7999446?v=4" width="50;" alt="Lucane"/>
@ -628,8 +643,7 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Madeleine Schönemann</b></sub> <sub><b>Madeleine Schönemann</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/marleypowell"> <a href="https://github.com/marleypowell">
<img src="https://avatars.githubusercontent.com/u/55280588?v=4" width="50;" alt="marleypowell"/> <img src="https://avatars.githubusercontent.com/u/55280588?v=4" width="50;" alt="marleypowell"/>
@ -657,7 +671,8 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Micky</b></sub> <sub><b>Micky</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/mvicomoya"> <a href="https://github.com/mvicomoya">
<img src="https://avatars.githubusercontent.com/u/24613599?v=4" width="50;" alt="mvicomoya"/> <img src="https://avatars.githubusercontent.com/u/24613599?v=4" width="50;" alt="mvicomoya"/>
@ -671,8 +686,7 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Nathan Miller</b></sub> <sub><b>Nathan Miller</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/cqxmzz"> <a href="https://github.com/cqxmzz">
<img src="https://avatars.githubusercontent.com/u/3071863?v=4" width="50;" alt="cqxmzz"/> <img src="https://avatars.githubusercontent.com/u/3071863?v=4" width="50;" alt="cqxmzz"/>
@ -700,7 +714,8 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Sean Callinan</b></sub> <sub><b>Sean Callinan</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/shoghicp"> <a href="https://github.com/shoghicp">
<img src="https://avatars.githubusercontent.com/u/516482?v=4" width="50;" alt="shoghicp"/> <img src="https://avatars.githubusercontent.com/u/516482?v=4" width="50;" alt="shoghicp"/>
@ -714,8 +729,7 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Teifun2</b></sub> <sub><b>Teifun2</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/thomasvt1"> <a href="https://github.com/thomasvt1">
<img src="https://avatars.githubusercontent.com/u/2271011?v=4" width="50;" alt="thomasvt1"/> <img src="https://avatars.githubusercontent.com/u/2271011?v=4" width="50;" alt="thomasvt1"/>
@ -743,7 +757,8 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Torkil</b></sub> <sub><b>Torkil</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/bybeet"> <a href="https://github.com/bybeet">
<img src="https://avatars.githubusercontent.com/u/1662279?v=4" width="50;" alt="bybeet"/> <img src="https://avatars.githubusercontent.com/u/1662279?v=4" width="50;" alt="bybeet"/>
@ -757,8 +772,7 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Xirg</b></sub> <sub><b>Xirg</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/bazhip"> <a href="https://github.com/bazhip">
<img src="https://avatars.githubusercontent.com/u/10350445?v=4" width="50;" alt="bazhip"/> <img src="https://avatars.githubusercontent.com/u/10350445?v=4" width="50;" alt="bazhip"/>
@ -786,7 +800,8 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Michael DiStaula</b></sub> <sub><b>Michael DiStaula</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/baikunz"> <a href="https://github.com/baikunz">
<img src="https://avatars.githubusercontent.com/u/984911?v=4" width="50;" alt="baikunz"/> <img src="https://avatars.githubusercontent.com/u/984911?v=4" width="50;" alt="baikunz"/>
@ -800,8 +815,7 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Echel0n</b></sub> <sub><b>Echel0n</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/m4tta"> <a href="https://github.com/m4tta">
<img src="https://avatars.githubusercontent.com/u/427218?v=4" width="50;" alt="m4tta"/> <img src="https://avatars.githubusercontent.com/u/427218?v=4" width="50;" alt="m4tta"/>
@ -829,6 +843,14 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Mkgeeky</b></sub> <sub><b>Mkgeeky</b></sub>
</a> </a>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/ryan-c44">
<img src="https://avatars.githubusercontent.com/u/54028283?v=4" width="50;" alt="ryan-c44"/>
<br />
<sub><b>Ryan-c44</b></sub>
</a>
</td> </td>
<td align="center"> <td align="center">
<a href="https://github.com/sir-marv"> <a href="https://github.com/sir-marv">
@ -843,8 +865,7 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Tdorsey</b></sub> <sub><b>Tdorsey</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/thegame3202"> <a href="https://github.com/thegame3202">
<img src="https://avatars.githubusercontent.com/u/22148848?v=4" width="50;" alt="thegame3202"/> <img src="https://avatars.githubusercontent.com/u/22148848?v=4" width="50;" alt="thegame3202"/>

@ -248,5 +248,37 @@ namespace Ombi.Api.Emby
req.AddContentHeader("Content-Type", "application/json"); req.AddContentHeader("Content-Type", "application/json");
req.AddHeader("Device", "Ombi"); req.AddHeader("Device", "Ombi");
} }
public async Task<EmbyItemContainer<EmbyMovie>> GetMoviesPlayed(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await GetPlayed<EmbyMovie>("Movie", apiKey, userId, baseUri, startIndex, count, parentIdFilder);
}
private async Task<EmbyItemContainer<T>> GetPlayed<T>(string type, string apiKey, string userId, string baseUri, int startIndex, int count, string parentIdFilder = default)
{
var request = new Request($"emby/items", baseUri, HttpMethod.Get);
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", "ProviderIds");
request.AddQueryString("UserId", userId);
request.AddQueryString("isPlayed", true.ToString());
// paginate and display recently played items first
request.AddQueryString("sortBy", "DatePlayed");
request.AddQueryString("SortOrder", "Descending");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());
if (!string.IsNullOrEmpty(parentIdFilder))
{
request.AddQueryString("ParentId", parentIdFilder);
}
AddHeaders(request, apiKey);
var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj;
}
} }
} }

@ -32,5 +32,7 @@ namespace Ombi.Api.Emby
Task<EmbyItemContainer<EmbyMovie>> RecentlyAddedMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); Task<EmbyItemContainer<EmbyMovie>> RecentlyAddedMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
Task<EmbyItemContainer<EmbyEpisodes>> RecentlyAddedEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); Task<EmbyItemContainer<EmbyEpisodes>> RecentlyAddedEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
Task<EmbyItemContainer<EmbySeries>> RecentlyAddedShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri); Task<EmbyItemContainer<EmbySeries>> RecentlyAddedShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetMoviesPlayed(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
} }
} }

@ -5,30 +5,8 @@ namespace Ombi.Api.Emby.Models.Movie
public class EmbyMovie public class EmbyMovie
{ {
public string Name { get; set; } public string Name { get; set; }
public string ServerId { get; set; }
public string Id { get; set; } public string Id { get; set; }
public string Container { get; set; }
public DateTime PremiereDate { get; set; }
public object[] ProductionLocations { get; set; }
public string OfficialRating { get; set; }
public float CommunityRating { get; set; }
public long RunTimeTicks { get; set; }
public string PlayAccess { get; set; }
public int ProductionYear { get; set; }
public bool IsPlaceHolder { get; set; }
public bool IsHD { get; set; }
public bool IsFolder { get; set; }
public string Type { get; set; } public string Type { get; set; }
public int LocalTrailerCount { get; set; }
public EmbyUserdata UserData { get; set; }
public string VideoType { get; set; }
public EmbyImagetags ImageTags { get; set; }
public string[] BackdropImageTags { get; set; }
public string LocationType { get; set; }
public string MediaType { get; set; }
public bool HasSubtitles { get; set; }
public int CriticRating { get; set; }
public string Overview { get; set; }
public EmbyProviderids ProviderIds { get; set; } public EmbyProviderids ProviderIds { get; set; }
public EmbyMediastream[] MediaStreams { get; set; } public EmbyMediastream[] MediaStreams { get; set; }
} }

@ -5,30 +5,8 @@ namespace Ombi.Api.Jellyfin.Models.Movie
public class JellyfinMovie public class JellyfinMovie
{ {
public string Name { get; set; } public string Name { get; set; }
public string ServerId { get; set; }
public string Id { get; set; } public string Id { get; set; }
public string Container { get; set; }
public DateTime PremiereDate { get; set; }
public object[] ProductionLocations { get; set; }
public string OfficialRating { get; set; }
public float CommunityRating { get; set; }
public long RunTimeTicks { get; set; }
public string PlayAccess { get; set; }
public int ProductionYear { get; set; }
public bool IsPlaceHolder { get; set; }
public bool IsHD { get; set; }
public bool IsFolder { get; set; }
public string Type { get; set; } public string Type { get; set; }
public int LocalTrailerCount { get; set; }
public JellyfinUserdata UserData { get; set; }
public string VideoType { get; set; }
public JellyfinImagetags ImageTags { get; set; }
public string[] BackdropImageTags { get; set; }
public string LocationType { get; set; }
public string MediaType { get; set; }
public bool HasSubtitles { get; set; }
public int CriticRating { get; set; }
public string Overview { get; set; }
public JellyfinProviderids ProviderIds { get; set; } public JellyfinProviderids ProviderIds { get; set; }
public JellyfinMediastream[] MediaStreams { get; set; } public JellyfinMediastream[] MediaStreams { get; set; }
} }

@ -3,23 +3,6 @@ namespace Ombi.Api.Sonarr
public class SystemStatus public class SystemStatus
{ {
public string version { get; set; } public string version { get; set; }
public string buildTime { get; set; }
public bool isDebug { get; set; }
public bool isProduction { get; set; }
public bool isAdmin { get; set; }
public bool isUserInteractive { get; set; }
public string startupPath { get; set; }
public string appData { get; set; }
public string osVersion { get; set; }
public bool isMonoRuntime { get; set; }
public bool isMono { get; set; }
public bool isLinux { get; set; }
public bool isOsx { get; set; }
public bool isWindows { get; set; }
public string branch { get; set; }
public string authentication { get; set; }
public string sqliteVersion { get; set; }
public string urlBase { get; set; } public string urlBase { get; set; }
public string runtimeVersion { get; set; }
} }
} }

@ -52,6 +52,7 @@ namespace Ombi.Core.Tests.Engine
_subject = _mocker.CreateInstance<MovieRequestEngine>(); _subject = _mocker.CreateInstance<MovieRequestEngine>();
var list = DbHelper.GetQueryableMockDbSet(new RequestSubscription()); var list = DbHelper.GetQueryableMockDbSet(new RequestSubscription());
_mocker.Setup<IRepository<RequestSubscription>, IQueryable<RequestSubscription>>(x => x.GetAll()).Returns(new List<RequestSubscription>().AsQueryable().BuildMock()); _mocker.Setup<IRepository<RequestSubscription>, IQueryable<RequestSubscription>>(x => x.GetAll()).Returns(new List<RequestSubscription>().AsQueryable().BuildMock());
_mocker.Setup<IUserPlayedMovieRepository, IQueryable<UserPlayedMovie>>(x => x.GetAll()).Returns(new List<UserPlayedMovie>().AsQueryable().BuildMock());
} }
[Test] [Test]

@ -46,8 +46,9 @@ namespace Ombi.Core.Tests.Engine.V2
var requestSubs = new Mock<IRepository<RequestSubscription>>(); var requestSubs = new Mock<IRepository<RequestSubscription>>();
var mediaCache = new Mock<IMediaCacheService>(); var mediaCache = new Mock<IMediaCacheService>();
var featureService = new Mock<IFeatureService>(); var featureService = new Mock<IFeatureService>();
var userPlayedMovieRepository = new Mock<IUserPlayedMovieRepository>();
_engine = new MovieRequestEngine(movieApi.Object, requestService.Object, user.Object, notificationHelper.Object, rules.Object, movieSender.Object, _engine = new MovieRequestEngine(movieApi.Object, requestService.Object, user.Object, notificationHelper.Object, rules.Object, movieSender.Object,
logger.Object, userManager.Object, requestLogRepo.Object, cache.Object, ombiSettings.Object, requestSubs.Object, mediaCache.Object, featureService.Object); logger.Object, userManager.Object, requestLogRepo.Object, cache.Object, ombiSettings.Object, requestSubs.Object, mediaCache.Object, featureService.Object, userPlayedMovieRepository.Object);
} }
[Test] [Test]

@ -173,6 +173,7 @@ namespace Ombi.Core.Tests.Services
[Test] [Test]
[Ignore("Flaky")]
public async Task GetRecentlyRequested_HideUsernames() public async Task GetRecentlyRequested_HideUsernames()
{ {
_mocker.Setup<ISettingsService<CustomizationSettings>, Task<CustomizationSettings>>(x => x.GetSettingsAsync()) _mocker.Setup<ISettingsService<CustomizationSettings>, Task<CustomizationSettings>>(x => x.GetSettingsAsync())

@ -33,7 +33,8 @@ namespace Ombi.Core.Engine
INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger<MovieRequestEngine> log, INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger<MovieRequestEngine> log,
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache, OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache,
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, IMediaCacheService mediaCacheService, ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, IMediaCacheService mediaCacheService,
IFeatureService featureService) IFeatureService featureService,
IUserPlayedMovieRepository userPlayedMovieRepository)
: base(user, requestService, r, manager, cache, ombiSettings, sub) : base(user, requestService, r, manager, cache, ombiSettings, sub)
{ {
MovieApi = movieApi; MovieApi = movieApi;
@ -43,6 +44,7 @@ namespace Ombi.Core.Engine
_requestLog = rl; _requestLog = rl;
_mediaCacheService = mediaCacheService; _mediaCacheService = mediaCacheService;
_featureService = featureService; _featureService = featureService;
_userPlayedMovieRepository = userPlayedMovieRepository;
} }
private IMovieDbApi MovieApi { get; } private IMovieDbApi MovieApi { get; }
@ -52,6 +54,7 @@ namespace Ombi.Core.Engine
private readonly IRepository<RequestLog> _requestLog; private readonly IRepository<RequestLog> _requestLog;
private readonly IMediaCacheService _mediaCacheService; private readonly IMediaCacheService _mediaCacheService;
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
protected readonly IUserPlayedMovieRepository _userPlayedMovieRepository;
/// <summary> /// <summary>
/// Requests the movie. /// Requests the movie.
@ -77,7 +80,8 @@ namespace Ombi.Core.Engine
var userDetails = await GetUser(); var userDetails = await GetUser();
var canRequestOnBehalf = model.RequestOnBehalf.HasValue(); var canRequestOnBehalf = model.RequestOnBehalf.HasValue();
var isAdmin = await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser) var isAdmin = Username.Equals("API", StringComparison.CurrentCultureIgnoreCase)
|| await UserManager.IsInRoleAsync(userDetails, OmbiRoles.PowerUser)
|| await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin); || await UserManager.IsInRoleAsync(userDetails, OmbiRoles.Admin);
if (canRequestOnBehalf && !isAdmin) if (canRequestOnBehalf && !isAdmin)
{ {
@ -252,7 +256,7 @@ namespace Ombi.Core.Engine
var requests = await (OrderMovies(allRequests, orderFilter.OrderType)).Skip(position).Take(count) var requests = await (OrderMovies(allRequests, orderFilter.OrderType)).Skip(position).Take(count)
.ToListAsync(); .ToListAsync();
await CheckForSubscription(shouldHide.UserId, requests); await FillAdditionalFields(shouldHide, requests);
return new RequestsViewModel<MovieRequests> return new RequestsViewModel<MovieRequests>
{ {
Collection = requests, Collection = requests,
@ -296,7 +300,7 @@ namespace Ombi.Core.Engine
var total = requests.Count(); var total = requests.Count();
requests = requests.Skip(position).Take(count).ToList(); requests = requests.Skip(position).Take(count).ToList();
await CheckForSubscription(shouldHide.UserId, requests); await FillAdditionalFields(shouldHide, requests);
return new RequestsViewModel<MovieRequests> return new RequestsViewModel<MovieRequests>
{ {
Collection = requests, Collection = requests,
@ -381,7 +385,7 @@ namespace Ombi.Core.Engine
// TODO fix this so we execute this on the server // TODO fix this so we execute this on the server
requests = requests.Skip(position).Take(count).ToList(); requests = requests.Skip(position).Take(count).ToList();
await CheckForSubscription(shouldHide.UserId, requests); await FillAdditionalFields(shouldHide, requests);
return new RequestsViewModel<MovieRequests> return new RequestsViewModel<MovieRequests>
{ {
Collection = requests, Collection = requests,
@ -424,7 +428,7 @@ namespace Ombi.Core.Engine
var total = requests.Count(); var total = requests.Count();
requests = requests.Skip(position).Take(count).ToList(); requests = requests.Skip(position).Take(count).ToList();
await CheckForSubscription(shouldHide.UserId, requests); await FillAdditionalFields(shouldHide, requests);
return new RequestsViewModel<MovieRequests> return new RequestsViewModel<MovieRequests>
{ {
Collection = requests, Collection = requests,
@ -506,18 +510,25 @@ namespace Ombi.Core.Engine
allRequests = await MovieRepository.GetWithUser().ToListAsync(); allRequests = await MovieRepository.GetWithUser().ToListAsync();
} }
await CheckForSubscription(shouldHide.UserId, allRequests); await FillAdditionalFields(shouldHide, allRequests);
return allRequests; return allRequests;
} }
public async Task<MovieRequests> GetRequest(int requestId) public async Task<MovieRequests> GetRequest(int requestId)
{ {
var shouldHide = await HideFromOtherUsers();
// TODO: this query should return the request only if the user is allowed to see it (see shouldHide implementations)
var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync(); var request = await MovieRepository.GetWithUser().Where(x => x.Id == requestId).FirstOrDefaultAsync();
await CheckForSubscription((await GetUser()).Id, new List<MovieRequests> { request }); await FillAdditionalFields(shouldHide, new List<MovieRequests> { request });
return request; return request;
} }
private async Task FillAdditionalFields(HideResult shouldHide, List<MovieRequests> requests)
{
await CheckForSubscription(shouldHide.UserId, requests);
await CheckForPlayed(shouldHide, requests);
}
private async Task CheckForSubscription(string UserId, List<MovieRequests> movieRequests) private async Task CheckForSubscription(string UserId, List<MovieRequests> movieRequests)
{ {
@ -543,6 +554,23 @@ namespace Ombi.Core.Engine
} }
} }
} }
private async Task CheckForPlayed(HideResult shouldHide, List<MovieRequests> movieRequests)
{
var theMovieDbIds = movieRequests.Select(x => x.TheMovieDbId);
var plays = await _userPlayedMovieRepository.GetAll().Where(x =>
theMovieDbIds.Contains(x.TheMovieDbId))
.ToListAsync();
foreach (var request in movieRequests)
{
request.WatchedByRequestedUser = plays.Exists(x => x.TheMovieDbId == request.TheMovieDbId && x.UserId == request.RequestedUserId);
if (!shouldHide.Hide)
{
request.PlayedByUsersCount = plays.Count(x => x.TheMovieDbId == request.TheMovieDbId);
}
}
}
/// <summary> /// <summary>
/// Searches the movie request. /// Searches the movie request.
@ -563,7 +591,7 @@ namespace Ombi.Core.Engine
} }
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList(); var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList();
await CheckForSubscription(shouldHide.UserId, results); await FillAdditionalFields(shouldHide, results);
return results; return results;
} }

@ -161,7 +161,7 @@ namespace Ombi.Core.Engine
var user = await GetUser(); var user = await GetUser();
var canRequestOnBehalf = tv.RequestOnBehalf.HasValue(); var canRequestOnBehalf = tv.RequestOnBehalf.HasValue();
var isAdmin = await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin); var isAdmin = Username.Equals("API", StringComparison.CurrentCultureIgnoreCase) || await UserManager.IsInRoleAsync(user, OmbiRoles.PowerUser) || await UserManager.IsInRoleAsync(user, OmbiRoles.Admin);
if (tv.RequestOnBehalf.HasValue() && !isAdmin) if (tv.RequestOnBehalf.HasValue() && !isAdmin)
{ {
return new RequestEngineResult return new RequestEngineResult

@ -16,6 +16,7 @@ namespace Ombi.Core.Models.Requests
public string Overview { get; set; } public string Overview { get; set; }
public DateTime ReleaseDate { get; set; } public DateTime ReleaseDate { get; set; }
public bool Approved { get; set; } public bool Approved { get; set; }
public bool Denied { get; set; }
public string MediaId { get; set; } public string MediaId { get; set; }
public string PosterPath { get; set; } public string PosterPath { get; set; }

@ -5,5 +5,6 @@
public bool IsValid { get; set; } public bool IsValid { get; set; }
public string Version { get; set; } public string Version { get; set; }
public string ExpectedSubDir { get; set; } public string ExpectedSubDir { get; set; }
public string AdditionalInformation { get; set; }
} }
} }

@ -100,7 +100,7 @@ namespace Ombi.Core.Senders
addOptions = new Addoptions addOptions = new Addoptions
{ {
monitored = true, monitored = true,
monitor = MonitorTypes.None, monitor = MonitorTypes.Existing,
searchForMissingAlbums = false, searchForMissingAlbums = false,
AlbumsToMonitor = new[] {model.ForeignAlbumId} AlbumsToMonitor = new[] {model.ForeignAlbumId}
}, },
@ -199,4 +199,4 @@ namespace Ombi.Core.Senders
return new SenderResult { Message = "Could not set album to monitored", Sent = false, Success = false }; return new SenderResult { Message = "Could not set album to monitored", Sent = false, Success = false };
} }
} }
} }

@ -88,6 +88,7 @@ namespace Ombi.Core.Services
Title = item.Title, Title = item.Title,
Type = RequestType.Movie, Type = RequestType.Movie,
Approved = item.Approved, Approved = item.Approved,
Denied = item.Denied ?? false,
UserId = item.RequestedUserId, UserId = item.RequestedUserId,
Username = item.RequestedUser.UserAlias, Username = item.RequestedUser.UserAlias,
MediaId = item.TheMovieDbId.ToString(), MediaId = item.TheMovieDbId.ToString(),
@ -108,6 +109,7 @@ namespace Ombi.Core.Services
Available = item.Available, Available = item.Available,
Overview = item.ArtistName, Overview = item.ArtistName,
Approved = item.Approved, Approved = item.Approved,
Denied = item.Denied ?? false,
ReleaseDate = item.ReleaseDate, ReleaseDate = item.ReleaseDate,
RequestDate = item.RequestedDate, RequestDate = item.RequestedDate,
Title = item.Title, Title = item.Title,
@ -135,6 +137,7 @@ namespace Ombi.Core.Services
Overview = item.ParentRequest.Overview, Overview = item.ParentRequest.Overview,
ReleaseDate = item.ParentRequest.ReleaseDate, ReleaseDate = item.ParentRequest.ReleaseDate,
Approved = item.Approved, Approved = item.Approved,
Denied = item.Denied ?? false,
RequestDate = item.RequestedDate, RequestDate = item.RequestedDate,
TvPartiallyAvailable = partialAvailability, TvPartiallyAvailable = partialAvailability,
Title = item.ParentRequest.Title, Title = item.ParentRequest.Title,

@ -197,6 +197,7 @@ namespace Ombi.DependencyInjection
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>(); services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
services.AddScoped<IJellyfinContentRepository, JellyfinContentRepository>(); services.AddScoped<IJellyfinContentRepository, JellyfinContentRepository>();
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>(); services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddScoped<IUserPlayedMovieRepository, UserPlayedMovieRepository>();
services.AddScoped<ITvRequestRepository, TvRequestRepository>(); services.AddScoped<ITvRequestRepository, TvRequestRepository>();
services.AddScoped<IMovieRequestRepository, MovieRequestRepository>(); services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
@ -244,6 +245,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IPlexContentSync, PlexContentSync>(); services.AddTransient<IPlexContentSync, PlexContentSync>();
services.AddTransient<IPlexWatchlistImport, PlexWatchlistImport>(); services.AddTransient<IPlexWatchlistImport, PlexWatchlistImport>();
services.AddTransient<IEmbyContentSync, EmbyContentSync>(); services.AddTransient<IEmbyContentSync, EmbyContentSync>();
services.AddTransient<IEmbyPlayedSync, EmbyPlayedSync>();
services.AddTransient<IEmbyEpisodeSync, EmbyEpisodeSync>(); services.AddTransient<IEmbyEpisodeSync, EmbyEpisodeSync>();
services.AddTransient<IEmbyAvaliabilityChecker, EmbyAvaliabilityChecker>(); services.AddTransient<IEmbyAvaliabilityChecker, EmbyAvaliabilityChecker>();
services.AddTransient<IJellyfinContentSync, JellyfinContentSync>(); services.AddTransient<IJellyfinContentSync, JellyfinContentSync>();

@ -1,47 +0,0 @@
using HealthChecks.Network;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System;
using System.Collections.Generic;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Ombi.HealthChecks.Checks
{
public class OmbiPingHealthCheck
: IHealthCheck
{
private readonly OmbiPingHealthCheckOptions _options;
public OmbiPingHealthCheck(OmbiPingHealthCheckOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
var configuredHosts = _options.ConfiguredHosts.Values;
try
{
foreach (var (host, timeout, status) in configuredHosts)
{
using (var ping = new Ping())
{
var pingReply = await ping.SendPingAsync(host, timeout);
if (pingReply.Status != IPStatus.Success)
{
return new HealthCheckResult(status, description: $"Ping check for host {host} is failed with status reply:{pingReply.Status}");
}
}
}
return HealthCheckResult.Healthy();
}
catch (Exception ex)
{
return new HealthCheckResult(context.Registration.FailureStatus, exception: ex);
}
}
}
}

@ -1,16 +0,0 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using System.Collections.Generic;
namespace Ombi.HealthChecks.Checks
{
public class OmbiPingHealthCheckOptions
{
internal Dictionary<string, (string Host, int TimeOut, HealthStatus status)> ConfiguredHosts { get; } = new Dictionary<string, (string, int, HealthStatus)>();
public OmbiPingHealthCheckOptions AddHost(string host, int timeout, HealthStatus status)
{
ConfiguredHosts.Add(host, (host, timeout, status));
return this;
}
}
}

@ -27,7 +27,7 @@ namespace Ombi.HealthChecks.Checks
var settings = await settingsProvider.GetSettingsAsync(); var settings = await settingsProvider.GetSettingsAsync();
if (settings == null) if (settings == null)
{ {
return HealthCheckResult.Healthy("Plex is not confiured."); return HealthCheckResult.Healthy("Plex is not configured.");
} }
var taskResult = new List<Task<PlexStatus>>(); var taskResult = new List<Task<PlexStatus>>();

@ -18,39 +18,8 @@ namespace Ombi.HealthChecks
builder.AddCheck<RadarrHealthCheck>("Radarr", tags: new string[] { "DVR" }); builder.AddCheck<RadarrHealthCheck>("Radarr", tags: new string[] { "DVR" });
builder.AddCheck<CouchPotatoHealthCheck>("CouchPotato", tags: new string[] { "DVR" }); builder.AddCheck<CouchPotatoHealthCheck>("CouchPotato", tags: new string[] { "DVR" });
builder.AddCheck<SickrageHealthCheck>("SickRage", tags: new string[] { "DVR" }); builder.AddCheck<SickrageHealthCheck>("SickRage", tags: new string[] { "DVR" });
builder.AddOmbiPingHealthCheck(options =>
{
options.AddHost("www.google.co.uk", 5000, HealthStatus.Unhealthy);
options.AddHost("www.google.com", 3000, HealthStatus.Degraded);
}, "External Ping", tags: new string[] { "System" });
return builder; return builder;
} }
/// <summary>
/// Add a health check for network ping.
/// </summary>
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
/// <param name="setup">The action to configure the ping parameters.</param>
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'ping' will be used for the name.</param>
/// <param name="failureStatus">
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported.
/// </param>
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param>
/// <param name="timeout">An optional System.TimeSpan representing the timeout of the check.</param>
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns></param>
public static IHealthChecksBuilder AddOmbiPingHealthCheck(this IHealthChecksBuilder builder, Action<OmbiPingHealthCheckOptions> setup, string name = default, HealthStatus? failureStatus = default, IEnumerable<string> tags = default, TimeSpan? timeout = default)
{
var options = new OmbiPingHealthCheckOptions();
setup?.Invoke(options);
return builder.Add(new HealthCheckRegistration(
name,
sp => new OmbiPingHealthCheck(options),
failureStatus,
tags,
timeout));
}
} }
} }

@ -118,13 +118,13 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="NewAlbums" xml:space="preserve"> <data name="NewAlbums" xml:space="preserve">
<value>New Albums</value> <value>Új album</value>
</data> </data>
<data name="NewMovies" xml:space="preserve"> <data name="NewMovies" xml:space="preserve">
<value>New Movies</value> <value>Új film</value>
</data> </data>
<data name="NewTV" xml:space="preserve"> <data name="NewTV" xml:space="preserve">
<value>New TV</value> <value>Új sorozat</value>
</data> </data>
<data name="GenresLabel" xml:space="preserve"> <data name="GenresLabel" xml:space="preserve">
<value>Műfaj:</value> <value>Műfaj:</value>
@ -139,18 +139,18 @@
<value>Epizódok:</value> <value>Epizódok:</value>
</data> </data>
<data name="PoweredBy" xml:space="preserve"> <data name="PoweredBy" xml:space="preserve">
<value>Powered by</value> <value>Biztosítja a(z)</value>
</data> </data>
<data name="Unsubscribe" xml:space="preserve"> <data name="Unsubscribe" xml:space="preserve">
<value>Unsubscribe</value> <value>Leiratkozás</value>
</data> </data>
<data name="Album" xml:space="preserve"> <data name="Album" xml:space="preserve">
<value>Album</value> <value>Album</value>
</data> </data>
<data name="Movie" xml:space="preserve"> <data name="Movie" xml:space="preserve">
<value>Movie</value> <value>Film</value>
</data> </data>
<data name="TvShow" xml:space="preserve"> <data name="TvShow" xml:space="preserve">
<value>TV Show</value> <value>Sorozat</value>
</data> </data>
</root> </root>

@ -148,9 +148,9 @@
<value>Album</value> <value>Album</value>
</data> </data>
<data name="Movie" xml:space="preserve"> <data name="Movie" xml:space="preserve">
<value>Movie</value> <value>Film</value>
</data> </data>
<data name="TvShow" xml:space="preserve"> <data name="TvShow" xml:space="preserve">
<value>TV Show</value> <value>TV-serie</value>
</data> </data>
</root> </root>

@ -118,16 +118,16 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="NewAlbums" xml:space="preserve"> <data name="NewAlbums" xml:space="preserve">
<value>New Albums</value> <value>Nye Album</value>
</data> </data>
<data name="NewMovies" xml:space="preserve"> <data name="NewMovies" xml:space="preserve">
<value>New Movies</value> <value>Nye filmer</value>
</data> </data>
<data name="NewTV" xml:space="preserve"> <data name="NewTV" xml:space="preserve">
<value>New TV</value> <value>Nye TV-serier</value>
</data> </data>
<data name="GenresLabel" xml:space="preserve"> <data name="GenresLabel" xml:space="preserve">
<value>Genres:</value> <value>Sjangere:</value>
</data> </data>
<data name="AlbumTypeLabel" xml:space="preserve"> <data name="AlbumTypeLabel" xml:space="preserve">
<value>Type:</value> <value>Type:</value>
@ -136,21 +136,21 @@
<value>Sesong:</value> <value>Sesong:</value>
</data> </data>
<data name="EpisodesLabel" xml:space="preserve"> <data name="EpisodesLabel" xml:space="preserve">
<value>Episodes:</value> <value>Episoder:</value>
</data> </data>
<data name="PoweredBy" xml:space="preserve"> <data name="PoweredBy" xml:space="preserve">
<value>Powered by</value> <value>Drevet av</value>
</data> </data>
<data name="Unsubscribe" xml:space="preserve"> <data name="Unsubscribe" xml:space="preserve">
<value>Unsubscribe</value> <value>Avslutt abonnement</value>
</data> </data>
<data name="Album" xml:space="preserve"> <data name="Album" xml:space="preserve">
<value>Album</value> <value>Album</value>
</data> </data>
<data name="Movie" xml:space="preserve"> <data name="Movie" xml:space="preserve">
<value>Movie</value> <value>Film</value>
</data> </data>
<data name="TvShow" xml:space="preserve"> <data name="TvShow" xml:space="preserve">
<value>TV Show</value> <value>TV-serie</value>
</data> </data>
</root> </root>

@ -153,6 +153,7 @@ namespace Ombi.Notifications
RequestedUser = req?.RequestedUser?.UserName; RequestedUser = req?.RequestedUser?.UserName;
RequestedDate = req?.RequestedDate.ToString("D"); RequestedDate = req?.RequestedDate.ToString("D");
DetailsUrl = GetDetailsUrl(s, req); DetailsUrl = GetDetailsUrl(s, req);
RequestedByAlias = req?.RequestedByAlias;
if (Type.IsNullOrEmpty()) if (Type.IsNullOrEmpty())
{ {
@ -276,6 +277,7 @@ namespace Ombi.Notifications
// User Defined // User Defined
public string RequestId { get; set; } public string RequestId { get; set; }
public string RequestedUser { get; set; } public string RequestedUser { get; set; }
public string RequestedByAlias { get; set; }
public string UserName { get; set; } public string UserName { get; set; }
public string IssueUser => UserName; public string IssueUser => UserName;
public string Alias { get; set; } public string Alias { get; set; }
@ -339,6 +341,7 @@ namespace Ombi.Notifications
{ nameof(IssueUser), IssueUser }, { nameof(IssueUser), IssueUser },
{ nameof(UserName), UserName }, { nameof(UserName), UserName },
{ nameof(Alias), Alias }, { nameof(Alias), Alias },
{ nameof(RequestedByAlias), RequestedByAlias },
{ nameof(UserPreference), UserPreference }, { nameof(UserPreference), UserPreference },
{ nameof(DenyReason), DenyReason }, { nameof(DenyReason), DenyReason },
{ nameof(AvailableDate), AvailableDate }, { nameof(AvailableDate), AvailableDate },

@ -8,10 +8,12 @@ using Ombi.Api.Emby;
using Ombi.Api.Emby.Models; using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Media.Tv; using Ombi.Api.Emby.Models.Media.Tv;
using Ombi.Api.Emby.Models.Movie; using Ombi.Api.Emby.Models.Movie;
using Ombi.Core.Services;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Hubs; using Ombi.Hubs;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities; using Ombi.Store.Entities;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using Quartz; using Quartz;
@ -19,108 +21,43 @@ using MediaType = Ombi.Store.Entities.MediaType;
namespace Ombi.Schedule.Jobs.Emby namespace Ombi.Schedule.Jobs.Emby
{ {
public class EmbyContentSync : IEmbyContentSync public class EmbyContentSync : EmbyLibrarySync, IEmbyContentSync
{ {
public EmbyContentSync(ISettingsService<EmbySettings> settings, IEmbyApiFactory api, ILogger<EmbyContentSync> logger, public EmbyContentSync(
IEmbyContentRepository repo, INotificationHubService notification) ISettingsService<EmbySettings> settings,
IEmbyApiFactory api,
ILogger<EmbyContentSync> logger,
IEmbyContentRepository repo,
INotificationHubService notification,
IFeatureService feature):
base(settings, api, logger, notification)
{ {
_logger = logger;
_settings = settings;
_apiFactory = api;
_repo = repo; _repo = repo;
_notification = notification; _feature = feature;
} }
private readonly ILogger<EmbyContentSync> _logger;
private readonly ISettingsService<EmbySettings> _settings;
private readonly IEmbyApiFactory _apiFactory;
private readonly IEmbyContentRepository _repo; private readonly IEmbyContentRepository _repo;
private readonly INotificationHubService _notification; private readonly IFeatureService _feature;
private const int AmountToTake = 100;
private IEmbyApi Api { get; set; } public async override Task Execute(IJobExecutionContext context)
public async Task Execute(IJobExecutionContext context)
{ {
JobDataMap dataMap = context.JobDetail.JobDataMap;
var recentlyAddedSearch = false;
if (dataMap.TryGetValue(JobDataKeys.EmbyRecentlyAddedSearch, out var recentlyAddedObj))
{
recentlyAddedSearch = Convert.ToBoolean(recentlyAddedObj);
}
var embySettings = await _settings.GetSettingsAsync();
if (!embySettings.Enable)
return;
Api = _apiFactory.CreateClient(embySettings);
await _notification.SendNotificationToAdmins(recentlyAddedSearch ? "Emby Recently Added Started" : "Emby Content Sync Started");
foreach (var server in embySettings.Servers) await base.Execute(context);
{
try
{
await StartServerCache(server, recentlyAddedSearch);
}
catch (Exception e)
{
await _notification.SendNotificationToAdmins("Emby Content Sync Failed");
_logger.LogError(e, "Exception when caching Emby for server {0}", server.Name);
}
}
await _notification.SendNotificationToAdmins("Emby Content Sync Finished");
// Episodes // Episodes
await OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IEmbyEpisodeSync), "Emby"), new JobDataMap(new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, recentlyAdded.ToString() } }));
// Played state
await OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IEmbyEpisodeSync), "Emby"), new JobDataMap(new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, recentlyAddedSearch.ToString() } })); var isPlayedSyncEnabled = await _feature.FeatureEnabled(FeatureNames.PlayedSync);
} if(isPlayedSyncEnabled)
private async Task StartServerCache(EmbyServers server, bool recentlyAdded)
{
if (!ValidateSettings(server))
{
return;
}
if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled))
{
var movieLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
foreach (var movieParentIdFilder in movieLibsToFilter)
{
_logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'");
await ProcessMovies(server, recentlyAdded, movieParentIdFilder.Key);
}
var tvLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
foreach (var tvParentIdFilter in tvLibsToFilter)
{
_logger.LogInformation($"Scanning Lib '{tvParentIdFilter.Title}'");
await ProcessTv(server, recentlyAdded, tvParentIdFilter.Key);
}
var mixedLibs = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "mixed");
foreach (var m in mixedLibs)
{
_logger.LogInformation($"Scanning Lib '{m.Title}'");
await ProcessTv(server, recentlyAdded, m.Key);
await ProcessMovies(server, recentlyAdded, m.Key);
}
}
else
{ {
await ProcessMovies(server, recentlyAdded); await OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IEmbyPlayedSync), "Emby"), new JobDataMap(new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, recentlyAdded.ToString() } }));
await ProcessTv(server, recentlyAdded);
} }
} }
private async Task ProcessTv(EmbyServers server, bool recentlyAdded, string parentId = default)
protected async override Task ProcessTv(EmbyServers server, string parentId = default)
{ {
// TV Time // TV Time
var mediaToAdd = new HashSet<EmbyContent>(); var mediaToAdd = new HashSet<EmbyContent>();
@ -196,7 +133,7 @@ namespace Ombi.Schedule.Jobs.Emby
await _repo.AddRange(mediaToAdd); await _repo.AddRange(mediaToAdd);
} }
private async Task ProcessMovies(EmbyServers server, bool recentlyAdded, string parentId = default) protected override async Task ProcessMovies(EmbyServers server, string parentId = default)
{ {
EmbyItemContainer<EmbyMovie> movies; EmbyItemContainer<EmbyMovie> movies;
if (recentlyAdded) if (recentlyAdded)
@ -263,7 +200,12 @@ namespace Ombi.Schedule.Jobs.Emby
// Check if it exists // Check if it exists
var existingMovie = await _repo.GetByEmbyId(movieInfo.Id); var existingMovie = await _repo.GetByEmbyId(movieInfo.Id);
var alreadyGoingToAdd = content.Any(x => x.EmbyId == movieInfo.Id); var alreadyGoingToAdd = content.Any(x => x.EmbyId == movieInfo.Id);
if (existingMovie == null && !alreadyGoingToAdd) if (alreadyGoingToAdd)
{
_logger.LogDebug($"Detected duplicate for {movieInfo.Name}");
return;
}
if (existingMovie == null)
{ {
if (!movieInfo.ProviderIds.Any()) if (!movieInfo.ProviderIds.Any())
{ {
@ -319,36 +261,6 @@ namespace Ombi.Schedule.Jobs.Emby
content.Quality = has4K ? null : quality; content.Quality = has4K ? null : quality;
content.Has4K = has4K; content.Has4K = has4K;
} }
private bool ValidateSettings(EmbyServers server)
{
if (server?.Ip == null || string.IsNullOrEmpty(server?.ApiKey))
{
_logger.LogInformation(LoggingEvents.EmbyContentCacher, $"Server {server?.Name} is not configured correctly");
return false;
}
return true;
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
//_settings?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
} }
} }

@ -0,0 +1,146 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Emby;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Hubs;
using Quartz;
namespace Ombi.Schedule.Jobs.Emby
{
public abstract class EmbyLibrarySync
{
public EmbyLibrarySync(ISettingsService<EmbySettings> settings, IEmbyApiFactory api, ILogger<EmbyContentSync> logger,
INotificationHubService notification)
{
_logger = logger;
_settings = settings;
_apiFactory = api;
_notification = notification;
}
protected readonly ILogger<EmbyContentSync> _logger;
protected readonly ISettingsService<EmbySettings> _settings;
protected readonly IEmbyApiFactory _apiFactory;
protected bool recentlyAdded;
protected readonly INotificationHubService _notification;
protected const int AmountToTake = 100;
protected IEmbyApi Api { get; set; }
public virtual async Task Execute(IJobExecutionContext context)
{
JobDataMap dataMap = context.MergedJobDataMap;
if (dataMap.TryGetValue(JobDataKeys.EmbyRecentlyAddedSearch, out var recentlyAddedObj))
{
recentlyAdded = Convert.ToBoolean(recentlyAddedObj);
}
await _notification.SendNotificationToAdmins(recentlyAdded ? "Emby Recently Added Started" : "Emby Content Sync Started");
var embySettings = await _settings.GetSettingsAsync();
if (!embySettings.Enable)
return;
Api = _apiFactory.CreateClient(embySettings);
foreach (var server in embySettings.Servers)
{
try
{
await StartServerCache(server);
}
catch (Exception e)
{
await _notification.SendNotificationToAdmins("Emby Content Sync Failed");
_logger.LogError(e, "Exception when caching Emby for server {0}", server.Name);
}
}
await _notification.SendNotificationToAdmins("Emby Content Sync Finished");
}
private async Task StartServerCache(EmbyServers server)
{
if (!ValidateSettings(server))
{
return;
}
if (server.EmbySelectedLibraries.Any() && server.EmbySelectedLibraries.Any(x => x.Enabled))
{
var movieLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "movies");
foreach (var movieParentIdFilder in movieLibsToFilter)
{
_logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'");
await ProcessMovies(server, movieParentIdFilder.Key);
}
var tvLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
foreach (var tvParentIdFilter in tvLibsToFilter)
{
_logger.LogInformation($"Scanning Lib '{tvParentIdFilter.Title}'");
await ProcessTv(server, tvParentIdFilter.Key);
}
var mixedLibs = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "mixed");
foreach (var m in mixedLibs)
{
_logger.LogInformation($"Scanning Lib '{m.Title}'");
await ProcessTv(server, m.Key);
await ProcessMovies(server, m.Key);
}
}
else
{
await ProcessMovies(server);
await ProcessTv(server);
}
}
protected abstract Task ProcessTv(EmbyServers server, string parentId = default);
protected abstract Task ProcessMovies(EmbyServers server, string parentId = default);
private bool ValidateSettings(EmbyServers server)
{
if (server?.Ip == null || string.IsNullOrEmpty(server?.ApiKey))
{
_logger.LogInformation(LoggingEvents.EmbyContentCacher, $"Server {server?.Name} is not configured correctly");
return false;
}
return true;
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
//_settings?.Dispose();
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}

@ -0,0 +1,110 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.Emby;
using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Movie;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Hubs;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Schedule.Jobs.Emby
{
public class EmbyPlayedSync : EmbyLibrarySync, IEmbyPlayedSync
{
public EmbyPlayedSync(ISettingsService<EmbySettings> settings, IEmbyApiFactory api, ILogger<EmbyContentSync> logger,
IUserPlayedMovieRepository repo, INotificationHubService notification, OmbiUserManager user) : base(settings, api, logger, notification)
{
_userManager = user;
_repo = repo;
}
private OmbiUserManager _userManager { get; }
private readonly IUserPlayedMovieRepository _repo;
protected override Task ProcessTv(EmbyServers server, string parentId = default)
{
// TODO
return Task.CompletedTask;
}
protected async override Task ProcessMovies(EmbyServers server, string parentId = default)
{
var allUsers = await _userManager.Users.Where(x => x.UserType == UserType.EmbyUser || x.UserType == UserType.EmbyConnectUser).ToListAsync();
foreach (var user in allUsers)
{
await ProcessMoviesUser(server, user, parentId);
}
}
private async Task ProcessMoviesUser(EmbyServers server, OmbiUser user, string parentId = default)
{
EmbyItemContainer<EmbyMovie> movies;
if (recentlyAdded)
{
var recentlyAddedAmountToTake = 5; // to be adjusted?
movies = await Api.GetMoviesPlayed(server.ApiKey, parentId, 0, recentlyAddedAmountToTake, user.ProviderUserId, server.FullUri);
// Setting this so we don't attempt to grab more than we need
if (movies.TotalRecordCount > recentlyAddedAmountToTake)
{
movies.TotalRecordCount = recentlyAddedAmountToTake;
}
}
else
{
movies = await Api.GetMoviesPlayed(server.ApiKey, parentId, 0, AmountToTake, user.ProviderUserId, server.FullUri);
}
var totalCount = movies.TotalRecordCount;
var processed = 0;
var mediaToAdd = new HashSet<UserPlayedMovie>();
while (processed < totalCount)
{
foreach (var movie in movies.Items)
{
await ProcessMovie(movie, user, mediaToAdd, server);
processed++;
}
// Get the next batch
// Recently Added should never be checked as the TotalRecords should equal the amount to take
if (!recentlyAdded)
{
movies = await Api.GetMoviesPlayed(server.ApiKey, parentId, processed, AmountToTake, user.ProviderUserId, server.FullUri);
}
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
}
private async Task ProcessMovie(EmbyMovie movieInfo, OmbiUser user, ICollection<UserPlayedMovie> content, EmbyServers server)
{
if (movieInfo.ProviderIds.Tmdb.IsNullOrEmpty())
{
_logger.LogWarning($"Movie {movieInfo.Name} has no relevant metadata. Skipping.");
return;
}
var userPlayedMovie = new UserPlayedMovie()
{
TheMovieDbId = int.Parse(movieInfo.ProviderIds.Tmdb),
UserId = user.Id
};
// Check if it exists
var existingMovie = await _repo.Get(userPlayedMovie.TheMovieDbId, userPlayedMovie.UserId);
var alreadyGoingToAdd = content.Any(x => x.TheMovieDbId == userPlayedMovie.TheMovieDbId && x.UserId == userPlayedMovie.UserId);
if (existingMovie == null && !alreadyGoingToAdd)
{
content.Add(userPlayedMovie);
}
}
}
}

@ -0,0 +1,6 @@
namespace Ombi.Schedule.Jobs.Emby
{
public interface IEmbyPlayedSync : IBaseJob
{
}
}

@ -228,7 +228,12 @@ namespace Ombi.Schedule.Jobs.Jellyfin
// Check if it exists // Check if it exists
var existingMovie = await _repo.GetByJellyfinId(movieInfo.Id); var existingMovie = await _repo.GetByJellyfinId(movieInfo.Id);
var alreadyGoingToAdd = content.Any(x => x.JellyfinId == movieInfo.Id); var alreadyGoingToAdd = content.Any(x => x.JellyfinId == movieInfo.Id);
if (existingMovie == null && !alreadyGoingToAdd) if (alreadyGoingToAdd)
{
_logger.LogDebug($"Detected duplicate for {movieInfo.Name}");
return;
}
if (existingMovie == null)
{ {
if (!movieInfo.ProviderIds.Any()) if (!movieInfo.ProviderIds.Any())
{ {

@ -15,15 +15,22 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
public class MediaDatabaseRefresh : IMediaDatabaseRefresh public class MediaDatabaseRefresh : IMediaDatabaseRefresh
{ {
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log, public MediaDatabaseRefresh(
IPlexContentRepository plexRepo, IEmbyContentRepository embyRepo, IJellyfinContentRepository jellyfinRepo, ISettingsService<PlexSettings> s,
ISettingsService<EmbySettings> embySettings, ISettingsService<JellyfinSettings> jellyfinSettings) ILogger<MediaDatabaseRefresh> log,
IPlexContentRepository plexRepo,
IEmbyContentRepository embyRepo,
IJellyfinContentRepository jellyfinRepo,
IUserPlayedMovieRepository userPlayedRepo,
ISettingsService<EmbySettings> embySettings,
ISettingsService<JellyfinSettings> jellyfinSettings)
{ {
_plexSettings = s; _plexSettings = s;
_log = log; _log = log;
_plexRepo = plexRepo; _plexRepo = plexRepo;
_embyRepo = embyRepo; _embyRepo = embyRepo;
_jellyfinRepo = jellyfinRepo; _jellyfinRepo = jellyfinRepo;
_userPlayedRepo = userPlayedRepo;
_embySettings = embySettings; _embySettings = embySettings;
_jellyfinSettings = jellyfinSettings; _jellyfinSettings = jellyfinSettings;
_plexSettings.ClearCache(); _plexSettings.ClearCache();
@ -34,6 +41,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly IPlexContentRepository _plexRepo; private readonly IPlexContentRepository _plexRepo;
private readonly IEmbyContentRepository _embyRepo; private readonly IEmbyContentRepository _embyRepo;
private readonly IJellyfinContentRepository _jellyfinRepo; private readonly IJellyfinContentRepository _jellyfinRepo;
private readonly IUserPlayedMovieRepository _userPlayedRepo;
private readonly ISettingsService<EmbySettings> _embySettings; private readonly ISettingsService<EmbySettings> _embySettings;
private readonly ISettingsService<JellyfinSettings> _jellyfinSettings; private readonly ISettingsService<JellyfinSettings> _jellyfinSettings;
@ -41,6 +49,7 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
try try
{ {
await RemovePlayedData();
await RemovePlexData(); await RemovePlexData();
await RemoveEmbyData(); await RemoveEmbyData();
await RemoveJellyfinData(); await RemoveJellyfinData();
@ -52,6 +61,20 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
private async Task RemovePlayedData()
{
try
{
const string movieSql = "DELETE FROM UserPlayedMovie";
await _userPlayedRepo.ExecuteSql(movieSql);
}
catch (Exception e)
{
_log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Played Data Failed");
}
}
private async Task RemoveEmbyData() private async Task RemoveEmbyData()
{ {
try try

@ -46,8 +46,8 @@ namespace Ombi.Schedule.Jobs.Radarr
await tran.CommitAsync(); await tran.CommitAsync();
var radarrSettings = _radarrSettings.GetSettingsAsync(); var radarrSettings = _radarrSettings.GetSettingsAsync();
var radarr4kSettings = _radarr4kSettings.GetSettingsAsync();
await Process(await radarrSettings); await Process(await radarrSettings);
var radarr4kSettings = _radarr4kSettings.GetSettingsAsync();
await Process(await radarr4kSettings); await Process(await radarr4kSettings);
} }
catch (Exception) catch (Exception)

@ -99,6 +99,7 @@ namespace Ombi.Schedule
await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync), "Emby", JobSettingsHelper.EmbyContent(s)); await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync), "Emby", JobSettingsHelper.EmbyContent(s));
await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync) + "RecentlyAdded", "Emby", JobSettingsHelper.EmbyRecentlyAddedSync(s), new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, "true" } }); await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync) + "RecentlyAdded", "Emby", JobSettingsHelper.EmbyRecentlyAddedSync(s), new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, "true" } });
await OmbiQuartz.Instance.AddJob<IEmbyEpisodeSync>(nameof(IEmbyEpisodeSync), "Emby", null); await OmbiQuartz.Instance.AddJob<IEmbyEpisodeSync>(nameof(IEmbyEpisodeSync), "Emby", null);
await OmbiQuartz.Instance.AddJob<IEmbyPlayedSync>(nameof(IEmbyPlayedSync), "Emby", null);
await OmbiQuartz.Instance.AddJob<IEmbyAvaliabilityChecker>(nameof(IEmbyAvaliabilityChecker), "Emby", null); await OmbiQuartz.Instance.AddJob<IEmbyAvaliabilityChecker>(nameof(IEmbyAvaliabilityChecker), "Emby", null);
await OmbiQuartz.Instance.AddJob<IEmbyUserImporter>(nameof(IEmbyUserImporter), "Emby", JobSettingsHelper.UserImporter(s)); await OmbiQuartz.Instance.AddJob<IEmbyUserImporter>(nameof(IEmbyUserImporter), "Emby", JobSettingsHelper.UserImporter(s));
} }

@ -21,5 +21,6 @@ namespace Ombi.Settings.Settings.Models
{ {
public const string Movie4KRequests = nameof(Movie4KRequests); public const string Movie4KRequests = nameof(Movie4KRequests);
public const string OldTrendingSource = nameof(OldTrendingSource); public const string OldTrendingSource = nameof(OldTrendingSource);
public const string PlayedSync = nameof(PlayedSync);
} }
} }

@ -1,5 +1,6 @@
using Ombi.Helpers; using Ombi.Helpers;
using Quartz; using Quartz;
using System;
namespace Ombi.Settings.Settings.Models namespace Ombi.Settings.Settings.Models
{ {
@ -104,7 +105,9 @@ namespace Ombi.Settings.Settings.Models
private static string ValidateCron(string cron) private static string ValidateCron(string cron)
{ {
if (CronExpression.IsValidExpression(cron)) CronExpression expression = new CronExpression(cron);
DateTimeOffset? nextFireUTCTime = expression.GetNextValidTimeAfter(DateTime.Now);
if (CronExpression.IsValidExpression(cron) && nextFireUTCTime != null)
{ {
return cron; return cron;
} }

@ -41,6 +41,7 @@ namespace Ombi.Store.Context
public DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; } public DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; }
public DbSet<SickRageCache> SickRageCache { get; set; } public DbSet<SickRageCache> SickRageCache { get; set; }
public DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; } public DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
public DbSet<UserPlayedMovie> UserPlayedMovie { get; set; }
protected override void OnModelCreating(ModelBuilder builder) protected override void OnModelCreating(ModelBuilder builder)
{ {

@ -84,5 +84,10 @@ namespace Ombi.Store.Entities.Requests
[NotMapped] [NotMapped]
public override bool CanApprove => !Approved && !Available || !Approved4K && !Available4K; public override bool CanApprove => !Approved && !Available || !Approved4K && !Available4K;
[NotMapped]
public bool WatchedByRequestedUser { get; set; }
[NotMapped]
public int PlayedByUsersCount { get; set; }
} }
} }

@ -0,0 +1,8 @@
namespace Ombi.Store.Entities
{
public class UserPlayedMovie : Entity
{
public int TheMovieDbId { get; set; }
public string UserId { get; set; }
}
}

@ -0,0 +1,566 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.MySql;
#nullable disable
namespace Ombi.Store.Migrations.ExternalMySql
{
[DbContext(typeof(ExternalMySqlContext))]
[Migration("20230406152218_MovieUserPlayed")]
partial class MovieUserPlayed
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "6.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<string>("EmbyId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<string>("Quality")
.HasColumnType("longtext");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.Property<int>("Type")
.HasColumnType("int");
b.Property<string>("Url")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<string>("EmbyId")
.HasColumnType("longtext");
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("ParentId")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("JellyfinId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<string>("Quality")
.HasColumnType("longtext");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.Property<int>("Type")
.HasColumnType("int");
b.Property<string>("Url")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("JellyfinContent");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("JellyfinId")
.HasColumnType("longtext");
b.Property<string>("ParentId")
.HasColumnType("varchar(255)");
b.Property<string>("ProviderId")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("JellyfinEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<int>("ArtistId")
.HasColumnType("int");
b.Property<string>("ForeignAlbumId")
.HasColumnType("longtext");
b.Property<bool>("Monitored")
.HasColumnType("tinyint(1)");
b.Property<decimal>("PercentOfTracks")
.HasColumnType("decimal(65,30)");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("datetime(6)");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<int>("TrackCount")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("LidarrAlbumCache");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("ArtistId")
.HasColumnType("int");
b.Property<string>("ArtistName")
.HasColumnType("longtext");
b.Property<string>("ForeignArtistId")
.HasColumnType("longtext");
b.Property<bool>("Monitored")
.HasColumnType("tinyint(1)");
b.HasKey("Id");
b.ToTable("LidarrArtistCache");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<string>("GrandparentKey")
.HasColumnType("varchar(255)");
b.Property<string>("Key")
.HasColumnType("longtext");
b.Property<string>("ParentKey")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<string>("Title")
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("ParentKey")
.HasColumnType("longtext");
b.Property<string>("PlexContentId")
.HasColumnType("longtext");
b.Property<int?>("PlexServerContentId")
.HasColumnType("int");
b.Property<string>("SeasonKey")
.HasColumnType("longtext");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("AddedAt")
.HasColumnType("datetime(6)");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<string>("ImdbId")
.HasColumnType("longtext");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("Quality")
.HasColumnType("longtext");
b.Property<string>("ReleaseYear")
.HasColumnType("longtext");
b.Property<int?>("RequestId")
.HasColumnType("int");
b.Property<string>("TheMovieDbId")
.HasColumnType("longtext");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<string>("TvDbId")
.HasColumnType("longtext");
b.Property<int>("Type")
.HasColumnType("int");
b.Property<string>("Url")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("TmdbId")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("PlexWatchlistHistory");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<bool>("Has4K")
.HasColumnType("tinyint(1)");
b.Property<bool>("HasFile")
.HasColumnType("tinyint(1)");
b.Property<bool>("HasRegular")
.HasColumnType("tinyint(1)");
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("TvDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<int>("TvDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.Property<int>("TvDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("EpisodeNumber")
.HasColumnType("int");
b.Property<bool>("HasFile")
.HasColumnType("tinyint(1)");
b.Property<int>("MovieDbId")
.HasColumnType("int");
b.Property<int>("SeasonNumber")
.HasColumnType("int");
b.Property<int>("TvDbId")
.HasColumnType("int");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.Property<string>("UserId")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("UserPlayedMovie");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("JellyfinId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", null)
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Navigation("Episodes");
b.Navigation("Seasons");
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalMySql
{
public partial class MovieUserPlayed : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "UserPlayedMovie",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
TheMovieDbId = table.Column<int>(type: "int", nullable: false),
UserId = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_UserPlayedMovie", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserPlayedMovie");
}
}
}

@ -16,7 +16,7 @@ namespace Ombi.Store.Migrations.ExternalMySql
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "6.0.0") .HasAnnotation("ProductVersion", "6.0.9")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
@ -488,6 +488,23 @@ namespace Ombi.Store.Migrations.ExternalMySql
b.ToTable("SonarrEpisodeCache"); b.ToTable("SonarrEpisodeCache");
}); });
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("TheMovieDbId")
.HasColumnType("int");
b.Property<string>("UserId")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("UserPlayedMovie");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{ {
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")

@ -0,0 +1,564 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Ombi.Store.Context.Sqlite;
#nullable disable
namespace Ombi.Store.Migrations.ExternalSqlite
{
[DbContext(typeof(ExternalSqliteContext))]
[Migration("20230310130339_MovieUserPlayed")]
partial class MovieUserPlayed
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<string>("EmbyId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<string>("Quality")
.HasColumnType("TEXT");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Url")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<string>("EmbyId")
.HasColumnType("TEXT");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("ParentId")
.HasColumnType("TEXT");
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("JellyfinId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<string>("Quality")
.HasColumnType("TEXT");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Url")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("JellyfinContent");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("JellyfinId")
.HasColumnType("TEXT");
b.Property<string>("ParentId")
.HasColumnType("TEXT");
b.Property<string>("ProviderId")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("JellyfinEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<int>("ArtistId")
.HasColumnType("INTEGER");
b.Property<string>("ForeignAlbumId")
.HasColumnType("TEXT");
b.Property<bool>("Monitored")
.HasColumnType("INTEGER");
b.Property<decimal>("PercentOfTracks")
.HasColumnType("TEXT");
b.Property<DateTime>("ReleaseDate")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<int>("TrackCount")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("LidarrAlbumCache");
});
modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ArtistId")
.HasColumnType("INTEGER");
b.Property<string>("ArtistName")
.HasColumnType("TEXT");
b.Property<string>("ForeignArtistId")
.HasColumnType("TEXT");
b.Property<bool>("Monitored")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("LidarrArtistCache");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<string>("GrandparentKey")
.HasColumnType("TEXT");
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<string>("ParentKey")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ParentKey")
.HasColumnType("TEXT");
b.Property<string>("PlexContentId")
.HasColumnType("TEXT");
b.Property<int?>("PlexServerContentId")
.HasColumnType("INTEGER");
b.Property<string>("SeasonKey")
.HasColumnType("TEXT");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("AddedAt")
.HasColumnType("TEXT");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<string>("ImdbId")
.HasColumnType("TEXT");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Quality")
.HasColumnType("TEXT");
b.Property<string>("ReleaseYear")
.HasColumnType("TEXT");
b.Property<int?>("RequestId")
.HasColumnType("INTEGER");
b.Property<string>("TheMovieDbId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("TvDbId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<string>("Url")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexWatchlistHistory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("TmdbId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("PlexWatchlistHistory");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("Has4K")
.HasColumnType("INTEGER");
b.Property<bool>("HasFile")
.HasColumnType("INTEGER");
b.Property<bool>("HasRegular")
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TvDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<int>("TvDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.Property<int>("TvDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("EpisodeNumber")
.HasColumnType("INTEGER");
b.Property<bool>("HasFile")
.HasColumnType("INTEGER");
b.Property<int>("MovieDbId")
.HasColumnType("INTEGER");
b.Property<int>("SeasonNumber")
.HasColumnType("INTEGER");
b.Property<int>("TvDbId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("UserPlayedMovie");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("EmbyId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.JellyfinContent", "Series")
.WithMany("Episodes")
.HasForeignKey("ParentId")
.HasPrincipalKey("JellyfinId");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
.WithMany("Episodes")
.HasForeignKey("GrandparentKey")
.HasPrincipalKey("Key");
b.Navigation("Series");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.HasOne("Ombi.Store.Entities.PlexServerContent", null)
.WithMany("Seasons")
.HasForeignKey("PlexServerContentId");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.JellyfinContent", b =>
{
b.Navigation("Episodes");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Navigation("Episodes");
b.Navigation("Seasons");
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,32 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Ombi.Store.Migrations.ExternalSqlite
{
public partial class MovieUserPlayed : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "UserPlayedMovie",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
TheMovieDbId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_UserPlayedMovie", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserPlayedMovie");
}
}
}

@ -15,7 +15,7 @@ namespace Ombi.Store.Migrations.ExternalSqlite
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.0"); modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{ {
@ -486,6 +486,23 @@ namespace Ombi.Store.Migrations.ExternalSqlite
b.ToTable("SonarrEpisodeCache"); b.ToTable("SonarrEpisodeCache");
}); });
modelBuilder.Entity("Ombi.Store.Entities.UserPlayedMovie", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("TheMovieDbId")
.HasColumnType("INTEGER");
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.HasKey("Id");
b.ToTable("UserPlayedMovie");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{ {
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface IUserPlayedMovieRepository : IExternalRepository<UserPlayedMovie>
{
Task<UserPlayedMovie> Get(int theMovieDbId, string userId);
}
}

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Ombi.Store.Context;
using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public class UserPlayedMovieRepository : ExternalRepository<UserPlayedMovie>, IUserPlayedMovieRepository
{
protected ExternalContext Db { get; }
public UserPlayedMovieRepository(ExternalContext db) : base(db)
{
Db = db;
}
public async Task<UserPlayedMovie> Get(int theMovieDbId, string userId)
{
return await Db.UserPlayedMovie.FirstOrDefaultAsync(x => x.TheMovieDbId == theMovieDbId && x.UserId == userId);
}
}
}

@ -1 +1,5 @@
nodeLinker: node-modules nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"

@ -13,26 +13,26 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^15.0.1", "@angular/animations": "^15.0.4",
"@angular/cdk": "^14.2.7", "@angular/cdk": "^14.2.7",
"@angular/common": "^15.0.1", "@angular/common": "^15.0.4",
"@angular/compiler": "^15.0.1", "@angular/compiler": "^15.0.4",
"@angular/core": "^15.0.1", "@angular/core": "^15.0.4",
"@angular/forms": "^15.0.1", "@angular/forms": "^15.0.4",
"@angular/localize": "^15.0.1", "@angular/localize": "^15.0.4",
"@angular/material": "^14.2.7", "@angular/material": "^14.2.7",
"@angular/platform-browser": "^15.0.1", "@angular/platform-browser": "^15.0.4",
"@angular/platform-browser-dynamic": "^15.0.1", "@angular/platform-browser-dynamic": "^15.0.4",
"@angular/platform-server": "^15.0.1", "@angular/platform-server": "^15.0.4",
"@angular/router": "^15.0.1", "@angular/router": "^15.0.4",
"@angularclass/hmr": "^3.0.0", "@angularclass/hmr": "^3.0.0",
"@auth0/angular-jwt": "^5.0.2", "@auth0/angular-jwt": "^5.0.2",
"@fortawesome/fontawesome-free": "^6.0.0", "@fortawesome/fontawesome-free": "^6.0.0",
"@microsoft/signalr": "^6.0.7", "@microsoft/signalr": "^6.0.7",
"@ngx-translate/core": "^14.0.0", "@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0", "@ngx-translate/http-loader": "^7.0.0",
"@ngxs/devtools-plugin": "^3.7.3", "@ngxs/devtools-plugin": "3.7.3",
"@ngxs/store": "^3.7.3", "@ngxs/store": "3.7.3",
"@types/jquery": "^3.5.14", "@types/jquery": "^3.5.14",
"@yellowspot/ng-truncate": "^2.0.0", "@yellowspot/ng-truncate": "^2.0.0",
"angular-router-loader": "^0.8.5", "angular-router-loader": "^0.8.5",
@ -58,7 +58,7 @@
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^15.0.2", "@angular-devkit/build-angular": "^15.0.2",
"@angular/cli": "^15.0.2", "@angular/cli": "^15.0.2",
"@angular/compiler-cli": "^15.0.1", "@angular/compiler-cli": "^15.0.4",
"@babel/core": "^7.18.9", "@babel/core": "^7.18.9",
"@compodoc/compodoc": "^1.1.19", "@compodoc/compodoc": "^1.1.19",
"@storybook/angular": "^6.5.9", "@storybook/angular": "^6.5.9",

@ -22,7 +22,23 @@
</div> </div>
<div class="row action-items"> <div class="row action-items">
<div class="col-12" *ngIf="isAdmin"> <div class="col-12" *ngIf="isAdmin">
<button *ngIf="!request.approved" id="detailed-request-approve-{{request.mediaId}}" color="accent" mat-raised-button (click)="approve()">{{'Common.Approve' | translate}}</button> <div *ngIf="!request.approved && !request.denied">
<button
id="detailed-request-approve-{{request.mediaId}}"
color="accent"
mat-raised-button
(click)="approve()"
matTooltip="{{'Common.Approve' | translate}}">
<mat-icon>check-circle</mat-icon>
</button>
<button
id="detailed-request-deny-{{request.mediaId}}"
color="accent"
mat-raised-button
(click)="deny()"
matTooltip="{{'Requests.Deny' | translate}}">
<mat-icon>cancel</mat-icon></button>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -50,5 +50,9 @@
position: absolute; position: absolute;
} }
} }
.action-items button {
margin: 4px;
}
} }

@ -22,6 +22,7 @@ export class DetailedCardComponent implements OnInit, OnDestroy {
@Input() public isAdmin: boolean = false; @Input() public isAdmin: boolean = false;
@Output() public onClick: EventEmitter<void> = new EventEmitter<void>(); @Output() public onClick: EventEmitter<void> = new EventEmitter<void>();
@Output() public onApprove: EventEmitter<void> = new EventEmitter<void>(); @Output() public onApprove: EventEmitter<void> = new EventEmitter<void>();
@Output() public onDeny: EventEmitter<void> = new EventEmitter<void>();
public RequestType = RequestType; public RequestType = RequestType;
public loading: false; public loading: false;
@ -41,6 +42,9 @@ export class DetailedCardComponent implements OnInit, OnDestroy {
} }
public getStatus(request: IRecentlyRequested) { public getStatus(request: IRecentlyRequested) {
if (request.denied) {
return "Common.Denied";
}
if (request.available) { if (request.available) {
return "Common.Available"; return "Common.Available";
} }
@ -62,7 +66,14 @@ export class DetailedCardComponent implements OnInit, OnDestroy {
this.onApprove.emit(); this.onApprove.emit();
} }
public deny() {
this.onDeny.emit();
}
public getClass(request: IRecentlyRequested) { public getClass(request: IRecentlyRequested) {
if (request.denied) {
return "danger";
}
if (request.available || request.tvPartiallyAvailable) { if (request.available || request.tvPartiallyAvailable) {
return "success"; return "success";
} }

@ -1,5 +1,8 @@
<div class="small-middle-container"> <div class="small-middle-container">
<div class="section">
<h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{'Discovery.Genres' | translate}}</h2>
<genre-button-select class="collapse show" id="genreCollapse"></genre-button-select>
</div>
<div class="section"> <div class="section">
<h2>{{'Discovery.RecentlyRequestedTab' | translate}}</h2> <h2>{{'Discovery.RecentlyRequestedTab' | translate}}</h2>
<div> <div>

@ -0,0 +1,13 @@
<div class="genre-container" *ngIf="movieGenreList$ | async as movies">
<mat-button-toggle-group name="discoverMode" (change)="toggleChanged($event, genre.type)" *ngFor="let genre of movies"
class="discover-filter-buttons-group">
<mat-button-toggle value="{{genre.id}}" class="discover-filter-button">{{genre.name}}</mat-button-toggle>
</mat-button-toggle-group>
</div>
<div class="genre-container" *ngIf="tvGenreList$ | async as tv">
<mat-button-toggle-group name="discoverMode" (change)="toggleChanged($event, genre.type)" *ngFor="let genre of tv"
class="discover-filter-buttons-group">
<mat-button-toggle value="{{genre.id}}" class="discover-filter-button">{{genre.name}}</mat-button-toggle>
</mat-button-toggle-group>
<mat-spinner *ngIf="isLoading" [diameter]="30"></mat-spinner>
</div>

@ -0,0 +1,35 @@
h2{
margin-top:40px;
margin-left:40px;
font-size: 24px;
}
.discover-filter-buttons-group {
border: 1px solid #293a4c;
border-radius: 15px;
color:#fff;
margin-bottom:5px;
margin-right: 5px;
.discover-filter-button{
transform: scale(0.9);
background:inherit;
color:inherit;
padding:0 0px;
border-radius: 30px;
padding-left: 10px;
padding-right: 10px;
border-left:none;
}
}
.button-active{
background:#293a4c;
}
.genre-container {
margin-left: 35px;
}

@ -0,0 +1,49 @@
import { Component, OnInit } from "@angular/core";
import { SearchV2Service } from "../../../services";
import { MatButtonToggleChange } from "@angular/material/button-toggle";
import { RequestType } from "../../../interfaces";
import { AdvancedSearchDialogDataService } from "app/shared/advanced-search-dialog/advanced-search-dialog-data.service";
import { Router } from "@angular/router";
import { map, Observable } from "rxjs";
interface IGenreSelect {
name: string;
id: number;
type: "movie"|"tv";
}
@Component({
selector: "genre-button-select",
templateUrl: "./genre-button-select.component.html",
styleUrls: ["./genre-button-select.component.scss"],
})
export class GenreButtonSelectComponent implements OnInit {
public movieGenreList$: Observable<IGenreSelect[]> = null;
public tvGenreList$: Observable<IGenreSelect[]> = null;
isLoading: boolean = false;
constructor(private searchService: SearchV2Service,
private advancedSearchService: AdvancedSearchDialogDataService,
private router: Router) { }
public ngOnInit(): void {
this.movieGenreList$ = this.searchService.getGenres("movie").pipe(map(x => x.slice(0, 10).map(y => ({ name: y.name, id: y.id, type: "movie" }))));
this.tvGenreList$ = this.searchService.getGenres("tv").pipe(map(x => x.slice(0, 10).map(y => ({ name: y.name, id: y.id, type: "tv" }))));
}
public async toggleChanged(event: MatButtonToggleChange, type: "movie"|"tv") {
this.isLoading = true;
const genres: number[] = [event.value];
const data = await this.searchService.advancedSearch({
watchProviders: [],
genreIds: genres,
keywordIds: [],
type: type,
}, 0, 30);
this.advancedSearchService.setData(data, type == "movie" ? RequestType.movie : RequestType.tvShow);
this.advancedSearchService.setOptions([], genres, [], null, type == "movie" ? RequestType.movie : RequestType.tvShow, 30);
this.router.navigate([`discover/advanced/search`]);
}
}

@ -12,6 +12,7 @@ import { MatDialog } from "@angular/material/dialog";
import { RequestServiceV2 } from "../../services/requestV2.service"; import { RequestServiceV2 } from "../../services/requestV2.service";
import { Routes } from "@angular/router"; import { Routes } from "@angular/router";
import { DetailedCardComponent } from "app/components"; import { DetailedCardComponent } from "app/components";
import { GenreButtonSelectComponent } from "./genre/genre-button-select.component";
export const components: any[] = [ export const components: any[] = [
DiscoverComponent, DiscoverComponent,
@ -22,6 +23,7 @@ export const components: any[] = [
CarouselListComponent, CarouselListComponent,
RecentlyRequestedListComponent, RecentlyRequestedListComponent,
DetailedCardComponent, DetailedCardComponent,
GenreButtonSelectComponent
]; ];
export const providers: any[] = [ export const providers: any[] = [

@ -3,8 +3,13 @@
<p-carousel #carousel [value]="requests" [numVisible]="3" [numScroll]="1" <p-carousel #carousel [value]="requests" [numVisible]="3" [numScroll]="1"
[responsiveOptions]="responsiveOptions" [page]="0"> [responsiveOptions]="responsiveOptions" [page]="0">
<ng-template let-result pTemplate="item"> <ng-template let-result pTemplate="item">
<ombi-detailed-card [request]="result" [isAdmin]="isAdmin" (onClick)="navigate(result)" <ombi-detailed-card
(onApprove)="approve(result)"></ombi-detailed-card> [request]="result"
[isAdmin]="isAdmin"
(onClick)="navigate(result)"
(onApprove)="approve(result)"
(onDeny)="deny(result)">
</ombi-detailed-card>
</ng-template> </ng-template>
</p-carousel> </p-carousel>
</div> </div>

@ -8,6 +8,8 @@ import { Router } from "@angular/router";
import { AuthService } from "app/auth/auth.service"; import { AuthService } from "app/auth/auth.service";
import { NotificationService, RequestService } from "app/services"; import { NotificationService, RequestService } from "app/services";
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from "@ngx-translate/core";
import { DenyDialogComponent } from '../../../media-details/components/shared/deny-dialog/deny-dialog.component';
import { MatDialog } from "@angular/material/dialog";
export enum DiscoverType { export enum DiscoverType {
Upcoming, Upcoming,
@ -42,7 +44,8 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
private router: Router, private router: Router,
private authService: AuthService, private authService: AuthService,
private notificationService: NotificationService, private notificationService: NotificationService,
private translateService: TranslateService) { private translateService: TranslateService,
public dialog: MatDialog) {
Carousel.prototype.onTouchMove = () => {}; Carousel.prototype.onTouchMove = () => {};
this.responsiveOptions = ResponsiveOptions; this.responsiveOptions = ResponsiveOptions;
} }
@ -81,6 +84,20 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
} }
} }
public deny(request: IRecentlyRequested) {
const dialogRef = this.dialog.open(DenyDialogComponent, {
width: '250px',
data: { requestId: request.requestId, is4K: false, requestType: request.type }
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
this.notificationService.success(this.translateService.instant("Requests.SuccessfullyDenied"));
request.denied = true;
}
});
}
private handleApproval(result: IRequestEngineResult, request: IRecentlyRequested) { private handleApproval(result: IRequestEngineResult, request: IRecentlyRequested) {
if (result.result) { if (result.result) {
this.notificationService.success(this.translateService.instant("Requests.SuccessfullyApproved")); this.notificationService.success(this.translateService.instant("Requests.SuccessfullyApproved"));

@ -9,7 +9,7 @@
[infiniteScrollDistance]="3" [infiniteScrollDistance]="3"
[infiniteScrollThrottle]="200" [infiniteScrollThrottle]="200"
(scrolled)="onScroll()"> (scrolled)="onScroll()">
<div id="searchResults" class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding" *ngFor="let result of discoverResults" data-test="searchResultsCount" attr.search-count="{{discoverResults.length}}"> <div id="searchResults" class="col-xl-2 col-lg-3 col-md-3 col-6 col-sm-4 small-padding card-container" *ngFor="let result of discoverResults" data-test="searchResultsCount" attr.search-count="{{discoverResults.length}}">
<discover-card [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card> <discover-card [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
</div> </div>
</div> </div>

@ -16,4 +16,4 @@
.loading-spinner { .loading-spinner {
margin: 10%; margin: 10%;
} }

@ -11,6 +11,7 @@ export interface IRecentlyRequested {
overview: string; overview: string;
releaseDate: Date; releaseDate: Date;
approved: boolean; approved: boolean;
denied: boolean;
mediaId: string; mediaId: string;
type: RequestType; type: RequestType;

@ -23,6 +23,8 @@ export interface IMovieRequests extends IFullBaseRequest {
deniedReason4K: string; deniedReason4K: string;
requestedDate4k: Date; requestedDate4k: Date;
requestedDate: Date; requestedDate: Date;
watchedByRequestedUser: boolean;
playedByUsersCount: number;
// For the UI // For the UI
rootPathOverrideTitle: string; rootPathOverrideTitle: string;
@ -212,4 +214,4 @@ export class BaseRequestOptions {
requestOnBehalf: string | undefined; requestOnBehalf: string | undefined;
rootFolderOverride: number | undefined; rootFolderOverride: number | undefined;
qualityPathOverride: number | undefined; qualityPathOverride: number | undefined;
} }

@ -2,4 +2,5 @@ export interface ITesterResult {
isValid: boolean; isValid: boolean;
version?: string; version?: string;
expectedSubDir?: string; expectedSubDir?: string;
additionalInformation?: string;
} }

@ -36,18 +36,7 @@
</div> </div>
<div *ngIf="(!movie.available && movie.requested) || (!movie.available4K && movie.has4KRequest)"> <div *ngIf="(!movie.available && movie.requested) || (!movie.available4K && movie.has4KRequest)">
<span class="label">{{'MediaDetails.RequestStatus' | translate }}</span> <span class="label">{{'MediaDetails.RequestStatus' | translate }}</span>
<div *ngIf="!movie.available && movie.requested"> <div>{{getStatus(movie) | translate}}</div>
<div *ngIf="movie.denied">{{'Common.RequestDenied' | translate}}</div>
<div *ngIf="movie.approved && !movie.available">{{'Common.ProcessingRequest' | translate}}</div>
<div *ngIf="movie.requested && !movie.approved">{{'Common.PendingApproval' | translate}}</div>
<!--<div *ngIf="!movie.requested && !movie.available && !movie.approved">{{'Common.NotRequested' | translate}}</div>-->
</div>
<div *ngIf="!movie.available4K && movie.has4KRequest">
<div *ngIf="movie.denied4K">{{'Common.RequestDenied4K' | translate}}</div>
<div *ngIf="movie.approved4K && !movie.available4K">{{'Common.ProcessingRequest4K' | translate}}</div>
<div *ngIf="movie.requested4K && !movie.approved4K && !movie.available4K">{{'Common.PendingApproval4K' | translate}}</div>
<!--<div *ngIf="!movie.requested4K && !movie.available4K && !movie.approved4K">{{'Common.NotRequested4K' | translate}}</div>-->
</div>
</div> </div>
<div *ngIf="request"> <div *ngIf="request">
@ -66,12 +55,12 @@
{{RequestSource[request.source]}} {{RequestSource[request.source]}}
</div> </div>
<div *ngIf="request"> <div *ngIf="request && ( request.deniedReason || request.deniedReason4K )">
<span class="label">{{'MediaDetails.DeniedReason' | translate }}</span> <span class="label">{{'MediaDetails.DeniedReason' | translate }}</span>
<div *ngIf="request.denied"> <div *ngIf="request.deniedReason">
<span id="deniedReasonInfo">{{request.deniedReason}}</span> <span id="deniedReasonInfo">{{request.deniedReason}}</span>
</div> </div>
<div *ngIf="request.denied4K"> <div *ngIf="request.deniedReason4K">
<span id="deniedReasonInfo4K">{{request.deniedReason4K}}</span> <span id="deniedReasonInfo4K">{{request.deniedReason4K}}</span>
</div> </div>
</div> </div>

@ -34,4 +34,28 @@ export class MovieInformationPanelComponent implements OnInit {
this.searchService.getMovieStreams(this.movie.id).subscribe(x => this.streams = x); this.searchService.getMovieStreams(this.movie.id).subscribe(x => this.streams = x);
} }
public getStatus(movie: ISearchMovieResultV2) {
if (!movie.available && movie.requested) {
if (movie.denied) {
return "Common.RequestDenied";
}
if (movie.approved) {
return "Common.ProcessingRequest";
} else {
return "Common.PendingApproval";
}
}
if (!movie.available4K && movie.has4KRequest) {
if (movie.denied4K) {
return "Common.RequestDenied4K";
}
if (movie.approved4K) {
return "Common.ProcessingRequest4K";
} else {
return "Common.PendingApproval4K";
}
}
}
} }

@ -63,6 +63,7 @@ $ombi-accent: #258a6d;
::ng-deep .discoverResults{ ::ng-deep .discoverResults{
margin-top:40px; margin-top:40px;
margin-left: 35px;
} }
::ng-deep button:focus{ ::ng-deep button:focus{

@ -52,7 +52,7 @@
<ng-container matColumnDef="requestedUser.requestedBy"> <ng-container matColumnDef="requestedUser.requestedBy">
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th> <th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th>
<td mat-cell id="requestedBy{{element.id}}" *matCellDef="let element"> {{element.requestedUser?.userAlias}} </td> <td mat-cell id="requestedBy{{element.id}}" *matCellDef="let element"> {{element.requestedByAlias ? element.requestedByAlias : element.requestedUser?.userAlias}} </td>
</ng-container> </ng-container>
@ -80,6 +80,23 @@
<td mat-cell id="requestedStatus{{element.id}}" *matCellDef="let element"> {{element.requestStatus | translate}} </td> <td mat-cell id="requestedStatus{{element.id}}" *matCellDef="let element"> {{element.requestStatus | translate}} </td>
</ng-container> </ng-container>
<ng-container matColumnDef="watchedByRequestedUser">
<th
mat-header-cell
*matHeaderCellDef
disableClear
matTooltip="{{ 'Requests.WatchedTooltip' | translate}}">
{{ 'Requests.Watched' | translate}}
</th>
<td mat-cell id="watchedByRequestedUser{{element.id}}" *matCellDef="let element">
<mat-checkbox
[checked]="element.watchedByRequestedUser"
disabled="true"
matTooltip="{{'Requests.WatchedByUsersCount' | translate: {count: element.playedByUsersCount} }}">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> </th> <th mat-header-cell *matHeaderCellDef> </th>
<td mat-cell *matCellDef="let element"> <td mat-cell *matCellDef="let element">

@ -24,10 +24,11 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
public dataSource: MatTableDataSource<IMovieRequests>; public dataSource: MatTableDataSource<IMovieRequests>;
public resultsLength: number; public resultsLength: number;
public isLoadingResults = true; public isLoadingResults = true;
public displayedColumns: string[] = ['title', 'requestedUser.requestedBy', 'status', 'requestStatus','requestedDate', 'actions']; public displayedColumns: string[];
public gridCount: string = "15"; public gridCount: string = "15";
public isAdmin: boolean; public isAdmin: boolean;
public is4kEnabled = false; public is4kEnabled = false;
public isPlayedSyncEnabled = false;
public manageOwnRequests: boolean; public manageOwnRequests: boolean;
public defaultSort: string = "requestedDate"; public defaultSort: string = "requestedDate";
public defaultOrder: string = "desc"; public defaultOrder: string = "desc";
@ -60,15 +61,10 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
public ngOnInit() { public ngOnInit() {
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
this.manageOwnRequests = this.auth.hasRole("ManageOwnRequests") this.manageOwnRequests = this.auth.hasRole("ManageOwnRequests")
if (this.isAdmin) {
this.displayedColumns.unshift('select');
}
this.is4kEnabled = this.featureFacade.is4kEnabled(); this.is4kEnabled = this.featureFacade.is4kEnabled();
if ((this.isAdmin || this.auth.hasRole("Request4KMovie")) this.isPlayedSyncEnabled = this.featureFacade.isPlayedSyncEnabled();
&& this.is4kEnabled) {
this.displayedColumns.splice(4, 0, 'has4kRequest');
}
const defaultCount = this.storageService.get(this.storageKeyGridCount); const defaultCount = this.storageService.get(this.storageKeyGridCount);
const defaultSort = this.storageService.get(this.storageKey); const defaultSort = this.storageService.get(this.storageKey);
@ -88,8 +84,31 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
} }
} }
setDisplayedColumns() {
this.displayedColumns = ['title', 'requestedUser.requestedBy', 'status', 'requestStatus','requestedDate'];
if (this.isAdmin) {
this.displayedColumns.unshift('select');
}
if ((this.isAdmin || this.auth.hasRole("Request4KMovie"))
&& this.is4kEnabled) {
this.displayedColumns.splice(4, 0, 'has4kRequest');
}
if (this.isPlayedSyncEnabled
&& ( this.currentFilter == RequestFilterType.All || this.currentFilter == RequestFilterType.Available ) ) {
this.displayedColumns.push('watchedByRequestedUser');
}
// always put the actions column at the end
this.displayedColumns.push('actions');
}
public async ngAfterViewInit() { public async ngAfterViewInit() {
this.setDisplayedColumns();
this.storageService.save(this.storageKeyGridCount, this.gridCount); this.storageService.save(this.storageKeyGridCount, this.gridCount);
this.storageService.save(this.storageKeyCurrentFilter, (+this.currentFilter).toString()); this.storageService.save(this.storageKeyCurrentFilter, (+this.currentFilter).toString());
@ -263,4 +282,4 @@ export class MoviesGridComponent implements OnInit, AfterViewInit {
} }
return request.requestedDate; return request.requestedDate;
} }
} }

@ -38,7 +38,7 @@
<ng-container matColumnDef="requestedBy"> <ng-container matColumnDef="requestedBy">
<th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th> <th mat-header-cell *matHeaderCellDef> {{'Requests.RequestedBy' | translate}} </th>
<td mat-cell id="requestedBy{{element.id}}" *matCellDef="let element"> {{element.requestedUser.userAlias}} </td> <td mat-cell id="requestedBy{{element.id}}" *matCellDef="let element"> {{element.requestedByAlias ? element.requestedByAlias : element.requestedUser.userAlias}} </td>
</ng-container> </ng-container>
<ng-container matColumnDef="requestedDate"> <ng-container matColumnDef="requestedDate">
@ -73,4 +73,4 @@
</table> </table>
<mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator> <mat-paginator [length]="resultsLength" [pageSize]="gridCount"></mat-paginator>
</div> </div>

@ -1,7 +1,7 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { UntypedFormBuilder, FormControl, UntypedFormGroup, Validators } from "@angular/forms"; import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms";
import { SonarrFacade } from "app/state/sonarr/sonarr.facade"; import { SonarrFacade } from "app/state/sonarr/sonarr.facade";
import { finalize, map } from "rxjs"; import { catchError, finalize, map, of } from "rxjs";
import { ILanguageProfiles, ISonarrProfile, ISonarrRootFolder, ITag } from "../../interfaces"; import { ILanguageProfiles, ISonarrProfile, ISonarrRootFolder, ITag } from "../../interfaces";
@ -95,7 +95,7 @@ export class SonarrComponent implements OnInit {
this.tags = []; this.tags = [];
this.animeTags = []; this.animeTags = [];
if (version.length > 0) { if (version?.length > 0) {
this.sonarrVersion = version[0]; this.sonarrVersion = version[0];
} }
@ -132,11 +132,19 @@ export class SonarrComponent implements OnInit {
public getProfiles(form: UntypedFormGroup) { public getProfiles(form: UntypedFormGroup) {
this.profilesRunning = true; this.profilesRunning = true;
this.sonarrService.getQualityProfiles(form.value) this.sonarrService.getQualityProfiles(form.value)
.pipe(catchError((_) => {
this.notificationService.error("Could not load Quality Profiles");
return of([]);
}))
.subscribe(x => { .subscribe(x => {
this.profilesRunning = false;
if (x.length === 0) {
return;
}
this.qualities = x; this.qualities = x;
this.qualitiesAnime = x; this.qualitiesAnime = x;
this.qualities.unshift({ name: "Please Select", id: -1 }); this.qualities.unshift({ name: "Please Select", id: -1 });
this.profilesRunning = false;
this.notificationService.success("Successfully retrieved the Quality Profiles"); this.notificationService.success("Successfully retrieved the Quality Profiles");
}); });
} }
@ -144,12 +152,19 @@ export class SonarrComponent implements OnInit {
public getRootFolders(form: UntypedFormGroup) { public getRootFolders(form: UntypedFormGroup) {
this.rootFoldersRunning = true; this.rootFoldersRunning = true;
this.sonarrService.getRootFolders(form.value) this.sonarrService.getRootFolders(form.value)
.pipe(catchError((_) => {
this.notificationService.error("Could not load Root Folders");
return of([]);
}))
.subscribe(x => { .subscribe(x => {
this.rootFoldersRunning = false;
if (x.length === 0) {
return;
}
this.rootFolders = x; this.rootFolders = x;
this.rootFolders.unshift({ path: "Please Select", id: -1 }); this.rootFolders.unshift({ path: "Please Select", id: -1 });
this.rootFoldersAnime = x; this.rootFoldersAnime = x;
this.rootFoldersRunning = false;
this.notificationService.success("Successfully retrieved the Root Folders"); this.notificationService.success("Successfully retrieved the Root Folders");
}); });
} }
@ -157,11 +172,18 @@ export class SonarrComponent implements OnInit {
public getLanguageProfiles(form: UntypedFormGroup) { public getLanguageProfiles(form: UntypedFormGroup) {
this.langRunning = true; this.langRunning = true;
this.sonarrService.getV3LanguageProfiles(form.value) this.sonarrService.getV3LanguageProfiles(form.value)
.pipe(catchError((_) => {
this.notificationService.error("Could not load Language Profiles");
return of([]);
}))
.subscribe(x => { .subscribe(x => {
this.langRunning = false;
if (x.length === 0) {
return;
}
this.languageProfiles = x; this.languageProfiles = x;
this.languageProfilesAnime = x; this.languageProfilesAnime = x;
this.langRunning = false;
this.notificationService.success("Successfully retrieved the Language Profiles"); this.notificationService.success("Successfully retrieved the Language Profiles");
}); });
} }
@ -169,11 +191,18 @@ export class SonarrComponent implements OnInit {
public getTags(form: UntypedFormGroup) { public getTags(form: UntypedFormGroup) {
this.tagsRunning = true; this.tagsRunning = true;
this.sonarrService.getTags(form.value).pipe( this.sonarrService.getTags(form.value).pipe(
catchError((_) => {
this.notificationService.error("Could not load Tags");
return of([]);
}),
finalize(() => { finalize(() => {
this.tagsRunning = false; this.tagsRunning = false;
if (this.tags.length === 0) {
return;
}
this.animeTags.unshift({ label: "None", id: -1 }); this.animeTags.unshift({ label: "None", id: -1 });
this.tags.unshift({ label: "None", id: -1 }); this.tags.unshift({ label: "None", id: -1 });
this.notificationService.success("Successfully retrieved the Tags"); this.notificationService.success("Successfully retrieved the Tags")
}), }),
map(result => { map(result => {
this.tags = result; this.tags = result;
@ -191,7 +220,11 @@ export class SonarrComponent implements OnInit {
} else if (result.expectedSubDir) { } else if (result.expectedSubDir) {
this.notificationService.error("Your Sonarr Base URL must be set to " + result.expectedSubDir); this.notificationService.error("Your Sonarr Base URL must be set to " + result.expectedSubDir);
} else { } else {
this.notificationService.error("We could not connect to Sonarr!"); if (result.additionalInformation) {
this.notificationService.error(result.additionalInformation);
} else {
this.notificationService.error("We could not connect to Sonarr!");
}
} }
}); });
} }
@ -204,16 +237,19 @@ export class SonarrComponent implements OnInit {
if (form.controls.defaultQualityProfile) { if (form.controls.defaultQualityProfile) {
if (form.controls.defaultQualityProfile.value === "-1") { if (form.controls.defaultQualityProfile.value === "-1") {
this.notificationService.error("Please check your entered values"); this.notificationService.error("Please check your entered values");
return;
} }
} }
if (form.controls.defaultRootPath) { if (form.controls.defaultRootPath) {
if (form.controls.defaultRootPath.value === "Please Select") { if (form.controls.defaultRootPath.value === "Please Select") {
this.notificationService.error("Please check your entered values"); this.notificationService.error("Please check your entered values");
return;
} }
} }
if (form.controls.languageProfile) { if (form.controls.languageProfile) {
if (form.controls.languageProfile.value === "Please Select") { if (form.controls.languageProfile.value === "Please Select") {
this.notificationService.error("Please check your entered values"); this.notificationService.error("Please check your entered values");
return;
} }
} }
if (form.controls.animeTag.value == -1) { if (form.controls.animeTag.value == -1) {

@ -23,4 +23,6 @@ export class FeaturesFacade {
public is4kEnabled = (): boolean => this.store.selectSnapshot(FeaturesSelectors.is4kEnabled); public is4kEnabled = (): boolean => this.store.selectSnapshot(FeaturesSelectors.is4kEnabled);
} public isPlayedSyncEnabled = (): boolean => this.store.selectSnapshot(FeaturesSelectors.isPlayedSyncEnabled);
}

@ -15,4 +15,9 @@ export class FeaturesSelectors {
return features.filter(x => x.name === "Movie4KRequests")[0].enabled; return features.filter(x => x.name === "Movie4KRequests")[0].enabled;
} }
} @Selector([FeaturesSelectors.features])
public static isPlayedSyncEnabled(features: IFeatureEnablement[]): boolean {
return features.filter(x => x.name === "PlayedSync")[0].enabled;
}
}

@ -18,7 +18,7 @@ export class RadarrSettingsState {
@Action(LoadSettings) @Action(LoadSettings)
public load({ setState }: StateContext<RadarrState>): Observable<RadarrState> { public load({ setState }: StateContext<RadarrState>): Observable<RadarrState> {
const isAdmin = this.authService.isAdmin(); const isAdmin = this.authService.hasRole("Admin");
const calls = isAdmin ? [this.settingsService.getRadarr()] : [of({})]; const calls = isAdmin ? [this.settingsService.getRadarr()] : [of({})];
return combineLatest(calls).pipe( return combineLatest(calls).pipe(

@ -18,7 +18,7 @@ export class SonarrSettingsState {
@Action(LoadSettings) @Action(LoadSettings)
public load({ setState }: StateContext<SonarrState>): Observable<SonarrState> { public load({ setState }: StateContext<SonarrState>): Observable<SonarrState> {
const isAdmin = this.authService.isAdmin(); const isAdmin = this.authService.hasRole("Admin");
const calls = isAdmin ? [this.sonarrService.getVersion(), this.settingsService.getSonarr()] : [of(""), of({})]; const calls = isAdmin ? [this.sonarrService.getVersion(), this.settingsService.getSonarr()] : [of(""), of({})];
return combineLatest(calls).pipe( return combineLatest(calls).pipe(
@ -31,7 +31,7 @@ export class SonarrSettingsState {
} }
@Action(UpdateSettings) @Action(UpdateSettings)
public enable(ctx: StateContext<SonarrState>, { settings }: UpdateSettings): Observable<SonarrState> { public update(ctx: StateContext<SonarrState>, { settings }: UpdateSettings): Observable<SonarrState> {
const state = ctx.getState(); const state = ctx.getState();
return this.settingsService.saveSonarr(settings).pipe( return this.settingsService.saveSonarr(settings).pipe(
tap((_) => ctx.setState({...state, settings})), tap((_) => ctx.setState({...state, settings})),

@ -204,14 +204,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/animations@npm:^15.0.1": "@angular/animations@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/animations@npm:15.0.1" resolution: "@angular/animations@npm:15.2.4"
dependencies: dependencies:
tslib: ^2.3.0 tslib: ^2.3.0
peerDependencies: peerDependencies:
"@angular/core": 15.0.1 "@angular/core": 15.2.4
checksum: 88b75c0c93acd462a793ab82f26463dc0868dcfd2552ede91ef76f72ec9d8723a48a15e026050b741944befb969461c4b1b18494dac2f926a1decee290caee3d checksum: 8fe91a126cac08f1bda95fe7d78bf789e61255211e545dfdd0378eb9f52e0a4a3c4683f1fc925fceed2bfaf9146ce872a7df11f73e92b184fc042565a41609b3
languageName: node languageName: node
linkType: hard linkType: hard
@ -260,98 +260,98 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/common@npm:^15.0.1": "@angular/common@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/common@npm:15.0.1" resolution: "@angular/common@npm:15.2.4"
dependencies: dependencies:
tslib: ^2.3.0 tslib: ^2.3.0
peerDependencies: peerDependencies:
"@angular/core": 15.0.1 "@angular/core": 15.2.4
rxjs: ^6.5.3 || ^7.4.0 rxjs: ^6.5.3 || ^7.4.0
checksum: 8cc0f0df94e92c83fffeb72b11e2b8f4d322cdcaedfc9b94cddaef9f2ef7a1a48a8db8db386c41245f9cffba922bec423f30674cb62d7f53262cdaea5a4198aa checksum: 089436758dc98d7a653bcf0301d090f588ee931d142033c06de5da569db2fd5e924ee84e8baac4fd8bdebdbd1d20b8af1611cc2b2a9bc4f7c7e7c2e9606f1934
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/compiler-cli@npm:^15.0.1": "@angular/compiler-cli@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/compiler-cli@npm:15.0.1" resolution: "@angular/compiler-cli@npm:15.2.4"
dependencies: dependencies:
"@babel/core": ^7.17.2 "@babel/core": 7.19.3
"@jridgewell/sourcemap-codec": ^1.4.14
chokidar: ^3.0.0 chokidar: ^3.0.0
convert-source-map: ^1.5.1 convert-source-map: ^1.5.1
dependency-graph: ^0.11.0 dependency-graph: ^0.11.0
magic-string: ^0.26.0 magic-string: ^0.27.0
reflect-metadata: ^0.1.2 reflect-metadata: ^0.1.2
semver: ^7.0.0 semver: ^7.0.0
sourcemap-codec: ^1.4.8
tslib: ^2.3.0 tslib: ^2.3.0
yargs: ^17.2.1 yargs: ^17.2.1
peerDependencies: peerDependencies:
"@angular/compiler": 15.0.1 "@angular/compiler": 15.2.4
typescript: ">=4.8.2 <4.9" typescript: ">=4.8.2 <5.0"
bin: bin:
ng-xi18n: bundles/src/bin/ng_xi18n.js ng-xi18n: bundles/src/bin/ng_xi18n.js
ngc: bundles/src/bin/ngc.js ngc: bundles/src/bin/ngc.js
ngcc: bundles/ngcc/main-ngcc.js ngcc: bundles/ngcc/main-ngcc.js
checksum: f92b6f579d72b7159ad33e5240e1dd87c109bee3628bb4a8c0dc8dd263dd021547eb6fbcd8ec7a4b7bec162b464cd8a40829c3d20cba649b0ee1685330591f62 checksum: 9ef61841627f336c5c40d0c3e3ef505390b2171f26d36dd242a00bef7c30c985b0edf198ca2d965ad1a2c779caab27204452d88dc865993af0e395023e261a53
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/compiler@npm:^15.0.1": "@angular/compiler@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/compiler@npm:15.0.1" resolution: "@angular/compiler@npm:15.2.4"
dependencies: dependencies:
tslib: ^2.3.0 tslib: ^2.3.0
peerDependencies: peerDependencies:
"@angular/core": 15.0.1 "@angular/core": 15.2.4
peerDependenciesMeta: peerDependenciesMeta:
"@angular/core": "@angular/core":
optional: true optional: true
checksum: 3c0d932805cd7e4164accdf6d8d00899dff286c85ab75f30434277a7ba8f92f2cbbbfc4dc1074dc49ce8c6250adb2e12e4f357a439c8ffe71fae14da3612898d checksum: f47676eb0160be35b5f44e625a3bea43e51fc84c3aa05a9bbebd1fcd357fa73331dc4de32af2dbec7e1f254fb0e65ed9e149e8e7cf61eb81464453c666ff78d1
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/core@npm:^15.0.1": "@angular/core@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/core@npm:15.0.1" resolution: "@angular/core@npm:15.2.4"
dependencies: dependencies:
tslib: ^2.3.0 tslib: ^2.3.0
peerDependencies: peerDependencies:
rxjs: ^6.5.3 || ^7.4.0 rxjs: ^6.5.3 || ^7.4.0
zone.js: ~0.11.4 || ~0.12.0 zone.js: ~0.11.4 || ~0.12.0 || ~0.13.0
checksum: c87fbbd4fefe237e311901d48f84e7790788acde30ee489af3e763f2423446a0010a4bc25d1e659b1170f8c1597ca306ae95e2e6956e93e02a7a708a1acb233b checksum: ab7ad7b6b55fe24ff1f390ea09c04d52301146075488fc4ee4700d77e9f24ad8b3a03f3ca48b6f4306f6b2106e22b541b23bfb5df3fca94606b94fd95b20df7b
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/forms@npm:^15.0.1": "@angular/forms@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/forms@npm:15.0.1" resolution: "@angular/forms@npm:15.2.4"
dependencies: dependencies:
tslib: ^2.3.0 tslib: ^2.3.0
peerDependencies: peerDependencies:
"@angular/common": 15.0.1 "@angular/common": 15.2.4
"@angular/core": 15.0.1 "@angular/core": 15.2.4
"@angular/platform-browser": 15.0.1 "@angular/platform-browser": 15.2.4
rxjs: ^6.5.3 || ^7.4.0 rxjs: ^6.5.3 || ^7.4.0
checksum: fd60fb50a8af40bbe4f725391b4b6c8f454953f980e3b50493b9d3cd637dd5f8d51ae30871661ab369996a7d8df37297eb816252447b87650ea32ca56b35d180 checksum: cc23288506d62f2e1e86e22ab795176b46d63aa0f821515b9549bb10b583975e45b67667d14d1eb52c24a645ad81e7e8693855d5d7f521c27ab4ef0ee871791b
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/localize@npm:^15.0.1": "@angular/localize@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/localize@npm:15.0.1" resolution: "@angular/localize@npm:15.2.4"
dependencies: dependencies:
"@babel/core": 7.19.3 "@babel/core": 7.19.3
glob: 8.0.3 glob: 8.1.0
yargs: ^17.2.1 yargs: ^17.2.1
peerDependencies: peerDependencies:
"@angular/compiler": 15.0.1 "@angular/compiler": 15.2.4
"@angular/compiler-cli": 15.0.1 "@angular/compiler-cli": 15.2.4
bin: bin:
localize-extract: tools/bundles/src/extract/cli.js localize-extract: tools/bundles/src/extract/cli.js
localize-migrate: tools/bundles/src/migrate/cli.js localize-migrate: tools/bundles/src/migrate/cli.js
localize-translate: tools/bundles/src/translate/cli.js localize-translate: tools/bundles/src/translate/cli.js
checksum: b03f50c4540a6c00e646ab67483ae732aeed57ec1d7c5a301031b6ca3304ec2a4846ae8911d8d3c6ca21fb2b59d33688a5c0f55bdff499abd529664206610697 checksum: 999976488ea1aacfd61f738bbb4933dff7e6b144bf066e3ede2ebd1bf267e70c8fffaeb8ef8666fe0c823827f35fc597dfb222014237c87cba92c06e753d0db5
languageName: node languageName: node
linkType: hard linkType: hard
@ -372,65 +372,65 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/platform-browser-dynamic@npm:^15.0.1": "@angular/platform-browser-dynamic@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/platform-browser-dynamic@npm:15.0.1" resolution: "@angular/platform-browser-dynamic@npm:15.2.4"
dependencies: dependencies:
tslib: ^2.3.0 tslib: ^2.3.0
peerDependencies: peerDependencies:
"@angular/common": 15.0.1 "@angular/common": 15.2.4
"@angular/compiler": 15.0.1 "@angular/compiler": 15.2.4
"@angular/core": 15.0.1 "@angular/core": 15.2.4
"@angular/platform-browser": 15.0.1 "@angular/platform-browser": 15.2.4
checksum: 8e8c9645b6d662f79ed769b96cd9c8da3edd536532d6accae53e69054dfd77841b449e76c271e756adae679ee0e4b2bc3f3ee06aec1133db50d067db3e2575df checksum: e48fe83b1d9723ee26a1b4343d7dfd1537027e01f559382b819174f4f25e700ff46c0390fc35dbc4550fa374d37a6d449f7a9504c55d0d7b8e7f90e2785c2048
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/platform-browser@npm:^15.0.1": "@angular/platform-browser@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/platform-browser@npm:15.0.1" resolution: "@angular/platform-browser@npm:15.2.4"
dependencies: dependencies:
tslib: ^2.3.0 tslib: ^2.3.0
peerDependencies: peerDependencies:
"@angular/animations": 15.0.1 "@angular/animations": 15.2.4
"@angular/common": 15.0.1 "@angular/common": 15.2.4
"@angular/core": 15.0.1 "@angular/core": 15.2.4
peerDependenciesMeta: peerDependenciesMeta:
"@angular/animations": "@angular/animations":
optional: true optional: true
checksum: 87ceb94dea9f9d4de3a444bcc307e15f8a7b53218143ae7dc30886cc64eba9e9a9b430e96a70d7f37b6d06aacb9a2e33c17dffac87452576c52b8099e02a3a0e checksum: 67a52b676362614840df1056a579daf9f3763bacec9c2e4fff64109070624b3d2daa3bd2bb8fad776a79e65239f90c9227aa97de3a1b22e8ac397b84038c02e9
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/platform-server@npm:^15.0.1": "@angular/platform-server@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/platform-server@npm:15.0.1" resolution: "@angular/platform-server@npm:15.2.4"
dependencies: dependencies:
domino: ^2.1.2 domino: ^2.1.2
tslib: ^2.3.0 tslib: ^2.3.0
xhr2: ^0.2.0 xhr2: ^0.2.0
peerDependencies: peerDependencies:
"@angular/animations": 15.0.1 "@angular/animations": 15.2.4
"@angular/common": 15.0.1 "@angular/common": 15.2.4
"@angular/compiler": 15.0.1 "@angular/compiler": 15.2.4
"@angular/core": 15.0.1 "@angular/core": 15.2.4
"@angular/platform-browser": 15.0.1 "@angular/platform-browser": 15.2.4
"@angular/platform-browser-dynamic": 15.0.1 "@angular/platform-browser-dynamic": 15.2.4
checksum: bfbb51013f21a6c889c39ba200573b922f58390dd8a86f1803a9f24d2ad984e5ab890e38dbcec612c5a35298aca92287de48d16a5762807972c3fe67a4fc3d1a checksum: e6f3d0b6222356c4af9201e8d8747e4657f1977beb223d39ab7ef60634937d49d89c0826db11ed3d8b46b92bf8dda38dffc1bb001195266ed1618edc8f378d96
languageName: node languageName: node
linkType: hard linkType: hard
"@angular/router@npm:^15.0.1": "@angular/router@npm:^15.0.4":
version: 15.0.1 version: 15.2.4
resolution: "@angular/router@npm:15.0.1" resolution: "@angular/router@npm:15.2.4"
dependencies: dependencies:
tslib: ^2.3.0 tslib: ^2.3.0
peerDependencies: peerDependencies:
"@angular/common": 15.0.1 "@angular/common": 15.2.4
"@angular/core": 15.0.1 "@angular/core": 15.2.4
"@angular/platform-browser": 15.0.1 "@angular/platform-browser": 15.2.4
rxjs: ^6.5.3 || ^7.4.0 rxjs: ^6.5.3 || ^7.4.0
checksum: f9a8b80398cf8024dfac30c2b36d1870c25b4f1f07df8e8039226e975fc7c7b977ca9e5c050dc707e8e2036f1577f799d74796c15dead8f256fa8c0199e6bd1a checksum: ef98f2f22e78379fdaf6dd6f4732c161aa6e596d236e8f7e8e893ab488342f28e3f76127b2b24076697800073dccaea29a6c6c376082cd2354be859f3faa273d
languageName: node languageName: node
linkType: hard linkType: hard
@ -568,7 +568,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@babel/core@npm:^7.17.2, @babel/core@npm:^7.17.5, @babel/core@npm:^7.18.9": "@babel/core@npm:^7.17.5, @babel/core@npm:^7.18.9":
version: 7.18.10 version: 7.18.10
resolution: "@babel/core@npm:7.18.10" resolution: "@babel/core@npm:7.18.10"
dependencies: dependencies:
@ -2391,7 +2391,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": "@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.13, @jridgewell/sourcemap-codec@npm:^1.4.14":
version: 1.4.14 version: 1.4.14
resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" resolution: "@jridgewell/sourcemap-codec@npm:1.4.14"
checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97
@ -2508,28 +2508,28 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@ngxs/devtools-plugin@npm:^3.7.3": "@ngxs/devtools-plugin@npm:3.7.3":
version: 3.7.6 version: 3.7.3
resolution: "@ngxs/devtools-plugin@npm:3.7.6" resolution: "@ngxs/devtools-plugin@npm:3.7.3"
dependencies: dependencies:
tslib: ^1.9.0 tslib: ^1.9.0
peerDependencies: peerDependencies:
"@angular/core": ">=6.1.0 <16.0.0" "@angular/core": ">=6.1.0 <14.0.0"
"@ngxs/store": ^3.7.6 || ^3.7.6-dev "@ngxs/store": ^3.7.3 || ^3.7.3-dev
rxjs: ">=6.5.5" rxjs: ">=6.5.5"
checksum: f20a5ecf0cdfb7af17ff3075819f7eb6c324dce07f4b111d518d66e573037d3f3f842f3ee4f4ec7a1b8603e8293355c9e5e3455ca532749e2bdcc7ac4fed5c00 checksum: c6c4bcbda46be3d73bbece2c6f9f5002df2564850d2bb6ab66f66751e3b54d6aba4363a29e26ca0f205b509468ec5d8a82c25409b3eb377d4efe3026663baa6c
languageName: node languageName: node
linkType: hard linkType: hard
"@ngxs/store@npm:^3.7.3": "@ngxs/store@npm:3.7.3":
version: 3.7.6 version: 3.7.3
resolution: "@ngxs/store@npm:3.7.6" resolution: "@ngxs/store@npm:3.7.3"
dependencies: dependencies:
tslib: ^1.9.0 tslib: ^1.9.0
peerDependencies: peerDependencies:
"@angular/core": ">=6.1.0 <16.0.0" "@angular/core": ">=6.1.0 <14.0.0"
rxjs: ">=6.5.5" rxjs: ">=6.5.5"
checksum: b1582d5157f36dfe1700d9f41ec6a935c794d732d474e998d3a276d6a5005e725071124e5137b83248ced2e1591c10b1fbff97c5db5d5fac21e3fc5bb70cb55b checksum: 756c47c3463a30bb0dc97a62d9a22928fbd4de1f15e05735457b3dfc0dadd6292273c5e8e71bf4b06f2ea9b339e995143284094264fa5136b82317c46ed61ae0
languageName: node languageName: node
linkType: hard linkType: hard
@ -2674,6 +2674,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@scarf/scarf@npm:^1.1.0":
version: 1.1.1
resolution: "@scarf/scarf@npm:1.1.1"
checksum: f3205e7a76fed1ec3328643a5f276f4aab5c67d0e17ca0a414755aacae529f4a3492b44607f9ad066470ecb3ee003e72b3d9581f58dc6c507c18180611ff113e
languageName: node
linkType: hard
"@schematics/angular@npm:15.0.2": "@schematics/angular@npm:15.0.2":
version: 15.0.2 version: 15.0.2
resolution: "@schematics/angular@npm:15.0.2" resolution: "@schematics/angular@npm:15.0.2"
@ -9221,6 +9228,19 @@ cors@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"glob@npm:8.1.0":
version: 8.1.0
resolution: "glob@npm:8.1.0"
dependencies:
fs.realpath: ^1.0.0
inflight: ^1.0.4
inherits: 2
minimatch: ^5.0.1
once: ^1.3.0
checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47
languageName: node
linkType: hard
"glob@npm:^7.0.3, glob@npm:^7.0.6, glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.2.0": "glob@npm:^7.0.3, glob@npm:^7.0.6, glob@npm:^7.1.1, glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.2.0":
version: 7.2.3 version: 7.2.3
resolution: "glob@npm:7.2.3" resolution: "glob@npm:7.2.3"
@ -11425,12 +11445,12 @@ cors@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"magic-string@npm:^0.26.0": "magic-string@npm:^0.27.0":
version: 0.26.2 version: 0.27.0
resolution: "magic-string@npm:0.26.2" resolution: "magic-string@npm:0.27.0"
dependencies: dependencies:
sourcemap-codec: ^1.4.8 "@jridgewell/sourcemap-codec": ^1.4.13
checksum: b4db4e2b370ac8d9ffc6443a2b591b75364bf1fc9121b5a4068d5b89804abff6709d1fa4a0e0c2d54f2e61e0e44db83efdfe219a5ab0ba6d25ee1f2b51fbed55 checksum: 273faaa50baadb7a2df6e442eac34ad611304fc08fe16e24fe2e472fd944bfcb73ffb50d2dc972dc04e92784222002af46868cb9698b1be181c81830fd95a13e
languageName: node languageName: node
linkType: hard linkType: hard
@ -12223,15 +12243,13 @@ cors@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"ngx-infinite-scroll@npm:^14.0.0": "ngx-infinite-scroll@npm:^9.0.0":
version: 14.0.1 version: 9.1.0
resolution: "ngx-infinite-scroll@npm:14.0.1" resolution: "ngx-infinite-scroll@npm:9.1.0"
dependencies: dependencies:
tslib: ^2.3.0 "@scarf/scarf": ^1.1.0
peerDependencies: opencollective-postinstall: ^2.0.2
"@angular/common": ">=14.0.0 <15.0.0" checksum: c1cb914ed49d377daeddce346485d2228826a22af418f9b72f6aa02823f50feb85b248fde04664bb2075c64070b6c8958e4f06a61ec0f0c153a75a4edfff4945
"@angular/core": ">=14.0.0 <15.0.0"
checksum: 7df67fc87a6638d4fa3ade5ea83549ccaf4ecf28e68eb2da81d8f8aec96c2edd69d7452de842f23d558f414cfb424738602aef509310ec25117eb25005056b75
languageName: node languageName: node
linkType: hard linkType: hard
@ -12773,20 +12791,20 @@ cors@latest:
resolution: "ombi@workspace:." resolution: "ombi@workspace:."
dependencies: dependencies:
"@angular-devkit/build-angular": ^15.0.2 "@angular-devkit/build-angular": ^15.0.2
"@angular/animations": ^15.0.1 "@angular/animations": ^15.0.4
"@angular/cdk": ^14.2.7 "@angular/cdk": ^14.2.7
"@angular/cli": ^15.0.2 "@angular/cli": ^15.0.2
"@angular/common": ^15.0.1 "@angular/common": ^15.0.4
"@angular/compiler": ^15.0.1 "@angular/compiler": ^15.0.4
"@angular/compiler-cli": ^15.0.1 "@angular/compiler-cli": ^15.0.4
"@angular/core": ^15.0.1 "@angular/core": ^15.0.4
"@angular/forms": ^15.0.1 "@angular/forms": ^15.0.4
"@angular/localize": ^15.0.1 "@angular/localize": ^15.0.4
"@angular/material": ^14.2.7 "@angular/material": ^14.2.7
"@angular/platform-browser": ^15.0.1 "@angular/platform-browser": ^15.0.4
"@angular/platform-browser-dynamic": ^15.0.1 "@angular/platform-browser-dynamic": ^15.0.4
"@angular/platform-server": ^15.0.1 "@angular/platform-server": ^15.0.4
"@angular/router": ^15.0.1 "@angular/router": ^15.0.4
"@angularclass/hmr": ^3.0.0 "@angularclass/hmr": ^3.0.0
"@auth0/angular-jwt": ^5.0.2 "@auth0/angular-jwt": ^5.0.2
"@babel/core": ^7.18.9 "@babel/core": ^7.18.9
@ -12795,8 +12813,8 @@ cors@latest:
"@microsoft/signalr": ^6.0.7 "@microsoft/signalr": ^6.0.7
"@ngx-translate/core": ^14.0.0 "@ngx-translate/core": ^14.0.0
"@ngx-translate/http-loader": ^7.0.0 "@ngx-translate/http-loader": ^7.0.0
"@ngxs/devtools-plugin": ^3.7.3 "@ngxs/devtools-plugin": 3.7.3
"@ngxs/store": ^3.7.3 "@ngxs/store": 3.7.3
"@storybook/angular": ^6.5.9 "@storybook/angular": ^6.5.9
"@types/jquery": ^3.5.14 "@types/jquery": ^3.5.14
"@yellowspot/ng-truncate": ^2.0.0 "@yellowspot/ng-truncate": ^2.0.0
@ -12810,7 +12828,7 @@ cors@latest:
moment: ^2.29.1 moment: ^2.29.1
ng2-cookies: ^1.0.12 ng2-cookies: ^1.0.12
ngx-clipboard: ^12.1.0 ngx-clipboard: ^12.1.0
ngx-infinite-scroll: ^14.0.0 ngx-infinite-scroll: ^9.0.0
ngx-moment: ^3.0.1 ngx-moment: ^3.0.1
ngx-order-pipe: ^2.2.0 ngx-order-pipe: ^2.2.0
popper.js: ^1.14.3 popper.js: ^1.14.3
@ -12898,7 +12916,7 @@ cors@latest:
languageName: node languageName: node
linkType: hard linkType: hard
"opencollective-postinstall@npm:^2.0.3": "opencollective-postinstall@npm:^2.0.2, opencollective-postinstall@npm:^2.0.3":
version: 2.0.3 version: 2.0.3
resolution: "opencollective-postinstall@npm:2.0.3" resolution: "opencollective-postinstall@npm:2.0.3"
bin: bin:

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -59,6 +60,7 @@ namespace Ombi.Controllers.V1.External
[PowerUser] [PowerUser]
public async Task<IEnumerable<SonarrProfile>> GetProfiles() public async Task<IEnumerable<SonarrProfile>> GetProfiles()
{ {
SonarrSettings.ClearCache();
var settings = await SonarrSettings.GetSettingsAsync(); var settings = await SonarrSettings.GetSettingsAsync();
if (settings.Enabled) if (settings.Enabled)
{ {
@ -75,6 +77,7 @@ namespace Ombi.Controllers.V1.External
[PowerUser] [PowerUser]
public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders() public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders()
{ {
SonarrSettings.ClearCache();
var settings = await SonarrSettings.GetSettingsAsync(); var settings = await SonarrSettings.GetSettingsAsync();
if (settings.Enabled) if (settings.Enabled)
{ {
@ -92,6 +95,7 @@ namespace Ombi.Controllers.V1.External
[PowerUser] [PowerUser]
public async Task<IEnumerable<LanguageProfiles>> GetLanguageProfiles() public async Task<IEnumerable<LanguageProfiles>> GetLanguageProfiles()
{ {
SonarrSettings.ClearCache();
var settings = await SonarrSettings.GetSettingsAsync(); var settings = await SonarrSettings.GetSettingsAsync();
if (settings.Enabled) if (settings.Enabled)
{ {
@ -147,6 +151,7 @@ namespace Ombi.Controllers.V1.External
[PowerUser] [PowerUser]
public async Task<bool> Enabled() public async Task<bool> Enabled()
{ {
SonarrSettings.ClearCache();
var settings = await SonarrSettings.GetSettingsAsync(); var settings = await SonarrSettings.GetSettingsAsync();
return settings.Enabled; return settings.Enabled;
} }
@ -155,13 +160,21 @@ namespace Ombi.Controllers.V1.External
[PowerUser] [PowerUser]
public async Task<string> SonarrVersion() public async Task<string> SonarrVersion()
{ {
SonarrSettings.ClearCache();
var settings = await SonarrSettings.GetSettingsAsync(); var settings = await SonarrSettings.GetSettingsAsync();
if (!settings.Enabled) if (!settings.Enabled)
{ {
return string.Empty; return string.Empty;
} }
var status = await SonarrV3Api.SystemStatus(settings.ApiKey, settings.FullUri); try
return status.version; {
var status = await SonarrV3Api.SystemStatus(settings.ApiKey, settings.FullUri);
return status.version;
}
catch (Exception)
{
return string.Empty;
}
} }
} }
} }

@ -410,6 +410,30 @@ namespace Ombi.Controllers.V1.External
{ {
try try
{ {
if (string.IsNullOrEmpty(settings.ApiKey))
{
return new TesterResultModel
{
IsValid = false,
AdditionalInformation = "NullApiKey"
};
}
if (string.IsNullOrEmpty(settings.Ip))
{
return new TesterResultModel
{
IsValid = false,
AdditionalInformation = "NullIp"
};
}
if (settings.Port <= 0)
{
return new TesterResultModel
{
IsValid = false,
AdditionalInformation = "BadPort"
};
}
var result = await SonarrApi.SystemStatus(settings.ApiKey, settings.FullUri); var result = await SonarrApi.SystemStatus(settings.ApiKey, settings.FullUri);
return new TesterResultModel return new TesterResultModel

@ -652,7 +652,9 @@ namespace Ombi.Controllers.V1
try try
{ {
var isValid = CronExpression.IsValidExpression(expression); var isValid = CronExpression.IsValidExpression(expression);
if (!isValid) CronExpression cron = new CronExpression(expression);
DateTimeOffset? nextFireUTCTime = cron.GetNextValidTimeAfter(DateTime.Now);
if (!isValid || nextFireUTCTime == null)
{ {
return new JobSettingsViewModel return new JobSettingsViewModel
{ {

@ -44,25 +44,33 @@ namespace Ombi.Controllers.V2
} }
[HttpGet("logs/{logFileName}")] [HttpGet("logs/{logFileName}")]
public async Task<IActionResult> ReadLogFile(string logFileName, CancellationToken token) public async Task<IActionResult> ReadLogFile(string logFileName)
{ {
var logFile = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs", logFileName); var logsFolder = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs");
using (var fs = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) var files = Directory.EnumerateFiles(logsFolder);
using (StreamReader reader = new StreamReader(fs)) var matchingFile = files.FirstOrDefault(x => Path.GetFileName(x).Equals(logFileName));
if (matchingFile != null)
{ {
using var fs = new FileStream(matchingFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using StreamReader reader = new(fs);
return Ok(await reader.ReadToEndAsync()); return Ok(await reader.ReadToEndAsync());
} }
return NotFound();
} }
[HttpGet("logs/download/{logFileName}")] [HttpGet("logs/download/{logFileName}")]
public IActionResult Download(string logFileName, CancellationToken token) public IActionResult Download(string logFileName)
{ {
var logFile = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs", logFileName); var logsFolder = Path.Combine(string.IsNullOrEmpty(Ombi.Helpers.StartupSingleton.Instance.StoragePath) ? _hosting.ContentRootPath : Helpers.StartupSingleton.Instance.StoragePath, "Logs");
using (var fs = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) var files = Directory.EnumerateFiles(logsFolder);
using (StreamReader reader = new StreamReader(fs)) var matchingFile = files.FirstOrDefault(x => Path.GetFileName(x).Equals(logFileName));
if (matchingFile != null)
{ {
using var fs = new FileStream(matchingFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using StreamReader reader = new(fs);
return File(reader.BaseStream, "application/octet-stream", logFileName); return File(reader.BaseStream, "application/octet-stream", logFileName);
} }
return NotFound();
} }
} }
} }

@ -159,6 +159,9 @@
"RequestedBy": "Заявено от", "RequestedBy": "Заявено от",
"Status": "Състояние", "Status": "Състояние",
"RequestStatus": "Състояние на заявката", "RequestStatus": "Състояние на заявката",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Отказано:", "Denied": " Отказано:",
"TheatricalRelease": "Кино премиера: {{date}}", "TheatricalRelease": "Кино премиера: {{date}}",
"ReleaseDate": "Премиера: {{date}}", "ReleaseDate": "Премиера: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Successfully Approved", "SuccessfullyApproved": "Successfully Approved",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Request successfully deleted", "SuccessfullyDeleted": "Request successfully deleted",
"NowAvailable": "Request is now available", "NowAvailable": "Request is now available",
"NowUnavailable": "Request is now unavailable", "NowUnavailable": "Request is now unavailable",
@ -403,6 +407,7 @@
"Movies": "Филми", "Movies": "Филми",
"Combined": "Комбинирано", "Combined": "Комбинирано",
"Tv": "Телевизия", "Tv": "Телевизия",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Наличност", "Availability": "Наличност",
"Studio": "Студио", "Studio": "Студио",

@ -159,6 +159,9 @@
"RequestedBy": "Sol·licitat per", "RequestedBy": "Sol·licitat per",
"Status": "Estat", "Status": "Estat",
"RequestStatus": "Estat de la sol·licitud", "RequestStatus": "Estat de la sol·licitud",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Denegat:", "Denied": " Denegat:",
"TheatricalRelease": "En cines: {{date}}", "TheatricalRelease": "En cines: {{date}}",
"ReleaseDate": "Llançament: {{date}}", "ReleaseDate": "Llançament: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Els elements seleccionats s'han denegat correctament" "Denied": "Els elements seleccionats s'han denegat correctament"
}, },
"SuccessfullyApproved": "Aprovat correctament", "SuccessfullyApproved": "Aprovat correctament",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "La sol·licitud s'ha suprimit correctament", "SuccessfullyDeleted": "La sol·licitud s'ha suprimit correctament",
"NowAvailable": "La sol·licitud ja està disponible", "NowAvailable": "La sol·licitud ja està disponible",
"NowUnavailable": "La sol·licitud ja no està disponible", "NowUnavailable": "La sol·licitud ja no està disponible",
@ -403,6 +407,7 @@
"Movies": "Pel·lícules", "Movies": "Pel·lícules",
"Combined": "Combinat", "Combined": "Combinat",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Disponibilitat", "Availability": "Disponibilitat",
"Studio": "Estudi", "Studio": "Estudi",

@ -159,6 +159,9 @@
"RequestedBy": "Požadováno od", "RequestedBy": "Požadováno od",
"Status": "Stav", "Status": "Stav",
"RequestStatus": "Stav požadavku", "RequestStatus": "Stav požadavku",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Zamítnuto:", "Denied": " Zamítnuto:",
"TheatricalRelease": "V kinech od: {{date}}", "TheatricalRelease": "V kinech od: {{date}}",
"ReleaseDate": "Vydáno: {{date}}", "ReleaseDate": "Vydáno: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Úspěšně schváleno", "SuccessfullyApproved": "Úspěšně schváleno",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Požadavek byl úspěšně odstraněn", "SuccessfullyDeleted": "Požadavek byl úspěšně odstraněn",
"NowAvailable": "Požadavek je nyní k dispozici", "NowAvailable": "Požadavek je nyní k dispozici",
"NowUnavailable": "Požadavek je nyní nedostupný", "NowUnavailable": "Požadavek je nyní nedostupný",
@ -403,6 +407,7 @@
"Movies": "Filmy", "Movies": "Filmy",
"Combined": "Kombinované", "Combined": "Kombinované",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Dostupnost", "Availability": "Dostupnost",
"Studio": "Studio", "Studio": "Studio",

@ -159,6 +159,9 @@
"RequestedBy": "Anmodet af", "RequestedBy": "Anmodet af",
"Status": "Status", "Status": "Status",
"RequestStatus": "Status for anmodning", "RequestStatus": "Status for anmodning",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Afvist:", "Denied": " Afvist:",
"TheatricalRelease": "Biografudgivelse: {{date}}", "TheatricalRelease": "Biografudgivelse: {{date}}",
"ReleaseDate": "Udgivet: {{date}}", "ReleaseDate": "Udgivet: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Godkendt", "SuccessfullyApproved": "Godkendt",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Anmodningen blev slettet", "SuccessfullyDeleted": "Anmodningen blev slettet",
"NowAvailable": "Anmodningen er nu tilgængelig", "NowAvailable": "Anmodningen er nu tilgængelig",
"NowUnavailable": "Anmodningen er nu utilgængelig", "NowUnavailable": "Anmodningen er nu utilgængelig",
@ -403,6 +407,7 @@
"Movies": "Film", "Movies": "Film",
"Combined": "Kombineret", "Combined": "Kombineret",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Tilgængelighed", "Availability": "Tilgængelighed",
"Studio": "Studie", "Studio": "Studie",

@ -159,6 +159,9 @@
"RequestedBy": "Angefordert von", "RequestedBy": "Angefordert von",
"Status": "Status", "Status": "Status",
"RequestStatus": "Anfrage Status", "RequestStatus": "Anfrage Status",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Abgelehnt:", "Denied": " Abgelehnt:",
"TheatricalRelease": "Kinostart: {{date}}", "TheatricalRelease": "Kinostart: {{date}}",
"ReleaseDate": "Veröffentlicht: {{date}}", "ReleaseDate": "Veröffentlicht: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Ausgewählte Elemente erfolgreich abgelehnt" "Denied": "Ausgewählte Elemente erfolgreich abgelehnt"
}, },
"SuccessfullyApproved": "Erfolgreich genehmigt", "SuccessfullyApproved": "Erfolgreich genehmigt",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Anfrage erfolgreich gelöscht", "SuccessfullyDeleted": "Anfrage erfolgreich gelöscht",
"NowAvailable": "Anfrage ist jetzt verfügbar", "NowAvailable": "Anfrage ist jetzt verfügbar",
"NowUnavailable": "Anfrage ist jetzt verfügbar", "NowUnavailable": "Anfrage ist jetzt verfügbar",
@ -403,6 +407,7 @@
"Movies": "Filme", "Movies": "Filme",
"Combined": "Kombiniert", "Combined": "Kombiniert",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Verfügbarkeit", "Availability": "Verfügbarkeit",
"Studio": "Studio", "Studio": "Studio",

@ -159,6 +159,9 @@
"RequestedBy": "Requested By", "RequestedBy": "Requested By",
"Status": "Status", "Status": "Status",
"RequestStatus": "Request status", "RequestStatus": "Request status",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Denied:", "Denied": " Denied:",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ReleaseDate": "Released: {{date}}", "ReleaseDate": "Released: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Successfully Approved", "SuccessfullyApproved": "Successfully Approved",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Request successfully deleted", "SuccessfullyDeleted": "Request successfully deleted",
"NowAvailable": "Request is now available", "NowAvailable": "Request is now available",
"NowUnavailable": "Request is now unavailable", "NowUnavailable": "Request is now unavailable",
@ -403,6 +407,7 @@
"Movies": "Movies", "Movies": "Movies",
"Combined": "Combined", "Combined": "Combined",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Availability", "Availability": "Availability",
"Studio": "Studio", "Studio": "Studio",

@ -159,6 +159,9 @@
"RequestedBy": "Solicitado por", "RequestedBy": "Solicitado por",
"Status": "Estado", "Status": "Estado",
"RequestStatus": "Estado de la solicitud", "RequestStatus": "Estado de la solicitud",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Denegado:", "Denied": " Denegado:",
"TheatricalRelease": "En cines: {{date}}", "TheatricalRelease": "En cines: {{date}}",
"ReleaseDate": "Publicado: {{date}}", "ReleaseDate": "Publicado: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Elementos seleccionados rechazados con éxito" "Denied": "Elementos seleccionados rechazados con éxito"
}, },
"SuccessfullyApproved": "Se ha aprobado con éxito", "SuccessfullyApproved": "Se ha aprobado con éxito",
"SuccessfullyDenied": "Denegado Correctamente",
"SuccessfullyDeleted": "Solicitud eliminada con éxito", "SuccessfullyDeleted": "Solicitud eliminada con éxito",
"NowAvailable": "La solicitud está disponible", "NowAvailable": "La solicitud está disponible",
"NowUnavailable": "La solicitud no está disponible", "NowUnavailable": "La solicitud no está disponible",
@ -403,6 +407,7 @@
"Movies": "Películas", "Movies": "Películas",
"Combined": "Combinado", "Combined": "Combinado",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Disponibilidad", "Availability": "Disponibilidad",
"Studio": "Estudio", "Studio": "Estudio",

@ -159,6 +159,9 @@
"RequestedBy": "Demandé par", "RequestedBy": "Demandé par",
"Status": "Statut", "Status": "Statut",
"RequestStatus": "Statut de la demande", "RequestStatus": "Statut de la demande",
"Watched": "Vu",
"WatchedTooltip": "L'utilisateur qui a fait la demande l'a regardé",
"WatchedByUsersCount": "{{count}} utilisateurs l'ont regardé.",
"Denied": " Refusé :", "Denied": " Refusé :",
"TheatricalRelease": "Sortie en salle : {{date}}", "TheatricalRelease": "Sortie en salle : {{date}}",
"ReleaseDate": "Sortie : {{date}}", "ReleaseDate": "Sortie : {{date}}",
@ -221,6 +224,7 @@
"Denied": "Les éléments sélectionnés ont été refusés" "Denied": "Les éléments sélectionnés ont été refusés"
}, },
"SuccessfullyApproved": "Approuvée avec succès", "SuccessfullyApproved": "Approuvée avec succès",
"SuccessfullyDenied": "Refusé avec succès",
"SuccessfullyDeleted": "Demande supprimée avec succès", "SuccessfullyDeleted": "Demande supprimée avec succès",
"NowAvailable": "La demande est maintenant disponible", "NowAvailable": "La demande est maintenant disponible",
"NowUnavailable": "La demande est maintenant indisponible", "NowUnavailable": "La demande est maintenant indisponible",
@ -403,6 +407,7 @@
"Movies": "Films", "Movies": "Films",
"Combined": "Tous", "Combined": "Tous",
"Tv": "Séries", "Tv": "Séries",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Disponibilité", "Availability": "Disponibilité",
"Studio": "Studio", "Studio": "Studio",

@ -14,7 +14,7 @@
"Common": { "Common": {
"ContinueButton": "Tovább", "ContinueButton": "Tovább",
"Available": "Elérhető", "Available": "Elérhető",
"Available4K": "Available 4K", "Available4K": "Elérhető 4K",
"Approved": "Jóváhagyva", "Approved": "Jóváhagyva",
"Approve4K": "Approve 4K", "Approve4K": "Approve 4K",
"Pending": "Függőben", "Pending": "Függőben",
@ -24,7 +24,7 @@
"ProcessingRequest": "Kérés feldolgozása", "ProcessingRequest": "Kérés feldolgozása",
"ProcessingRequest4K": "Processing Request 4K", "ProcessingRequest4K": "Processing Request 4K",
"PendingApproval": "Jóváhagyásra vár", "PendingApproval": "Jóváhagyásra vár",
"PendingApproval4K": "Pending Approval 4K", "PendingApproval4K": "Függőben 4K",
"RequestDenied": "Kérés megtagadva", "RequestDenied": "Kérés megtagadva",
"RequestDenied4K": "Request Denied 4K", "RequestDenied4K": "Request Denied 4K",
"NotRequested": "Nincs kérve", "NotRequested": "Nincs kérve",
@ -159,6 +159,9 @@
"RequestedBy": "Kérte", "RequestedBy": "Kérte",
"Status": "Állapot", "Status": "Állapot",
"RequestStatus": "Kérés állapota", "RequestStatus": "Kérés állapota",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Megtagadta:", "Denied": " Megtagadta:",
"TheatricalRelease": "Mozis kiadás: {{date}}", "TheatricalRelease": "Mozis kiadás: {{date}}",
"ReleaseDate": "Kiadva: {{date}}", "ReleaseDate": "Kiadva: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Sikeresen jóváhagyva", "SuccessfullyApproved": "Sikeresen jóváhagyva",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Kérés sikeresen törölve", "SuccessfullyDeleted": "Kérés sikeresen törölve",
"NowAvailable": "Kérés elérhető", "NowAvailable": "Kérés elérhető",
"NowUnavailable": "Kérés nem elérhető", "NowUnavailable": "Kérés nem elérhető",
@ -312,7 +316,7 @@
}, },
"MediaDetails": { "MediaDetails": {
"Denied": "Megtagadva", "Denied": "Megtagadva",
"Denied4K": "Denied 4K", "Denied4K": "Megtagadva 4K",
"Trailers": "Előzetesek", "Trailers": "Előzetesek",
"RecommendationsTitle": "Ajánlások", "RecommendationsTitle": "Ajánlások",
"SimilarTitle": "Hasonló", "SimilarTitle": "Hasonló",
@ -365,7 +369,7 @@
"CastTitle": "Szereplők" "CastTitle": "Szereplők"
}, },
"Crews": { "Crews": {
"CrewTitle": "Crew" "CrewTitle": "Stáb"
}, },
"EpisodeSelector": { "EpisodeSelector": {
"AllSeasonsTooltip": "Ezzel kérni fogja a sorozat összes évadát", "AllSeasonsTooltip": "Ezzel kérni fogja a sorozat összes évadát",
@ -392,7 +396,7 @@
"StartDate": "Kezdés dátuma:", "StartDate": "Kezdés dátuma:",
"EndDate": "Befejezés dátuma:" "EndDate": "Befejezés dátuma:"
}, },
"RequestSource": "Source:" "RequestSource": "Forrás:"
}, },
"Discovery": { "Discovery": {
"PopularTab": "Népszerű", "PopularTab": "Népszerű",
@ -403,6 +407,7 @@
"Movies": "Filmek", "Movies": "Filmek",
"Combined": "Kombinált", "Combined": "Kombinált",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Elérhetőség", "Availability": "Elérhetőség",
"Studio": "Stúdió", "Studio": "Stúdió",

@ -159,6 +159,9 @@
"RequestedBy": "Richiesta da", "RequestedBy": "Richiesta da",
"Status": "Stato", "Status": "Stato",
"RequestStatus": "Stato della richiesta", "RequestStatus": "Stato della richiesta",
"Watched": "Visti",
"WatchedTooltip": "L'utente che ha fatto la richiesta l'ha visto",
"WatchedByUsersCount": "Visto da {{count}} utenti.",
"Denied": "Negata:", "Denied": "Negata:",
"TheatricalRelease": "Uscita nei cinema: {{date}}", "TheatricalRelease": "Uscita nei cinema: {{date}}",
"ReleaseDate": "Rilasciato il {{date}}", "ReleaseDate": "Rilasciato il {{date}}",
@ -221,6 +224,7 @@
"Denied": "Elementi selezionati rifiutati con successo" "Denied": "Elementi selezionati rifiutati con successo"
}, },
"SuccessfullyApproved": "Approvata", "SuccessfullyApproved": "Approvata",
"SuccessfullyDenied": "Negata Con Successo",
"SuccessfullyDeleted": "Richiesta eliminata correttamente", "SuccessfullyDeleted": "Richiesta eliminata correttamente",
"NowAvailable": "Richiesta ora disponibile", "NowAvailable": "Richiesta ora disponibile",
"NowUnavailable": "Richiesta ora non disponibile", "NowUnavailable": "Richiesta ora non disponibile",
@ -365,7 +369,7 @@
"CastTitle": "Trasmetti" "CastTitle": "Trasmetti"
}, },
"Crews": { "Crews": {
"CrewTitle": "Crew" "CrewTitle": "Gruppo"
}, },
"EpisodeSelector": { "EpisodeSelector": {
"AllSeasonsTooltip": "Richiederà tutte le stagioni per questa serie", "AllSeasonsTooltip": "Richiederà tutte le stagioni per questa serie",
@ -403,6 +407,7 @@
"Movies": "Film", "Movies": "Film",
"Combined": "Combinato", "Combined": "Combinato",
"Tv": "TV", "Tv": "TV",
"Genres": "Generi",
"CardDetails": { "CardDetails": {
"Availability": "Disponibilità", "Availability": "Disponibilità",
"Studio": "Studio", "Studio": "Studio",

@ -30,10 +30,10 @@
"NotRequested": "Niet verzocht", "NotRequested": "Niet verzocht",
"NotRequested4K": "Niet aangevraagd 4K", "NotRequested4K": "Niet aangevraagd 4K",
"Requested": "Aangevraagd", "Requested": "Aangevraagd",
"Requested4K": "Requested 4K", "Requested4K": "Aangevraagd 4K",
"Search": "Zoeken", "Search": "Zoeken",
"Request": "Aanvragen", "Request": "Aanvragen",
"Request4K": "Request 4K", "Request4K": "Aanvragen 4K",
"Denied": "Afgewezen", "Denied": "Afgewezen",
"Approve": "Accepteer", "Approve": "Accepteer",
"PartlyAvailable": "Deels Beschikbaar", "PartlyAvailable": "Deels Beschikbaar",
@ -43,7 +43,7 @@
}, },
"Cancel": "Annuleren", "Cancel": "Annuleren",
"Submit": "Verzenden", "Submit": "Verzenden",
"Update": "Update", "Update": "Bijwerken",
"tvShow": "Tv programma", "tvShow": "Tv programma",
"movie": "Film", "movie": "Film",
"album": "Album" "album": "Album"
@ -64,14 +64,14 @@
"CheckPageForUpdates": "Controleer deze pagina voor updates." "CheckPageForUpdates": "Controleer deze pagina voor updates."
}, },
"ErrorPages": { "ErrorPages": {
"NotFound": "Page not found", "NotFound": "Pagina niet gevonden",
"SomethingWentWrong": "Something went wrong!" "SomethingWentWrong": "Sorry, er ging iets mis!"
}, },
"NavigationBar": { "NavigationBar": {
"Discover": "Ontdekken", "Discover": "Ontdekken",
"Search": "Zoeken", "Search": "Zoeken",
"Requests": "Verzoeken", "Requests": "Verzoeken",
"UserManagement": "Gebruikersmanagement", "UserManagement": "Gebruikers",
"Issues": "Problemen", "Issues": "Problemen",
"Vote": "Stem", "Vote": "Stem",
"Donate": "Doneer!", "Donate": "Doneer!",
@ -87,7 +87,7 @@
"ChangeTheme": "Thema wijzigen", "ChangeTheme": "Thema wijzigen",
"Calendar": "Agenda", "Calendar": "Agenda",
"UserPreferences": "Instellingen", "UserPreferences": "Instellingen",
"FeatureSuggestion": "Feature Suggestion", "FeatureSuggestion": "Ideeën delen",
"FeatureSuggestionTooltip": "Heb je een geweldig nieuw idee? Stel het hier voor!", "FeatureSuggestionTooltip": "Heb je een geweldig nieuw idee? Stel het hier voor!",
"Filter": { "Filter": {
"Movies": "Films", "Movies": "Films",
@ -105,8 +105,8 @@
"MoviesTab": "Films", "MoviesTab": "Films",
"TvTab": "TV Series", "TvTab": "TV Series",
"MusicTab": "Muziek", "MusicTab": "Muziek",
"AdvancedSearch": "You can fill in any of the below to discover new media. All of the results are sorted by popularity", "AdvancedSearch": "Je kunt hieronder een van de onderstaande invullen om nieuwe media te ontdekken. Alle resultaten zijn gesorteerd op populariteit",
"AdvancedSearchHeader": "Advanced Search", "AdvancedSearchHeader": "Geavanceerd Zoeken",
"Suggestions": "Suggesties", "Suggestions": "Suggesties",
"NoResults": "Sorry, er zijn geen resultaten gevonden!", "NoResults": "Sorry, er zijn geen resultaten gevonden!",
"DigitalDate": "Digitale Uitgave: {{date}}", "DigitalDate": "Digitale Uitgave: {{date}}",
@ -141,12 +141,12 @@
"Season": "Seizoen {{seasonNumber}}", "Season": "Seizoen {{seasonNumber}}",
"SelectAllInSeason": "Selecteer Alles in het Seizoen {{seasonNumber}}" "SelectAllInSeason": "Selecteer Alles in het Seizoen {{seasonNumber}}"
}, },
"AdvancedSearchInstructions": "Please choose what type of media you are searching for:", "AdvancedSearchInstructions": "Kies naar welk type media je zoekt:",
"YearOfRelease": "Year of Release", "YearOfRelease": "Jaar van de uitgave",
"SearchGenre": "Search Genre", "SearchGenre": "Zoeken op genre",
"SearchKeyword": "Search Keyword", "SearchKeyword": "Trefwoorden zoeken",
"SearchProvider": "Search Provider", "SearchProvider": "Zoekmachine",
"KeywordSearchingDisclaimer": "Please note that Keyword Searching is very hit and miss due to the inconsistent data in TheMovieDb" "KeywordSearchingDisclaimer": "Houd er rekening mee dat het zoeken naar trefwoord erg hit en miss is door de inconsistente gegevens in TheMovieDb"
}, },
"Requests": { "Requests": {
"Title": "Verzoeken", "Title": "Verzoeken",
@ -159,6 +159,9 @@
"RequestedBy": "Verzocht Door", "RequestedBy": "Verzocht Door",
"Status": "Status", "Status": "Status",
"RequestStatus": "Aanvraagstatus", "RequestStatus": "Aanvraagstatus",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Geweigerd:", "Denied": " Geweigerd:",
"TheatricalRelease": "Cinema Uitgave: {{date}}", "TheatricalRelease": "Cinema Uitgave: {{date}}",
"ReleaseDate": "Uitgekomen: {{date}}", "ReleaseDate": "Uitgekomen: {{date}}",
@ -170,15 +173,15 @@
"ChangeRootFolder": "Hoofdmap wijzigen", "ChangeRootFolder": "Hoofdmap wijzigen",
"ChangeQualityProfile": "Kwaliteitsprofiel wijzigen", "ChangeQualityProfile": "Kwaliteitsprofiel wijzigen",
"MarkUnavailable": "Markeren als onbeschikbaar", "MarkUnavailable": "Markeren als onbeschikbaar",
"MarkUnavailable4K": "Mark Unavailable 4K", "MarkUnavailable4K": "Markeer niet beschikbaar 4K",
"MarkAvailable": "Markeren als beschikbaar", "MarkAvailable": "Markeren als beschikbaar",
"MarkAvailable4K": "Mark Available 4K", "MarkAvailable4K": "Markeer beschikbaar 4K",
"Remove": "Verwijderen", "Remove": "Verwijderen",
"Deny": "Weigeren", "Deny": "Weigeren",
"Deny4K": "Deny 4K", "Deny4K": "Weiger 4K",
"Has4KRequest": "Has 4K Request", "Has4KRequest": "Heeft 4K verzoek",
"DenyReason": "Reden van afwijzing", "DenyReason": "Reden van afwijzing",
"DeniedReason": "Denied Reason", "DeniedReason": "Reden van afwijzing",
"Season": "Seizoen", "Season": "Seizoen",
"GridTitle": "Titel", "GridTitle": "Titel",
"AirDate": "Uitzenddatum", "AirDate": "Uitzenddatum",
@ -212,52 +215,53 @@
"RequestPanel": { "RequestPanel": {
"Delete": "Verwijder Verzoek", "Delete": "Verwijder Verzoek",
"Approve": "Verzoek Goedkeuren", "Approve": "Verzoek Goedkeuren",
"Deny": "Deny Request", "Deny": "Aanvraag weigeren",
"Approve4K": "Approve 4K Request", "Approve4K": "4K Verzoek Goedkeuren",
"Deny4K": "Deny 4K Request", "Deny4K": "4K Verzoek Weigeren",
"ChangeAvailability": "Markeer beschikbaar", "ChangeAvailability": "Markeer beschikbaar",
"Deleted": "Successfully deleted selected items", "Deleted": "Geselecteerde items succesvol verwijderd",
"Approved": "Successfully approved selected items", "Approved": "Geselecteerde items succesvol goedgekeurd",
"Denied": "Successfully denied selected items" "Denied": "Geselecteerde items succesvol afgekeurd"
}, },
"SuccessfullyApproved": "Successfully Approved", "SuccessfullyApproved": "Succesvol goedgekeurd",
"SuccessfullyDeleted": "Request successfully deleted", "SuccessfullyDenied": "Successfully Denied",
"NowAvailable": "Request is now available", "SuccessfullyDeleted": "Verzoek succesvol verwijderd",
"NowUnavailable": "Request is now unavailable", "NowAvailable": "Verzoek is nu beschikbaar",
"SuccessfullyReprocessed": "Successfully Re-processed the request", "NowUnavailable": "Verzoek is nu niet beschikbaar",
"DeniedRequest": "Denied Request", "SuccessfullyReprocessed": "De aanvraag is met succes opnieuw verwerkt",
"RequestCollection": "Request Collection", "DeniedRequest": "Geweigerde verzoek(en)",
"CollectionSuccesfullyAdded": "The collection {{name}} has been successfully added!", "RequestCollection": "Collectie aanvragen",
"NeedToSelectEpisodes": "You need to select some episodes!", "CollectionSuccesfullyAdded": "De collectie {{name}} is succesvol toegevoegd!",
"NeedToSelectEpisodes": "Je moet enkele afleveringen selecteren!",
"RequestAddedSuccessfully": "Aanvraag voor {{title}} is succesvol toegevoegd", "RequestAddedSuccessfully": "Aanvraag voor {{title}} is succesvol toegevoegd",
"ErrorCodes": { "ErrorCodes": {
"AlreadyRequested": "This has already been requested", "AlreadyRequested": "Dit is reeds aangevraagd",
"EpisodesAlreadyRequested": "We already have episodes requested from this series", "EpisodesAlreadyRequested": "We hebben al aanvragen voor deze serie",
"NoPermissionsOnBehalf": "You do not have the correct permissions to request on behalf of users!", "NoPermissionsOnBehalf": "Je hebt niet de juiste rechten om namens gebruikers aan te vragen!",
"NoPermissions": "You do not have the correct permissions!", "NoPermissions": "Je hebt de juiste rechten niet!",
"RequestDoesNotExist": "Request does not exist", "RequestDoesNotExist": "Verzoek bestaat niet",
"ChildRequestDoesNotExist": "Child Request does not exist", "ChildRequestDoesNotExist": "Child Request does not exist",
"NoPermissionsRequestMovie": "You do not have permissions to Request a Movie", "NoPermissionsRequestMovie": "Je bent niet gemachtigd om een film aan te vragen",
"NoPermissionsRequestTV": "You do not have permissions to Request a TV Show", "NoPermissionsRequestTV": "Je bent niet gemachtigd om een serie aan te vragen",
"NoPermissionsRequestAlbum": "You do not have permissions to Request an Album", "NoPermissionsRequestAlbum": "Je bent niet gemachtigd om een album aan te vragen",
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "Je hebt het maximale aantal film aanvragen bereikt!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "Je hebt het maximale aantal afleveringen aanvragen bereikt!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "Je hebt het maximale aantal albums aanvragen bereikt!"
}, },
"Notify": "Notify", "Notify": "Notificatie",
"RemoveNotification": "Remove Notifications", "RemoveNotification": "Notificaties verwijderen",
"SuccessfulNotify": "You will now be notified for title {{title}}", "SuccessfulNotify": "Je krijgt nu een melding voor titel {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}", "SuccessfulUnNotify": "Je wordt niet langer op de hoogte gehouden voor titel {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}" "CouldntNotify": "Kan titel {{title}} niet melden"
}, },
"Issues": { "Issues": {
"Title": "Problemen", "Title": "Problemen",
"IssuesForTitle": "Issues for {{title}}", "IssuesForTitle": "Problemen voor {{title}}",
"PendingTitle": "Onopgeloste Problemen", "PendingTitle": "Onopgeloste Problemen",
"InProgressTitle": "Problemen in Behandeling", "InProgressTitle": "Problemen in Behandeling",
"ResolvedTitle": "Opgeloste Problemen", "ResolvedTitle": "Opgeloste Problemen",
"ColumnTitle": "Titel", "ColumnTitle": "Titel",
"Count": "Count", "Count": "Aantal",
"Category": "Categorie", "Category": "Categorie",
"Status": "Status", "Status": "Status",
"Details": "Details", "Details": "Details",
@ -284,10 +288,10 @@
"MarkedAsInProgress": "Dit probleem is nu gemarkeerd als in behandeling!", "MarkedAsInProgress": "Dit probleem is nu gemarkeerd als in behandeling!",
"Delete": "Verwijder probleem", "Delete": "Verwijder probleem",
"DeletedIssue": "Probleem is verwijderd", "DeletedIssue": "Probleem is verwijderd",
"Chat": "Chat", "Chat": "Chatten",
"EnterYourMessage": "Enter Your Message", "EnterYourMessage": "Voer je bericht in",
"Requested": "Aangevraagd", "Requested": "Aangevraagd",
"UserOnDate": "{{user}} on {{date}}" "UserOnDate": "{{user}} op {{date}}"
}, },
"Filter": { "Filter": {
"ClearFilter": "Verwijder Filter", "ClearFilter": "Verwijder Filter",
@ -295,8 +299,8 @@
"FilterHeaderRequestStatus": "Status", "FilterHeaderRequestStatus": "Status",
"Approved": "Goedgekeurd", "Approved": "Goedgekeurd",
"PendingApproval": "In afwachting van goedkeuring", "PendingApproval": "In afwachting van goedkeuring",
"WatchProviders": "Watch Providers", "WatchProviders": "Bekijk aanbieders",
"Keywords": "Keywords" "Keywords": "Trefwoorden"
}, },
"UserManagment": { "UserManagment": {
"TvRemaining": "Tv: {{remaining}}/{{total}} Resterend", "TvRemaining": "Tv: {{remaining}}/{{total}} Resterend",
@ -312,7 +316,7 @@
}, },
"MediaDetails": { "MediaDetails": {
"Denied": "Afgewezen", "Denied": "Afgewezen",
"Denied4K": "Denied 4K", "Denied4K": "Geweigerd 4K",
"Trailers": "Trailers", "Trailers": "Trailers",
"RecommendationsTitle": "Aanbevelingen", "RecommendationsTitle": "Aanbevelingen",
"SimilarTitle": "Vergelijkbaar", "SimilarTitle": "Vergelijkbaar",
@ -324,28 +328,28 @@
"ViewCollection": "Bekijk collectie", "ViewCollection": "Bekijk collectie",
"NotEnoughInfo": "Helaas is er nog niet genoeg informatie over deze tv-serie!", "NotEnoughInfo": "Helaas is er nog niet genoeg informatie over deze tv-serie!",
"AdvancedOptions": "Geavanceerde opties", "AdvancedOptions": "Geavanceerde opties",
"AutoApproveOptions": "You can configure the request here, once requested it will be sent to your DVR application and will be auto approved! Please note, this is optional, just press Request to skip!", "AutoApproveOptions": "U kunt het verzoek hier configureren, eenmaal gevraagd zal het naar uw DVR-applicatie worden gestuurd en automatisch worden goedgekeurd! Let op, dit is optioneel, druk op Verzoek om over te slaan!",
"AutoApproveOptionsTv": "You can configure the request here, once requested it will be sent to your DVR application and will be auto approved! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it! Please note, this is optional, just press Request to skip!", "AutoApproveOptionsTv": "U kunt het verzoek hier configureren, eenmaal gevraagd zal het naar uw DVR-applicatie worden gestuurd en automatisch worden goedgekeurd! Als het verzoek al in Sonarr is, zullen we de hoofdmap of het kwaliteitsprofiel niet wijzigen als je het instelt! Let op, dit is optioneel, druk op Verzoek om over te slaan!",
"AutoApproveOptionsTvShort": "You can configure the request here, once requested it will be sent to your DVR application! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it! Please note, this is optional, just press Request to skip!", "AutoApproveOptionsTvShort": "U kunt het verzoek hier configureren, eenmaal gevraagd zal het naar uw DVR-applicatie worden gestuurd en automatisch worden goedgekeurd! Als het verzoek al in Sonarr is, zullen we de hoofdmap of het kwaliteitsprofiel niet wijzigen als je het instelt! Let op, dit is optioneel, druk op Verzoek om over te slaan!",
"QualityProfilesSelect": "Selecteer een kwaliteitsprofiel", "QualityProfilesSelect": "Selecteer een kwaliteitsprofiel",
"RootFolderSelect": "Selecteer een hoofdmap", "RootFolderSelect": "Selecteer een hoofdmap",
"LanguageProfileSelect": "Select A Language Profile", "LanguageProfileSelect": "Selecteer een taalprofiel",
"Status": "Status:", "Status": "Status:",
"StatusValues": { "StatusValues": {
"Rumored": "Rumored", "Rumored": "Geruchten",
"Planned": "Planned", "Planned": "Gepland",
"In Production": "In Production", "In Production": "In productie",
"Post Production": "Post Production", "Post Production": "Postproductie",
"Released": "Released", "Released": "Uitgebracht",
"Running": "Running", "Running": "Lopende series",
"Returning Series": "Returning Series", "Returning Series": "Terugkerende series",
"Ended": "Ended", "Ended": "Beëindigd",
"Canceled": "Canceled" "Canceled": "Geannuleerd"
}, },
"Seasons": "Seasons:", "Seasons": "Seizoenen:",
"Episodes": "Afleveringen:", "Episodes": "Afleveringen:",
"Availability": "Beschikbaarheid:", "Availability": "Beschikbaarheid:",
"RequestStatus": "Request Status:", "RequestStatus": "Aanvraag status:",
"Quality": "Kwaliteit:", "Quality": "Kwaliteit:",
"RootFolderOverride": "Hoofdmap overschrijven:", "RootFolderOverride": "Hoofdmap overschrijven:",
"QualityOverride": "Kwaliteit overschrijven:", "QualityOverride": "Kwaliteit overschrijven:",
@ -371,7 +375,7 @@
"AllSeasonsTooltip": "Dit verzoekt ieder seizoen van deze serie", "AllSeasonsTooltip": "Dit verzoekt ieder seizoen van deze serie",
"FirstSeasonTooltip": "Dit verzoekt alleen het eerste seizoen van deze serie", "FirstSeasonTooltip": "Dit verzoekt alleen het eerste seizoen van deze serie",
"LatestSeasonTooltip": "Dit verzoekt alleen het laatste seizoen van deze show", "LatestSeasonTooltip": "Dit verzoekt alleen het laatste seizoen van deze show",
"NoEpisodes": "There unfortunately is no episode data for this show yet!", "NoEpisodes": "Er zijn helaas nog geen aflevering gegevens voor deze TV-serie!",
"SeasonNumber": "Seizoen {{number}}" "SeasonNumber": "Seizoen {{number}}"
}, },
"SonarrConfiguration": "Sonarr configuratie", "SonarrConfiguration": "Sonarr configuratie",
@ -380,29 +384,30 @@
"PleaseSelectUser": "Selecteer een gebruiker", "PleaseSelectUser": "Selecteer een gebruiker",
"StreamingOn": "Streamt op:", "StreamingOn": "Streamt op:",
"RequestedBy": "Verzocht Door:", "RequestedBy": "Verzocht Door:",
"OnDate": "On:", "OnDate": "Op:",
"RequestedByOn": "Requested By {{user}} on {{date}}", "RequestedByOn": "Aangevraagd door {{user}} op {{date}}",
"RequestDate": "Aanvraag Datum:", "RequestDate": "Aanvraag Datum:",
"DeniedReason": "Denied Reason:", "DeniedReason": "Reden van afwijzing:",
"ReProcessRequest": "Re-Process Request", "ReProcessRequest": "Aanvraag opnieuw verwerken",
"ReProcessRequest4K": "Re-Process 4K Request", "ReProcessRequest4K": "Aanvraag opnieuw verwerken",
"Music": { "Music": {
"Type": "Type:", "Type": "Type:",
"Country": "Country:", "Country": "Land:",
"StartDate": "Start Date:", "StartDate": "Startdatum:",
"EndDate": "EndDate:" "EndDate": "Einddatum:"
}, },
"RequestSource": "Source:" "RequestSource": "Bron:"
}, },
"Discovery": { "Discovery": {
"PopularTab": "Populair", "PopularTab": "Populair",
"TrendingTab": "Populair", "TrendingTab": "Populair",
"UpcomingTab": "Aankomend", "UpcomingTab": "Aankomend",
"SeasonalTab": "Seasonal", "SeasonalTab": "Seizoen",
"RecentlyRequestedTab": "Recently Requested", "RecentlyRequestedTab": "Recente verzoeken",
"Movies": "Films", "Movies": "Films",
"Combined": "Gecombineerd", "Combined": "Gecombineerd",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Beschikbaarheid", "Availability": "Beschikbaarheid",
"Studio": "Studio", "Studio": "Studio",
@ -423,37 +428,37 @@
"DarkMode": "Donkere Modus", "DarkMode": "Donkere Modus",
"Updated": "Succesvol bijgewerkt", "Updated": "Succesvol bijgewerkt",
"StreamingCountry": "Streaming Land", "StreamingCountry": "Streaming Land",
"StreamingCountryDescription": "This is the country code that we will display streaming information for. If you are in the US please select US and you will get US related streaming information.", "StreamingCountryDescription": "Dit is de landcode waarvoor we streaming informatie tonen. Als u in de VS bent, selecteer dan US en u krijgt Amerikaanse gerelateerde streaming informatie.",
"LanguageDescription": "Dit is de taal waarin Ombi in wordt weergegeven.", "LanguageDescription": "Dit is de taal waarin Ombi in wordt weergegeven.",
"MobileQRCode": "QR code voor mobiel", "MobileQRCode": "QR code voor mobiel",
"LegacyApp": "Start legacy app", "LegacyApp": "Start legacy app",
"NoQrCode": "Please contact your administrator to enable QR codes", "NoQrCode": "Neem contact op met de beheerder om QR-codes in te schakelen",
"UserType": "User Type:", "UserType": "Gebruikerstype:",
"ChangeDetails": "Change Details", "ChangeDetails": "Gegevens wijzigen",
"NeedCurrentPassword": "You need your current password to make any changes here", "NeedCurrentPassword": "Je hebt je huidige wachtwoord nodig om hier wijzigingen aan te brengen",
"CurrentPassword": "Current Password", "CurrentPassword": "Huidige wachtwoord",
"EmailAddress": "E-mail adres", "EmailAddress": "E-mail adres",
"NewPassword": "New Password", "NewPassword": "Nieuw Wachtwoord",
"NewPasswordConfirm": "New Password Confirm", "NewPasswordConfirm": "Nieuw wachtwoord bevestigen",
"Security": "Security", "Security": "Beveiliging",
"Profile": "Profile", "Profile": "Profiel",
"UpdatedYourInformation": "Updated your information", "UpdatedYourInformation": "Gegevens bijgewerkt",
"Unsubscribed": "Unsubscribed!" "Unsubscribed": "Afgemeld!"
}, },
"UserTypeLabel": { "UserTypeLabel": {
"1": "Local User", "1": "Lokale gebruiker",
"2": "Plex User", "2": "Plex gebruiker",
"3": "Emby User", "3": "Emby gebruiker",
"4": "Emby Connect User", "4": "Emby Connect gebruiker",
"5": "Jellyfin User" "5": "Jellyfin gebruiker"
}, },
"Paginator": { "Paginator": {
"itemsPerPageLabel": "Items per page:", "itemsPerPageLabel": "Items per pagina:",
"nextPageLabel": "Next page", "nextPageLabel": "Volgende pagina",
"previousPageLabel": "Previous page", "previousPageLabel": "Vorige pagina",
"firstPageLabel": "First page", "firstPageLabel": "Eerste pagina",
"lastPageLabel": "Last page", "lastPageLabel": "Laatste pagina",
"rangePageLabel1": "0 of {{length}}", "rangePageLabel1": "0 van {{length}}",
"rangePageLabel2": "{{startIndex}} {{endIndex}} of {{length}}" "rangePageLabel2": "{{startIndex}} - {{endIndex}} van {{length}}"
} }
} }

@ -14,26 +14,26 @@
"Common": { "Common": {
"ContinueButton": "Gå videre", "ContinueButton": "Gå videre",
"Available": "Tilgjengelig", "Available": "Tilgjengelig",
"Available4K": "Available 4K", "Available4K": "Tilgjengelig i 4K",
"Approved": "Godkjent", "Approved": "Godkjent",
"Approve4K": "Approve 4K", "Approve4K": "Godkjenn 4K",
"Pending": "Pending", "Pending": "Ventende",
"PartiallyAvailable": "Delvis tilgjengelig", "PartiallyAvailable": "Delvis tilgjengelig",
"Monitored": "Overvåket", "Monitored": "Overvåket",
"NotAvailable": "Ikke tilgjengelig", "NotAvailable": "Ikke tilgjengelig",
"ProcessingRequest": "Behandler forespørsel", "ProcessingRequest": "Behandler forespørsel",
"ProcessingRequest4K": "Processing Request 4K", "ProcessingRequest4K": "Behandler Forespørsel 4K",
"PendingApproval": "Venter på godkjenning", "PendingApproval": "Venter på godkjenning",
"PendingApproval4K": "Pending Approval 4K", "PendingApproval4K": "Venter på godkjenning 4K",
"RequestDenied": "Forespørsel avslått", "RequestDenied": "Forespørsel avslått",
"RequestDenied4K": "Request Denied 4K", "RequestDenied4K": "Forespørsel Avvist 4K",
"NotRequested": "Ikke forespurt", "NotRequested": "Ikke forespurt",
"NotRequested4K": "Not Requested 4K", "NotRequested4K": "Ikke Forespurt 4K",
"Requested": "Forespurt", "Requested": "Forespurt",
"Requested4K": "Requested 4K", "Requested4K": "Forespurt 4K",
"Search": "Søk", "Search": "Søk",
"Request": "Forespørsel", "Request": "Forespørsel",
"Request4K": "Request 4K", "Request4K": "Forespørsel 4K",
"Denied": "Avslått", "Denied": "Avslått",
"Approve": "Godkjenn", "Approve": "Godkjenn",
"PartlyAvailable": "Delvis tilgjengelig", "PartlyAvailable": "Delvis tilgjengelig",
@ -42,10 +42,10 @@
"Validation": "Kontroller de angitte verdiene" "Validation": "Kontroller de angitte verdiene"
}, },
"Cancel": "Avbryt", "Cancel": "Avbryt",
"Submit": "Submit", "Submit": "Send",
"Update": "Update", "Update": "Oppdater",
"tvShow": "TV Show", "tvShow": "TV Serie",
"movie": "Movie", "movie": "Film",
"album": "Album" "album": "Album"
}, },
"PasswordReset": { "PasswordReset": {
@ -64,11 +64,11 @@
"CheckPageForUpdates": "Sjekk denne siden for kontinuerlige oppdateringer." "CheckPageForUpdates": "Sjekk denne siden for kontinuerlige oppdateringer."
}, },
"ErrorPages": { "ErrorPages": {
"NotFound": "Page not found", "NotFound": "Siden ble ikke funnet",
"SomethingWentWrong": "Something went wrong!" "SomethingWentWrong": "Noe gikk galt!"
}, },
"NavigationBar": { "NavigationBar": {
"Discover": "Discover", "Discover": "Oppdag",
"Search": "Søk", "Search": "Søk",
"Requests": "Forespørsler", "Requests": "Forespørsler",
"UserManagement": "Brukeradministrasjon", "UserManagement": "Brukeradministrasjon",
@ -84,20 +84,20 @@
"Logout": "Logg av", "Logout": "Logg av",
"OpenMobileApp": "Åpne mobilapp", "OpenMobileApp": "Åpne mobilapp",
"RecentlyAdded": "Nylig lagt til", "RecentlyAdded": "Nylig lagt til",
"ChangeTheme": "Change Theme", "ChangeTheme": "Endre Tema",
"Calendar": "Calendar", "Calendar": "Kalender",
"UserPreferences": "Preferences", "UserPreferences": "Brukervalg",
"FeatureSuggestion": "Feature Suggestion", "FeatureSuggestion": "Feature Suggestion",
"FeatureSuggestionTooltip": "Have a great new idea? Suggest it here!", "FeatureSuggestionTooltip": "Har du en god idé? Foreslå den her!",
"Filter": { "Filter": {
"Movies": "Filmer", "Movies": "Filmer",
"TvShows": "TV serier", "TvShows": "TV serier",
"Music": "Musikk", "Music": "Musikk",
"People": "People" "People": "Personer"
}, },
"MorningWelcome": "Good morning!", "MorningWelcome": "God morgen!",
"AfternoonWelcome": "Good afternoon!", "AfternoonWelcome": "God ettermiddag!",
"EveningWelcome": "Good evening!" "EveningWelcome": "God kveld!"
}, },
"Search": { "Search": {
"Title": "Søk", "Title": "Søk",
@ -105,15 +105,15 @@
"MoviesTab": "Filmer", "MoviesTab": "Filmer",
"TvTab": "TV serier", "TvTab": "TV serier",
"MusicTab": "Musikk", "MusicTab": "Musikk",
"AdvancedSearch": "You can fill in any of the below to discover new media. All of the results are sorted by popularity", "AdvancedSearch": "Du kan fylle ut hvilket som helst av punktene nedenfor for å oppdage nye medier. Alle resultatene er sortert etter popularitet",
"AdvancedSearchHeader": "Advanced Search", "AdvancedSearchHeader": "Avansert Søk",
"Suggestions": "Forslag", "Suggestions": "Forslag",
"NoResults": "Beklager, vi fant ingen resultater!", "NoResults": "Beklager, vi fant ingen resultater!",
"DigitalDate": "Digital utgivelse: {{date}}", "DigitalDate": "Digital utgivelse: {{date}}",
"TheatricalRelease": "Kinopremiere: {{date}}", "TheatricalRelease": "Kinopremiere: {{date}}",
"ViewOnPlex": "Spill av på Plex", "ViewOnPlex": "Spill av på Plex",
"ViewOnEmby": "Spill av på Emby", "ViewOnEmby": "Spill av på Emby",
"ViewOnJellyfin": "Play On Jellyfin", "ViewOnJellyfin": "Spill av i Jellyfin",
"RequestAdded": "Forespørsel om {{title}} er lagt til", "RequestAdded": "Forespørsel om {{title}} er lagt til",
"Similar": "Lignende", "Similar": "Lignende",
"Refine": "Spesifiser", "Refine": "Spesifiser",
@ -141,24 +141,27 @@
"Season": "Sesong {{seasonNumber}}", "Season": "Sesong {{seasonNumber}}",
"SelectAllInSeason": "Velg alle i sesong {{seasonNumber}}" "SelectAllInSeason": "Velg alle i sesong {{seasonNumber}}"
}, },
"AdvancedSearchInstructions": "Please choose what type of media you are searching for:", "AdvancedSearchInstructions": "Velg medietypen du søker etter:",
"YearOfRelease": "Year of Release", "YearOfRelease": "Utgitt",
"SearchGenre": "Search Genre", "SearchGenre": "Søk etter Sjanger",
"SearchKeyword": "Search Keyword", "SearchKeyword": "Søk etter nøkkelord",
"SearchProvider": "Search Provider", "SearchProvider": "Søkeleverandør",
"KeywordSearchingDisclaimer": "Please note that Keyword Searching is very hit and miss due to the inconsistent data in TheMovieDb" "KeywordSearchingDisclaimer": "Vær oppmerksom på at nøkkelordsøk kan gi varierende kvalitet i resultater grunnet inkonsekvente data i TheMovieDb"
}, },
"Requests": { "Requests": {
"Title": "Forespørsler", "Title": "Forespørsler",
"Paragraph": "Nedenfor kan du se dine og alle andres forespørsler, du ser også status for nedlasting og godkjenning.", "Paragraph": "Nedenfor kan du se dine og alle andres forespørsler, du ser også status for nedlasting og godkjenning.",
"MoviesTab": "Filmer", "MoviesTab": "Filmer",
"ArtistName": "Artist", "ArtistName": "Artist",
"AlbumName": "Album Name", "AlbumName": "Albumnavn",
"TvTab": "TV serier", "TvTab": "TV serier",
"MusicTab": "Musikk", "MusicTab": "Musikk",
"RequestedBy": "Etterspurt av", "RequestedBy": "Etterspurt av",
"Status": "Status", "Status": "Status",
"RequestStatus": "Status for forespørsel", "RequestStatus": "Status for forespørsel",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Avslått:", "Denied": " Avslått:",
"TheatricalRelease": "Kinopremiere: {{date}}", "TheatricalRelease": "Kinopremiere: {{date}}",
"ReleaseDate": "Utgitt: {{date}}", "ReleaseDate": "Utgitt: {{date}}",
@ -170,15 +173,15 @@
"ChangeRootFolder": "Endre rotmappe", "ChangeRootFolder": "Endre rotmappe",
"ChangeQualityProfile": "Endre kvalitetsprofil", "ChangeQualityProfile": "Endre kvalitetsprofil",
"MarkUnavailable": "Merk utilgjengelig", "MarkUnavailable": "Merk utilgjengelig",
"MarkUnavailable4K": "Mark Unavailable 4K", "MarkUnavailable4K": "Merk Utilgjengelig 4K",
"MarkAvailable": "Merk tilgjengelig", "MarkAvailable": "Merk tilgjengelig",
"MarkAvailable4K": "Mark Available 4K", "MarkAvailable4K": "Merk Tilgjengelig 4K",
"Remove": "Fjern", "Remove": "Fjern",
"Deny": "Avslå", "Deny": "Avslå",
"Deny4K": "Deny 4K", "Deny4K": "Avvis 4K",
"Has4KRequest": "Has 4K Request", "Has4KRequest": "Har 4K-forespørsel",
"DenyReason": "Deny Reason", "DenyReason": "Avslå årsak",
"DeniedReason": "Denied Reason", "DeniedReason": "Årsak for avslag",
"Season": "Sesong", "Season": "Sesong",
"GridTitle": "Tittel", "GridTitle": "Tittel",
"AirDate": "Sendedato", "AirDate": "Sendedato",
@ -200,64 +203,65 @@
"NextMinutes": "En ny foresøprel vil bli lagt til om {{time}} minutter", "NextMinutes": "En ny foresøprel vil bli lagt til om {{time}} minutter",
"NextMinute": "En ny foresøprel vil bli lagt til om {{time}} minutt" "NextMinute": "En ny foresøprel vil bli lagt til om {{time}} minutt"
}, },
"AllRequests": "All Requests", "AllRequests": "Alle Forespørsler",
"PendingRequests": "Pending Requests", "PendingRequests": "Ventende Forespørsler",
"ProcessingRequests": "Processing Requests", "ProcessingRequests": "Behandler Forespørsler",
"AvailableRequests": "Available Requests", "AvailableRequests": "Tilgjengelige Forespørsler",
"DeniedRequests": "Denied Requests", "DeniedRequests": "Avviste Forespørsler",
"RequestsToDisplay": "Requests to display", "RequestsToDisplay": "Forespørsler å vise",
"RequestsTitle": "Tittel", "RequestsTitle": "Tittel",
"Details": "Detaljer", "Details": "Detaljer",
"Options": "Options", "Options": "Alternativer",
"RequestPanel": { "RequestPanel": {
"Delete": "Delete Request", "Delete": "Slett forespørsel",
"Approve": "Approve Request", "Approve": "Godkjenn forespørsel",
"Deny": "Deny Request", "Deny": "Avslå forespørsel",
"Approve4K": "Approve 4K Request", "Approve4K": "Godkjenn 4K forespørsel",
"Deny4K": "Deny 4K Request", "Deny4K": "Avslå 4K Forespørsel",
"ChangeAvailability": "Merk tilgjengelig", "ChangeAvailability": "Merk tilgjengelig",
"Deleted": "Successfully deleted selected items", "Deleted": "Valgte elementer ble slettet",
"Approved": "Successfully approved selected items", "Approved": "Valgte elementer ble godkjent",
"Denied": "Successfully denied selected items" "Denied": "Valgte elementer ble avvist"
}, },
"SuccessfullyApproved": "Successfully Approved", "SuccessfullyApproved": "Godkjenning vellykket",
"SuccessfullyDeleted": "Request successfully deleted", "SuccessfullyDenied": "Successfully Denied",
"NowAvailable": "Request is now available", "SuccessfullyDeleted": "Forespørselen ble slettet",
"NowUnavailable": "Request is now unavailable", "NowAvailable": "Forespørselen er nå tilgjengelig",
"SuccessfullyReprocessed": "Successfully Re-processed the request", "NowUnavailable": "Forespørselen er nå utilgjengelig",
"DeniedRequest": "Denied Request", "SuccessfullyReprocessed": "Forespørselen ble behandlet på nytt",
"RequestCollection": "Request Collection", "DeniedRequest": "Avvist forespørsel",
"CollectionSuccesfullyAdded": "The collection {{name}} has been successfully added!", "RequestCollection": "Be om samling",
"NeedToSelectEpisodes": "You need to select some episodes!", "CollectionSuccesfullyAdded": "Samlingen {{name}} har blitt lagt til!",
"NeedToSelectEpisodes": "Du må velge noen episoder!",
"RequestAddedSuccessfully": "Forespørsel om {{title}} er lagt til", "RequestAddedSuccessfully": "Forespørsel om {{title}} er lagt til",
"ErrorCodes": { "ErrorCodes": {
"AlreadyRequested": "This has already been requested", "AlreadyRequested": "Dette har allerede blitt forespurt",
"EpisodesAlreadyRequested": "We already have episodes requested from this series", "EpisodesAlreadyRequested": "Vi har allerede forespurte episoder fra denne serien",
"NoPermissionsOnBehalf": "You do not have the correct permissions to request on behalf of users!", "NoPermissionsOnBehalf": "Du har ikke rettigheter til å be om innhold på vegne av andre brukere!",
"NoPermissions": "You do not have the correct permissions!", "NoPermissions": "Du har ikke riktige rettigheter!",
"RequestDoesNotExist": "Request does not exist", "RequestDoesNotExist": "Forespørselen eksisterer ikke",
"ChildRequestDoesNotExist": "Child Request does not exist", "ChildRequestDoesNotExist": "Underforespørsel eksisterer ikke",
"NoPermissionsRequestMovie": "You do not have permissions to Request a Movie", "NoPermissionsRequestMovie": "Du har ikke rettigheter til å be om en film",
"NoPermissionsRequestTV": "You do not have permissions to Request a TV Show", "NoPermissionsRequestTV": "Du har ikke rettigheter til å be om en TV-serie",
"NoPermissionsRequestAlbum": "You do not have permissions to Request an Album", "NoPermissionsRequestAlbum": "Du har ikke rettigheter til å be om et album",
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "Du har overskredet din kvote for forespørsler av Filmer!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "Du har overskredet din kvote for forespørsler av Episoder!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "Du har overskredet din kvote for forespørsler av Album!"
}, },
"Notify": "Notify", "Notify": "Gi beskjed",
"RemoveNotification": "Remove Notifications", "RemoveNotification": "Fjern Varslinger",
"SuccessfulNotify": "You will now be notified for title {{title}}", "SuccessfulNotify": "Du vil nå bli varslet om tittelen {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}", "SuccessfulUnNotify": "Du vil ikke lenger bli varslet om tittelen {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}" "CouldntNotify": "Kunne ikke varsle for tittelen {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Mangler", "Title": "Mangler",
"IssuesForTitle": "Issues for {{title}}", "IssuesForTitle": "Problemer med {{title}}",
"PendingTitle": "Ventende løsninger", "PendingTitle": "Ventende løsninger",
"InProgressTitle": "Mangler under behandling", "InProgressTitle": "Mangler under behandling",
"ResolvedTitle": "Løste mangler", "ResolvedTitle": "Løste mangler",
"ColumnTitle": "Tittel", "ColumnTitle": "Tittel",
"Count": "Count", "Count": "Antall",
"Category": "Kategori", "Category": "Kategori",
"Status": "Status", "Status": "Status",
"Details": "Detaljer", "Details": "Detaljer",
@ -271,23 +275,23 @@
"WriteMessagePlaceholder": "Skriv meldingen din her...", "WriteMessagePlaceholder": "Skriv meldingen din her...",
"ReportedBy": "Rapportert av", "ReportedBy": "Rapportert av",
"IssueDialog": { "IssueDialog": {
"Title": "Report an issue", "Title": "Rapporter et problem",
"DescriptionPlaceholder": "Please describe the issue", "DescriptionPlaceholder": "Beskriv problemet",
"TitlePlaceholder": "Short title of your issue", "TitlePlaceholder": "Kort tittel på saken din",
"SelectCategory": "Select Category", "SelectCategory": "Velg Kategori",
"IssueCreated": "Issue has been created" "IssueCreated": "Saken er opprettet"
}, },
"Outstanding": "There are outstanding issues", "Outstanding": "Det finnes utestående saker",
"ResolvedDate": "Resolved date", "ResolvedDate": "Løst dato",
"CreatedDate": "Raised on", "CreatedDate": "Opprettet den",
"MarkedAsResolved": "This issue has now been marked as resolved!", "MarkedAsResolved": "Denne saken er nå markert som løst!",
"MarkedAsInProgress": "This issue has now been marked as in progress!", "MarkedAsInProgress": "Saken har nå blitt merket som pågår!",
"Delete": "Delete issue", "Delete": "Slett sak",
"DeletedIssue": "Issue has been deleted", "DeletedIssue": "Saken har blitt slettet",
"Chat": "Chat", "Chat": "Chatte",
"EnterYourMessage": "Enter Your Message", "EnterYourMessage": "Skriv meldingen din",
"Requested": "Forespurt", "Requested": "Forespurt",
"UserOnDate": "{{user}} on {{date}}" "UserOnDate": "{{user}} den {{date}}"
}, },
"Filter": { "Filter": {
"ClearFilter": "Tøm filter", "ClearFilter": "Tøm filter",
@ -295,8 +299,8 @@
"FilterHeaderRequestStatus": "Status", "FilterHeaderRequestStatus": "Status",
"Approved": "Godkjent", "Approved": "Godkjent",
"PendingApproval": "Venter på godkjenning", "PendingApproval": "Venter på godkjenning",
"WatchProviders": "Watch Providers", "WatchProviders": "Se tilbydere",
"Keywords": "Keywords" "Keywords": "Nøkkelord"
}, },
"UserManagment": { "UserManagment": {
"TvRemaining": "TV: {{remaining}}/{{total}} gjenstående", "TvRemaining": "TV: {{remaining}}/{{total}} gjenstående",
@ -312,148 +316,149 @@
}, },
"MediaDetails": { "MediaDetails": {
"Denied": "Avslått", "Denied": "Avslått",
"Denied4K": "Denied 4K", "Denied4K": "Avvist 4K",
"Trailers": "Trailers", "Trailers": "Trailere",
"RecommendationsTitle": "Recommendations", "RecommendationsTitle": "Anbefalinger",
"SimilarTitle": "Lignende", "SimilarTitle": "Lignende",
"VideosTitle": "Videos", "VideosTitle": "Videoer",
"AlbumsTitle": "Albums", "AlbumsTitle": "Album",
"RequestAllAlbums": "Request All Albums", "RequestAllAlbums": "Forespør alle album",
"ClearSelection": "Clear Selection", "ClearSelection": "Opphev valg",
"RequestSelectedAlbums": "Request Selected Albums", "RequestSelectedAlbums": "Forespør valgte album",
"ViewCollection": "View Collection", "ViewCollection": "Vis samling",
"NotEnoughInfo": "Unfortunately there is not enough information about this show yet!", "NotEnoughInfo": "Det er dessverre ikke nok informasjon om denne serien enda!",
"AdvancedOptions": "Advanced Options", "AdvancedOptions": "Avanserte alternativer",
"AutoApproveOptions": "You can configure the request here, once requested it will be sent to your DVR application and will be auto approved! Please note, this is optional, just press Request to skip!", "AutoApproveOptions": "Du kan konfigurere forespørselen her. Ved forespørsel blir den sendt til din DVR-applikasjon og godkjent automatisk! Merk: Dette er valgfritt. Trykk på Forespør for å hoppe over!",
"AutoApproveOptionsTv": "You can configure the request here, once requested it will be sent to your DVR application and will be auto approved! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it! Please note, this is optional, just press Request to skip!", "AutoApproveOptionsTv": "Du kan konfigurere forespørselen her. Når forespurt vil den bli sendt til DVR-programmet ditt og vil automatisk bli godkjent. Hvis forespørselen allerede eksisterer i Sonarr, vil vi ikke endre rotmappen eller kvalitetsprofilen hvis du har valgt det! Vær oppmerksom på at dette er valgfritt, bare trykk på Forespør for å hoppe over!",
"AutoApproveOptionsTvShort": "You can configure the request here, once requested it will be sent to your DVR application! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it! Please note, this is optional, just press Request to skip!", "AutoApproveOptionsTvShort": "Du kan konfigurere forespørselen her. Når forespurt vil den bli sendt til DVR-applikasjonen din! Hvis forespørselen allerede eksisterer i Sonarr, vil vi ikke forandre rotmappen eller kvalitetsprofilen hvis du endrer denne. Merk: Dette er valgfritt, trykk på Forespør for å hoppe over!",
"QualityProfilesSelect": "Select A Quality Profile", "QualityProfilesSelect": "Velg en kvalitetsprofil",
"RootFolderSelect": "Select A Root Folder", "RootFolderSelect": "Velg en rotmappe",
"LanguageProfileSelect": "Select A Language Profile", "LanguageProfileSelect": "Velg en språkprofil",
"Status": "Status:", "Status": "Status:",
"StatusValues": { "StatusValues": {
"Rumored": "Rumored", "Rumored": "Ryktede",
"Planned": "Planned", "Planned": "Planlagte",
"In Production": "In Production", "In Production": "I Produksjon",
"Post Production": "Post Production", "Post Production": "Post-produksjon",
"Released": "Released", "Released": "Utgitt",
"Running": "Running", "Running": "Løpende",
"Returning Series": "Returning Series", "Returning Series": "Serier som returnerer",
"Ended": "Ended", "Ended": "Avsluttet",
"Canceled": "Canceled" "Canceled": "Kansellert"
}, },
"Seasons": "Seasons:", "Seasons": "Sesonger:",
"Episodes": "Episodes:", "Episodes": "Episoder:",
"Availability": "Tilgjengelighet:", "Availability": "Tilgjengelighet:",
"RequestStatus": "Request Status:", "RequestStatus": "Status for forespørsel:",
"Quality": "Quality:", "Quality": "Kvalitet:",
"RootFolderOverride": "Overstyring av rotmappe:", "RootFolderOverride": "Overstyring av rotmappe:",
"QualityOverride": "Overstyr kvalitet:", "QualityOverride": "Overstyr kvalitet:",
"Network": "Network:", "Network": "Nettverk:",
"GenresLabel": "Genres:", "GenresLabel": "Sjangere:",
"Genres": "Genres", "Genres": "Sjangere",
"FirstAired": "First Aired:", "FirstAired": "Først sendt:",
"TheatricalRelease": "Kinopremiere:", "TheatricalRelease": "Kinopremiere:",
"DigitalRelease": "Digital Release:", "DigitalRelease": "Digital utgivelse:",
"Votes": "Votes:", "Votes": "Stemmer:",
"Runtime": "Runtime:", "Runtime": "Spilletid:",
"Minutes": "{{runtime}} Minutes", "Minutes": "{{runtime}} Minutter",
"Revenue": "Revenue:", "Revenue": "Omsetning:",
"Budget": "Budget:", "Budget": "Budsjett:",
"Keywords": "Keywords/Tags:", "Keywords": "Nøkkelord/Etiketter:",
"Casts": { "Casts": {
"CastTitle": "Cast" "CastTitle": "Rollebesetning"
}, },
"Crews": { "Crews": {
"CrewTitle": "Crew" "CrewTitle": "Crew"
}, },
"EpisodeSelector": { "EpisodeSelector": {
"AllSeasonsTooltip": "This will request every season for this show", "AllSeasonsTooltip": "Dette vil be om hver sesong for denne serien",
"FirstSeasonTooltip": "This will only request the First Season for this show", "FirstSeasonTooltip": "Dette vil kun be om første sesong for denne serien",
"LatestSeasonTooltip": "This will only request the Latest Season for this show", "LatestSeasonTooltip": "Dette vil kun be om siste sesong for denne serien",
"NoEpisodes": "There unfortunately is no episode data for this show yet!", "NoEpisodes": "Det finnes dessverre ingen episode-data for denne serien ennå!",
"SeasonNumber": "Sesong {{number}}" "SeasonNumber": "Sesong {{number}}"
}, },
"SonarrConfiguration": "Sonarr Configuration", "SonarrConfiguration": "Sonarr Konfigurasjon",
"RadarrConfiguration": "Radarr Configuration", "RadarrConfiguration": "Radarr Konfigurasjon",
"RequestOnBehalf": "Request on behalf of", "RequestOnBehalf": "Forespør på vegne av",
"PleaseSelectUser": "Please select a user", "PleaseSelectUser": "Vennligst velg en bruker",
"StreamingOn": "Streaming On:", "StreamingOn": "Strømmer på:",
"RequestedBy": "Etterspurt av:", "RequestedBy": "Etterspurt av:",
"OnDate": "On:", "OnDate": ":",
"RequestedByOn": "Requested By {{user}} on {{date}}", "RequestedByOn": "Forespurt av {{user}} den {{date}}",
"RequestDate": "Dato for forespørsel:", "RequestDate": "Dato for forespørsel:",
"DeniedReason": "Denied Reason:", "DeniedReason": "Årsak for avslag:",
"ReProcessRequest": "Re-Process Request", "ReProcessRequest": "Re-Prosessér Forespørsel",
"ReProcessRequest4K": "Re-Process 4K Request", "ReProcessRequest4K": "Re-Prosessér 4K-forespørsel",
"Music": { "Music": {
"Type": "Type:", "Type": "Type:",
"Country": "Country:", "Country": "Land:",
"StartDate": "Start Date:", "StartDate": "Startdato:",
"EndDate": "EndDate:" "EndDate": "Sluttdato:"
}, },
"RequestSource": "Source:" "RequestSource": "Kilde:"
}, },
"Discovery": { "Discovery": {
"PopularTab": "Populært", "PopularTab": "Populært",
"TrendingTab": "På vei opp", "TrendingTab": "På vei opp",
"UpcomingTab": "Upcoming", "UpcomingTab": "Kommende",
"SeasonalTab": "Seasonal", "SeasonalTab": "Årstid",
"RecentlyRequestedTab": "Recently Requested", "RecentlyRequestedTab": "Nylig Forespurte",
"Movies": "Filmer", "Movies": "Filmer",
"Combined": "Combined", "Combined": "Kombinert",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Tilgjengelighet", "Availability": "Tilgjengelighet",
"Studio": "Studio", "Studio": "Studio",
"Network": "Network", "Network": "Nettverk",
"UnknownNetwork": "Unknown", "UnknownNetwork": "Ukjent",
"RequestStatus": "Request Status", "RequestStatus": "Status for Forespørsel",
"Director": "Director", "Director": "Regissør",
"InCinemas": "In Cinemas", "InCinemas": "På Kino",
"FirstAired": "First Aired", "FirstAired": "Først Sendt",
"Writer": "Writer", "Writer": "Forfatter",
"ExecProducer": "Exec Producer" "ExecProducer": "Eksekutiv Produsent"
}, },
"NoSearch": "Sorry, nothing matches your search!" "NoSearch": "Beklager, ingenting samsvarer med søket!"
}, },
"UserPreferences": { "UserPreferences": {
"Welcome": "Velkommen {{username}}!", "Welcome": "Velkommen {{username}}!",
"OmbiLanguage": "Language", "OmbiLanguage": "Språk",
"DarkMode": "Dark Mode", "DarkMode": "Mørkt Tema",
"Updated": "Successfully Updated", "Updated": "Oppdatering Vellykket",
"StreamingCountry": "Streaming Country", "StreamingCountry": "Strømmeland",
"StreamingCountryDescription": "This is the country code that we will display streaming information for. If you are in the US please select US and you will get US related streaming information.", "StreamingCountryDescription": "Dette er landskoden vi vil vise strømmeinformasjon for. Hvis du er i USA, kan du velge USA og du vil motta USAs relaterte strømmeinformasjon.",
"LanguageDescription": "This is the language you would like the Ombi interface to be displayed in.", "LanguageDescription": "Dette er språket du ønsker at Ombis grensesnitt skal vises i.",
"MobileQRCode": "Mobile QR Code", "MobileQRCode": "QR-kode for mobil",
"LegacyApp": "Launch Legacy App", "LegacyApp": "Start tidligere app",
"NoQrCode": "Please contact your administrator to enable QR codes", "NoQrCode": "Kontakt din administrator for å aktivere QR-koder",
"UserType": "User Type:", "UserType": "Brukertype:",
"ChangeDetails": "Change Details", "ChangeDetails": "Endre Detaljer",
"NeedCurrentPassword": "You need your current password to make any changes here", "NeedCurrentPassword": "Du trenger ditt nåværende passord for å gjøre endringer her",
"CurrentPassword": "Current Password", "CurrentPassword": "Nåværende Passord",
"EmailAddress": "E-postadresse", "EmailAddress": "E-postadresse",
"NewPassword": "New Password", "NewPassword": "Nytt passord",
"NewPasswordConfirm": "New Password Confirm", "NewPasswordConfirm": "Bekreft nytt passord",
"Security": "Security", "Security": "Sikkerhet",
"Profile": "Profile", "Profile": "Profil",
"UpdatedYourInformation": "Updated your information", "UpdatedYourInformation": "Oppdaterte informasjonen din",
"Unsubscribed": "Unsubscribed!" "Unsubscribed": "Avsluttet abonnement!"
}, },
"UserTypeLabel": { "UserTypeLabel": {
"1": "Local User", "1": "Lokal Bruker",
"2": "Plex User", "2": "Plex Bruker",
"3": "Emby User", "3": "Emby Bruker",
"4": "Emby Connect User", "4": "Emby koble til bruker",
"5": "Jellyfin User" "5": "Jellyfin Bruker"
}, },
"Paginator": { "Paginator": {
"itemsPerPageLabel": "Items per page:", "itemsPerPageLabel": "Elementer per side:",
"nextPageLabel": "Next page", "nextPageLabel": "Neste side",
"previousPageLabel": "Previous page", "previousPageLabel": "Forrige side",
"firstPageLabel": "First page", "firstPageLabel": "Første side",
"lastPageLabel": "Last page", "lastPageLabel": "Siste side",
"rangePageLabel1": "0 of {{length}}", "rangePageLabel1": "0 av {{length}}",
"rangePageLabel2": "{{startIndex}} {{endIndex}} of {{length}}" "rangePageLabel2": "{{startIndex}} {{endIndex}} av {{length}}"
} }
} }

@ -159,6 +159,9 @@
"RequestedBy": "Zgłoszone przez", "RequestedBy": "Zgłoszone przez",
"Status": "Stan", "Status": "Stan",
"RequestStatus": "Status zgłoszenia", "RequestStatus": "Status zgłoszenia",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": "Odrzucono:", "Denied": "Odrzucono:",
"TheatricalRelease": "Premiera kinowa: {{date}}", "TheatricalRelease": "Premiera kinowa: {{date}}",
"ReleaseDate": "Wydany: {{date}}", "ReleaseDate": "Wydany: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Zatwierdzono pomyślnie", "SuccessfullyApproved": "Zatwierdzono pomyślnie",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Prośba pomyślnie usunięta", "SuccessfullyDeleted": "Prośba pomyślnie usunięta",
"NowAvailable": "Request is now available", "NowAvailable": "Request is now available",
"NowUnavailable": "Request is now unavailable", "NowUnavailable": "Request is now unavailable",
@ -403,6 +407,7 @@
"Movies": "Filmy", "Movies": "Filmy",
"Combined": "Połączone", "Combined": "Połączone",
"Tv": "Seriale", "Tv": "Seriale",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Dostępność", "Availability": "Dostępność",
"Studio": "Studio", "Studio": "Studio",

@ -159,6 +159,9 @@
"RequestedBy": "Solicitado por", "RequestedBy": "Solicitado por",
"Status": "Status", "Status": "Status",
"RequestStatus": "Status da solicitação", "RequestStatus": "Status da solicitação",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Negados:", "Denied": " Negados:",
"TheatricalRelease": "Lançamento nos Cinemas: {{date}}", "TheatricalRelease": "Lançamento nos Cinemas: {{date}}",
"ReleaseDate": "Lançado: {{date}}", "ReleaseDate": "Lançado: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Itens selecionados negados com sucesso" "Denied": "Itens selecionados negados com sucesso"
}, },
"SuccessfullyApproved": "Aprovado com Sucesso", "SuccessfullyApproved": "Aprovado com Sucesso",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Solicitação excluída com sucesso", "SuccessfullyDeleted": "Solicitação excluída com sucesso",
"NowAvailable": "O pedido agora está disponível", "NowAvailable": "O pedido agora está disponível",
"NowUnavailable": "A solicitação está indisponível", "NowUnavailable": "A solicitação está indisponível",
@ -403,6 +407,7 @@
"Movies": "Filmes", "Movies": "Filmes",
"Combined": "Combinados", "Combined": "Combinados",
"Tv": "Serie", "Tv": "Serie",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Disponibilidade", "Availability": "Disponibilidade",
"Studio": "Estúdio", "Studio": "Estúdio",

@ -159,6 +159,9 @@
"RequestedBy": "Requested By", "RequestedBy": "Requested By",
"Status": "Status", "Status": "Status",
"RequestStatus": "Request status", "RequestStatus": "Request status",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Denied:", "Denied": " Denied:",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ReleaseDate": "Released: {{date}}", "ReleaseDate": "Released: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Successfully Approved", "SuccessfullyApproved": "Successfully Approved",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Request successfully deleted", "SuccessfullyDeleted": "Request successfully deleted",
"NowAvailable": "Request is now available", "NowAvailable": "Request is now available",
"NowUnavailable": "Request is now unavailable", "NowUnavailable": "Request is now unavailable",
@ -403,6 +407,7 @@
"Movies": "Movies", "Movies": "Movies",
"Combined": "Combined", "Combined": "Combined",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Availability", "Availability": "Availability",
"Studio": "Studio", "Studio": "Studio",

@ -159,6 +159,9 @@
"RequestedBy": "Автор запроса", "RequestedBy": "Автор запроса",
"Status": "Статус", "Status": "Статус",
"RequestStatus": "Статус запроса", "RequestStatus": "Статус запроса",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Отказано:", "Denied": " Отказано:",
"TheatricalRelease": "Релиз в кинотеатрах: {{date}}", "TheatricalRelease": "Релиз в кинотеатрах: {{date}}",
"ReleaseDate": "Дата выхода: {{date}}", "ReleaseDate": "Дата выхода: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Successfully Approved", "SuccessfullyApproved": "Successfully Approved",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Request successfully deleted", "SuccessfullyDeleted": "Request successfully deleted",
"NowAvailable": "Request is now available", "NowAvailable": "Request is now available",
"NowUnavailable": "Request is now unavailable", "NowUnavailable": "Request is now unavailable",
@ -403,6 +407,7 @@
"Movies": "Фильмы", "Movies": "Фильмы",
"Combined": "Combined", "Combined": "Combined",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Доступность", "Availability": "Доступность",
"Studio": "Studio", "Studio": "Studio",

@ -159,6 +159,9 @@
"RequestedBy": "Vyžiadané od", "RequestedBy": "Vyžiadané od",
"Status": "Stav", "Status": "Stav",
"RequestStatus": "Stav požiadavky", "RequestStatus": "Stav požiadavky",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Zamietnuté:", "Denied": " Zamietnuté:",
"TheatricalRelease": "Kino vydanie: {{date}}", "TheatricalRelease": "Kino vydanie: {{date}}",
"ReleaseDate": "Vydané: {{date}}", "ReleaseDate": "Vydané: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Úspešne odstránené vybrané položky" "Denied": "Úspešne odstránené vybrané položky"
}, },
"SuccessfullyApproved": "Úspešne schválené", "SuccessfullyApproved": "Úspešne schválené",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Žiadosť bola úspešne vymazaná", "SuccessfullyDeleted": "Žiadosť bola úspešne vymazaná",
"NowAvailable": "Žiadosť je teraz k dispozícii", "NowAvailable": "Žiadosť je teraz k dispozícii",
"NowUnavailable": "Žiadosť je teraz nedostupná", "NowUnavailable": "Žiadosť je teraz nedostupná",
@ -403,6 +407,7 @@
"Movies": "Filmy", "Movies": "Filmy",
"Combined": "Kombinované", "Combined": "Kombinované",
"Tv": "Seriály", "Tv": "Seriály",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Dostupnosť", "Availability": "Dostupnosť",
"Studio": "Štúdio", "Studio": "Štúdio",

@ -159,6 +159,9 @@
"RequestedBy": "Efterfrågats av", "RequestedBy": "Efterfrågats av",
"Status": "Status", "Status": "Status",
"RequestStatus": "Status för begäran", "RequestStatus": "Status för begäran",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": " Nekad:", "Denied": " Nekad:",
"TheatricalRelease": "Biopremiär: {{date}}", "TheatricalRelease": "Biopremiär: {{date}}",
"ReleaseDate": "Releasedatum: {{date}}", "ReleaseDate": "Releasedatum: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "Successfully Approved", "SuccessfullyApproved": "Successfully Approved",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "Request successfully deleted", "SuccessfullyDeleted": "Request successfully deleted",
"NowAvailable": "Request is now available", "NowAvailable": "Request is now available",
"NowUnavailable": "Request is now unavailable", "NowUnavailable": "Request is now unavailable",
@ -403,6 +407,7 @@
"Movies": "Filmer", "Movies": "Filmer",
"Combined": "Kombinerad", "Combined": "Kombinerad",
"Tv": "TV", "Tv": "TV",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "Tillgänglighet", "Availability": "Tillgänglighet",
"Studio": "Filmstudio", "Studio": "Filmstudio",

@ -159,6 +159,9 @@
"RequestedBy": "请求者", "RequestedBy": "请求者",
"Status": "发行状态", "Status": "发行状态",
"RequestStatus": "申请状态", "RequestStatus": "申请状态",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": "已拒绝:", "Denied": "已拒绝:",
"TheatricalRelease": "剧场版发行:{{date}}", "TheatricalRelease": "剧场版发行:{{date}}",
"ReleaseDate": "已发行: {{date}}", "ReleaseDate": "已发行: {{date}}",
@ -221,6 +224,7 @@
"Denied": "Successfully denied selected items" "Denied": "Successfully denied selected items"
}, },
"SuccessfullyApproved": "批准成功", "SuccessfullyApproved": "批准成功",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "删除请求成功", "SuccessfullyDeleted": "删除请求成功",
"NowAvailable": "请求现在可观看", "NowAvailable": "请求现在可观看",
"NowUnavailable": "请求现在不可观看", "NowUnavailable": "请求现在不可观看",
@ -403,6 +407,7 @@
"Movies": "电影", "Movies": "电影",
"Combined": "混合", "Combined": "混合",
"Tv": "电视节目", "Tv": "电视节目",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "可用性", "Availability": "可用性",
"Studio": "工作室", "Studio": "工作室",

@ -159,6 +159,9 @@
"RequestedBy": "请求者", "RequestedBy": "请求者",
"Status": "发行状态", "Status": "发行状态",
"RequestStatus": "申请状态", "RequestStatus": "申请状态",
"Watched": "Watched",
"WatchedTooltip": "The user who made the request has watched it",
"WatchedByUsersCount": "{{count}} users have watched this.",
"Denied": "已拒绝:", "Denied": "已拒绝:",
"TheatricalRelease": "剧场版发行:{{date}}", "TheatricalRelease": "剧场版发行:{{date}}",
"ReleaseDate": "已发行: {{date}}", "ReleaseDate": "已发行: {{date}}",
@ -221,6 +224,7 @@
"Denied": "所选项目已拒绝" "Denied": "所选项目已拒绝"
}, },
"SuccessfullyApproved": "批准成功", "SuccessfullyApproved": "批准成功",
"SuccessfullyDenied": "Successfully Denied",
"SuccessfullyDeleted": "删除请求成功", "SuccessfullyDeleted": "删除请求成功",
"NowAvailable": "请求现在可观看", "NowAvailable": "请求现在可观看",
"NowUnavailable": "请求现在不可观看", "NowUnavailable": "请求现在不可观看",
@ -403,6 +407,7 @@
"Movies": "电影", "Movies": "电影",
"Combined": "混合", "Combined": "混合",
"Tv": "电视节目", "Tv": "电视节目",
"Genres": "Genres",
"CardDetails": { "CardDetails": {
"Availability": "可用性", "Availability": "可用性",
"Studio": "工作室", "Studio": "工作室",

@ -1,3 +1,3 @@
{ {
"version": "4.35.11" "version": "4.39.0"
} }
Loading…
Cancel
Save