diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 000000000..07f8a1a2b --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,47 @@ +name: 'Chromatic' + +# Event for the workflow +on: + push: + workflow_dispatch: + +# List of jobs +jobs: + storybook-build: + # Operating System + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: NodeModules Cache + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install dependencies + working-directory: ./src/Ombi/ClientApp + run: yarn + + - name: Publish to Chromatic + if: github.ref != 'refs/heads/master' + uses: chromaui/action@v1 + with: + projectToken: 7c47e1a1a4bd + exitZeroOnChanges: true + workingDir: ./src/Ombi/ClientApp + buildScriptName: storybookbuild + exitOnceUploaded: true + + - name: Publish to Chromatic and auto accept changes + if: github.ref == 'refs/heads/develop' + uses: chromaui/action@v1 + with: + projectToken: 7c47e1a1a4bd + autoAcceptChanges: true # 👈 Option to accept all changes + workingDir: ./src/Ombi/ClientApp + buildScriptName: storybookbuild + exitOnceUploaded: true diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 2e382eee3..2dc51179e 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -34,15 +34,29 @@ jobs: - name: Install Frontend Deps run: yarn --cwd ./src/Ombi/ClientApp install - - name: Install Automation Deps - run: yarn --cwd ./tests install - - - name: Start Backend - run: | - nohup dotnet run --project ./src/Ombi -- --host http://*:3577 & - - name: Start Frontend - run: | - nohup yarn --cwd ./src/Ombi/ClientApp start & + - name: Build Frontend + run: yarn --cwd ./src/Ombi/ClientApp build + + - name: Build Docker Image + run: docker build -t ombi src/ + + - name: Run Docker Image + run: nohup docker run --rm -p 5000:5000 ombi & + + - name: Sleep for server to start + run: sleep 20 + + # - name: Start Frontend + # run: | + # nohup yarn --cwd ./src/Ombi/ClientApp start & + + # - name: Install Automation Deps + # run: yarn --cwd ./tests install + + # - name: Start Backend + # run: | + # nohup dotnet run --project ./src/Ombi -- --host http://*:3577 & + - name: Cypress Tests uses: cypress-io/github-action@v2.8.2 with: @@ -50,9 +64,9 @@ jobs: browser: chrome headless: true working-directory: tests - wait-on: http://localhost:3577/ - # 7 minutes - wait-on-timeout: 420 + wait-on: http://localhost:5000/ + # 10 minutes + wait-on-timeout: 600 env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml index 7c724a62a..402c5ffd0 100644 --- a/.github/workflows/label.yml +++ b/.github/workflows/label.yml @@ -8,6 +8,8 @@ name: Labeler on: [pull_request] +permissions: write-all + jobs: label: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 961871f8a..7465635cc 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,6 +5,11 @@ on: types: [opened, synchronize, reopened] workflow_dispatch: +permissions: + pull-requests: write + issues: write + repository-projects: write + jobs: build-ui: runs-on: ubuntu-latest @@ -28,6 +33,7 @@ jobs: unit-test: runs-on: ubuntu-latest + steps: - uses: actions/checkout@v2 - uses: actions/setup-dotnet@v1 @@ -45,7 +51,7 @@ jobs: - name: Run Unit Tests run: | cd src - dotnet test --logger trx --results-directory "TestResults" + dotnet test --configuration "Release" --logger "trx;LogFileName=test-results.trx" analysis: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 845eda385..cae15017a 100644 --- a/.gitignore +++ b/.gitignore @@ -251,3 +251,4 @@ _Pvt_Extensions /src/Ombi/databases.json /src/Ombi/healthchecksdb /src/Ombi/ClientApp/package-lock.json +/src/Ombi.Core/Properties/launchSettings.json diff --git a/.mergify.yml b/.mergify.yml new file mode 100644 index 000000000..fac2166e3 --- /dev/null +++ b/.mergify.yml @@ -0,0 +1,7 @@ +pull_request_rules: + - name: Automatic merge on approval + conditions: + - "#approved-reviews-by>=1" + actions: + merge: + method: merge diff --git a/CHANGELOG.md b/CHANGELOG.md index 2915d98bb..cce336ad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,360 +1,363 @@ -## [4.16.12](https://github.com/Ombi-app/Ombi/compare/v4.16.11...v4.16.12) (2022-04-19) - - - -## [4.16.11](https://github.com/Ombi-app/Ombi/compare/v4.16.10...v4.16.11) (2022-04-14) +## [4.22.4](https://github.com/Ombi-app/Ombi/compare/v4.22.3...v4.22.4) (2022-08-04) ### Bug Fixes -* Set the default job for the watchlist import to hourly instead of daily ([75906af](https://github.com/Ombi-app/Ombi/commit/75906af0adee3e3c68d825c3aaa8f7b918461b1f)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0e8a64b](https://github.com/Ombi-app/Ombi/commit/0e8a64b8ca00d210fbe843ac2c3f6af218d80cbc)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([7b0ad61](https://github.com/Ombi-app/Ombi/commit/7b0ad61bfcff3986b33180dc64022cba7ea8eefb)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([4fc2c1f](https://github.com/Ombi-app/Ombi/commit/4fc2c1f24534085a783a3d5791f5533b68272153)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([76ab733](https://github.com/Ombi-app/Ombi/commit/76ab733b91791e4d93d184f3c7d0779c6a388695)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([06e4cef](https://github.com/Ombi-app/Ombi/commit/06e4cefa7b4e55b860da9a64f461f6ec8fa17367)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([c12d89d](https://github.com/Ombi-app/Ombi/commit/c12d89d6781a337520977ad285f8d08c93f434dd)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([bc0c2f6](https://github.com/Ombi-app/Ombi/commit/bc0c2f622e34fb5a2711039d9ed7aad34f982b15)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([e4b00e6](https://github.com/Ombi-app/Ombi/commit/e4b00e6b3468bd9389eeb02fc6ad7daf27abc3b3)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d1998d3](https://github.com/Ombi-app/Ombi/commit/d1998d326f999a38586d0a351a20c5448df95842)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([bee4ccb](https://github.com/Ombi-app/Ombi/commit/bee4ccb804594e7385b1fbdc9fe2ef5c42e0d21f)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([80233ed](https://github.com/Ombi-app/Ombi/commit/80233ed560cc976e83570d0655c3472f20171fb3)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([8a78adc](https://github.com/Ombi-app/Ombi/commit/8a78adc9bb62f277f2b213dcb3847ed6d0089fcb)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d04c60a](https://github.com/Ombi-app/Ombi/commit/d04c60aa5909b47ba6bffa6f66b03079cbd43521)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([92a785e](https://github.com/Ombi-app/Ombi/commit/92a785e736fa4b72a45270da2d0f4661df433078)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([634982d](https://github.com/Ombi-app/Ombi/commit/634982df2661cefab5ea9f5163fe04a005cc0171)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([b404baa](https://github.com/Ombi-app/Ombi/commit/b404baad6d0aeaa1561701e0db8db4e78613a364)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d14f11e](https://github.com/Ombi-app/Ombi/commit/d14f11e0eb20ab0a68e765ee77968b3b3e54e995)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([7cf64f9](https://github.com/Ombi-app/Ombi/commit/7cf64f909d78908edaabeffb8a39a7d02e73fe7e)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0c9e1ec](https://github.com/Ombi-app/Ombi/commit/0c9e1ec090827080cc8f7393e5e91456ff37d691)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([3b0b730](https://github.com/Ombi-app/Ombi/commit/3b0b730cb02efe24f6d4026e5fdb20d37e495119)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([6ed1a03](https://github.com/Ombi-app/Ombi/commit/6ed1a03b7ff4077f09ea9e13394b18b0d138f4c3)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([2941acd](https://github.com/Ombi-app/Ombi/commit/2941acd3b2ec74a5e6aeea275ab5a39d2653f37f)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([c075a1a](https://github.com/Ombi-app/Ombi/commit/c075a1a66784d975eaf60f2dfbbcbe048f2f63d7)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([76bd81c](https://github.com/Ombi-app/Ombi/commit/76bd81c3ca55a98c6ec944a838dc01294a6193a6)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0d38275](https://github.com/Ombi-app/Ombi/commit/0d3827507e002bcf58f673e97ffcc3bd25dcf337)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([5c99601](https://github.com/Ombi-app/Ombi/commit/5c99601b07aec1a65d0186a4c4327440811e64c6)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([01546a0](https://github.com/Ombi-app/Ombi/commit/01546a0f7f86379528b486463246ef9bdfb9033e)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d7fea78](https://github.com/Ombi-app/Ombi/commit/d7fea7843aaaab7ddff8dc31ca6d2a9117471dcc)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([1a6b95d](https://github.com/Ombi-app/Ombi/commit/1a6b95d45c220310213b8d811272a63f0f6ff42b)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([fa10174](https://github.com/Ombi-app/Ombi/commit/fa1017422c4efd4b0897871bd3c671151774d7c3)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0c31e62](https://github.com/Ombi-app/Ombi/commit/0c31e628df376aac6d56ae67c7c705a9a4a7c080)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([6399643](https://github.com/Ombi-app/Ombi/commit/63996437a02fe10ffae6822ffa15369bec0a6b36)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([5826e2d](https://github.com/Ombi-app/Ombi/commit/5826e2d9a1c3f1210a87fa270dc0c81bac32944a)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d434514](https://github.com/Ombi-app/Ombi/commit/d43451405be489254d7cdc7755d5f516a1e495a5)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([0b9596d](https://github.com/Ombi-app/Ombi/commit/0b9596d807178f5e071113ec0347868ec7f0960b)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([8c4c0b2](https://github.com/Ombi-app/Ombi/commit/8c4c0b262978c1303767af360d802c4b4c2b4d24)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([289ab77](https://github.com/Ombi-app/Ombi/commit/289ab77b0e04aae235b6f6cebc86e0a8d1f0cf2b)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([30e3417](https://github.com/Ombi-app/Ombi/commit/30e3417285a4eed18d429d7776f0e74096e834c0)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([6c0a5da](https://github.com/Ombi-app/Ombi/commit/6c0a5dadd4b8f37760252eb0fe7f88908f55506d)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d5bf969](https://github.com/Ombi-app/Ombi/commit/d5bf9692ce1fc0ccfe7beca6dd200c78be177bdc)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([8a9e7ea](https://github.com/Ombi-app/Ombi/commit/8a9e7ea588aefbcd73ed82625887e3614e1703ea)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([01047a3](https://github.com/Ombi-app/Ombi/commit/01047a3fd67153f3ff16f860d2c7b50213e8d9b2)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([698a23f](https://github.com/Ombi-app/Ombi/commit/698a23fb83f323cdd1dd57cb49803079d44214a7)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([24eb842](https://github.com/Ombi-app/Ombi/commit/24eb842fc4424f7bcc3ec2949d7f5472492e96f6)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([ac8b16a](https://github.com/Ombi-app/Ombi/commit/ac8b16a3051ad71dbd54a8973c7dd847b564a515)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([f428ce6](https://github.com/Ombi-app/Ombi/commit/f428ce6a700c081437703839bc84d2f2b1138bcc)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([94b16df](https://github.com/Ombi-app/Ombi/commit/94b16dfe09bf1d2cd6286777d74eb5d4496abbbb)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([4881775](https://github.com/Ombi-app/Ombi/commit/4881775eda69a8f136ce0d8fbbf970e3d0406dc9)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([8297db9](https://github.com/Ombi-app/Ombi/commit/8297db91e85da308bde6fb09ad78347dee063630)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([d1152ab](https://github.com/Ombi-app/Ombi/commit/d1152ab7674243daa528c524c0cdc87d81ad49c9)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([eb2788b](https://github.com/Ombi-app/Ombi/commit/eb2788b761b55c487a59a049427ca08f6c10e836)) -* **translations:** 🌐 New translations from Crowdin [skip ci] ([21a794c](https://github.com/Ombi-app/Ombi/commit/21a794cbc0a5fa735ca0347c8f7f1ac04a487fbc)) +* :bug: Fixed missing externals ([#4712](https://github.com/Ombi-app/Ombi/issues/4712)) ([fcc1eaa](https://github.com/Ombi-app/Ombi/commit/fcc1eaaa377683dcdc81d62a2a688fb0c4490c7b)) +* fixed trakt image not loading when base url present ([#4711](https://github.com/Ombi-app/Ombi/issues/4711)) ([f102dcf](https://github.com/Ombi-app/Ombi/commit/f102dcf751c2eb62ebfe30f9f8e4b2ad863c3b0d)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([#4713](https://github.com/Ombi-app/Ombi/issues/4713)) ([ff142b0](https://github.com/Ombi-app/Ombi/commit/ff142b09abbb2f9540387284222552e6e12639fe)) -## [4.10.2](https://github.com/Ombi-app/Ombi/compare/v4.10.1...v4.10.2) (2022-01-22) +## [4.22.3](https://github.com/Ombi-app/Ombi/compare/v4.22.2...v4.22.3) (2022-07-28) +### Bug Fixes -## [4.16.10](https://github.com/Ombi-app/Ombi/compare/v4.16.9...v4.16.10) (2022-04-13) +* Override Sonarr V3 Profiles endpoint ([#4678](https://github.com/Ombi-app/Ombi/issues/4678)) ([875da95](https://github.com/Ombi-app/Ombi/commit/875da959f353119b05138d68ee6d32a49e14b91e)) -## [4.16.9](https://github.com/Ombi-app/Ombi/compare/v4.16.8...v4.16.9) (2022-04-13) +## [4.22.2](https://github.com/Ombi-app/Ombi/compare/v4.22.1...v4.22.2) (2022-07-25) ### Bug Fixes -* **plex-watchlist:** Only request the latest season when importing from the watchlist ([77a47ff](https://github.com/Ombi-app/Ombi/commit/77a47ff157c6c5feafe3f2a29a3fcba8df4fdfef)) +* fixed an issue where I broke images for some users ([81ddc85](https://github.com/Ombi-app/Ombi/commit/81ddc8553b9094c3f6843b036daebb2eb9262e00)) -## [4.16.8](https://github.com/Ombi-app/Ombi/compare/v4.16.7...v4.16.8) (2022-04-13) +## [4.22.1](https://github.com/Ombi-app/Ombi/compare/v4.22.0...v4.22.1) (2022-07-25) ### 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)) +* **discover:** :bug: Created new Image component to handle 429's from TMDB ([#4698](https://github.com/Ombi-app/Ombi/issues/4698)) and fixed [#4635](https://github.com/Ombi-app/Ombi/issues/4635) ([#4699](https://github.com/Ombi-app/Ombi/issues/4699)) ([f22d3da](https://github.com/Ombi-app/Ombi/commit/f22d3da765799365455b919027f7563e52b347c3)) -## [4.16.7](https://github.com/Ombi-app/Ombi/compare/v4.16.6...v4.16.7) (2022-04-12) +# [4.22.0](https://github.com/Ombi-app/Ombi/compare/v4.21.2...v4.22.0) (2022-07-22) +### Features -## [4.16.6](https://github.com/Ombi-app/Ombi/compare/v4.16.5...v4.16.6) (2022-04-11) +* **discover:** ✨ Added infinite scroll on advanced search results ([898bc89](https://github.com/Ombi-app/Ombi/commit/898bc89fa78245c1f3de9481f6c724f087a16e39)) -## [4.16.5](https://github.com/Ombi-app/Ombi/compare/v4.16.4...v4.16.5) (2022-04-08) +## [4.21.2](https://github.com/Ombi-app/Ombi/compare/v4.21.1...v4.21.2) (2022-07-22) ### Bug Fixes -* **watchlist:** actually fixed it this time... ([d962a32](https://github.com/Ombi-app/Ombi/commit/d962a3211eca29520662ddce962676e3aea17ec5)) +* Landing and Login page improvements ([#4690](https://github.com/Ombi-app/Ombi/issues/4690)) ([6d423b5](https://github.com/Ombi-app/Ombi/commit/6d423b5447c52c5e59d8d2bd92a23b47468eb736)) -## [4.16.4](https://github.com/Ombi-app/Ombi/compare/v4.16.3...v4.16.4) (2022-04-08) +## [4.21.1](https://github.com/Ombi-app/Ombi/compare/v4.21.0...v4.21.1) (2022-07-11) +### Bug Fixes -## [4.16.3](https://github.com/Ombi-app/Ombi/compare/v4.16.2...v4.16.3) (2022-04-08) +* **images:** Retry images with a backoff when we get a Too Many requests from TheMovieDb [#4685](https://github.com/Ombi-app/Ombi/issues/4685) ([3f1f35d](https://github.com/Ombi-app/Ombi/commit/3f1f35df3164db6739691cdda8f925c296239791)) -### 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)) +# [4.21.0](https://github.com/Ombi-app/Ombi/compare/v4.20.4...v4.21.0) (2022-06-22) +### Features -## [4.16.2](https://github.com/Ombi-app/Ombi/compare/v4.16.1...v4.16.2) (2022-04-07) +* Upgrade to Angular14 ([#4668](https://github.com/Ombi-app/Ombi/issues/4668)) ([b9d55a4](https://github.com/Ombi-app/Ombi/commit/b9d55a469b412558cbf67c1e25db7fdda5964cd8)) -### Bug Fixes +### Performance Improvements -* **wizard:** Fixed an issue when using Plex OAuth it could fail setting up ([b743cf4](https://github.com/Ombi-app/Ombi/commit/b743cf4fafa7341ad1b163276f006d7ab0e9dcff)) +* stop populating obsolete subscribe fields ([#4625](https://github.com/Ombi-app/Ombi/issues/4625)) ([9a73463](https://github.com/Ombi-app/Ombi/commit/9a734637665f671b17c2bb440d93b35a891c142b)) -## [4.16.1](https://github.com/Ombi-app/Ombi/compare/v4.16.0...v4.16.1) (2022-04-07) +## [4.20.4](https://github.com/Ombi-app/Ombi/compare/v4.20.3...v4.20.4) (2022-06-15) +### Bug Fixes -# [4.16.0](https://github.com/Ombi-app/Ombi/compare/v4.15.6...v4.16.0) (2022-04-07) +* fixed build ([f877921](https://github.com/Ombi-app/Ombi/commit/f8779219146051ea74f8b6408658ff7975afb88b)) -## [4.15.6](https://github.com/Ombi-app/Ombi/compare/v4.15.5...v4.15.6) (2022-04-07) +## [4.20.3](https://github.com/Ombi-app/Ombi/compare/v4.20.2...v4.20.3) (2022-06-05) ### 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)) +* **plex:** 🐛 Fixed an issue with the Plex Sync ([ab1a11a](https://github.com/Ombi-app/Ombi/commit/ab1a11af78efbe9d37bd55aa80a640796c138a98)) -## [4.15.5](https://github.com/Ombi-app/Ombi/compare/v4.15.4...v4.15.5) (2022-04-06) +## [4.20.2](https://github.com/Ombi-app/Ombi/compare/v4.20.1...v4.20.2) (2022-06-03) +### Bug Fixes -## [4.15.4](https://github.com/Ombi-app/Ombi/compare/v4.15.3...v4.15.4) (2022-03-29) +* :bug: Fixed the Request on Behalf of having blanks ([#4667](https://github.com/Ombi-app/Ombi/issues/4667)) ([7dd9b1c](https://github.com/Ombi-app/Ombi/commit/7dd9b1cac07f571dd35b362544e4fe0226c4b817)) -## [4.15.3](https://github.com/Ombi-app/Ombi/compare/v4.15.2...v4.15.3) (2022-03-24) +## [4.20.1](https://github.com/Ombi-app/Ombi/compare/v4.20.0...v4.20.1) (2022-05-27) +### Bug Fixes -## [4.15.2](https://github.com/Ombi-app/Ombi/compare/v4.15.1...v4.15.2) (2022-03-23) +* added media type tag to media type text ([#4638](https://github.com/Ombi-app/Ombi/issues/4638)) ([fe501d3](https://github.com/Ombi-app/Ombi/commit/fe501d34a0c36ac9f000b107eca49dbc6694d006)) +* **API:** Fix pagination in some edge cases ([#4649](https://github.com/Ombi-app/Ombi/issues/4649)) ([a70bf8f](https://github.com/Ombi-app/Ombi/commit/a70bf8f46c76d74c9dfdf908c53bd9955ca0a35d)) +* **discover:** Carousel touch not working when scrolling page and recommendations and similar movie navigation ([#4633](https://github.com/Ombi-app/Ombi/issues/4633)) ([d5ef1d5](https://github.com/Ombi-app/Ombi/commit/d5ef1d53e5f77d19dba8b8059c80b538a3e14f2a)) +* Improve Swagger documentation ([#4652](https://github.com/Ombi-app/Ombi/issues/4652)) ([181892b](https://github.com/Ombi-app/Ombi/commit/181892bcfe88e6d76febf49ef57745d04552d08e)) +* Missing Poster broken link fix ([#4637](https://github.com/Ombi-app/Ombi/issues/4637)) ([4070f0d](https://github.com/Ombi-app/Ombi/commit/4070f0d093b1c92487a1c80cabad8283a9650f51)) +* **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](https://github.com/Ombi-app/Ombi/issues/4648)) ([6d16442](https://github.com/Ombi-app/Ombi/commit/6d16442d4d714920367df065a3ced42b729f4233)) +* **sync:** Emby+Jellyfin - sync multi-episode files of 3+ episodes ([bd8fd89](https://github.com/Ombi-app/Ombi/commit/bd8fd890554c9d85d6da4d2cee813e82ce698e52)) -### 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)) +# [4.20.0](https://github.com/Ombi-app/Ombi/compare/v4.19.1...v4.20.0) (2022-04-28) +### Features -## [4.15.1](https://github.com/Ombi-app/Ombi/compare/v4.15.0...v4.15.1) (2022-03-18) +* **discover:** Show more relevant shows in upcoming TV ([8357819](https://github.com/Ombi-app/Ombi/commit/8357819b53b8c675c0b246d7006b5a778bdba33f)) -### 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)) +## [4.19.1](https://github.com/Ombi-app/Ombi/compare/v4.19.0...v4.19.1) (2022-04-27) -# [4.15.0](https://github.com/Ombi-app/Ombi/compare/v4.14.4...v4.15.0) (2022-03-17) +# [4.19.0](https://github.com/Ombi-app/Ombi/compare/v4.18.0...v4.19.0) (2022-04-27) -### Bug Fixes +### Features -* **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) +* **sync:** Detect reidentified movies in Emby and Jellyfin ([5938077](https://github.com/Ombi-app/Ombi/commit/5938077d82a5357f79c07b218b3986557a5816e8)) +* **sync:** Detect reidentified series in Emby and Jellyfin ([9096e91](https://github.com/Ombi-app/Ombi/commit/9096e91d55d268819bce22831f8a8b27f2a1776b)) -## [4.14.4](https://github.com/Ombi-app/Ombi/compare/v4.14.3...v4.14.4) (2022-03-10) +# [4.18.0](https://github.com/Ombi-app/Ombi/compare/v4.17.0...v4.18.0) (2022-04-26) ### 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) -* **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)) +* **discover:** Fix cache mix up ([03d9422](https://github.com/Ombi-app/Ombi/commit/03d94220c7eaafb50c6c80a6ed1150794b873ac3)) +* **discover:** Fix new trending feature detection ([6794b88](https://github.com/Ombi-app/Ombi/commit/6794b887f6544fb41528bdd9728b7824b65e47ee)) +* **settings:** Allow toggling features when there are more than one ([a373359](https://github.com/Ombi-app/Ombi/commit/a373359ae8e6bad42b558a6e01a8ff2840d3bbaa)) +### Features -## [4.14.3](https://github.com/Ombi-app/Ombi/compare/v4.14.2...v4.14.3) (2022-03-06) +* **discover:** Add new trending source experimental feature ([1a0823c](https://github.com/Ombi-app/Ombi/commit/1a0823ca80559417c67323aaeaa1ef5243e98031)) +* **discover:** Default trending source to new logic ([4f12939](https://github.com/Ombi-app/Ombi/commit/4f12939e22020a67a5ee75e2907923faea136e8d)) -### Bug Fixes -* **availability:** :bug: Fixed an issue where with 4k content, we could repeat notifications ([f9ebc1c](https://github.com/Ombi-app/Ombi/commit/f9ebc1cc2e13c7cd335121cd86295b10eda529ba)) +# [4.17.0](https://github.com/Ombi-app/Ombi/compare/v4.16.17...v4.17.0) (2022-04-25) +### Features -## [4.14.2](https://github.com/Ombi-app/Ombi/compare/v4.14.1...v4.14.2) (2022-03-05) +* **discover:** Add original language filter ([ef7ec86](https://github.com/Ombi-app/Ombi/commit/ef7ec861d8aede2a4817752c990617f583805391)) -### 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) +## [4.16.17](https://github.com/Ombi-app/Ombi/compare/v4.16.16...v4.16.17) (2022-04-25) -## [4.14.1](https://github.com/Ombi-app/Ombi/compare/v4.14.0...v4.14.1) (2022-03-03) +## [4.16.16](https://github.com/Ombi-app/Ombi/compare/v4.16.15...v4.16.16) (2022-04-25) +### Bug Fixes -# [4.14.0](https://github.com/Ombi-app/Ombi/compare/v4.13.2...v4.14.0) (2022-03-02) +* **4616:** :bug: fixed mandatory fields ([d8f2260](https://github.com/Ombi-app/Ombi/commit/d8f2260c7ae3ed48386743b7adbd06e284487034)) -## [4.13.2](https://github.com/Ombi-app/Ombi/compare/v4.13.1...v4.13.2) (2022-03-01) +## [4.16.15](https://github.com/Ombi-app/Ombi/compare/v4.16.14...v4.16.15) (2022-04-24) -### 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)) +## [4.16.14](https://github.com/Ombi-app/Ombi/compare/v4.16.13...v4.16.14) (2022-04-19) -## [4.13.1](https://github.com/Ombi-app/Ombi/compare/v4.13.0...v4.13.1) (2022-03-01) +## [4.16.13](https://github.com/Ombi-app/Ombi/compare/v4.16.12...v4.16.13) (2022-04-19) -### 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)) -* **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.16.12](https://github.com/Ombi-app/Ombi/compare/v4.16.11...v4.16.12) (2022-04-19) -# [4.13.0](https://github.com/Ombi-app/Ombi/compare/v4.12.7...v4.13.0) (2022-02-25) +## [4.16.11](https://github.com/Ombi-app/Ombi/compare/v4.16.10...v4.16.11) (2022-04-14) ### 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)) -* **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)) +* Set the default job for the watchlist import to hourly instead of daily ([75906af](https://github.com/Ombi-app/Ombi/commit/75906af0adee3e3c68d825c3aaa8f7b918461b1f)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([0e8a64b](https://github.com/Ombi-app/Ombi/commit/0e8a64b8ca00d210fbe843ac2c3f6af218d80cbc)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([7b0ad61](https://github.com/Ombi-app/Ombi/commit/7b0ad61bfcff3986b33180dc64022cba7ea8eefb)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([4fc2c1f](https://github.com/Ombi-app/Ombi/commit/4fc2c1f24534085a783a3d5791f5533b68272153)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([76ab733](https://github.com/Ombi-app/Ombi/commit/76ab733b91791e4d93d184f3c7d0779c6a388695)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([06e4cef](https://github.com/Ombi-app/Ombi/commit/06e4cefa7b4e55b860da9a64f461f6ec8fa17367)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([c12d89d](https://github.com/Ombi-app/Ombi/commit/c12d89d6781a337520977ad285f8d08c93f434dd)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([bc0c2f6](https://github.com/Ombi-app/Ombi/commit/bc0c2f622e34fb5a2711039d9ed7aad34f982b15)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([e4b00e6](https://github.com/Ombi-app/Ombi/commit/e4b00e6b3468bd9389eeb02fc6ad7daf27abc3b3)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([d1998d3](https://github.com/Ombi-app/Ombi/commit/d1998d326f999a38586d0a351a20c5448df95842)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([bee4ccb](https://github.com/Ombi-app/Ombi/commit/bee4ccb804594e7385b1fbdc9fe2ef5c42e0d21f)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([80233ed](https://github.com/Ombi-app/Ombi/commit/80233ed560cc976e83570d0655c3472f20171fb3)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([8a78adc](https://github.com/Ombi-app/Ombi/commit/8a78adc9bb62f277f2b213dcb3847ed6d0089fcb)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([d04c60a](https://github.com/Ombi-app/Ombi/commit/d04c60aa5909b47ba6bffa6f66b03079cbd43521)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([92a785e](https://github.com/Ombi-app/Ombi/commit/92a785e736fa4b72a45270da2d0f4661df433078)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([634982d](https://github.com/Ombi-app/Ombi/commit/634982df2661cefab5ea9f5163fe04a005cc0171)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([b404baa](https://github.com/Ombi-app/Ombi/commit/b404baad6d0aeaa1561701e0db8db4e78613a364)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([d14f11e](https://github.com/Ombi-app/Ombi/commit/d14f11e0eb20ab0a68e765ee77968b3b3e54e995)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([7cf64f9](https://github.com/Ombi-app/Ombi/commit/7cf64f909d78908edaabeffb8a39a7d02e73fe7e)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([0c9e1ec](https://github.com/Ombi-app/Ombi/commit/0c9e1ec090827080cc8f7393e5e91456ff37d691)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([3b0b730](https://github.com/Ombi-app/Ombi/commit/3b0b730cb02efe24f6d4026e5fdb20d37e495119)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([6ed1a03](https://github.com/Ombi-app/Ombi/commit/6ed1a03b7ff4077f09ea9e13394b18b0d138f4c3)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([2941acd](https://github.com/Ombi-app/Ombi/commit/2941acd3b2ec74a5e6aeea275ab5a39d2653f37f)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([c075a1a](https://github.com/Ombi-app/Ombi/commit/c075a1a66784d975eaf60f2dfbbcbe048f2f63d7)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([76bd81c](https://github.com/Ombi-app/Ombi/commit/76bd81c3ca55a98c6ec944a838dc01294a6193a6)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([0d38275](https://github.com/Ombi-app/Ombi/commit/0d3827507e002bcf58f673e97ffcc3bd25dcf337)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([5c99601](https://github.com/Ombi-app/Ombi/commit/5c99601b07aec1a65d0186a4c4327440811e64c6)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([01546a0](https://github.com/Ombi-app/Ombi/commit/01546a0f7f86379528b486463246ef9bdfb9033e)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([d7fea78](https://github.com/Ombi-app/Ombi/commit/d7fea7843aaaab7ddff8dc31ca6d2a9117471dcc)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([1a6b95d](https://github.com/Ombi-app/Ombi/commit/1a6b95d45c220310213b8d811272a63f0f6ff42b)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([fa10174](https://github.com/Ombi-app/Ombi/commit/fa1017422c4efd4b0897871bd3c671151774d7c3)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([0c31e62](https://github.com/Ombi-app/Ombi/commit/0c31e628df376aac6d56ae67c7c705a9a4a7c080)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([6399643](https://github.com/Ombi-app/Ombi/commit/63996437a02fe10ffae6822ffa15369bec0a6b36)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([5826e2d](https://github.com/Ombi-app/Ombi/commit/5826e2d9a1c3f1210a87fa270dc0c81bac32944a)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([d434514](https://github.com/Ombi-app/Ombi/commit/d43451405be489254d7cdc7755d5f516a1e495a5)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([0b9596d](https://github.com/Ombi-app/Ombi/commit/0b9596d807178f5e071113ec0347868ec7f0960b)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([8c4c0b2](https://github.com/Ombi-app/Ombi/commit/8c4c0b262978c1303767af360d802c4b4c2b4d24)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([289ab77](https://github.com/Ombi-app/Ombi/commit/289ab77b0e04aae235b6f6cebc86e0a8d1f0cf2b)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([30e3417](https://github.com/Ombi-app/Ombi/commit/30e3417285a4eed18d429d7776f0e74096e834c0)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([6c0a5da](https://github.com/Ombi-app/Ombi/commit/6c0a5dadd4b8f37760252eb0fe7f88908f55506d)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([d5bf969](https://github.com/Ombi-app/Ombi/commit/d5bf9692ce1fc0ccfe7beca6dd200c78be177bdc)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([8a9e7ea](https://github.com/Ombi-app/Ombi/commit/8a9e7ea588aefbcd73ed82625887e3614e1703ea)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([01047a3](https://github.com/Ombi-app/Ombi/commit/01047a3fd67153f3ff16f860d2c7b50213e8d9b2)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([698a23f](https://github.com/Ombi-app/Ombi/commit/698a23fb83f323cdd1dd57cb49803079d44214a7)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([24eb842](https://github.com/Ombi-app/Ombi/commit/24eb842fc4424f7bcc3ec2949d7f5472492e96f6)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([ac8b16a](https://github.com/Ombi-app/Ombi/commit/ac8b16a3051ad71dbd54a8973c7dd847b564a515)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([f428ce6](https://github.com/Ombi-app/Ombi/commit/f428ce6a700c081437703839bc84d2f2b1138bcc)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([94b16df](https://github.com/Ombi-app/Ombi/commit/94b16dfe09bf1d2cd6286777d74eb5d4496abbbb)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([4881775](https://github.com/Ombi-app/Ombi/commit/4881775eda69a8f136ce0d8fbbf970e3d0406dc9)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([8297db9](https://github.com/Ombi-app/Ombi/commit/8297db91e85da308bde6fb09ad78347dee063630)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([d1152ab](https://github.com/Ombi-app/Ombi/commit/d1152ab7674243daa528c524c0cdc87d81ad49c9)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([eb2788b](https://github.com/Ombi-app/Ombi/commit/eb2788b761b55c487a59a049427ca08f6c10e836)) +* **translations:** 🌐 New translations from Crowdin [skip ci] ([21a794c](https://github.com/Ombi-app/Ombi/commit/21a794cbc0a5fa735ca0347c8f7f1ac04a487fbc)) -### 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)) -* **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.10.2](https://github.com/Ombi-app/Ombi/compare/v4.10.1...v4.10.2) (2022-01-22) -## [4.12.7](https://github.com/Ombi-app/Ombi/compare/v4.12.6...v4.12.7) (2022-02-23) +## [4.16.10](https://github.com/Ombi-app/Ombi/compare/v4.16.9...v4.16.10) (2022-04-13) -## [4.12.6](https://github.com/Ombi-app/Ombi/compare/v4.12.5...v4.12.6) (2022-02-22) +## [4.16.9](https://github.com/Ombi-app/Ombi/compare/v4.16.8...v4.16.9) (2022-04-13) ### 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)) -* **mediaserver:** fixed some more issues in the media server sync and availability checks ([f3ea979](https://github.com/Ombi-app/Ombi/commit/f3ea979b8bd77842780ce8e6928b16237dd779cf)) +* **plex-watchlist:** Only request the latest season when importing from the watchlist ([77a47ff](https://github.com/Ombi-app/Ombi/commit/77a47ff157c6c5feafe3f2a29a3fcba8df4fdfef)) -## [4.12.5](https://github.com/Ombi-app/Ombi/compare/v4.12.4...v4.12.5) (2022-02-21) +## [4.16.8](https://github.com/Ombi-app/Ombi/compare/v4.16.7...v4.16.8) (2022-04-13) ### 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)) -* **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)) +* **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)) -## [4.12.4](https://github.com/Ombi-app/Ombi/compare/v4.12.3...v4.12.4) (2022-02-17) +## [4.16.7](https://github.com/Ombi-app/Ombi/compare/v4.16.6...v4.16.7) (2022-04-12) -## [4.12.3](https://github.com/Ombi-app/Ombi/compare/v4.12.2...v4.12.3) (2022-02-16) +## [4.16.6](https://github.com/Ombi-app/Ombi/compare/v4.16.5...v4.16.6) (2022-04-11) -## [4.12.2](https://github.com/Ombi-app/Ombi/compare/v4.12.1...v4.12.2) (2022-02-16) +## [4.16.5](https://github.com/Ombi-app/Ombi/compare/v4.16.4...v4.16.5) (2022-04-08) ### 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)) +* **watchlist:** actually fixed it this time... ([d962a32](https://github.com/Ombi-app/Ombi/commit/d962a3211eca29520662ddce962676e3aea17ec5)) + + + +## [4.16.4](https://github.com/Ombi-app/Ombi/compare/v4.16.3...v4.16.4) (2022-04-08) -## [4.12.1](https://github.com/Ombi-app/Ombi/compare/v4.12.0...v4.12.1) (2022-02-16) +## [4.16.3](https://github.com/Ombi-app/Ombi/compare/v4.16.2...v4.16.3) (2022-04-08) ### 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) +* **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)) -# [4.12.0](https://github.com/Ombi-app/Ombi/compare/v4.11.8...v4.12.0) (2022-02-14) +## [4.16.2](https://github.com/Ombi-app/Ombi/compare/v4.16.1...v4.16.2) (2022-04-07) -### Features +### Bug Fixes -* **radarr:** 4K Requests and Radarr 4K support ([ba88848](https://github.com/Ombi-app/Ombi/commit/ba88848866b0a9dedb1e79b55c4d81a0fd453843)) +* **wizard:** Fixed an issue when using Plex OAuth it could fail setting up ([b743cf4](https://github.com/Ombi-app/Ombi/commit/b743cf4fafa7341ad1b163276f006d7ab0e9dcff)) -## [4.11.8](https://github.com/Ombi-app/Ombi/compare/v4.11.7...v4.11.8) (2022-02-13) +## [4.16.1](https://github.com/Ombi-app/Ombi/compare/v4.16.0...v4.16.1) (2022-04-07) -### 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)) +# [4.16.0](https://github.com/Ombi-app/Ombi/compare/v4.15.6...v4.16.0) (2022-04-07) -## [4.11.7](https://github.com/Ombi-app/Ombi/compare/v4.11.6...v4.11.7) (2022-02-12) +## [4.15.6](https://github.com/Ombi-app/Ombi/compare/v4.15.5...v4.15.6) (2022-04-07) ### Bug Fixes -* **notifications:** :bug: This is a fix for some of the duplicate notification issues [#3825](https://github.com/Ombi-app/Ombi/issues/3825) ([22bb422](https://github.com/Ombi-app/Ombi/commit/22bb4226ead2d62e8c2c2c05be47d7da621402e2)) +* **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)) + +## [4.15.5](https://github.com/Ombi-app/Ombi/compare/v4.15.4...v4.15.5) (2022-04-06) -## [4.11.6](https://github.com/Ombi-app/Ombi/compare/v4.11.5...v4.11.6) (2022-02-10) -### Bug Fixes +## [4.15.4](https://github.com/Ombi-app/Ombi/compare/v4.15.3...v4.15.4) (2022-03-29) + + -* **plex:** Fixed an issue where in a rare case we couldn't sync the data [#4502](https://github.com/Ombi-app/Ombi/issues/4502) ([191318d](https://github.com/Ombi-app/Ombi/commit/191318ddad5a8148422955bf928f1c49b890e3eb)) +## [4.15.3](https://github.com/Ombi-app/Ombi/compare/v4.15.2...v4.15.3) (2022-03-24) -## [4.11.5](https://github.com/Ombi-app/Ombi/compare/v4.11.4...v4.11.5) (2022-02-05) +## [4.15.2](https://github.com/Ombi-app/Ombi/compare/v4.15.1...v4.15.2) (2022-03-23) ### Bug Fixes -* **sonarr:** Fixed where requesting all seasons would only mark the latest as monitored [#4496](https://github.com/Ombi-app/Ombi/issues/4496) ([cfb85c2](https://github.com/Ombi-app/Ombi/commit/cfb85c23d77626b9ec1d99a6cf76497c438d0338)) +* **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)) diff --git a/README.md b/README.md index 32552817e..c8fde6e73 100644 --- a/README.md +++ b/README.md @@ -100,17 +100,17 @@ Here are some of the features Ombi has: - - anojht + + sephrat
- Anojh Thayaparan + Sephrat
- - sephrat + + anojht
- Sephrat + Anojh Thayaparan
@@ -222,10 +222,10 @@ Here are some of the features Ombi has: - - steffokeffo + + grimsan55
- Steffokeffo + Stefan
@@ -286,6 +286,13 @@ Here are some of the features Ombi has: + + + dr3am37 +
+ Dr3amer +
+ mhann @@ -320,15 +327,15 @@ Here are some of the features Ombi has:
Austin Jackson
- + + D34DC3N73R
D34DC3N73R
- - + pooley182 @@ -363,15 +370,15 @@ Here are some of the features Ombi has:
Jeffrey Peters
- + + MariusSchiffer
Marius Schiffer
- - + Qstick @@ -406,15 +413,15 @@ Here are some of the features Ombi has:
Abe Kline
- + + XanderStrike
Alexander Standke
- - + Alasano @@ -449,15 +456,15 @@ Here are some of the features Ombi has:
Chris Lees
- + + cdemi
Christopher Demicoli
- - + Codehhh @@ -492,15 +499,15 @@ Here are some of the features Ombi has:
Devin Buhl
- + + elisspace
Eli
- - + Fish2 @@ -515,6 +522,13 @@ Here are some of the features Ombi has: Haries Ramdhani + + + comigor +
+ Igor Borges +
+ ImgBotApp @@ -528,7 +542,8 @@ Here are some of the features Ombi has:
Jacob Pyke
- + + jamesmacwhite @@ -542,8 +557,7 @@ Here are some of the features Ombi has:
Joe Groocock
- - + errorhandler @@ -571,7 +585,8 @@ Here are some of the features Ombi has:
Kris Klosterman
- + + kmlucy @@ -585,8 +600,14 @@ Here are some of the features Ombi has:
Lightkeeper
- - + + + + Lucane +
+ Lucane +
+ devbymadde @@ -594,13 +615,21 @@ Here are some of the features Ombi has: Madeleine Schönemann + + + marleypowell +
+ Marley +
+ mattmattmatt
Matt
- + + LMaxence @@ -628,8 +657,7 @@ Here are some of the features Ombi has:
Qiming Chen
- - + randallbruder @@ -643,7 +671,8 @@ Here are some of the features Ombi has:
Rob Gökemeijer
- + + sambartik @@ -665,21 +694,28 @@ Here are some of the features Ombi has: Shoghi + + + Teifun2 +
+ Teifun2 +
+ thomasvt1
Thomas Van Tilburg
- - + Tim-Trott
Tim Trott
- + + tombomb @@ -714,15 +750,15 @@ Here are some of the features Ombi has:
Tim OBrien
- - + x-limitless-x
Blake Drumm
- + + camjac251 @@ -744,6 +780,13 @@ Here are some of the features Ombi has: Dorian ALKOUM + + + echel0n +
+ Echel0n +
+ m4tta @@ -766,6 +809,13 @@ Here are some of the features Ombi has: Patrick Weber + + + mkgeeky +
+ Mkgeeky +
+ sir-marv diff --git a/src/.dockerignore b/src/.dockerignore new file mode 100644 index 000000000..252736d92 --- /dev/null +++ b/src/.dockerignore @@ -0,0 +1,285 @@ +**/bin/ +**/obj/ +**/.angular/ +**/node_modules/ +.gitignore +.git/ + + +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ +Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ +tools/Cake.CoreCLR +.vscode +tools +.dotnet +Dockerfile + +# .env file contains default environment variables for docker +.env +.git/ \ No newline at end of file diff --git a/src/Ombi.Api.Emby/EmbyApi.cs b/src/Ombi.Api.Emby/EmbyApi.cs index a1791494d..e9e5f0fca 100644 --- a/src/Ombi.Api.Emby/EmbyApi.cs +++ b/src/Ombi.Api.Emby/EmbyApi.cs @@ -106,7 +106,7 @@ namespace Ombi.Api.Emby request.AddQueryString("Fields", "ProviderIds,Overview"); - request.AddQueryString("IsVirtualItem", "False"); + request.AddQueryString("IsMissing", "False"); return await Api.Request>(request); } @@ -180,7 +180,7 @@ namespace Ombi.Api.Emby request.AddQueryString("ParentId", parentIdFilder); } - request.AddQueryString("IsVirtualItem", "False"); + request.AddQueryString("IsMissing", "False"); AddHeaders(request, apiKey); @@ -207,7 +207,7 @@ namespace Ombi.Api.Emby request.AddQueryString("IncludeItemTypes", type); request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); - request.AddQueryString("IsVirtualItem", "False"); + request.AddQueryString("IsMissing", "False"); AddHeaders(request, apiKey); @@ -229,7 +229,7 @@ namespace Ombi.Api.Emby request.AddQueryString("ParentId", parentIdFilder); } - request.AddQueryString("IsVirtualItem", "False"); + request.AddQueryString("isMissing", "False"); AddHeaders(request, apiKey); diff --git a/src/Ombi.Api.Jellyfin/JellyfinApi.cs b/src/Ombi.Api.Jellyfin/JellyfinApi.cs index f6afb7912..0604cb984 100644 --- a/src/Ombi.Api.Jellyfin/JellyfinApi.cs +++ b/src/Ombi.Api.Jellyfin/JellyfinApi.cs @@ -82,7 +82,7 @@ namespace Ombi.Api.Jellyfin request.AddQueryString("Fields", "ProviderIds,Overview"); - request.AddQueryString("IsVirtualItem", "False"); + request.AddQueryString("isMissing", "False"); return await Api.Request>(request); } @@ -143,7 +143,7 @@ namespace Ombi.Api.Jellyfin request.AddQueryString("IncludeItemTypes", type); request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); - request.AddQueryString("IsVirtualItem", "False"); + request.AddQueryString("isMissing", "False"); AddHeaders(request, apiKey); @@ -165,7 +165,7 @@ namespace Ombi.Api.Jellyfin request.AddQueryString("ParentId", parentIdFilder); } - request.AddQueryString("IsVirtualItem", "False"); + request.AddQueryString("isMissing", "False"); AddHeaders(request, apiKey); diff --git a/src/Ombi.Api.Plex/Models/Metadata.cs b/src/Ombi.Api.Plex/Models/Metadata.cs index 3481e9b57..97cab391b 100644 --- a/src/Ombi.Api.Plex/Models/Metadata.cs +++ b/src/Ombi.Api.Plex/Models/Metadata.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Newtonsoft.Json; namespace Ombi.Api.Plex.Models { diff --git a/src/Ombi.Api.SickRage/Models/SickRageEpisodeStatus.cs b/src/Ombi.Api.SickRage/Models/SickRageEpisodeStatus.cs index 4f131d5e3..9142e2b42 100644 --- a/src/Ombi.Api.SickRage/Models/SickRageEpisodeStatus.cs +++ b/src/Ombi.Api.SickRage/Models/SickRageEpisodeStatus.cs @@ -9,7 +9,7 @@ public class SickRageEpisodeSetStatus { - public Data data { get; set; } + public Data[] data { get; set; } public string message { get; set; } public string result { get; set; } } diff --git a/src/Ombi.Api.Sonarr/SonarrApi.cs b/src/Ombi.Api.Sonarr/SonarrApi.cs index d2df50cd9..50e4f04ff 100644 --- a/src/Ombi.Api.Sonarr/SonarrApi.cs +++ b/src/Ombi.Api.Sonarr/SonarrApi.cs @@ -19,7 +19,7 @@ namespace Ombi.Api.Sonarr protected IApi Api { get; } protected virtual string ApiBaseUrl => "/api/"; - public async Task> GetProfiles(string apiKey, string baseUrl) + public virtual async Task> GetProfiles(string apiKey, string baseUrl) { var request = new Request($"{ApiBaseUrl}profile", baseUrl, HttpMethod.Get); request.AddHeader("X-Api-Key", apiKey); diff --git a/src/Ombi.Api.Sonarr/SonarrV3Api.cs b/src/Ombi.Api.Sonarr/SonarrV3Api.cs index 64377ee4a..71c230f35 100644 --- a/src/Ombi.Api.Sonarr/SonarrV3Api.cs +++ b/src/Ombi.Api.Sonarr/SonarrV3Api.cs @@ -1,6 +1,8 @@ using System.Net.Http; using System.Collections.Generic; using System.Threading.Tasks; + +using Ombi.Api.Sonarr.Models; using Ombi.Api.Sonarr.Models.V3; namespace Ombi.Api.Sonarr @@ -21,5 +23,12 @@ namespace Ombi.Api.Sonarr return await Api.Request>(request); } + + public override async Task> GetProfiles(string apiKey, string baseUrl) + { + var request = new Request($"{ApiBaseUrl}qualityprofile", baseUrl, HttpMethod.Get); + request.AddHeader("X-Api-Key", apiKey); + return await Api.Request>(request); + } } } diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index cb4cb4ba6..e5114677b 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -136,6 +136,7 @@ namespace Ombi.Core.Engine Status = movieInfo.Status, RequestedDate = model.Is4kRequest ? DateTime.MinValue : DateTime.Now, Approved = false, + Approved4K = false, RequestedUserId = canRequestOnBehalf ? model.RequestOnBehalf : userDetails.Id, Background = movieInfo.BackdropPath, LangCode = model.LanguageCode, @@ -151,7 +152,7 @@ namespace Ombi.Core.Engine var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US"); requestModel.DigitalReleaseDate = usDates?.ReleaseDate ?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate; - + var ruleResults = (await RunRequestRules(requestModel)).ToList(); var ruleResultInError = ruleResults.Find(x => !x.Success); if (ruleResultInError != null) @@ -163,7 +164,7 @@ namespace Ombi.Core.Engine }; } - if (requestModel.Approved) // The rules have auto approved this + if (requestModel.Approved || requestModel.Approved4K) // The rules have auto approved this { var requestEngineResult = await AddMovieRequest(requestModel, fullMovieName, model.RequestOnBehalf, isExisting, is4kRequest); if (requestEngineResult.Result) @@ -333,7 +334,7 @@ namespace Ombi.Core.Engine allRequests = allRequests.Where(x => (x.RequestedDate != DateTime.MinValue && x.Approved && !x.Available && (!x.Denied.HasValue || !x.Denied.Value)) || - (x.Has4KRequest && x.Approved4K && !x.Available && (!x.Denied.HasValue || !x.Denied.Value)) + (x.Has4KRequest && x.Approved4K && !x.Available4K && (!x.Denied4K.HasValue || !x.Denied4K.Value)) ); break; case RequestStatus.Available: @@ -533,7 +534,10 @@ namespace Ombi.Core.Engine } else { - x.ShowSubscribe = true; + if (!x.Available && !x.Available4K && (!x.Denied ?? true) && (!x.Denied4K ?? true)) + { + x.ShowSubscribe = true; + } var hasSub = sub.FirstOrDefault(r => r.RequestId == x.Id); x.Subscribed = hasSub != null; } diff --git a/src/Ombi.Core/Engine/MovieSearchEngine.cs b/src/Ombi.Core/Engine/MovieSearchEngine.cs index 682a999eb..ae24e275a 100644 --- a/src/Ombi.Core/Engine/MovieSearchEngine.cs +++ b/src/Ombi.Core/Engine/MovieSearchEngine.cs @@ -16,7 +16,6 @@ using Ombi.Store.Repository; using System; using System.Collections.Generic; using System.Linq; -using System.Security.Principal; using System.Threading.Tasks; namespace Ombi.Core.Engine @@ -161,7 +160,7 @@ namespace Ombi.Core.Engine var result = await Cache.GetOrAddAsync(CacheKeys.UpcomingMovies, async () => { var langCode = await DefaultLanguageCode(null); - return await MovieApi.Upcoming(langCode); + return await MovieApi.UpcomingMovies(langCode); }, DateTimeOffset.Now.AddHours(12)); if (result != null) { @@ -216,34 +215,9 @@ namespace Ombi.Core.Engine await RunSearchRules(viewMovie); - // This requires the rules to be run first to populate the RequestId property - await CheckForSubscription(viewMovie); - return viewMovie; } - private async Task CheckForSubscription(SearchMovieViewModel viewModel) - { - // Check if this user requested it - var user = await GetUser(); - if (user == null) - { - return; - } - var request = await RequestService.MovieRequestService.GetAll() - .AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id); - if (request || viewModel.Available) - { - viewModel.ShowSubscribe = false; - } - else - { - viewModel.ShowSubscribe = true; - var sub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(s => s.UserId == user.Id - && s.RequestId == viewModel.RequestId && s.RequestType == RequestType.Movie); - viewModel.Subscribed = sub != null; - } - } private async Task ProcessSingleMovie(MovieDbSearchResult movie) { diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs index 640d2ade3..67caa81a2 100644 --- a/src/Ombi.Core/Engine/MusicRequestEngine.cs +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -270,7 +270,10 @@ namespace Ombi.Core.Engine } else { - x.ShowSubscribe = true; + if (!x.Available && (!x.Denied ?? false)) + { + x.ShowSubscribe = true; + } var hasSub = sub.FirstOrDefault(r => r.RequestId == x.Id); x.Subscribed = hasSub != null; } diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 5664d276f..8ccc6d17e 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -886,7 +886,10 @@ namespace Ombi.Core.Engine } else { - x.ShowSubscribe = true; + if (!x.Available && (!x.Denied ?? true)) + { + x.ShowSubscribe = true; + } var result = relevantSubs.FirstOrDefault(s => s.RequestId == x.Id); x.Subscribed = result != null; } diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index 2747f262a..ac2ed9979 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -106,11 +106,17 @@ namespace Ombi.Core.Engine SeasonNumber = e.season, Episodes = new List() }; + var hasAirDate = e.airstamp.HasValue(); + var airDate = DateTime.MinValue; + if (hasAirDate) + { + hasAirDate = DateTime.TryParse(e.airdate, out airDate); + } newSeason.Episodes.Add(new EpisodeRequests { Url = e.url.ToHttpsUrl(), Title = e.name, - AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue, + AirDate = airDate, EpisodeNumber = e.number, }); @@ -118,12 +124,18 @@ namespace Ombi.Core.Engine } else { + var hasAirDate = e.airstamp.HasValue(); + var airDate = DateTime.MinValue; + if (hasAirDate) + { + hasAirDate = DateTime.TryParse(e.airdate, out airDate); + } // We already have the season, so just add the episode season.Episodes.Add(new EpisodeRequests { Url = e.url.ToHttpsUrl(), Title = e.name, - AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue, + AirDate = airDate, EpisodeNumber = e.number, }); } diff --git a/src/Ombi.Core/Engine/V2/IMultiSearchEngine.cs b/src/Ombi.Core/Engine/V2/IMultiSearchEngine.cs index 7ad8c509a..7666db6fe 100644 --- a/src/Ombi.Core/Engine/V2/IMultiSearchEngine.cs +++ b/src/Ombi.Core/Engine/V2/IMultiSearchEngine.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Ombi.Api.TheMovieDb.Models; using Ombi.Core.Models.Search.V2; +using Ombi.TheMovieDbApi.Models; // Due to conflicting Genre models in // Ombi.TheMovieDbApi.Models and Ombi.Api.TheMovieDb.Models @@ -14,5 +15,6 @@ namespace Ombi.Core.Engine.V2 { Task> MultiSearch(string searchTerm, MultiSearchFilter filter, CancellationToken cancellationToken); Task> GetGenres(string media, CancellationToken requestAborted); + Task> GetLanguages(CancellationToken requestAborted); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs index 3acd7d1f0..044d79969 100644 --- a/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/MovieSearchEngineV2.cs @@ -11,6 +11,7 @@ using Ombi.Core.Models.Search; using Ombi.Core.Models.Search.V2; using Ombi.Core.Models.UI; using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Services; using Ombi.Core.Settings; using Ombi.Helpers; using Ombi.Settings.Settings.Models; @@ -31,7 +32,8 @@ namespace Ombi.Core.Engine.V2 { public MovieSearchEngineV2(ICurrentUser identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper, ILogger logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService s, IRepository sub, - ISettingsService customizationSettings, IMovieRequestEngine movieRequestEngine, IHttpClientFactory httpClientFactory) + ISettingsService customizationSettings, IMovieRequestEngine movieRequestEngine, IHttpClientFactory httpClientFactory, + IFeatureService feature) : base(identity, service, r, um, mem, s, sub) { MovieApi = movApi; @@ -40,6 +42,7 @@ namespace Ombi.Core.Engine.V2 _customizationSettings = customizationSettings; _movieRequestEngine = movieRequestEngine; _client = httpClientFactory.CreateClient(); + _feature = feature; } private IMovieDbApi MovieApi { get; } @@ -48,6 +51,7 @@ namespace Ombi.Core.Engine.V2 private readonly ISettingsService _customizationSettings; private readonly IMovieRequestEngine _movieRequestEngine; private readonly HttpClient _client; + private readonly IFeatureService _feature; public async Task GetFullMovieInformation(int theMovieDbId, CancellationToken cancellationToken, string langCode = null) { @@ -148,15 +152,15 @@ namespace Ombi.Core.Engine.V2 { var langCode = await DefaultLanguageCode(null); - //var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems); + var pages = PaginationHelper.GetNextPages(currentlyLoaded, toLoad, _theMovieDbMaxPageItems); var results = new List(); - //foreach (var pagesToLoad in pages) - //{ - var apiResult = await MovieApi.AdvancedSearch(model, cancellationToken); - //results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); - //} - return await TransformMovieResultsToResponse(apiResult); + foreach (var pagesToLoad in pages) + { + var apiResult = await MovieApi.AdvancedSearch(model, pagesToLoad.Page, cancellationToken); + results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); + } + return await TransformMovieResultsToResponse(results); } /// @@ -196,14 +200,19 @@ namespace Ombi.Core.Engine.V2 public async Task> NowPlayingMovies(int currentPosition, int amountToLoad) { var langCode = await DefaultLanguageCode(null); + var isOldTrendingSourceEnabled = await _feature.FeatureEnabled(FeatureNames.OldTrendingSource); var pages = PaginationHelper.GetNextPages(currentPosition, amountToLoad, _theMovieDbMaxPageItems); var results = new List(); foreach (var pagesToLoad in pages) { + var search = () => (isOldTrendingSourceEnabled) ? + MovieApi.NowPlaying(langCode, pagesToLoad.Page) + : MovieApi.TrendingMovies(langCode, pagesToLoad.Page); + var apiResult = await Cache.GetOrAddAsync(nameof(NowPlayingMovies) + pagesToLoad.Page + langCode, - () => MovieApi.NowPlaying(langCode, pagesToLoad.Page), DateTimeOffset.Now.AddHours(12)); + search, DateTimeOffset.Now.AddHours(12)); results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); } return await TransformMovieResultsToResponse(results); @@ -278,7 +287,7 @@ namespace Ombi.Core.Engine.V2 var result = await Cache.GetOrAddAsync(CacheKeys.UpcomingMovies, async () => { var langCode = await DefaultLanguageCode(null); - return await MovieApi.Upcoming(langCode); + return await MovieApi.UpcomingMovies(langCode); }, DateTimeOffset.Now.AddHours(12)); if (result != null) { @@ -298,7 +307,7 @@ namespace Ombi.Core.Engine.V2 foreach (var pagesToLoad in pages) { var apiResult = await Cache.GetOrAddAsync(nameof(UpcomingMovies) + pagesToLoad.Page + langCode, - () => MovieApi.Upcoming(langCode, pagesToLoad.Page), DateTimeOffset.Now.AddHours(12)); + () => MovieApi.UpcomingMovies(langCode, pagesToLoad.Page), DateTimeOffset.Now.AddHours(12)); results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); } return await TransformMovieResultsToResponse(results); @@ -393,8 +402,6 @@ namespace Ombi.Core.Engine.V2 await RunSearchRules(viewMovie); - // This requires the rules to be run first to populate the RequestId property - await CheckForSubscription(viewMovie); var mapped = Mapper.Map(movie); mapped.Available = viewMovie.Available; @@ -406,8 +413,6 @@ namespace Ombi.Core.Engine.V2 mapped.PlexUrl = viewMovie.PlexUrl; mapped.EmbyUrl = viewMovie.EmbyUrl; mapped.JellyfinUrl = viewMovie.JellyfinUrl; - mapped.Subscribed = viewMovie.Subscribed; - mapped.ShowSubscribe = viewMovie.ShowSubscribe; mapped.DigitalReleaseDate = viewMovie.DigitalReleaseDate; mapped.RequestedDate4k = viewMovie.RequestedDate4k; mapped.Approved4K = viewMovie.Approved4K; @@ -429,8 +434,6 @@ namespace Ombi.Core.Engine.V2 var mappedMovie = Mapper.Map(movie); await RunSearchRules(mappedMovie); - // This requires the rules to be run first to populate the RequestId property - await CheckForSubscription(mappedMovie); var mapped = Mapper.Map(movie); mapped.Available = movie.Available; @@ -440,8 +443,6 @@ namespace Ombi.Core.Engine.V2 mapped.PlexUrl = movie.PlexUrl; mapped.EmbyUrl = movie.EmbyUrl; mapped.JellyfinUrl = movie.JellyfinUrl; - mapped.Subscribed = movie.Subscribed; - mapped.ShowSubscribe = movie.ShowSubscribe; mapped.ReleaseDate = movie.ReleaseDate; } return viewMovie; @@ -470,34 +471,9 @@ namespace Ombi.Core.Engine.V2 await RunSearchRules(viewMovie); - // This requires the rules to be run first to populate the RequestId property - await CheckForSubscription(viewMovie); - return viewMovie; } - private async Task CheckForSubscription(SearchViewModel viewModel) - { - // Check if this user requested it - var user = await GetUser(); - if (user == null) - { - return; - } - var request = await RequestService.MovieRequestService.GetAll() - .AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id); - if (request) - { - viewModel.ShowSubscribe = false; - } - else - { - viewModel.ShowSubscribe = true; - var sub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(s => s.UserId == user.Id - && s.RequestId == viewModel.RequestId && s.RequestType == RequestType.Movie); - viewModel.Subscribed = sub != null; - } - } public async Task GetMovieInfoByImdbId(string imdbId, CancellationToken cancellationToken) { diff --git a/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs b/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs index 362a79e43..3f96f1598 100644 --- a/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs +++ b/src/Ombi.Core/Engine/V2/MultiSearchEngine.cs @@ -17,6 +17,7 @@ using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models.External; using Ombi.Store.Entities; using Ombi.Store.Repository; +using Ombi.TheMovieDbApi.Models; // Due to conflicting Genre models in // Ombi.TheMovieDbApi.Models and Ombi.Api.TheMovieDb.Models @@ -124,5 +125,9 @@ namespace Ombi.Core.Engine.V2 var lang = await DefaultLanguageCode(null); return await _movieDbApi.GetGenres(media, cancellationToken, lang); } + public async Task> GetLanguages(CancellationToken cancellationToken) + { + return await _movieDbApi.GetLanguages(cancellationToken); + } } } diff --git a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs index 344ced813..ee21db8dd 100644 --- a/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs +++ b/src/Ombi.Core/Engine/V2/TvSearchEngineV2.cs @@ -26,6 +26,7 @@ using System.Diagnostics; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Models.UI; using Ombi.Core.Helpers; +using Ombi.Core.Services; namespace Ombi.Core.Engine.V2 { @@ -37,10 +38,12 @@ namespace Ombi.Core.Engine.V2 private readonly IMovieDbApi _movieApi; private readonly ISettingsService _customization; private readonly ITvRequestEngine _requestEngine; + private readonly IFeatureService _feature; public TvSearchEngineV2(ICurrentUser identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache, ISettingsService s, - IRepository sub, IMovieDbApi movieApi, ISettingsService customization, ITvRequestEngine requestEngine) + IRepository sub, IMovieDbApi movieApi, ISettingsService customization, ITvRequestEngine requestEngine, + IFeatureService feature) : base(identity, service, r, um, memCache, s, sub) { _tvMaze = tvMaze; @@ -49,6 +52,7 @@ namespace Ombi.Core.Engine.V2 _movieApi = movieApi; _customization = customization; _requestEngine = requestEngine; + _feature = feature; } @@ -132,15 +136,19 @@ namespace Ombi.Core.Engine.V2 } public async Task> Trending(int currentlyLoaded, int amountToLoad) - { + { var langCode = await DefaultLanguageCode(null); + var isOldTrendingSourceEnabled = await _feature.FeatureEnabled(FeatureNames.OldTrendingSource); var pages = PaginationHelper.GetNextPages(currentlyLoaded, amountToLoad, ResultLimit); var results = new List(); foreach (var pagesToLoad in pages) { + var search = ( async () => (isOldTrendingSourceEnabled) ? + await _movieApi.TopRatedTv(langCode, pagesToLoad.Page) + : await _movieApi.TrendingTv(langCode, pagesToLoad.Page)); var apiResult = await Cache.GetOrAddAsync(nameof(Trending) + langCode + pagesToLoad.Page, - async () => await _movieApi.TopRatedTv(langCode, pagesToLoad.Page), DateTimeOffset.Now.AddHours(12)); + search, DateTimeOffset.Now.AddHours(12)); results.AddRange(apiResult.Skip(pagesToLoad.Skip).Take(pagesToLoad.Take)); } @@ -264,11 +272,17 @@ namespace Ombi.Core.Engine.V2 Overview = tvSeason.overview, Episodes = new List() }; + var hasAirDate = episode.air_date.HasValue(); + var airDate = DateTime.MinValue; + if (hasAirDate) + { + DateTime.TryParse(episode.air_date, out airDate); + } newSeason.Episodes.Add(new EpisodeRequests { //Url = episode...ToHttpsUrl(), Title = episode.name, - AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue, + AirDate = airDate, EpisodeNumber = episode.episode_number, }); @@ -276,12 +290,18 @@ namespace Ombi.Core.Engine.V2 } else { + var hasAirDate = episode.air_date.HasValue(); + var airDate = DateTime.MinValue; + if (hasAirDate) + { + DateTime.TryParse(episode.air_date, out airDate); + } // We already have the season, so just add the episode season.Episodes.Add(new EpisodeRequests { //Url = e.url.ToHttpsUrl(), Title = episode.name, - AirDate = episode.air_date.HasValue() ? DateTime.Parse(episode.air_date) : DateTime.MinValue, + AirDate = airDate, EpisodeNumber = episode.episode_number, }); } diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs index fc66d197d..5fe378465 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -261,9 +261,16 @@ namespace Ombi.Core.Helpers return this; } - private DateTime FormatDate(string date) + private static DateTime FormatDate(string date) { - return string.IsNullOrEmpty(date) ? DateTime.MinValue : DateTime.Parse(date); + if (date.HasValue()) + { + if (DateTime.TryParse(date, out var d)) + { + return d; + } + } + return DateTime.MinValue; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs index 4053109da..5bb4e61fd 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilderV2.cs @@ -9,6 +9,7 @@ using Ombi.Store.Entities; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository.Requests; using System.Threading; +using Ombi.Helpers; namespace Ombi.Core.Helpers { @@ -255,9 +256,16 @@ namespace Ombi.Core.Helpers return this; } - private DateTime FormatDate(string date) + private static DateTime FormatDate(string date) { - return string.IsNullOrEmpty(date) ? DateTime.MinValue : DateTime.Parse(date); + if (date.HasValue()) + { + if (DateTime.TryParse(date, out var d)) + { + return d; + } + } + return DateTime.MinValue; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Search/SearchViewModel.cs b/src/Ombi.Core/Models/Search/SearchViewModel.cs index e1a2f2850..4dda0444e 100644 --- a/src/Ombi.Core/Models/Search/SearchViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchViewModel.cs @@ -36,6 +36,7 @@ namespace Ombi.Core.Models.Search [Obsolete("Use request service instead")] public bool Subscribed { get; set; } [NotMapped] + [Obsolete("Use request service instead")] public bool ShowSubscribe { get; set; } } } diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs index 6390578dd..260b008fe 100644 --- a/src/Ombi.Core/Senders/MusicSender.cs +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -70,7 +70,7 @@ namespace Ombi.Core.Senders } - return new SenderResult { Success = false, Sent = false, Message = "Something went wrong!" }; + return new SenderResult { Success = false, Sent = false }; } private async Task SendToLidarr(AlbumRequest model, LidarrSettings settings) diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 3676d05ee..22f6aadcd 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -133,8 +133,7 @@ namespace Ombi.Core.Senders return new SenderResult { - Success = false, - Message = "Something went wrong!" + Success = false }; } @@ -343,8 +342,6 @@ namespace Ombi.Core.Senders await Task.Delay(500); } - var seriesChanges = false; - foreach (var season in model.SeasonRequests) { foreach (var ep in season.Episodes) @@ -359,72 +356,36 @@ namespace Ombi.Core.Senders } existingSeason = result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber); - var sonarrEpisodeList = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber).ToList(); - var sonarrEpCount = sonarrEpisodeList.Count; - var ourRequestCount = season.Episodes.Count; - - var ourEpisodes = season.Episodes.Select(x => x.EpisodeNumber).ToList(); - var unairedEpisodes = sonarrEpisodeList.Where(x => x.airDateUtc > DateTime.UtcNow).Select(x => x.episodeNumber).ToList(); - //// Check if we have requested all the latest episodes, if we have then monitor - //// NOTE, not sure if needed since ombi ui displays future episodes anyway... - //ourEpisodes.AddRange(unairedEpisodes); - //var distinctEpisodes = ourEpisodes.Distinct().ToList(); - //var missingEpisodes = Enumerable.Range(distinctEpisodes.Min(), distinctEpisodes.Count).Except(distinctEpisodes); - - if (sonarrEpCount == ourRequestCount /*|| !missingEpisodes.Any()*/) + // Make sure this season is set to monitored + if (!existingSeason.monitored) { - // We have the same amount of requests as all of the episodes in the season. + // We need to monitor it, problem being is all episodes will now be monitored + // So we need to monitor the series but unmonitor every episode existingSeason.monitored = true; - seriesChanges = true; + var sea = result.seasons.FirstOrDefault(x => x.seasonNumber == existingSeason.seasonNumber); + sea.monitored = true; - // We do not need to update the episodes as marking the season as monitored will mark the episodes as monitored. - var seasonToUpdate = result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber); - seasonToUpdate.monitored = true; // Update by ref - } - else - { - // Make sure this season is set to monitored - if (!existingSeason.monitored) + result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); + var epToUnmonitored = new List(); + var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the original member + foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList()) { - // We need to monitor it, problem being is all episodes will now be monitored - // So we need to monitor the series but unmonitor every episode - // Except the episodes that are already monitored before we update the series (we do not want to unmonitored episodes that are monitored beforehand) - existingSeason.monitored = true; - var sea = result.seasons.FirstOrDefault(x => x.seasonNumber == existingSeason.seasonNumber); - sea.monitored = true; - //var previouslyMonitoredEpisodes = sonarrEpList.Where(x => - // x.seasonNumber == existingSeason.seasonNumber && x.monitored).Select(x => x.episodeNumber).ToList(); // We probably don't actually care about this - result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); - var epToUnmonitored = new List(); - var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the original member - foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList()) - { - //if (previouslyMonitoredEpisodes.Contains(ep.episodeNumber)) - //{ - // // This was previously monitored. - // continue; - //} - ep.monitored = false; - epToUnmonitored.Add(ep); - } - - foreach (var epToUpdate in epToUnmonitored) - { - await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); - } + ep.monitored = false; + epToUnmonitored.Add(ep); } - // Now update the episodes that need updating - foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber)) + + foreach (var epToUpdate in epToUnmonitored) { await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); } } - } - if (seriesChanges) - { - await SonarrApi.SeasonPass(s.ApiKey, s.FullUri, result); + // Now update the episodes that need updating + foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber)) + { + await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); + } } if (!s.AddOnly) diff --git a/src/Ombi.Helpers.Tests/PagnationHelperTests.cs b/src/Ombi.Helpers.Tests/PagnationHelperTests.cs index 4b058c403..44bbfdd8e 100644 --- a/src/Ombi.Helpers.Tests/PagnationHelperTests.cs +++ b/src/Ombi.Helpers.Tests/PagnationHelperTests.cs @@ -83,6 +83,8 @@ namespace Ombi.Helpers.Tests .SetName("PaginationPosition_Load_SecondHalf_FirstPage_FirstHalf_SecondPage"); yield return new TestCaseData(0, 40, 20, new List { new MultiplePagesTestData(1, 20, 0), new MultiplePagesTestData(2, 20, 0) }) .SetName("PaginationPosition_Load_Full_First_And_SecondPage"); + yield return new TestCaseData(40, 40, 20, new List { new MultiplePagesTestData(3, 20, 0), new MultiplePagesTestData(4, 20, 0) }) + .SetName("PaginationPosition_Load_Full_Third_And_ForthPage"); yield return new TestCaseData(35, 15, 20, new List { new MultiplePagesTestData(2, 5, 15), new MultiplePagesTestData(3, 10, 0) }) .SetName("PaginationPosition_Load_EndSecondPage_Beginning_ThirdPage"); yield return new TestCaseData(18, 22, 20, new List { new MultiplePagesTestData(1, 2, 18), new MultiplePagesTestData(2, 20, 0) }) diff --git a/src/Ombi.Helpers/PaginationHelper.cs b/src/Ombi.Helpers/PaginationHelper.cs index 520f56b19..2caafe186 100644 --- a/src/Ombi.Helpers/PaginationHelper.cs +++ b/src/Ombi.Helpers/PaginationHelper.cs @@ -17,7 +17,7 @@ namespace Ombi.Helpers var lastPage = lastItemIndex / maxItemsPerPage + 1; var stopPos = lastItemIndex % maxItemsPerPage + 1; - while (currentlyLoaded > maxItemsPerPage) + while (currentlyLoaded >= maxItemsPerPage) { currentlyLoaded -= maxItemsPerPage; } diff --git a/src/Ombi.I18n/Resources/Texts.de.resx b/src/Ombi.I18n/Resources/Texts.de.resx index a222d02b0..91a4aced4 100644 --- a/src/Ombi.I18n/Resources/Texts.de.resx +++ b/src/Ombi.I18n/Resources/Texts.de.resx @@ -139,7 +139,7 @@ Episoden: - Powered by + Betrieben durch Abbestellen @@ -148,9 +148,9 @@ Album - Movie + Film - TV Show + TV-Serie \ No newline at end of file diff --git a/src/Ombi.I18n/Resources/Texts.pl.resx b/src/Ombi.I18n/Resources/Texts.pl.resx index c8e5e5ee1..f7833ced6 100644 --- a/src/Ombi.I18n/Resources/Texts.pl.resx +++ b/src/Ombi.I18n/Resources/Texts.pl.resx @@ -118,13 +118,13 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Nowy Album + Nowe Albumy - Nowy film + Nowe filmy - Nowy telewizor + Nowe seriale Gatunki: @@ -136,21 +136,21 @@ Sezon: - Odcinków: + Odcinki: Wspierane przez - Wypisać się + Wypisz się Album - Movie + Film - TV Show + Serial \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs index 9d88c5907..8e8b75e45 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs @@ -7,10 +7,12 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Ombi.Core; using Ombi.Core.Notifications; +using Ombi.Core.Services; using Ombi.Helpers; using Ombi.Hubs; using Ombi.Notifications.Models; using Ombi.Schedule.Jobs.Ombi; +using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; @@ -21,7 +23,7 @@ namespace Ombi.Schedule.Jobs.Emby public class EmbyAvaliabilityChecker : IEmbyAvaliabilityChecker { public EmbyAvaliabilityChecker(IEmbyContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m, - INotificationHelper n, ILogger log, IHubContext notification) + INotificationHelper n, ILogger log, IHubContext notification, IFeatureService featureService) { _repo = repo; _tvRepo = t; @@ -29,6 +31,7 @@ namespace Ombi.Schedule.Jobs.Emby _notificationService = n; _log = log; _notification = notification; + _featureService = featureService; } private readonly ITvRequestRepository _tvRepo; @@ -37,6 +40,7 @@ namespace Ombi.Schedule.Jobs.Emby private readonly INotificationHelper _notificationService; private readonly ILogger _log; private readonly IHubContext _notification; + private readonly IFeatureService _featureService; public async Task Execute(IJobExecutionContext job) { @@ -53,6 +57,7 @@ namespace Ombi.Schedule.Jobs.Emby private async Task ProcessMovies() { + var feature4kEnabled = await _featureService.FeatureEnabled(FeatureNames.Movie4KRequests); var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available || (!x.Available4K && x.Has4KRequest)); foreach (var movie in movies) @@ -85,8 +90,8 @@ namespace Ombi.Schedule.Jobs.Emby notify = true; } - // If we have a non-4k versison then mark as available - if (embyContent.Quality != null && !movie.Available) + // If we have a non-4k version or we don't care about versions, then mark as available + if (!movie.Available && ( !feature4kEnabled || embyContent.Quality != null )) { movie.Available = true; movie.MarkedAsAvailable = DateTime.Now; diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs index b4d61f93d..27cdf0f3a 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs @@ -157,9 +157,20 @@ namespace Ombi.Schedule.Jobs.Emby } var existingTv = await _repo.GetByEmbyId(tvShow.Id); + + if (existingTv != null && + ( existingTv.ImdbId != tvShow.ProviderIds?.Imdb + || existingTv.TheMovieDbId != tvShow.ProviderIds?.Tmdb + || existingTv.TvDbId != tvShow.ProviderIds?.Tvdb)) + { + _logger.LogDebug($"Series '{tvShow.Name}' has different IDs, probably a reidentification."); + await _repo.DeleteTv(existingTv); + existingTv = null; + } + if (existingTv == null) { - _logger.LogDebug("Adding new TV Show {0}", tvShow.Name); + _logger.LogDebug("Adding TV Show {0}", tvShow.Name); mediaToAdd.Add(new EmbyContent { TvDbId = tvShow.ProviderIds?.Tvdb, @@ -265,23 +276,21 @@ namespace Ombi.Schedule.Jobs.Emby return; } _logger.LogDebug($"Adding new movie {movieInfo.Name}"); - - content.Add(new EmbyContent - { - ImdbId = movieInfo.ProviderIds.Imdb, - TheMovieDbId = movieInfo.ProviderIds?.Tmdb, - Title = movieInfo.Name, - Type = MediaType.Movie, - EmbyId = movieInfo.Id, - Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname), - AddedAt = DateTime.UtcNow, - Quality = has4K ? null : quality, - Has4K = has4K - }); + var newMovie = new EmbyContent(); + newMovie.AddedAt = DateTime.UtcNow; + MapEmbyContent(newMovie, movieInfo, server, has4K, quality); + content.Add(newMovie); } else { - if (!quality.Equals(existingMovie?.Quality, StringComparison.InvariantCultureIgnoreCase)) + var movieHasChanged = false; + if (existingMovie.ImdbId != movieInfo.ProviderIds.Imdb || existingMovie.TheMovieDbId != movieInfo.ProviderIds.Tmdb) + { + _logger.LogDebug($"Updating existing movie '{movieInfo.Name}'"); + MapEmbyContent(existingMovie, movieInfo, server, has4K, quality); + movieHasChanged = true; + } + else if (!quality.Equals(existingMovie?.Quality, StringComparison.InvariantCultureIgnoreCase)) { _logger.LogDebug($"We have found another quality for Movie '{movieInfo.Name}', Quality: '{quality}'"); existingMovie.Quality = has4K ? null : quality; @@ -290,6 +299,11 @@ namespace Ombi.Schedule.Jobs.Emby // Probably could refactor here // If a 4k movie comes in (we don't store the quality on 4k) // it will always get updated even know it's not changed + movieHasChanged = true; + } + + if (movieHasChanged) + { toUpdate.Add(existingMovie); } else @@ -300,6 +314,17 @@ namespace Ombi.Schedule.Jobs.Emby } } + private void MapEmbyContent(EmbyContent content, EmbyMovie movieInfo, EmbyServers server, bool has4K, string quality){ + content.ImdbId = movieInfo.ProviderIds.Imdb; + content.TheMovieDbId = movieInfo.ProviderIds?.Tmdb; + content.Title = movieInfo.Name; + content.Type = MediaType.Movie; + content.EmbyId = movieInfo.Id; + content.Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname); + content.Quality = has4K ? null : quality; + content.Has4K = has4K; + } + private bool ValidateSettings(EmbyServers server) { if (server?.Ip == null || string.IsNullOrEmpty(server?.ApiKey)) diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs index b3c1ffbab..5293d30b4 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs @@ -130,12 +130,6 @@ namespace Ombi.Schedule.Jobs.Emby { processed++; - if (ep.LocationType?.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase) ?? false) - { - // For some reason Emby is not respecting the `IsVirtualItem` field. - continue; - } - // Let's make sure we have the parent request, stop those pesky forign key errors, // Damn me having data integrity var parent = await _repo.GetByEmbyId(ep.SeriesId); @@ -176,18 +170,26 @@ namespace Ombi.Schedule.Jobs.Emby if (ep.IndexNumberEnd.HasValue && ep.IndexNumberEnd.Value != ep.IndexNumber) { - epToAdd.Add(new EmbyEpisode + int episodeNumber = ep.IndexNumber; + do { - EmbyId = ep.Id, - EpisodeNumber = ep.IndexNumberEnd.Value, - SeasonNumber = ep.ParentIndexNumber, - ParentId = ep.SeriesId, - TvDbId = ep.ProviderIds.Tvdb, - TheMovieDbId = ep.ProviderIds.Tmdb, - ImdbId = ep.ProviderIds.Imdb, - Title = ep.Name, - AddedAt = DateTime.UtcNow - }); + _logger.LogDebug($"Multiple-episode file detected. Adding episode ${episodeNumber}"); + episodeNumber++; + epToAdd.Add(new EmbyEpisode + { + EmbyId = ep.Id, + EpisodeNumber = episodeNumber, + SeasonNumber = ep.ParentIndexNumber, + ParentId = ep.SeriesId, + TvDbId = ep.ProviderIds.Tvdb, + TheMovieDbId = ep.ProviderIds.Tmdb, + ImdbId = ep.ProviderIds.Imdb, + Title = ep.Name, + AddedAt = DateTime.UtcNow + }); + + } while (episodeNumber < ep.IndexNumberEnd.Value); + } } } diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs index b1dafa71f..79eb5d6a4 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinAvaliabilityChecker.cs @@ -33,9 +33,11 @@ using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Ombi.Core; +using Ombi.Core.Services; using Ombi.Helpers; using Ombi.Hubs; using Ombi.Notifications.Models; +using Ombi.Settings.Settings.Models; using Ombi.Store.Entities; using Ombi.Store.Repository; using Ombi.Store.Repository.Requests; @@ -46,7 +48,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin public class JellyfinAvaliabilityChecker : IJellyfinAvaliabilityChecker { public JellyfinAvaliabilityChecker(IJellyfinContentRepository repo, ITvRequestRepository t, IMovieRequestRepository m, - INotificationHelper n, ILogger log, IHubContext notification) + INotificationHelper n, ILogger log, IHubContext notification, IFeatureService featureService) { _repo = repo; _tvRepo = t; @@ -54,6 +56,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin _notificationService = n; _log = log; _notification = notification; + _featureService = featureService; } private readonly ITvRequestRepository _tvRepo; @@ -62,6 +65,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin private readonly INotificationHelper _notificationService; private readonly ILogger _log; private readonly IHubContext _notification; + private readonly IFeatureService _featureService; public async Task Execute(IJobExecutionContext job) { @@ -78,6 +82,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin private async Task ProcessMovies() { + var feature4kEnabled = await _featureService.FeatureEnabled(FeatureNames.Movie4KRequests); var movies = _movieRepo.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available || (!x.Available4K && x.Has4KRequest)); foreach (var movie in movies) @@ -110,8 +115,8 @@ namespace Ombi.Schedule.Jobs.Jellyfin notify = true; } - // If we have a non-4k versison then mark as available - if (jellyfinContent.Quality != null && !movie.Available) + // If we have a non-4k version or we don't care about versions, then mark as available + if (!movie.Available && ( !feature4kEnabled || jellyfinContent.Quality != null )) { movie.Available = true; movie.MarkedAsAvailable = DateTime.Now; diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs index 060c974e7..5519b3413 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinContentSync.cs @@ -132,9 +132,20 @@ namespace Ombi.Schedule.Jobs.Jellyfin } var existingTv = await _repo.GetByJellyfinId(tvShow.Id); + + if (existingTv != null && + ( existingTv.ImdbId != tvShow.ProviderIds?.Imdb + || existingTv.TheMovieDbId != tvShow.ProviderIds?.Tmdb + || existingTv.TvDbId != tvShow.ProviderIds?.Tvdb)) + { + _logger.LogDebug($"Series '{tvShow.Name}' has different IDs, probably a reidentification."); + await _repo.DeleteTv(existingTv); + existingTv = null; + } + if (existingTv == null) { - _logger.LogDebug("Adding new TV Show {0}", tvShow.Name); + _logger.LogDebug("Adding TV Show {0}", tvShow.Name); mediaToAdd.Add(new JellyfinContent { TvDbId = tvShow.ProviderIds?.Tvdb, @@ -230,22 +241,21 @@ namespace Ombi.Schedule.Jobs.Jellyfin return; } _logger.LogDebug($"Adding new movie {movieInfo.Name}"); - content.Add(new JellyfinContent - { - ImdbId = movieInfo.ProviderIds.Imdb, - TheMovieDbId = movieInfo.ProviderIds?.Tmdb, - Title = movieInfo.Name, - Type = MediaType.Movie, - JellyfinId = movieInfo.Id, - Url = JellyfinHelper.GetJellyfinMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname), - AddedAt = DateTime.UtcNow, - Quality = has4K ? null : quality, - Has4K = has4K - }); + var newMovie = new JellyfinContent(); + newMovie.AddedAt = DateTime.UtcNow; + MapJellyfinMovie(newMovie, movieInfo, server, has4K, quality); + content.Add(newMovie);; } else { - if (!quality.Equals(existingMovie?.Quality, StringComparison.InvariantCultureIgnoreCase)) + var movieHasChanged = false; + if (existingMovie.ImdbId != movieInfo.ProviderIds.Imdb || existingMovie.TheMovieDbId != movieInfo.ProviderIds.Tmdb) + { + _logger.LogDebug($"Updating existing movie '{movieInfo.Name}'"); + MapJellyfinMovie(existingMovie, movieInfo, server, has4K, quality); + movieHasChanged = true; + } + else if (!quality.Equals(existingMovie?.Quality, StringComparison.InvariantCultureIgnoreCase)) { _logger.LogDebug($"We have found another quality for Movie '{movieInfo.Name}', Quality: '{quality}'"); existingMovie.Quality = has4K ? null : quality; @@ -255,6 +265,12 @@ namespace Ombi.Schedule.Jobs.Jellyfin // If a 4k movie comes in (we don't store the quality on 4k) // it will always get updated even know it's not changed toUpdate.Add(existingMovie); + movieHasChanged = true; + } + + if (movieHasChanged) + { + toUpdate.Add(existingMovie); } else { @@ -264,6 +280,18 @@ namespace Ombi.Schedule.Jobs.Jellyfin } } + private void MapJellyfinMovie(JellyfinContent content, JellyfinMovie movieInfo, JellyfinServers server, bool has4K, string quality) + { + content.ImdbId = movieInfo.ProviderIds.Imdb; + content.TheMovieDbId = movieInfo.ProviderIds?.Tmdb; + content.Title = movieInfo.Name; + content.Type = MediaType.Movie; + content.JellyfinId = movieInfo.Id; + content.Url = JellyfinHelper.GetJellyfinMediaUrl(movieInfo.Id, server?.ServerId, server.ServerHostname); + content.Quality = has4K ? null : quality; + content.Has4K = has4K; + } + private bool ValidateSettings(JellyfinServers server) { if (server?.Ip == null || string.IsNullOrEmpty(server?.ApiKey)) diff --git a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs index 6e2daa7b3..31b880114 100644 --- a/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Jellyfin/JellyfinEpisodeSync.cs @@ -106,12 +106,6 @@ namespace Ombi.Schedule.Jobs.Jellyfin { processed++; - if (ep.LocationType?.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase) ?? false) - { - // For some reason Jellyfin is not respecting the `IsVirtualItem` field. - continue; - } - // Let's make sure we have the parent request, stop those pesky forign key errors, // Damn me having data integrity var parent = await _repo.GetByJellyfinId(ep.SeriesId); @@ -152,18 +146,25 @@ namespace Ombi.Schedule.Jobs.Jellyfin if (ep.IndexNumberEnd.HasValue && ep.IndexNumberEnd.Value != ep.IndexNumber) { - epToAdd.Add(new JellyfinEpisode + int episodeNumber = ep.IndexNumber; + do { - JellyfinId = ep.Id, - EpisodeNumber = ep.IndexNumberEnd.Value, - SeasonNumber = ep.ParentIndexNumber, - ParentId = ep.SeriesId, - TvDbId = ep.ProviderIds.Tvdb, - TheMovieDbId = ep.ProviderIds.Tmdb, - ImdbId = ep.ProviderIds.Imdb, - Title = ep.Name, - AddedAt = DateTime.UtcNow - }); + _logger.LogDebug($"Multiple-episode file detected. Adding episode ${episodeNumber}"); + episodeNumber++; + epToAdd.Add(new JellyfinEpisode + { + JellyfinId = ep.Id, + EpisodeNumber = episodeNumber, + SeasonNumber = ep.ParentIndexNumber, + ParentId = ep.SeriesId, + TvDbId = ep.ProviderIds.Tvdb, + TheMovieDbId = ep.ProviderIds.Tmdb, + ImdbId = ep.ProviderIds.Imdb, + Title = ep.Name, + AddedAt = DateTime.UtcNow + }); + + } while (episodeNumber < ep.IndexNumberEnd.Value); } } } diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index a11a9ff80..0782a796a 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -409,7 +409,9 @@ namespace Ombi.Schedule.Jobs.Ombi private HashSet FilterEpisodes(IEnumerable source, IEnumerable recentlyAdded) { var itemsToReturn = new HashSet(); - foreach (var ep in source.Where(x => x.Series.HasTvDb)) + foreach (var ep in source.Where(x => x.Series.HasTvDb // needed for recentlyAddedLog + && x.Series.HasTheMovieDb // needed to fetch info to publish, this is just in case... + )) { var tvDbId = StringHelper.IntParseLinq(ep.Series.TvDbId); if (recentlyAdded.Any(x => x.ContentId == tvDbId && x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == ep.SeasonNumber)) @@ -501,16 +503,11 @@ namespace Ombi.Schedule.Jobs.Ombi foreach (var content in ordered) { int.TryParse(content.TheMovieDbId, out var movieDbId); - if (movieDbId <= 0) - { - _log.LogWarning($"{content.Title} does not have a TMDB ID, it won't be published."); - continue; - } var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId, defaultLanguageCode); var mediaurl = content.Url; if (info == null) { - _log.LogWarning($"TMDB does not know movie {content.Title}, it won't be published."); + _log.LogError($"TMDB does not know movie {content.Title}. This shouldn't happen because our media server knows it as ID '{movieDbId}'."); continue; } try @@ -665,48 +662,18 @@ namespace Ombi.Schedule.Jobs.Ombi var orderedTv = series.OrderByDescending(x => x.AddedAt); foreach (var t in orderedTv) { - if (!t.HasTvDb) - { - // We may need to use themoviedb for the imdbid or their own id to get info - if (t.HasTheMovieDb) - { - int.TryParse(t.TheMovieDbId, out var movieId); - var externals = await _movieApi.GetTvExternals(movieId); - if (externals == null || externals.tvdb_id <= 0) - { - // needed later for recently added log - _log.LogWarning($"{t.Title} has no TVDB ID, it won't be published."); - continue; - } - t.TvDbId = externals.tvdb_id.ToString(); - } - // WE could check the below but we need to get the moviedb and then perform the above, let the metadata job figure this out. - //else if(t.HasImdb) - //{ - // // Check the imdbid - // var externals = await _movieApi.Find(t.ImdbId, ExternalSource.imdb_id); - // if (externals?.tv_results == null || externals.tv_results.Length <= 0) - // { - // continue; - // } - // t.TvDbId = externals.tv_results.FirstOrDefault()..ToString(); - //} - - } - try { var tvInfo = await _movieApi.GetTVInfo(t.TheMovieDbId, languageCode); if (tvInfo == null) { - _log.LogWarning($"TMDB does not know series {t.Title}, it won't be published."); + _log.LogError($"TMDB does not know series {t.Title}. This shouldn't happen because our media server knows it as ID '{t.TheMovieDbId}'."); continue; } if (tvInfo.backdrop_path.HasValue()) { - AddBackgroundInsideTable($"https://image.tmdb.org/t/p/w500{tvInfo.backdrop_path}"); } else @@ -732,7 +699,7 @@ namespace Ombi.Schedule.Jobs.Ombi } catch (Exception e) { - _log.LogError(e, "Error when processing Plex TV {0}", t.Title); + _log.LogError(e, "Error when processing TV {0}", t.Title); } finally { diff --git a/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs b/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs index c2df1b9b3..cbb0233a0 100644 --- a/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/TheMovieDbSettings.cs @@ -11,5 +11,7 @@ namespace Ombi.Core.Settings.Models.External public List ExcludedMovieGenreIds { get; set; } public List ExcludedTvGenreIds { get; set; } + + public List OriginalLanguages { get; set; } } } diff --git a/src/Ombi.Settings/Settings/Models/FeatureSettings.cs b/src/Ombi.Settings/Settings/Models/FeatureSettings.cs index 6a285d422..9d0149e5d 100644 --- a/src/Ombi.Settings/Settings/Models/FeatureSettings.cs +++ b/src/Ombi.Settings/Settings/Models/FeatureSettings.cs @@ -20,5 +20,6 @@ namespace Ombi.Settings.Settings.Models public static class FeatureNames { public const string Movie4KRequests = nameof(Movie4KRequests); + public const string OldTrendingSource = nameof(OldTrendingSource); } } diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index 0681d9ca0..77298e719 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -212,7 +212,7 @@ namespace Ombi.Store.Context notificationToAdd = new NotificationTemplates { NotificationType = notificationType, - Message = "Your TV request is now partially available! Season {PartiallyAvailableSeasonNumber} Episodes {PartiallyAvailableEpisodeNumbers}!", + Message = "Your TV request for {Title} is now partially available! Season {PartiallyAvailableSeasonNumber} Episodes {PartiallyAvailableEpisodeNumbers}!", Subject = "{ApplicationName}: Partially Available Request!", Agent = agent, Enabled = true, diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index df88d8292..8f9904ebb 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -102,6 +102,13 @@ namespace Ombi.Store.Repository return InternalSaveChanges(); } + public override async Task DeleteTv(EmbyContent tv) + { + var episodesToDelete = GetAllEpisodes().Cast().Where(x => x.ParentId == tv.EmbyId).ToList(); + Db.EmbyEpisode.RemoveRange(episodesToDelete); + await Delete(tv); + } + public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Emby; } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IMediaServerContentRepository.cs b/src/Ombi.Store/Repository/IMediaServerContentRepository.cs index db4835a16..3f165b7e6 100644 --- a/src/Ombi.Store/Repository/IMediaServerContentRepository.cs +++ b/src/Ombi.Store/Repository/IMediaServerContentRepository.cs @@ -14,6 +14,7 @@ namespace Ombi.Store.Repository IQueryable GetAllEpisodes(); Task Add(IMediaServerEpisode content); Task AddRange(IEnumerable content); + Task DeleteTv(Content tv); void UpdateWithoutSave(IMediaServerContent existingContent); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/JellyfinContentRepository.cs b/src/Ombi.Store/Repository/JellyfinContentRepository.cs index c3451da62..8dc729a6b 100644 --- a/src/Ombi.Store/Repository/JellyfinContentRepository.cs +++ b/src/Ombi.Store/Repository/JellyfinContentRepository.cs @@ -104,6 +104,13 @@ namespace Ombi.Store.Repository return InternalSaveChanges(); } + public override async Task DeleteTv(JellyfinContent tv) + { + var episodesToDelete = GetAllEpisodes().Cast().Where(x => x.ParentId == tv.JellyfinId).ToList(); + Db.JellyfinEpisode.RemoveRange(episodesToDelete); + await Delete(tv); + } + public override RecentlyAddedType RecentlyAddedType => RecentlyAddedType.Jellyfin; } } diff --git a/src/Ombi.Store/Repository/MediaServerRepository.cs b/src/Ombi.Store/Repository/MediaServerRepository.cs index c1c7d4ec8..251fe3cea 100644 --- a/src/Ombi.Store/Repository/MediaServerRepository.cs +++ b/src/Ombi.Store/Repository/MediaServerRepository.cs @@ -22,5 +22,6 @@ namespace Ombi.Store.Repository public abstract Task AddRange(IEnumerable content); public abstract void UpdateWithoutSave(IMediaServerContent existingContent); public abstract Task UpdateRange(IEnumerable existingContent); + public abstract Task DeleteTv(T tv); } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/PlexContentRepository.cs b/src/Ombi.Store/Repository/PlexContentRepository.cs index 96dfbe8e1..9f34af6a1 100644 --- a/src/Ombi.Store/Repository/PlexContentRepository.cs +++ b/src/Ombi.Store/Repository/PlexContentRepository.cs @@ -169,5 +169,12 @@ namespace Ombi.Store.Repository Db.PlexServerContent.UpdateRange((IEnumerable)existingContent); return InternalSaveChanges(); } + + public override Task DeleteTv(PlexServerContent tv) + { + // not used for now + // TODO: delete episodes, then delete series + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index dc2e5525c..9b277c091 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -15,14 +15,16 @@ namespace Ombi.Api.TheMovieDb Task GetMovieInformation(int movieId); Task GetMovieInformationWithExtraInfo(int movieId, string langCode = "en"); Task> NowPlaying(string languageCode, int? page = null); + Task> TrendingMovies(string languageCode, int? page = null); Task> PopularMovies(string languageCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken)); Task> PopularTv(string langCode, int? page = null, CancellationToken cancellationToken = default(CancellationToken)); Task> SearchMovie(string searchTerm, int? year, string languageCode); Task> GetMoviesViaKeywords(string keywordId, string langCode, CancellationToken cancellationToken, int? page = null); Task> SearchTv(string searchTerm, string year = default); Task> TopRated(string languageCode, int? page = null); - Task> Upcoming(string languageCode, int? page = null); + Task> UpcomingMovies(string languageCode, int? page = null); Task> TopRatedTv(string languageCode, int? page = null); + Task> TrendingTv(string languageCode, int? page = null); Task> UpcomingTv(string languageCode, int? page = null); Task> SimilarMovies(int movieId, string langCode); Task Find(string externalId, ExternalSource source); @@ -41,7 +43,8 @@ namespace Ombi.Api.TheMovieDb Task GetMovieWatchProviders(int theMoviedbId, CancellationToken token); Task GetTvWatchProviders(int theMoviedbId, CancellationToken token); Task> GetGenres(string media, CancellationToken cancellationToken, string languageCode); + Task> GetLanguages(CancellationToken cancellationToken); Task> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken); - Task> AdvancedSearch(DiscoverModel model, CancellationToken cancellationToken); + Task> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken); } } diff --git a/src/Ombi.TheMovieDbApi/Models/Language.cs b/src/Ombi.TheMovieDbApi/Models/Language.cs new file mode 100644 index 000000000..c6b14a654 --- /dev/null +++ b/src/Ombi.TheMovieDbApi/Models/Language.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace Ombi.TheMovieDbApi.Models +{ + public class Language + { + [JsonProperty("iso_639_1")] + public string Id { get; set; } + [JsonProperty("english_name")] + public string EnglishName { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 335ea7da3..055265701 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -70,11 +70,11 @@ namespace Ombi.Api.TheMovieDb - public async Task> AdvancedSearch(DiscoverModel model, CancellationToken cancellationToken) + public async Task> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken) { var request = new Request($"discover/{model.Type}", BaseUri, HttpMethod.Get); request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); - if(model.ReleaseYear.HasValue && model.ReleaseYear.Value > 1900) + if (model.ReleaseYear.HasValue && model.ReleaseYear.Value > 1900) { request.FullUri = request.FullUri.AddQueryParameter("year", model.ReleaseYear.Value.ToString()); } @@ -92,6 +92,9 @@ namespace Ombi.Api.TheMovieDb } //request.FullUri = request.FullUri.AddQueryParameter("sort_by", "popularity.desc"); + request.AddQueryString("page", page.ToString()); + + var result = await Api.Request>(request, cancellationToken); return Mapper.Map>(result.results); } @@ -139,7 +142,7 @@ namespace Ombi.Api.TheMovieDb var result = await Api.Request(request); return result; } - + public async Task GetActorTvCredits(int actorId, string langCode) { var request = new Request($"person/{actorId}/tv_credits", BaseUri, HttpMethod.Get); @@ -282,21 +285,38 @@ namespace Ombi.Api.TheMovieDb return Mapper.Map>(result.results); } - public Task> Upcoming(string langCode, int? page = null) + public Task> TrendingMovies(string langCode, int? page = null) { - return Upcoming("movie", langCode, page); + return Trending("movie", langCode, page); } - public Task> UpcomingTv(string langCode, int? page = null) + + public Task> TrendingTv(string langCode, int? page = null) { - return Upcoming("tv", langCode, page); + return Trending("tv", langCode, page); } + private async Task> Trending(string type, string langCode, int? page = null) + { + // https://developers.themoviedb.org/3/trending/get-trending + var timeWindow = "week"; // another option can be 'day' + var request = new Request($"trending/{type}/{timeWindow}", BaseUri, HttpMethod.Get); + request.AddQueryString("api_key", ApiToken); + request.AddQueryString("language", langCode); + + if (page != null) + { + request.AddQueryString("page", page.ToString()); + } + AddRetry(request); + var result = await Api.Request>(request); + return Mapper.Map>(result.results); + } /// /// Maintains filter parity with /movie/upcoming. /// - private async Task> Upcoming(string type, string langCode, int? page = null) + public async Task> UpcomingMovies(string langCode, int? page = null) { - var request = new Request($"discover/{type}", BaseUri, HttpMethod.Get); + var request = new Request($"discover/movie", BaseUri, HttpMethod.Get); request.AddQueryString("api_key", ApiToken); request.AddQueryString("language", langCode); @@ -313,7 +333,27 @@ namespace Ombi.Api.TheMovieDb request.AddQueryString("page", page.ToString()); } await AddDiscoverSettings(request); - await AddGenreFilter(request, type); + await AddGenreFilter(request, "movie"); + AddRetry(request); + var result = await Api.Request>(request); + return Mapper.Map>(result.results); + } + public async Task> UpcomingTv(string langCode, int? page = null) + { + var request = new Request($"discover/tv", BaseUri, HttpMethod.Get); + request.AddQueryString("api_key", ApiToken); + request.AddQueryString("language", langCode); + + // Search for shows that will air in the next month + var startDate = DateTime.Today.AddDays(1); + request.AddQueryString($"first_air_date.gte", startDate.ToString("yyyy-MM-dd")); + request.AddQueryString($"first_air_date.lte", startDate.AddDays(30).ToString("yyyy-MM-dd")); + if (page != null) + { + request.AddQueryString("page", page.ToString()); + } + await AddDiscoverSettings(request); + await AddGenreFilter(request, "tv"); AddRetry(request); var result = await Api.Request>(request); return Mapper.Map>(result.results); @@ -376,8 +416,8 @@ namespace Ombi.Api.TheMovieDb request.AddQueryString("language", langCode); request.AddQueryString("sort_by", "vote_average.desc"); - request.AddQueryString("with_keywords", keywordId); - + request.AddQueryString("with_keywords", keywordId); + // `vote_count` consideration isn't explicitly documented, but using only the `sort_by` filter // does not provide the same results as `/movie/top_rated`. This appears to be adequate enough // to filter out extremely high-rated movies due to very little votes @@ -438,6 +478,16 @@ namespace Ombi.Api.TheMovieDb return result.genres ?? new List(); } + public async Task> GetLanguages(CancellationToken cancellationToken) + { + var request = new Request($"/configuration/languages", BaseUri, HttpMethod.Get); + request.AddQueryString("api_key", ApiToken); + AddRetry(request); + + var result = await Api.Request>(request, cancellationToken); + return result ?? new List(); + } + public Task> MultiSearch(string searchTerm, string languageCode, CancellationToken cancellationToken) { var request = new Request("search/multi", BaseUri, HttpMethod.Get); @@ -472,6 +522,10 @@ namespace Ombi.Api.TheMovieDb { request.AddQueryString("without_keywords", string.Join(",", settings.ExcludedKeywordIds)); } + if (settings.OriginalLanguages?.Any() == true) + { + request.AddQueryString("with_original_language", string.Join("|", settings.OriginalLanguages)); + } } private async Task AddGenreFilter(Request request, string media_type) @@ -479,7 +533,8 @@ namespace Ombi.Api.TheMovieDb var settings = await Settings; List excludedGenres; - switch (media_type) { + switch (media_type) + { case "tv": excludedGenres = settings.ExcludedTvGenreIds; break; diff --git a/src/Ombi/.vscode/launch.json b/src/Ombi/.vscode/launch.json index 642e584c5..9b6838630 100644 --- a/src/Ombi/.vscode/launch.json +++ b/src/Ombi/.vscode/launch.json @@ -6,7 +6,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceFolder}/bin/Debug/net5.0/ombi.dll", + "program": "${workspaceFolder}/bin/Debug/net6.0/ombi.dll", "args": ["--host", "http://localhost:3577"], "cwd": "${workspaceFolder}", "stopAtEntry": false, @@ -47,4 +47,4 @@ } }, ] - } \ No newline at end of file + } diff --git a/src/Ombi/ClientApp/.storybook/main.js b/src/Ombi/ClientApp/.storybook/main.js new file mode 100644 index 000000000..3f1f26236 --- /dev/null +++ b/src/Ombi/ClientApp/.storybook/main.js @@ -0,0 +1,19 @@ +module.exports = { + "stories": [ + "../src/**/*.stories.mdx", + "../src/**/*.stories.@(js|jsx|ts|tsx)" + ], + "addons": [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions" + ], + "framework": "@storybook/angular", + "core": { + "builder": "@storybook/builder-webpack5" + }, + "staticDirs": [{from :'../../wwwroot/images', to: 'images'}], + "features": { + interactionsDebugger: true, + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/.storybook/preview-body.html b/src/Ombi/ClientApp/.storybook/preview-body.html new file mode 100644 index 000000000..b043609bf --- /dev/null +++ b/src/Ombi/ClientApp/.storybook/preview-body.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/src/Ombi/ClientApp/.storybook/preview.js b/src/Ombi/ClientApp/.storybook/preview.js new file mode 100644 index 000000000..a20381a89 --- /dev/null +++ b/src/Ombi/ClientApp/.storybook/preview.js @@ -0,0 +1,14 @@ +import { setCompodocJson } from "@storybook/addon-docs/angular"; +import docJson from "../documentation.json"; +setCompodocJson(docJson); + +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + docs: { inlineStories: true }, +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/.storybook/tsconfig.json b/src/Ombi/ClientApp/.storybook/tsconfig.json new file mode 100644 index 000000000..54887e29c --- /dev/null +++ b/src/Ombi/ClientApp/.storybook/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../src/tsconfig.json", + "compilerOptions": { + "types": [ + "node" + ], + "typeRoots": [ + "../node_modules/@typings" + ], + "allowSyntheticDefaultImports": true + }, + "exclude": [ + "../src/test.ts", + "../src/**/*.spec.ts", + "../projects/**/*.spec.ts" + ], + "include": [ + "../src/**/*", + "../projects/**/*" + ], + "files": [ + "./typings.d.ts" + ] +} diff --git a/src/Ombi/ClientApp/.storybook/typings.d.ts b/src/Ombi/ClientApp/.storybook/typings.d.ts new file mode 100644 index 000000000..f73d61b39 --- /dev/null +++ b/src/Ombi/ClientApp/.storybook/typings.d.ts @@ -0,0 +1,4 @@ +declare module '*.md' { + const content: string; + export default content; +} diff --git a/src/Ombi/ClientApp/angular.json b/src/Ombi/ClientApp/angular.json index 066a75be5..d50a19844 100644 --- a/src/Ombi/ClientApp/angular.json +++ b/src/Ombi/ClientApp/angular.json @@ -19,7 +19,7 @@ "index": "src/index.html", "main": "src/main.ts", "polyfills": "src/polyfills.ts", - "tsConfig": "src/tsconfig.app.json", + "tsConfig": "src/tsconfig.json", "assets": [ "src/assets" ], @@ -114,7 +114,7 @@ "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ - "src/tsconfig.app.json" + "src/tsconfig.json" ], "exclude": [ "**/node_modules/**" @@ -124,7 +124,6 @@ } } }, - "defaultProject": "ombi", "cli": { "analytics": false } diff --git a/src/Ombi/ClientApp/documentation.json b/src/Ombi/ClientApp/documentation.json new file mode 100644 index 000000000..901f0d264 --- /dev/null +++ b/src/Ombi/ClientApp/documentation.json @@ -0,0 +1,18 @@ +{ + "pipes": [], + "interfaces": [], + "injectables": [], + "guards": [], + "interceptors": [], + "classes": [], + "directives": [], + "components": [], + "modules": [], + "miscellaneous": [], + "routes": [], + "coverage": { + "count": 0, + "status": "low", + "files": [] + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/package.json b/src/Ombi/ClientApp/package.json index 80a9842b4..a2271f2bc 100644 --- a/src/Ombi/ClientApp/package.json +++ b/src/Ombi/ClientApp/package.json @@ -4,37 +4,40 @@ "scripts": { "ng": "ng", "start": "ng serve --port 3578 --configuration hmr", - "build": "node --max_old_space_size=6144 node_modules/@angular/cli/bin/ng build --prod", - "lint": "ng lint" + "build": "node --max_old_space_size=6144 node_modules/@angular/cli/bin/ng build -c production", + "lint": "ng lint", + "docs:json": "compodoc -p ./tsconfig.json -e json -d .", + "storybook": "start-storybook -p 6006", + "chromatic": "chromatic --exit-zero-on-changes", + "storybookbuild": "yarn build-storybook" }, "private": true, "dependencies": { - "@angular/animations": "^13.2.0", + "@angular/animations": "^14.0.0", "@angular/cdk": "^13.2.0", - "@angular/common": "^13.2.0", - "@angular/compiler": "^13.2.0", - "@angular/core": "^13.2.0", - "@angular/forms": "^13.2.0", - "@angular/localize": "^13.2.0", + "@angular/common": "^14.0.0", + "@angular/compiler": "^14.0.0", + "@angular/core": "^14.0.0", + "@angular/forms": "^14.0.0", + "@angular/localize": "^14.0.0", "@angular/material": "^13.2.0", - "@angular/platform-browser": "^13.2.0", - "@angular/platform-browser-dynamic": "^13.2.0", - "@angular/platform-server": "^13.2.0", - "@angular/router": "^13.2.0", + "@angular/platform-browser": "^14.0.0", + "@angular/platform-browser-dynamic": "^14.0.0", + "@angular/platform-server": "^14.0.0", + "@angular/router": "^14.0.0", "@angularclass/hmr": "^3.0.0", - "@aspnet/signalr": "^1.1.0", "@auth0/angular-jwt": "^5.0.2", "@fortawesome/fontawesome-free": "^6.0.0", "@fullcalendar/core": "^4.2.0", "@fullcalendar/daygrid": "^4.4.0", "@fullcalendar/interaction": "^4.2.0", + "@microsoft/signalr": "^6.0.7", "@ngx-translate/core": "^14.0.0", "@ngx-translate/http-loader": "^7.0.0", "@ngxs/devtools-plugin": "^3.7.3", "@ngxs/store": "^3.7.3", "@types/jquery": "^3.5.13", "@yellowspot/ng-truncate": "^2.0.0", - "angular-bootstrap-md": "^7.5.4", "angular-router-loader": "^0.8.5", "angularx-qrcode": "^13.0.3", "bootstrap": "^4.2.1", @@ -48,34 +51,52 @@ "moment": "^2.29.1", "ng2-cookies": "^1.0.12", "ngx-clipboard": "^12.1.0", - "ngx-infinite-scroll": "^9.0.0", + "ngx-infinite-scroll": "^14.0.0", "ngx-moment": "^3.0.1", "ngx-order-pipe": "^2.2.0", "popper.js": "^1.14.3", "primeicons": "^5.0.0", "primeng": "^13.2.0", + "protractor": "~5.4.0", "rxjs": "^7.5.4", "sass-recursive-map-merge": "^1.0.1", "store": "^2.0.12", "ts-md5": "^1.2.7", + "ts-node": "~5.0.1", "tslib": "^1.10.0", + "tslint": "^5.12.0", "tslint-angular": "^1.1.2", "zone.js": "~0.11.4" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.2.0", - "@angular/cli": "~13.2.0", - "@angular/compiler-cli": "^13.2.0", - "@angular/language-service": "^13.2.0", + "@angular-devkit/build-angular": "^14.0.0", + "@angular/cli": "^14.0.0", + "@angular/compiler-cli": "^14.0.0", + "@angular/language-service": "^14.0.0", + "@babel/core": "^7.18.9", + "@compodoc/compodoc": "^1.1.19", + "@storybook/addon-actions": "^6.5.9", + "@storybook/addon-essentials": "^6.5.9", + "@storybook/addon-interactions": "^6.5.9", + "@storybook/addon-links": "^6.5.9", + "@storybook/angular": "^6.5.9", + "@storybook/builder-webpack5": "^6.5.9", + "@storybook/jest": "^0.0.10", + "@storybook/manager-webpack5": "^6.5.9", + "@storybook/testing-library": "^0.0.13", "@types/jasmine": "~3.6.7", "@types/jasminewd2": "~2.0.8", - "@types/node": "^16.10.9", + "@types/node": "^16.11.45", + "babel-loader": "^8.2.5", + "chromatic": "^6.7.1", "codelyzer": "^6.0.1", - "typescript": "~4.5.5" + "typescript": "~4.7.3" }, "optionalDependencies": { "protractor": "~5.4.0", "ts-node": "~5.0.1", "tslint": "^5.12.0" - } + }, + "readme": "ERROR: No README data found!", + "_id": "ombi@3.0.0" } diff --git a/src/Ombi/ClientApp/src/app/app.module.ts b/src/Ombi/ClientApp/src/app/app.module.ts index 735dce36b..24644d1df 100644 --- a/src/Ombi/ClientApp/src/app/app.module.ts +++ b/src/Ombi/ClientApp/src/app/app.module.ts @@ -1,5 +1,4 @@ import { APP_BASE_HREF, CommonModule, PlatformLocation } from "@angular/common"; -import { CardsFreeModule, MDBBootstrapModule, NavbarModule } from "angular-bootstrap-md"; import { CustomPageService, ImageService, RequestService, SettingsService } from "./services"; import { FormsModule, ReactiveFormsModule } from "@angular/forms"; import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from "@angular/common/http"; @@ -69,6 +68,7 @@ import { TooltipModule } from "primeng/tooltip"; import { TranslateHttpLoader } from "@ngx-translate/http-loader"; import { TranslateService } from "@ngx-translate/core"; import { UnauthorizedInterceptor } from "./auth/unauthorized.interceptor"; +import { ImageBackgroundComponent, ImageComponent } from "./components/"; import { environment } from "../environments/environment"; const routes: Routes = [ @@ -131,7 +131,6 @@ export function JwtTokenGetter() { MatSnackBarModule, DialogModule, MatButtonModule, - NavbarModule, MatCardModule, MatTooltipModule, MatMenuModule, @@ -145,11 +144,9 @@ export function JwtTokenGetter() { ConfirmDialogModule, OverlayPanelModule, CommonModule, - CardsFreeModule, OverlayModule, MatCheckboxModule, MatProgressSpinnerModule, - MDBBootstrapModule.forRoot(), JwtModule.forRoot({ config: { tokenGetter: JwtTokenGetter, @@ -170,7 +167,9 @@ export function JwtTokenGetter() { ...environment.production ? [] : [ NgxsReduxDevtoolsPluginModule.forRoot(), - ] + ], + ImageBackgroundComponent, + ImageComponent, ], declarations: [ AppComponent, diff --git a/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.html b/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.html new file mode 100644 index 000000000..97fff3671 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.html @@ -0,0 +1,6 @@ +
+ + +
{{name}}
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.scss b/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.scss new file mode 100644 index 000000000..177a22d01 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.scss @@ -0,0 +1,26 @@ +.login-gradient-bar{ + background: linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.6) 20.0%, rgba(0,0,0,0.6) 80.0%, transparent 60%),transparent; + height:100%; + width:100%; + position: absolute; +} + +.bg { + background-position: center center; + background-repeat: no-repeat; + background-attachment: fixed; + background-size: cover; + height: 100vh; + width: 100vw; + position: fixed; +} + +.poster-desc { + padding-left: 1%; + color: white; + height: 100vh; + width: 100vw; + display: flex; + justify-content: end; + flex-direction: column; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.ts b/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.ts new file mode 100644 index 000000000..ebd228d85 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/components/image-background/image-background.component.ts @@ -0,0 +1,43 @@ +import { OmbiCommonModules } from "../modules"; +import { Component, OnDestroy, OnInit } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { ImageService } from "../../services"; +import { fadeInOutAnimation } from "app/animations/fadeinout"; + +@Component({ + standalone: true, + selector: 'ombi-image-background', + templateUrl: './image-background.component.html', + styleUrls: ['./image-background.component.scss'], + imports: [...OmbiCommonModules, BrowserAnimationsModule], + providers: [ ImageService ], + animations: [ fadeInOutAnimation ], + }) + export class ImageBackgroundComponent implements OnInit, OnDestroy { + + public background: any; + public name: string; + private timer: NodeJS.Timer; + + constructor(private images: ImageService, private sanitizer: DomSanitizer) { } + + public ngOnDestroy(): void { + clearTimeout(this.timer); + } + + public ngOnInit(): void { + this.cycleBackground(); + + this.timer = setInterval(() => { + this.cycleBackground(); + }, 30000); + } + + private cycleBackground() { + this.images.getRandomBackgroundWithInfo().subscribe((x) => { + this.background = this.sanitizer.bypassSecurityTrustStyle("url(" + x.url + ")"); + this.name = x.name; + }); + } + } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/components/image/image.component.html b/src/Ombi/ClientApp/src/app/components/image/image.component.html new file mode 100644 index 000000000..9ee9d7d19 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/components/image/image.component.html @@ -0,0 +1 @@ + diff --git a/src/Ombi/ClientApp/src/app/components/image/image.component.stories.ts b/src/Ombi/ClientApp/src/app/components/image/image.component.stories.ts new file mode 100644 index 000000000..b6f7b21fc --- /dev/null +++ b/src/Ombi/ClientApp/src/app/components/image/image.component.stories.ts @@ -0,0 +1,73 @@ +// also exported from '@storybook/angular' if you can deal with breaking changes in 6.1 +import { APP_BASE_HREF } from '@angular/common'; +import { Story, Meta, moduleMetadata } from '@storybook/angular'; +import { RequestType } from '../../interfaces'; +import { ImageComponent } from './image.component'; + +// More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export +export default { + title: 'Image Component', + component: ImageComponent, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: APP_BASE_HREF, + useValue: "" + }, + ] + }) + ] +} as Meta; + +// More on component templates: https://storybook.js.org/docs/angular/writing-stories/introduction#using-args +const Template: Story = (args: ImageComponent) => ({ + props: args, +}); + +export const Primary = Template.bind({}); +// More on args: https://storybook.js.org/docs/angular/writing-stories/args +Primary.args = { + src: 'https://ombi.io/img/logo-orange-small.png', + type: RequestType.movie +}; + +export const ClassApplied = Template.bind({}); +ClassApplied.args = { + src: 'https://ombi.io/img/logo-orange-small.png', + type: RequestType.movie, + class: 'test-class' +}; + +export const StyleApplied = Template.bind({}); +StyleApplied.args = { + src: 'https://ombi.io/img/logo-orange-small.png', + type: RequestType.movie, + style: 'background-color: red;' +}; + +export const IdApplied = Template.bind({}); +IdApplied.args = { + src: 'https://ombi.io/img/logo-orange-small.png', + type: RequestType.movie, + id: 'testId123' +}; + +// export const InvalidMovieImage = Template.bind({}); +// InvalidMovieImage.args = { +// src: 'https://httpstat.us/429', +// type: RequestType.movie, +// id: 'testId123' +// }; + +// export const InvalidTvImage = Template.bind({}); +// InvalidTvImage.args = { +// src: 'https://httpstat.us/429', +// type: RequestType.tvShow, +// }; + +// export const InvalidMusicImage = Template.bind({}); +// InvalidMusicImage.args = { +// src: 'https://httpstat.us/429', +// type: RequestType.album, +// }; diff --git a/src/Ombi/ClientApp/src/app/components/image/image.component.ts b/src/Ombi/ClientApp/src/app/components/image/image.component.ts new file mode 100644 index 000000000..57099016a --- /dev/null +++ b/src/Ombi/ClientApp/src/app/components/image/image.component.ts @@ -0,0 +1,57 @@ +import { OmbiCommonModules } from "../modules"; +import { ChangeDetectionStrategy, Component, ElementRef, Inject, Input, ViewEncapsulation } from "@angular/core"; +import { RequestType } from "../../interfaces"; +import { APP_BASE_HREF } from "@angular/common"; + +@Component({ + standalone: true, + selector: 'ombi-image', + imports: [...OmbiCommonModules], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './image.component.html', + }) + export class ImageComponent { + + @Input() public src: string; + @Input() public type: RequestType; + + // Attributes from the parent + @Input() public class: string; + @Input() public id: string; + @Input() public alt: string; + @Input() public style: string; + + public baseUrl: string = ""; + + public defaultTv = "/images/default_tv_poster.png"; + private defaultMovie = "/images/default_movie_poster.png"; + private defaultMusic = "i/mages/default-music-placeholder.png"; + + constructor (@Inject(APP_BASE_HREF) public href: string) { + if (this.href.length > 1) { + this.baseUrl = this.href; + } + } + + public onError(event: any) { + // set to a placeholder + switch(this.type) { + case RequestType.movie: + event.target.src = this.baseUrl + this.defaultMovie; + break; + case RequestType.tvShow: + event.target.src = this.baseUrl + this.defaultTv; + break; + case RequestType.album: + event.target.src = this.baseUrl + this.defaultMusic; + break; + } + + // Retry the original image + const timeout = setTimeout(() => { + event.target.src = this.src; + clearTimeout(timeout); + }, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000); + } + } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/components/index.ts b/src/Ombi/ClientApp/src/app/components/index.ts new file mode 100644 index 000000000..092f6504b --- /dev/null +++ b/src/Ombi/ClientApp/src/app/components/index.ts @@ -0,0 +1,2 @@ +export * from "./image-background/image-background.component"; +export * from "./image/image.component"; \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/components/modules.ts b/src/Ombi/ClientApp/src/app/components/modules.ts new file mode 100644 index 000000000..1bf5697e6 --- /dev/null +++ b/src/Ombi/ClientApp/src/app/components/modules.ts @@ -0,0 +1,3 @@ +import { CommonModule } from "@angular/common"; + +export const OmbiCommonModules = [ CommonModule ]; \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/custompage/custompage.component.ts b/src/Ombi/ClientApp/src/app/custompage/custompage.component.ts index 61094e793..37e2dce77 100644 --- a/src/Ombi/ClientApp/src/app/custompage/custompage.component.ts +++ b/src/Ombi/ClientApp/src/app/custompage/custompage.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, SecurityContext } from "@angular/core"; -import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; import { DomSanitizer } from "@angular/platform-browser"; import { AuthService } from "../auth/auth.service"; import { CustomPageService, NotificationService } from "../services"; @@ -10,11 +10,11 @@ import { CustomPageService, NotificationService } from "../services"; }) export class CustomPageComponent implements OnInit { - public form: FormGroup; + public form: UntypedFormGroup; public isEditing: boolean; public isAdmin: boolean; - constructor(private auth: AuthService, private settings: CustomPageService, private fb: FormBuilder, + constructor(private auth: AuthService, private settings: CustomPageService, private fb: UntypedFormBuilder, private notificationService: NotificationService, private sanitizer: DomSanitizer) { } diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html index 9ca607082..a479b5072 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.html @@ -2,15 +2,14 @@
-
+
{{ 'Common.' + RequestType[result.type] | translate }}
{{getAvailabilityStatus()}}
- {{result.title}} + \ No newline at end of file +
diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.scss b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.scss index 5fe422fd7..6a7911257 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.scss +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.scss @@ -83,7 +83,7 @@ small { } -.image { +::ng-deep .image { border-radius: 10px; opacity: 1; display: block; diff --git a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts index 559a962b5..d5557c0b5 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/card/discover-card.component.ts @@ -169,6 +169,19 @@ export class DiscoverCardComponent implements OnInit { } } + public onImageError(event: any) { + const originalSrc = event.target.src; + + // set to a placeholder + event.target.src = "../../../images/default_movie_poster.png"; + + // Retry the original image + const timeout = setTimeout(() => { + event.target.src = originalSrc; + clearTimeout(timeout); + }, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000); + } + private getExtraMovieInfo() { if (!this.result.imdbid) { this.searchService.getFullMovieDetails(+this.result.id) diff --git a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts index 9617c993e..43cde5036 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/carousel-list/carousel-list.component.ts @@ -48,6 +48,7 @@ export class CarouselListComponent implements OnInit { constructor(private searchService: SearchV2Service, private storageService: StorageService, private featureFacade: FeaturesFacade) { + Carousel.prototype.onTouchMove = () => { }, this.responsiveOptions = [ { breakpoint: '4000px', diff --git a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html index b1911ceee..97ff038a0 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.html @@ -2,7 +2,13 @@
-
+ +
diff --git a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts index a8544365d..295902927 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/search-results/search-results.component.ts @@ -29,6 +29,7 @@ export class DiscoverSearchResultsComponent implements OnInit { public filter: SearchFilter; private isAdvancedSearch: boolean; + private loadPosition: number = 30; constructor(private searchService: SearchV2Service, private route: ActivatedRoute, @@ -65,7 +66,7 @@ export class DiscoverSearchResultsComponent implements OnInit { } }); - if (this.advancedDataService) { + if (this.isAdvancedSearch) { return; } this.loadingFlag = true; @@ -179,6 +180,31 @@ export class DiscoverSearchResultsComponent implements OnInit { }); } + public onScroll() { + console.log("scrolled"); + if (this.advancedDataService) { + this.loadMoreAdvancedSearch(); + return; + } + } + + private loadMoreAdvancedSearch() { + const advancedOptions = this.advancedDataService.getOptions(); + + this.searchService.advancedSearch({ + type: advancedOptions.type == RequestType.movie ? "movie" : "tv", + companies: advancedOptions.companies, + genreIds: advancedOptions.genres, + keywordIds : advancedOptions.keywords, + releaseYear: advancedOptions.releaseYear, + watchProviders: advancedOptions.watchProviders, + }, this.loadPosition, 30).then(x => { + + this.loadPosition += 30; + this.mapAdvancedData(x); + }); + } + private async search() { this.clear(); this.results = await this.searchService diff --git a/src/Ombi/ClientApp/src/app/discover/discover.module.ts b/src/Ombi/ClientApp/src/app/discover/discover.module.ts index 414c881e6..eade4e451 100644 --- a/src/Ombi/ClientApp/src/app/discover/discover.module.ts +++ b/src/Ombi/ClientApp/src/app/discover/discover.module.ts @@ -8,6 +8,7 @@ import { PipeModule } from "../pipes/pipe.module"; import { RouterModule } from "@angular/router"; import { SharedModule } from "../shared/shared.module"; import { SkeletonModule } from 'primeng/skeleton'; +import { ImageComponent } from 'app/components'; @NgModule({ imports: [ @@ -18,6 +19,7 @@ import { SkeletonModule } from 'primeng/skeleton'; MatButtonToggleModule, InfiniteScrollModule, SkeletonModule, + ImageComponent ], declarations: [ ...fromComponents.components diff --git a/src/Ombi/ClientApp/src/app/interfaces/IImages.ts b/src/Ombi/ClientApp/src/app/interfaces/IImages.ts index 0c73df490..baa1a9829 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IImages.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IImages.ts @@ -1,3 +1,7 @@ export interface IImages { url: string; } +export interface IImagesInfo { + url: string; + name: string; +} diff --git a/src/Ombi/ClientApp/src/app/interfaces/IMovieDb.ts b/src/Ombi/ClientApp/src/app/interfaces/IMovieDb.ts index f82225434..77352de4b 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IMovieDb.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IMovieDb.ts @@ -17,3 +17,8 @@ export interface IDiscoverModel { watchProviders?: number[]; companies?: number[]; } +export interface ILanguage { + iso_639_1 : string; + english_name : string; + name : string; +} diff --git a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts index 95b1fba00..4594d2a2f 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/ISettings.ts @@ -339,7 +339,8 @@ export interface ITheMovieDbSettings extends ISettings { showAdultMovies: boolean; excludedKeywordIds: number[]; excludedMovieGenreIds: number[]; - excludedTvGenreIds: number[] + excludedTvGenreIds: number[]; + originalLanguages: string[]; } export interface IUpdateModel diff --git a/src/Ombi/ClientApp/src/app/issues/issuestable.component.html b/src/Ombi/ClientApp/src/app/issues/issuestable.component.html index 8e22d3e51..99b297b60 100644 --- a/src/Ombi/ClientApp/src/app/issues/issuestable.component.html +++ b/src/Ombi/ClientApp/src/app/issues/issuestable.component.html @@ -14,7 +14,7 @@ - +
{{ 'Issues.Details' | translate}} diff --git a/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.html b/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.html index abad6473f..3b8a46db7 100644 --- a/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.html +++ b/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.html @@ -1,5 +1,5 @@ -
-
+
+
diff --git a/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.scss b/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.scss index 7eb4a4b3c..4c6e7ea9a 100644 --- a/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.scss +++ b/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.scss @@ -26,15 +26,6 @@ div.centered { transform: translate(-50%, -50%); } -div.bg { - background-position: center center; - background-repeat: no-repeat; - background-attachment: fixed; - background-size: cover; - height: 100vh; - width: 100vw; - position: fixed; -} .online{ color:lightgreen; diff --git a/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.ts b/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.ts index 33f8873f8..e2758b146 100644 --- a/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.ts +++ b/src/Ombi/ClientApp/src/app/landingpage/landingpage.component.ts @@ -1,48 +1,34 @@ -import { PlatformLocation, APP_BASE_HREF } from "@angular/common"; -import { Component, OnDestroy, OnInit, Inject } from "@angular/core"; +import { APP_BASE_HREF } from "@angular/common"; +import { Component, OnInit, Inject } from "@angular/core"; import { IMediaServerStatus } from "../interfaces"; import { ICustomizationSettings, ILandingPageSettings } from "../interfaces"; import { LandingPageService } from "../services"; import { SettingsService } from "../services"; -import { DomSanitizer } from "@angular/platform-browser"; -import { ImageService } from "../services"; - -import { fadeInOutAnimation } from "../animations/fadeinout"; import { CustomizationFacade } from "../state/customization"; -import { ThousandShortPipe } from "../pipes/ThousandShortPipe"; @Component({ templateUrl: "./landingpage.component.html", - animations: [fadeInOutAnimation], styleUrls: ["./landingpage.component.scss"], }) -export class LandingPageComponent implements OnDestroy, OnInit { +export class LandingPageComponent implements OnInit { public customizationSettings: ICustomizationSettings; public landingPageSettings: ILandingPageSettings; - public background: any; public mediaServerStatus: IMediaServerStatus; public baseUrl: string; - private timer: any; private href: string; constructor(private settingsService: SettingsService, - private images: ImageService, private sanitizer: DomSanitizer, private landingPageService: LandingPageService, + private landingPageService: LandingPageService, private customizationFacade: CustomizationFacade, @Inject(APP_BASE_HREF) href :string) { this.href = href } public ngOnInit() { this.customizationFacade.settings$().subscribe(x => this.customizationSettings = x); this.settingsService.getLandingPage().subscribe(x => this.landingPageSettings = x); - this.images.getRandomBackground().subscribe(x => { - this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 19%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 79%, transparent 80%), url(" + x.url + ")"); - }); - this.timer = setInterval(() => { - this.cycleBackground(); - }, 30000); const base = this.href; if (base.length > 1) { @@ -53,18 +39,4 @@ export class LandingPageComponent implements OnDestroy, OnInit { this.mediaServerStatus = x; }); } - - public ngOnDestroy() { - clearInterval(this.timer); - } - - public cycleBackground() { - this.images.getRandomBackground().subscribe(x => { - this.background = ""; - }); - this.images.getRandomBackground().subscribe(x => { - this.background = this.sanitizer - .bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")"); - }); - } } diff --git a/src/Ombi/ClientApp/src/app/login/login.component.html b/src/Ombi/ClientApp/src/app/login/login.component.html index c0932999b..71caa80b4 100644 --- a/src/Ombi/ClientApp/src/app/login/login.component.html +++ b/src/Ombi/ClientApp/src/app/login/login.component.html @@ -1,7 +1,4 @@ -
- -
+
diff --git a/src/Ombi/ClientApp/src/app/login/login.component.scss b/src/Ombi/ClientApp/src/app/login/login.component.scss index 4ad645dd8..df368ee0c 100644 --- a/src/Ombi/ClientApp/src/app/login/login.component.scss +++ b/src/Ombi/ClientApp/src/app/login/login.component.scss @@ -11,23 +11,6 @@ img.center { max-width: 100%; } -.login-gradient-bar{ - background: linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.6) 20.0%, rgba(0,0,0,0.6) 80.0%, transparent 60%),transparent; - height:100%; - width:100%; - position: absolute; -} - -div.bg { - background-position: center center; - background-repeat: no-repeat; - background-attachment: fixed; - background-size: cover; - height: 100vh; - width: 100vw; - position: fixed; -} - .card-container.card { max-width: 500px; padding: 45px 45px; diff --git a/src/Ombi/ClientApp/src/app/login/login.component.ts b/src/Ombi/ClientApp/src/app/login/login.component.ts index f6f519191..6e8efc00b 100644 --- a/src/Ombi/ClientApp/src/app/login/login.component.ts +++ b/src/Ombi/ClientApp/src/app/login/login.component.ts @@ -1,5 +1,5 @@ import { Component, OnDestroy, OnInit, Inject } from "@angular/core"; -import { FormBuilder, FormGroup, Validators } from "@angular/forms"; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; @@ -10,25 +10,19 @@ import { PlexTvService } from "../services"; import { SettingsService } from "../services"; import { StatusService } from "../services"; -import { DomSanitizer } from "@angular/platform-browser"; -import { ImageService } from "../services"; - -import { fadeInOutAnimation } from "../animations/fadeinout"; import { StorageService } from "../shared/storage/storage-service"; import { MatSnackBar } from "@angular/material/snack-bar"; import { CustomizationFacade } from "../state/customization"; @Component({ templateUrl: "./login.component.html", - animations: [fadeInOutAnimation], styleUrls: ["./login.component.scss"], }) export class LoginComponent implements OnDestroy, OnInit { - public form: FormGroup; + public form: UntypedFormGroup; public customizationSettings: ICustomizationSettings; public authenticationSettings: IAuthenticationSettings; public plexEnabled: boolean; - public background: any; public landingFlag: boolean; public baseUrl: string; public loginWithOmbi: boolean; @@ -46,7 +40,6 @@ export class LoginComponent implements OnDestroy, OnInit { public get appNameTranslate(): object { return { appName: this.appName }; } - private timer: any; private clientId: string; private errorBody: string; @@ -59,11 +52,9 @@ export class LoginComponent implements OnDestroy, OnInit { private authService: AuthService, private router: Router, private status: StatusService, - private fb: FormBuilder, + private fb: UntypedFormBuilder, private settingsService: SettingsService, private customziationFacade: CustomizationFacade, - private images: ImageService, - private sanitizer: DomSanitizer, private route: ActivatedRoute, @Inject(APP_BASE_HREF) href: string, private translate: TranslateService, @@ -111,14 +102,6 @@ export class LoginComponent implements OnDestroy, OnInit { this.headerAuth(); }); this.settingsService.getClientId().subscribe((x) => (this.clientId = x)); - this.images.getRandomBackground().subscribe((x) => { - this.background = this.sanitizer.bypassSecurityTrustStyle( - "url(" + x.url + ")" - ); - }); - this.timer = setInterval(() => { - this.cycleBackground(); - }, 30000); const base = this.href; if (base.length > 1) { @@ -132,7 +115,7 @@ export class LoginComponent implements OnDestroy, OnInit { .subscribe((x) => (this.errorValidation = x)); } - public onSubmit(form: FormGroup) { + public onSubmit(form: UntypedFormGroup) { if (form.invalid) { this.notify.open(this.errorValidation, "OK", { duration: 300000, @@ -284,18 +267,6 @@ export class LoginComponent implements OnDestroy, OnInit { } public ngOnDestroy() { - clearInterval(this.timer); clearInterval(this.pinTimer); } - - private cycleBackground() { - this.images.getRandomBackground().subscribe((x) => { - this.background = ""; - }); - this.images.getRandomBackground().subscribe((x) => { - this.background = this.sanitizer.bypassSecurityTrustStyle( - "url(" + x.url + ")" - ); - }); - } } diff --git a/src/Ombi/ClientApp/src/app/login/resetpassword.component.html b/src/Ombi/ClientApp/src/app/login/resetpassword.component.html index 9fbeb7eda..201223800 100644 --- a/src/Ombi/ClientApp/src/app/login/resetpassword.component.html +++ b/src/Ombi/ClientApp/src/app/login/resetpassword.component.html @@ -1,8 +1,5 @@  -
- -
+
diff --git a/src/Ombi/ClientApp/src/app/login/resetpassword.component.ts b/src/Ombi/ClientApp/src/app/login/resetpassword.component.ts index cb030b386..bd0376cb1 100644 --- a/src/Ombi/ClientApp/src/app/login/resetpassword.component.ts +++ b/src/Ombi/ClientApp/src/app/login/resetpassword.component.ts @@ -1,30 +1,27 @@ -import { PlatformLocation, APP_BASE_HREF } from "@angular/common"; +import { APP_BASE_HREF } from "@angular/common"; import { Component, OnInit, Inject } from "@angular/core"; -import { FormBuilder, FormGroup, Validators } from "@angular/forms"; -import { DomSanitizer } from "@angular/platform-browser"; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; import { fadeInOutAnimation } from "../animations/fadeinout"; import { ICustomizationSettings } from "../interfaces"; -import { IdentityService, ImageService, NotificationService, SettingsService } from "../services"; +import { IdentityService, NotificationService, SettingsService } from "../services"; import { CustomizationFacade } from "../state/customization"; @Component({ templateUrl: "./resetpassword.component.html", - animations: [fadeInOutAnimation], styleUrls: ["./login.component.scss"], }) export class ResetPasswordComponent implements OnInit { - public form: FormGroup; + public form: UntypedFormGroup; public customizationSettings: ICustomizationSettings; public emailSettingsEnabled: boolean; public baseUrl: string; - public background: any; private href: string; constructor(private identityService: IdentityService, private notify: NotificationService, - private fb: FormBuilder, private settingsService: SettingsService, @Inject(APP_BASE_HREF) href:string, - private images: ImageService, private sanitizer: DomSanitizer, private customizationFacade: CustomizationFacade) { + private fb: UntypedFormBuilder, private settingsService: SettingsService, @Inject(APP_BASE_HREF) href:string, + private customizationFacade: CustomizationFacade) { this.href = href; this.form = this.fb.group({ email: ["", [Validators.required]], @@ -32,9 +29,7 @@ export class ResetPasswordComponent implements OnInit { } public ngOnInit() { - this.images.getRandomBackground().subscribe(x => { - this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")"); - }); + const base = this.href; if (base.length > 1) { this.baseUrl = base; @@ -43,7 +38,7 @@ export class ResetPasswordComponent implements OnInit { this.settingsService.getEmailSettingsEnabled().subscribe(x => this.emailSettingsEnabled = x); } - public onSubmit(form: FormGroup) { + public onSubmit(form: UntypedFormGroup) { if (this.emailSettingsEnabled) { if (form.invalid) { diff --git a/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.html b/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.html index 6a1c2567f..bd8bc3e38 100644 --- a/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.html +++ b/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.html @@ -1,11 +1,7 @@ - -
- -
+
- +
diff --git a/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts b/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts index 695a79abb..b7ef08281 100644 --- a/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts +++ b/src/Ombi/ClientApp/src/app/login/tokenresetpassword.component.ts @@ -1,10 +1,9 @@ import { ActivatedRoute, Params } from "@angular/router"; import { Component, OnInit } from "@angular/core"; -import { FormBuilder, FormGroup, Validators } from "@angular/forms"; -import { IdentityService, ImageService } from "../services"; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from "@angular/forms"; +import { IdentityService } from "../services"; import { CustomizationFacade } from "../state/customization"; -import { DomSanitizer } from "@angular/platform-browser"; import { ICustomizationSettings } from "../interfaces"; import { IResetPasswordToken } from "../interfaces"; import { NotificationService } from "../services"; @@ -17,15 +16,12 @@ import { Router } from "@angular/router"; }) export class TokenResetPasswordComponent implements OnInit { - public form: FormGroup; + public form: UntypedFormGroup; public customizationSettings: ICustomizationSettings; - public background: any; public baseUrl: string; constructor(private identityService: IdentityService, private router: Router, private route: ActivatedRoute, private notify: NotificationService, - private fb: FormBuilder, private location: PlatformLocation, private images: ImageService, - private sanitizer: DomSanitizer, private customizationFacade: CustomizationFacade, - ) { + private fb: UntypedFormBuilder, private location: PlatformLocation, private customizationFacade: CustomizationFacade) { this.route.queryParams .subscribe((params: Params) => { @@ -39,9 +35,6 @@ export class TokenResetPasswordComponent implements OnInit { } public ngOnInit() { - this.images.getRandomBackground().subscribe(x => { - this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")"); - }); const base = this.location.getBaseHrefFromDOM(); if (base.length > 1) { this.baseUrl = base; @@ -49,7 +42,7 @@ export class TokenResetPasswordComponent implements OnInit { this.customizationFacade.settings$().subscribe(x => this.customizationSettings = x); } - public onSubmit(form: FormGroup) { + public onSubmit(form: UntypedFormGroup) { if (form.invalid) { this.notify.error("Email address is required"); return; @@ -65,6 +58,5 @@ export class TokenResetPasswordComponent implements OnInit { }); } }); - } } diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html index 2207ea323..20f5d303b 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.html @@ -10,9 +10,8 @@ @@ -24,7 +23,7 @@
- +
@@ -107,7 +106,7 @@ - +
- + {{'MediaDetails.RecommendationsTitle' | translate}} -
+ - + {{'MediaDetails.SimilarTitle' | translate}} -
+
\ No newline at end of file +
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts index c040fe4e0..8d1e5976b 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/movie-details.component.ts @@ -1,6 +1,6 @@ -import { AfterViewInit, Component, OnInit, ViewChild, ViewEncapsulation } from "@angular/core"; +import { Component, OnInit, ViewEncapsulation } from "@angular/core"; import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService, SettingsStateService } from "../../../services"; -import { ActivatedRoute } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { DomSanitizer } from "@angular/platform-browser"; import { ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; import { MatDialog } from "@angular/material/dialog"; @@ -12,7 +12,6 @@ import { NewIssueComponent } from "../shared/new-issue/new-issue.component"; import { TranslateService } from "@ngx-translate/core"; import { MovieAdvancedOptionsComponent } from "./panels/movie-advanced-options/movie-advanced-options.component"; import { RequestServiceV2 } from "../../../services/requestV2.service"; -import { RequestBehalfComponent } from "../shared/request-behalf/request-behalf.component"; import { firstValueFrom, forkJoin } from "rxjs"; import { AdminRequestDialogComponent } from "../../../shared/admin-request-dialog/admin-request-dialog.component"; import { FeaturesFacade } from "../../../state/features/features.facade"; @@ -32,31 +31,45 @@ export class MovieDetailsComponent implements OnInit{ public issuesEnabled: boolean; public roleName4k = "Request4KMovie"; public is4KEnabled = false; - public requestType = RequestType.movie; - - private theMovidDbId: number; private imdbId: string; + private snapMovieId: string; - constructor(private searchService: SearchV2Service, private route: ActivatedRoute, + + constructor(private searchService: SearchV2Service, private route: ActivatedRoute, private router: Router, private sanitizer: DomSanitizer, private imageService: ImageService, public dialog: MatDialog, private requestService: RequestService, private requestService2: RequestServiceV2, private radarrService: RadarrService, public messageService: MessageService, private auth: AuthService, private settingsState: SettingsStateService, private translate: TranslateService, private featureFacade: FeaturesFacade) { - this.route.params.subscribe(async (params: any) => { - if (typeof params.movieDbId === 'string' || params.movieDbId instanceof String) { - if (params.movieDbId.startsWith("tt")) { - this.imdbId = params.movieDbId; - } - } - this.theMovidDbId = params.movieDbId; + this.snapMovieId = this.route.snapshot.params.movieDbId; + this.route.params.subscribe(async (params: any) => { + if (typeof params.movieDbId === 'string' || params.movieDbId instanceof String) { + if (params.movieDbId.startsWith("tt")) { + this.imdbId = params.movieDbId; + // Check if we user navigated to another movie and if so reload the component + if (this.imdbId !== this.snapMovieId) { + this.reloadComponent() + } + } + } + this.theMovidDbId = params.movieDbId; + // Check if we user navigated to another movie and if so reload the component + if (params.movieDbId !== this.snapMovieId) { + this.reloadComponent() + } }); } - async ngOnInit() { + reloadComponent() { + let currentUrl = this.router.url; + this.router.routeReuseStrategy.shouldReuseRoute = () => false; + this.router.onSameUrlNavigation = 'reload'; + this.router.navigate([currentUrl]); + } + async ngOnInit() { this.is4KEnabled = this.featureFacade.is4kEnabled(); this.issuesEnabled = this.settingsState.getIssue(); this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); @@ -68,6 +81,7 @@ export class MovieDetailsComponent implements OnInit{ if (this.imdbId) { this.searchService.getMovieByImdbId(this.imdbId).subscribe(async x => { this.movie = x; + this.checkPoster(); if (this.movie.requestId > 0) { // Load up this request this.hasRequest = true; @@ -78,6 +92,7 @@ export class MovieDetailsComponent implements OnInit{ } else { this.searchService.getFullMovieDetails(this.theMovidDbId).subscribe(async x => { this.movie = x; + this.checkPoster(); if (this.movie.requestId > 0) { // Load up this request this.hasRequest = true; @@ -272,7 +287,14 @@ export class MovieDetailsComponent implements OnInit{ } }); } - + private checkPoster() { + if (this.movie.posterPath == null) { + this.movie.posterPath = "../../../images/default_movie_poster.png"; + } + else { + this.movie.posterPath = "https://image.tmdb.org/t/p/w300/" + this.movie.posterPath + }; + } private loadAdvancedInfo() { const profile = this.radarrService.getQualityProfilesFromSettings(); const folders = this.radarrService.getRootFoldersFromSettings(); diff --git a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html index 7fd3ce0ea..29d2c6beb 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/movie/panels/movie-information-panel.component.html @@ -30,28 +30,35 @@ {{ this.movie.status | translateStatus }}
- {{'MediaDetails.Availability' | translate }} - {{'Common.Available' | translate}} - {{'Common.NotAvailable' | translate}} -
-
- {{'MediaDetails.RequestStatus' | translate }} -
{{'Common.Denied' | translate}}
-
{{'Common.ProcessingRequest' | translate}}
-
{{'Common.PendingApproval' | translate}} -
-
{{'Common.NotRequested' | translate}} -
+ {{'MediaDetails.Availability' | translate }} + {{'Common.Available' | translate}} + {{'Common.NotAvailable' | translate}} +
+
+ {{'MediaDetails.RequestStatus' | translate }} +
+
{{'Common.RequestDenied' | translate}}
+
{{'Common.ProcessingRequest' | translate}}
+
{{'Common.PendingApproval' | translate}}
+ +
+
+
{{'Common.RequestDenied4K' | translate}}
+
{{'Common.ProcessingRequest4K' | translate}}
+
{{'Common.PendingApproval4K' | translate}}
+ +
- {{'MediaDetails.RequestedBy' | translate }} - {{request.requestedUser.userAlias}} + {{'MediaDetails.RequestedBy' | translate }} + {{request.requestedUser.userAlias}}
- {{'MediaDetails.RequestDate' | translate }} - {{request.requestedDate | amUserLocale | amDateFormat: 'LL'}} + {{'MediaDetails.RequestDate' | translate }} + {{request.requestedDate4k | amUserLocale | amDateFormat: 'LL'}} + {{request.requestedDate | amUserLocale | amDateFormat: 'LL'}}
@@ -59,9 +66,14 @@ {{RequestSource[request.source]}}
-
- {{'MediaDetails.DeniedReason' | translate }} +
+ {{'MediaDetails.DeniedReason' | translate }} +
{{request.deniedReason}} +
+
+ {{request.deniedReason4K}} +
@@ -72,7 +84,7 @@
{{'MediaDetails.Quality' | translate }}  - {{"4K" | quality}} + {{"4K" | quality}}
diff --git a/src/Ombi/ClientApp/src/app/media-details/components/shared/cast-carousel/cast-carousel.component.html b/src/Ombi/ClientApp/src/app/media-details/components/shared/cast-carousel/cast-carousel.component.html index 49875514d..e14323a0e 100644 --- a/src/Ombi/ClientApp/src/app/media-details/components/shared/cast-carousel/cast-carousel.component.html +++ b/src/Ombi/ClientApp/src/app/media-details/components/shared/cast-carousel/cast-carousel.component.html @@ -6,10 +6,10 @@