Merge pull request #4474 from Ombi-app/develop

Release
pull/4484/head
Jamie 3 years ago committed by GitHub
commit ae17abdd8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,6 +3,7 @@ name: CI Build
on:
push:
branches: [ develop, master ]
workflow_dispatch:
jobs:
build-ui:
@ -38,7 +39,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
dotnet-version: '6.0.x'
- name: Nuget Cache
uses: actions/cache@v2
@ -67,6 +68,7 @@ jobs:
uses: TriPSs/conventional-changelog-action@v3
with:
version-file: 'version.json'
release-count: 40
skip-on-empty: 'false'
git-message: 'chore(release): :rocket: {version}'
@ -102,6 +104,12 @@ jobs:
format: tar.gz
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
- name: Nuget Cache
uses: actions/cache@v2

@ -7,6 +7,7 @@ on:
branches: [ develop ]
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
automation-tests:
@ -18,7 +19,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
dotnet-version: 6.0.x
- uses: actions/setup-node@v2
with:
node-version: '14'
@ -38,7 +39,7 @@ jobs:
- name: Start Backend
run: |
nohup dotnet run -p ./src/Ombi -- --host http://*:3577 &
nohup dotnet run --project ./src/Ombi -- --host http://*:3577 &
- name: Start Frontend
run: |
nohup yarn --cwd ./src/Ombi/ClientApp start &

@ -3,6 +3,7 @@ name: PR Build
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
jobs:
build-ui:
@ -31,7 +32,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
with:
dotnet-version: '5.0.x'
dotnet-version: '6.0.x'
- name: Nuget Cache
uses: actions/cache@v2
@ -89,6 +90,9 @@ jobs:
format: tar.gz
steps:
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
- name: Nuget Cache
uses: actions/cache@v2

@ -1,3 +1,248 @@
## [4.10.1](https://github.com/Ombi-app/Ombi/compare/v4.10.0...v4.10.1) (2022-01-22)
## [4.3.3](https://github.com/Ombi-app/Ombi/compare/v4.3.2...v4.3.3) (2021-11-05)
# [4.10.0](https://github.com/Ombi-app/Ombi/compare/v4.9.2...v4.10.0) (2022-01-14)
### Features
* **notifications:** :sparkles: Send new request email notifications to power users ([#4462](https://github.com/Ombi-app/Ombi/issues/4462)) ([10cc0c0](https://github.com/Ombi-app/Ombi/commit/10cc0c0951f13221179516f8ff5c44dbecc9a0fd))
## [4.9.2](https://github.com/Ombi-app/Ombi/compare/v4.9.1...v4.9.2) (2022-01-14)
### Bug Fixes
* :bug: Add UI for Emby recently added cronjob settings ([#4469](https://github.com/Ombi-app/Ombi/issues/4469)) ([7d47bbe](https://github.com/Ombi-app/Ombi/commit/7d47bbe92204855bf75d70b8fa548f9c3f3612bc))
* **sonarr:** :bug: Fixed an issue where we could attempt to add a series to sonarr before sonarr has got all the metadata [#4459](https://github.com/Ombi-app/Ombi/issues/4459) ([5c691dc](https://github.com/Ombi-app/Ombi/commit/5c691dc98437a4cd24560ff625414fe05dd22f89))
* **wizard:** :bug: Fixed the issue where the Application Url wasn't validated in the wizard ([33b8d11](https://github.com/Ombi-app/Ombi/commit/33b8d1111a1c6663d8c0bbd912be4660da7d013f)), closes [#4417](https://github.com/Ombi-app/Ombi/issues/4417)
## [4.9.1](https://github.com/Ombi-app/Ombi/compare/v4.9.0...v4.9.1) (2022-01-14)
### Bug Fixes
* **discover:** 🌐 Localize episodes names in TV details ([#4467](https://github.com/Ombi-app/Ombi/issues/4467)) [skip ci] ([35806ea](https://github.com/Ombi-app/Ombi/commit/35806ea2d2c866d628cf08577026a02ab04e49d9))
* **email-notifications:** :bug: Fixed the issue where legacy requests were showing broken poster images [#4452](https://github.com/Ombi-app/Ombi/issues/4452) ([0ece2fd](https://github.com/Ombi-app/Ombi/commit/0ece2fd6e0eb01e0b7d4d2a01e1a276c7a9c5a51))
* **emby/jellyfin:** :bug: A more reliable Emby and Jellyfin sync [skip ci] ([ad677fa](https://github.com/Ombi-app/Ombi/commit/ad677fa02eb75633014e9c9791c21ed2d6a23229))
# [4.9.0](https://github.com/Ombi-app/Ombi/compare/v4.8.1...v4.9.0) (2022-01-05)
### Features
* **customization:** :sparkles: Added possibility for custom favicons ([40af659](https://github.com/Ombi-app/Ombi/commit/40af6593b668d4712327c18f92f5b7b5a0a65e26))
## [4.8.1](https://github.com/Ombi-app/Ombi/compare/v4.8.0...v4.8.1) (2022-01-04)
# [4.8.0](https://github.com/Ombi-app/Ombi/compare/v4.7.11...v4.8.0) (2021-12-22)
### Bug Fixes
* **auto-delete:** :bug: We now also auto delete music requests, this was previously missing ([9fe1f8e](https://github.com/Ombi-app/Ombi/commit/9fe1f8e988aa31d36e7a685ae19f72d9c8414dc0))
### Features
* **details:** :sparkles: Added the notify button back into the details pages for requests ([8b33cdc](https://github.com/Ombi-app/Ombi/commit/8b33cdccef83db8794414b247438214b00860fac))
## [4.7.11](https://github.com/Ombi-app/Ombi/compare/v4.7.10...v4.7.11) (2021-12-17)
### Bug Fixes
* **availability-rules:** :bug: Show the 'Requested' button when a show has all of the episodes marked as requested ([cb7ecf6](https://github.com/Ombi-app/Ombi/commit/cb7ecf684ac3ab204f329a28baecfd4f6cd408f7))
## [4.7.10](https://github.com/Ombi-app/Ombi/compare/v4.7.9...v4.7.10) (2021-12-16)
### Bug Fixes
* **discover:** :bug: Fixed an issue where monitored movies in radarr were not correctly represented on the search results ([75b15bc](https://github.com/Ombi-app/Ombi/commit/75b15bc7cba21f0a14a18c8e64fd52482f5c6325))
## [4.7.9](https://github.com/Ombi-app/Ombi/compare/v4.7.8...v4.7.9) (2021-12-16)
### Bug Fixes
* **sonarr:** :bug: Fixed an issue where we were sometimes incorrectly setting the state of episodes that are already monitored in sonarr ([fd1acb8](https://github.com/Ombi-app/Ombi/commit/fd1acb88cbc5e73f91b7f81e6e28ee06d66b277e))
## [4.7.8](https://github.com/Ombi-app/Ombi/compare/v4.7.7...v4.7.8) (2021-12-11)
### Bug Fixes
* **notifications:** :bug: Fixed the DenyReason sometimes not appearing in the notification message [#4409](https://github.com/Ombi-app/Ombi/issues/4409) ([209e311](https://github.com/Ombi-app/Ombi/commit/209e31175c95f6ee8909d878d45bf8269a9842d9))
* **oauth:** :lock: Fixed the issue where some users running their browsers in a different language could not open the Plex OAuth window ([d5404ea](https://github.com/Ombi-app/Ombi/commit/d5404eaad7837010d6e4563cd8f7a1009231d362)), closes [#4408](https://github.com/Ombi-app/Ombi/issues/4408)
* **translations:** 🌐 New translations from Crowdin ([5cfb76c](https://github.com/Ombi-app/Ombi/commit/5cfb76cad7a25eed8b452bf9c01cef8c32804369))
## [4.7.7](https://github.com/Ombi-app/Ombi/compare/v4.7.6...v4.7.7) (2021-12-08)
### Bug Fixes
* **notifications:** 🐛 Do not notify user upon auto approval of a TV show ([#4432](https://github.com/Ombi-app/Ombi/issues/4432)) ([3ad3bdd](https://github.com/Ombi-app/Ombi/commit/3ad3bddd8313d607ee2a39a51a92e61a3673082c)), closes [#4431](https://github.com/Ombi-app/Ombi/issues/4431)
* **translations:** 🌐 New translations from Crowdin ([8943a97](https://github.com/Ombi-app/Ombi/commit/8943a978bf459eaeb496d50c61c4d1506c727366))
## [4.7.6](https://github.com/Ombi-app/Ombi/compare/v4.7.5...v4.7.6) (2021-12-02)
### Bug Fixes
* **user-management:** :bug: Fixed an issue where you couldn't 'unset' a users custom quality and root folders ([bae96af](https://github.com/Ombi-app/Ombi/commit/bae96af17f50a80ae3ade235a5ef68d5d2dc12ba))
## [4.7.5](https://github.com/Ombi-app/Ombi/compare/v4.7.4...v4.7.5) (2021-11-28)
### Bug Fixes
* **notifications:** fixed an error that could happen when Ombi sends out a issue notification ([7442dcf](https://github.com/Ombi-app/Ombi/commit/7442dcf59da5d2190cc3087b10402e85bcfcf83b))
* **translations:** 🌐 Fix incorrect text translation reference RequestedByOn ([#4420](https://github.com/Ombi-app/Ombi/issues/4420)) ([202d155](https://github.com/Ombi-app/Ombi/commit/202d155493c29a6ddd4c5507186bf376a28f4c1d))
* **translations:** 🌐 New translations from Crowdin ([473c172](https://github.com/Ombi-app/Ombi/commit/473c1724922515fe376e0b2058ac391807c923f2))
## [4.7.4](https://github.com/Ombi-app/Ombi/compare/v4.7.3...v4.7.4) (2021-11-25)
### Bug Fixes
* **availability-rules:** :bug: Fixed a small issue where some shows would not appear as Available even know they had no future unaired episodes listed ([914b096](https://github.com/Ombi-app/Ombi/commit/914b096781c9b73292a533a010a5dd05ecfd0aac))
* **emby:** :bug: Fixed an issue where we were not properly syncing episodes ([75529dd](https://github.com/Ombi-app/Ombi/commit/75529dd972c5102f3c5234a2acf6fe664a1bcfad))
## [4.7.3](https://github.com/Ombi-app/Ombi/compare/v4.7.2...v4.7.3) (2021-11-23)
## [4.7.2](https://github.com/Ombi-app/Ombi/compare/v4.7.1...v4.7.2) (2021-11-22)
### Bug Fixes
* **request-list:** :bug: Fixed an issue where the bulk delete would not work for movie requests ([4b540fb](https://github.com/Ombi-app/Ombi/commit/4b540fb45bcc389664f0953159802288d005db9f))
## [4.7.1](https://github.com/Ombi-app/Ombi/compare/v4.7.0...v4.7.1) (2021-11-22)
### Bug Fixes
* **emby:** :bug: Fixed an issue where we slightly broke the full sync ([332d934](https://github.com/Ombi-app/Ombi/commit/332d9344d002a5ffd5aeac516c7441dcdec52248))
# [4.7.0](https://github.com/Ombi-app/Ombi/compare/v4.6.5...v4.7.0) (2021-11-19)
### Features
* **emby:** :sparkles: Added a emby recently added sync! ([a0e1406](https://github.com/Ombi-app/Ombi/commit/a0e14068f4bc457f8a4a565de71707a8f16c803c))
## [4.6.5](https://github.com/Ombi-app/Ombi/compare/v4.6.4...v4.6.5) (2021-11-15)
### Bug Fixes
* **issues:** :bug: Added the issue category to the issue 'cards' [#4403](https://github.com/Ombi-app/Ombi/issues/4403) ([a3739f3](https://github.com/Ombi-app/Ombi/commit/a3739f375c49f48e34da12f0a74e4e068f12ab40))
* **issues:** :bug: Added the issues back to the details page for TV Shows ([0225000](https://github.com/Ombi-app/Ombi/commit/02250000c08a253e57d8a0a855c2d30b8a1e5baa))
* **issues:** :bug: Fixed an issue where you couldn't navigate to the details page from TV issues ([1a2825b](https://github.com/Ombi-app/Ombi/commit/1a2825bf3839b891b16e1dde4030afe53efe090e))
* **issues:** :bug: Fixed where we did not show the poster when an issue is raised for media we do not have a request for [#4402](https://github.com/Ombi-app/Ombi/issues/4402) ([15e37b5](https://github.com/Ombi-app/Ombi/commit/15e37b532a83097dbdf1a9fea3eead7d0e211898))
## [4.6.4](https://github.com/Ombi-app/Ombi/compare/v4.6.3...v4.6.4) (2021-11-12)
## [4.6.3](https://github.com/Ombi-app/Ombi/compare/v4.6.2...v4.6.3) (2021-11-11)
### Bug Fixes
* **discover:** :bug: Display TV + movies on actor page in user language ([#4395](https://github.com/Ombi-app/Ombi/issues/4395)) ([fe635c7](https://github.com/Ombi-app/Ombi/commit/fe635c7106bc487ff879bdc8a73bab16cb389b97))
* **permissions:** :bug: Improved the security around the role "Manage Own Requests" ([#4397](https://github.com/Ombi-app/Ombi/issues/4397)) ([334a32b](https://github.com/Ombi-app/Ombi/commit/334a32bca42f90198d9b720d2bdb710a583be47f)), closes [#4391](https://github.com/Ombi-app/Ombi/issues/4391)
* **search:** Fixed some cases where search wouldn't work correctly ([#4398](https://github.com/Ombi-app/Ombi/issues/4398)) ([4410790](https://github.com/Ombi-app/Ombi/commit/4410790bc096826bc11554098f846e3acb59589a))
## [4.6.2](https://github.com/Ombi-app/Ombi/compare/v4.6.1...v4.6.2) (2021-11-10)
### Bug Fixes
* **discover:** TV shows now display on the Actor Pages ([#4388](https://github.com/Ombi-app/Ombi/issues/4388)) ([6b716e7](https://github.com/Ombi-app/Ombi/commit/6b716e722076e3d1e6bf2097c5263645d5ea9edf))
## [4.6.1](https://github.com/Ombi-app/Ombi/compare/v4.6.0...v4.6.1) (2021-11-10)
### Bug Fixes
* :bug: Fixed the MySQL issue after .net 6 upgrade [#4393](https://github.com/Ombi-app/Ombi/issues/4393) ([fea7ff0](https://github.com/Ombi-app/Ombi/commit/fea7ff05139e9ff50c8097fa5389b4ef9ad21a15))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([c6acb45](https://github.com/Ombi-app/Ombi/commit/c6acb45f8d3f371c0b4024c4272849d0d0cc867f))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([18c220a](https://github.com/Ombi-app/Ombi/commit/18c220a0cd0d19e45a07d0c319da2b9512778a8a))
# [4.6.0](https://github.com/Ombi-app/Ombi/compare/v4.4.0...v4.6.0) (2021-11-09)
### Features
* :sparkles: Upgrade Ombi to .NET 6 ([#4390](https://github.com/Ombi-app/Ombi/issues/4390)) ([719eb7d](https://github.com/Ombi-app/Ombi/commit/719eb7dbe37b3a72d264e2f670067518eef70694)), closes [#4392](https://github.com/Ombi-app/Ombi/issues/4392)
# [4.4.0](https://github.com/Ombi-app/Ombi/compare/v4.3.3...v4.4.0) (2021-11-06)
### Bug Fixes
* **request-list:** :bug: Fixed an issue where the request options were not appearing for Music requests ([c0406a2](https://github.com/Ombi-app/Ombi/commit/c0406a2ddebafb03d98ed25cdf7d89dc9a600c7d))
### Features
* **mass-email:** :sparkles: Added the ability to configure the Mass Email, we can now send BCC and we are less likely to be rate limited when not using bcc [#4377](https://github.com/Ombi-app/Ombi/issues/4377) ([ca655ae](https://github.com/Ombi-app/Ombi/commit/ca655ae57042dec44106a2f2ef5ba2e6f1019ee4))
## [4.3.3](https://github.com/Ombi-app/Ombi/compare/v4.3.2...v4.3.3) (2021-11-05)
@ -60,3 +305,78 @@
## [4.2.12](https://github.com/Ombi-app/Ombi/compare/v4.2.11...v4.2.12) (2021-10-20)
### Bug Fixes
* **newsletter:** :bug: Fixed a few small bugs in the newsletter ([21dba4c](https://github.com/Ombi-app/Ombi/commit/21dba4c524b98b9f2b883d97e7e13329425a8762))
* **translations:** 🌐 New translations en.json from Crowdin [skip ci] ([52eda6a](https://github.com/Ombi-app/Ombi/commit/52eda6ab917a73842bc02b0d8e0c442e564ca8f0))
* **translations:** 🌐 New translations en.json from Crowdin [skip ci] ([1095d52](https://github.com/Ombi-app/Ombi/commit/1095d524962648a1e427f0bcd8105fa734dd5b60))
## [4.2.11](https://github.com/Ombi-app/Ombi/compare/v4.2.10...v4.2.11) (2021-10-18)
## [4.2.10](https://github.com/Ombi-app/Ombi/compare/v4.2.9...v4.2.10) (2021-10-15)
### Bug Fixes
* :bug: Really really fix it this time? ([543d36e](https://github.com/Ombi-app/Ombi/commit/543d36e5615341bc8378cac377b843a3dbc1ef99))
## [4.2.9](https://github.com/Ombi-app/Ombi/compare/v4.2.8...v4.2.9) (2021-10-15)
### Bug Fixes
* :fire: Really fix the base url issue this time ([9f36923](https://github.com/Ombi-app/Ombi/commit/9f36923c51bfabf9cb026f2da14f9947050af0d9))
## [4.2.8](https://github.com/Ombi-app/Ombi/compare/v4.2.7...v4.2.8) (2021-10-15)
### Bug Fixes
* :adhesive_bandage: See if this fixes the proxy issue ([74d1aca](https://github.com/Ombi-app/Ombi/commit/74d1acae499707a7e21401f53eb2bb90c5bb9cfa))
* :bug: Fixed Ombi not writing the baseUrl correctly ([e9cc8b6](https://github.com/Ombi-app/Ombi/commit/e9cc8b6fe71d3e10c1a901e70227989b3362afe3))
## [4.2.7](https://github.com/Ombi-app/Ombi/compare/v4.2.6...v4.2.7) (2021-10-14)
### Bug Fixes
* :bug: Fixed the issue parsing TheMovieDB dates. They have broken something... ([6e397e0](https://github.com/Ombi-app/Ombi/commit/6e397e02e95f894a92e8bf02428efdcac1275b31))
## [4.2.6](https://github.com/Ombi-app/Ombi/compare/v4.2.5...v4.2.6) (2021-10-14)
### Performance Improvements
* :zap: Use ngxs store for the whole customization section of the app ([97b493d](https://github.com/Ombi-app/Ombi/commit/97b493d869feee59d360b484a6c59388a2aead1f))
## [4.2.5](https://github.com/Ombi-app/Ombi/compare/v4.2.4...v4.2.5) (2021-10-14)
## [4.2.4](https://github.com/Ombi-app/Ombi/compare/v4.2.3...v4.2.4) (2021-10-13)
### Bug Fixes
* **#4344:** :bug: Fixed an issue where we errored on Plex Episode Scan ([cd5532f](https://github.com/Ombi-app/Ombi/commit/cd5532fa8f7ebbfaf942841398672bafb9a405d4))
* **#4345:** :bug: Fixed the issue where denied requests we not appearing correctly ([5a2f652](https://github.com/Ombi-app/Ombi/commit/5a2f652a28f5699dd667afef8dde129817e53392))

@ -18,7 +18,7 @@ Don't worry, it's grandma friendly, and more importantly; has wife approval cert
| Service | Stable | Develop
|----------|:---------------------------:|:----------------------------:|
| Build Status | [![CI Build](https://github.com/Ombi-app/Ombi/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/Ombi-app/Ombi/actions/workflows/build.yml) | [![CI Build](https://github.com/Ombi-app/Ombi/actions/workflows/build.yml/badge.svg)](https://github.com/Ombi-app/Ombi/actions/workflows/build.yml) | [![Build Status](https://dev.azure.com/tidusjar/Ombi/_apis/build/status/Ombi%20CI?branchName=feature%2Fv4)](https://dev.azure.com/tidusjar/Ombi/_build/latest?definitionId=18&branchName=feature%2Fv4)
| Build Status | [![CI Build](https://github.com/Ombi-app/Ombi/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/Ombi-app/Ombi/actions/workflows/build.yml) | [![CI Build](https://github.com/Ombi-app/Ombi/actions/workflows/build.yml/badge.svg?branch=develop)](https://github.com/Ombi-app/Ombi/actions/workflows/build.yml) | [![Build Status](https://dev.azure.com/tidusjar/Ombi/_apis/build/status/Ombi%20CI?branchName=feature%2Fv4)](https://dev.azure.com/tidusjar/Ombi/_build/latest?definitionId=18&branchName=feature%2Fv4)
| Download |[![Download](https://img.shields.io/badge/-Download-blue)](https://github.com/Ombi-app/Ombi/releases) | [![Download](https://img.shields.io/badge/-Download-blue)](https://ci.appveyor.com/project/tidusjar/requestplex/branch/develop/artifacts) | [![Download](https://img.shields.io/badge/-Download-blue)](https://github.com/ombi-app/ombi/releases) |
# Feature Requests
@ -135,6 +135,13 @@ Here are some of the features Ombi has:
<sub><b>Matt Jeanes</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/sephrat">
<img src="https://avatars.githubusercontent.com/u/34862846?v=4" width="50;" alt="sephrat"/>
<br />
<sub><b>Sephrat</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/bernarden">
<img src="https://avatars.githubusercontent.com/u/12289537?v=4" width="50;" alt="bernarden"/>
@ -148,15 +155,15 @@ Here are some of the features Ombi has:
<br />
<sub><b>Dhruv Bhavsar</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/joshuaboniface">
<img src="https://avatars.githubusercontent.com/u/4031396?v=4" width="50;" alt="joshuaboniface"/>
<br />
<sub><b>Joshua M. Boniface</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/bruvv">
<img src="https://avatars.githubusercontent.com/u/3063928?v=4" width="50;" alt="bruvv"/>
@ -164,13 +171,6 @@ Here are some of the features Ombi has:
<sub><b>Bruvv</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/sephrat">
<img src="https://avatars.githubusercontent.com/u/34862846?v=4" width="50;" alt="sephrat"/>
<br />
<sub><b>Sephrat</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/louis-lau">
<img src="https://avatars.githubusercontent.com/u/1346804?v=4" width="50;" alt="louis-lau"/>
@ -222,10 +222,10 @@ Here are some of the features Ombi has:
</a>
</td>
<td align="center">
<a href="https://github.com/stefangross">
<img src="https://avatars.githubusercontent.com/u/8499989?v=4" width="50;" alt="stefangross"/>
<a href="https://github.com/steffokeffo">
<img src="https://avatars.githubusercontent.com/u/8499989?v=4" width="50;" alt="steffokeffo"/>
<br />
<sub><b>Stefangross</b></sub>
<sub><b>Steffokeffo</b></sub>
</a>
</td>
<td align="center">
@ -730,6 +730,13 @@ Here are some of the features Ombi has:
<sub><b>M4tta</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/maartenheebink">
<img src="https://avatars.githubusercontent.com/u/28894544?v=4" width="50;" alt="maartenheebink"/>
<br />
<sub><b>Maartenheebink</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/masterhuck">
<img src="https://avatars.githubusercontent.com/u/4671442?v=4" width="50;" alt="masterhuck"/>
@ -750,15 +757,15 @@ Here are some of the features Ombi has:
<br />
<sub><b>Tdorsey</b></sub>
</a>
</td>
</td></tr>
<tr>
<td align="center">
<a href="https://github.com/thegame3202">
<img src="https://avatars.githubusercontent.com/u/22148848?v=4" width="50;" alt="thegame3202"/>
<br />
<sub><b>Mike</b></sub>
</a>
</td></tr>
<tr>
</td>
<td align="center">
<a href="https://github.com/zobe123">
<img src="https://avatars.githubusercontent.com/u/13840542?v=4" width="50;" alt="zobe123"/>

@ -0,0 +1,17 @@
backend:
cd src/Ombi && dotnet watch run -- --host http://*:3577
frontend:
cd src/Ombi/ClientApp && yarn start
install-frontend:
cd src/Ombi/ClientApp && yarn
install-frontend-tests:
cd tests && yarn
frontend-tests:
cd tests && npx cypress run
backend-tests:
cd src/Ombi.Core.Tests && dotnet test

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,12 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json;
using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Media;
using Ombi.Api.Emby.Models.Media.Tv;
using Ombi.Api.Emby.Models.Movie;
using Ombi.Helpers;
@ -124,22 +120,36 @@ namespace Ombi.Api.Emby
return response;
}
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count, parentIdFilder);
}
public async Task<EmbyItemContainer<EmbyMovie>> RecentlyAddedMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await RecentlyAdded<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count, parentIdFilder);
}
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
}
public async Task<EmbyItemContainer<EmbyEpisodes>> RecentlyAddedEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await RecentlyAdded<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
}
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
}
public async Task<EmbyItemContainer<EmbySeries>> RecentlyAddedShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri)
{
return await RecentlyAdded<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count, parentIdFilder);
}
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
{
return await GetInformation<SeriesInformation>(mediaId, apiKey, userId, baseUrl);
@ -154,6 +164,31 @@ namespace Ombi.Api.Emby
return await GetInformation<EpisodeInformation>(mediaId, apiKey, userId, baseUrl);
}
private async Task<EmbyItemContainer<T>> RecentlyAdded<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count, string parentIdFilder = default)
{
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());
request.AddQueryString("sortBy", "DateCreated");
request.AddQueryString("SortOrder", "Descending");
if (!string.IsNullOrEmpty(parentIdFilder))
{
request.AddQueryString("ParentId", parentIdFilder);
}
request.AddQueryString("IsVirtualItem", "False");
AddHeaders(request, apiKey);
var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj;
}
private async Task<T> GetInformation<T>(string mediaId, string apiKey, string userId, string baseUrl)
{
var request = new Request($"emby/users/{userId}/items/{mediaId}", baseUrl, HttpMethod.Get);

@ -29,5 +29,8 @@ namespace Ombi.Api.Emby
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);
Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl);
Task<PublicInfo> GetPublicInformation(string baseUrl);
Task<EmbyItemContainer<EmbyMovie>> RecentlyAddedMovies(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
Task<EmbyItemContainer<EmbyEpisodes>> RecentlyAddedEpisodes(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
Task<EmbyItemContainer<EmbySeries>> RecentlyAddedShows(string apiKey, string parentIdFilder, int startIndex, int count, string userId, string baseUri);
}
}

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
</ItemGroup>
<ItemGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
</ItemGroup>
<ItemGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -66,6 +66,8 @@ namespace Ombi.Api
startingTag = builder.Query.Contains("?") ? "&" : "?";
}
value = Uri.EscapeDataString(value);
builder.Query = hasQuery
? $"{builder.Query}{startingTag}{parameter}={value}"
: $"{startingTag}{parameter}={value}";

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -11,8 +11,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Polly" Version="7.1.0" />
</ItemGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<Configurations>Debug;Release;NonUiBuild</Configurations>
@ -9,7 +9,7 @@
<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.11.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageReference Include="Moq" Version="4.15.1" />
<PackageReference Include="Moq.AutoMock" Version="3.0.0" />
<PackageReference Include="Nunit" Version="3.12.0" />

@ -130,5 +130,76 @@ namespace Ombi.Core.Tests.Rule.Search
Assert.False(search.Approved);
Assert.False(search.Requested);
}
[Test]
public async Task ShouldBeFullyAvailable_NoFutureAiredEpisodes_NoRequest()
{
var search = new SearchTvShowViewModel()
{
Id = 999,
SeasonRequests = new List<SeasonRequests>
{
new SeasonRequests
{
Episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
Available = true,
AirDate = new System.DateTime(2020,01,01)
},
new EpisodeRequests
{
Available = true,
AirDate = new System.DateTime(2020,01,02)
},
}
}
}
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.That(search.FullyAvailable, Is.True);
Assert.That(search.PartlyAvailable, Is.False);
}
[Test]
public async Task ShouldBeFullyAvailable_AndPartly_FutureAiredEpisodes_NoRequest()
{
var search = new SearchTvShowViewModel()
{
Id = 999,
SeasonRequests = new List<SeasonRequests>
{
new SeasonRequests
{
Episodes = new List<EpisodeRequests>
{
new EpisodeRequests
{
Available = true,
AirDate = new System.DateTime(2020,01,01)
},
new EpisodeRequests
{
Available = true,
AirDate = new System.DateTime(2020,01,02)
},
new EpisodeRequests
{
Available = true,
AirDate = new System.DateTime(2029,01,02)
},
}
}
}
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.That(search.FullyAvailable, Is.True);
Assert.That(search.PartlyAvailable, Is.True);
}
}
}

@ -0,0 +1,228 @@
using Microsoft.Extensions.Logging;
using MockQueryable.Moq;
using Moq;
using Moq.AutoMock;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Core.Models;
using Ombi.Core.Senders;
using Ombi.Notifications;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ombi.Core.Tests.Senders
{
[TestFixture]
public class MassEmailSenderTests
{
private MassEmailSender _subject;
private AutoMocker _mocker;
[SetUp]
public void Setup()
{
_mocker = new AutoMocker();
_subject = _mocker.CreateInstance<MassEmailSender>();
}
[Test]
public async Task SendMassEmail_SingleUser()
{
var model = new MassEmailModel
{
Body = "Test",
Subject = "Subject",
Users = new List<OmbiUser>
{
new OmbiUser
{
Id = "a"
}
}
};
_mocker.Setup<OmbiUserManager, IQueryable<OmbiUser>>(x => x.Users).Returns(new List<OmbiUser>
{
new OmbiUser
{
Id = "a",
Email = "Test@test.com"
}
}.AsQueryable().BuildMock().Object);
var result = await _subject.SendMassEmail(model);
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.Is<NotificationMessage>(m => m.Subject == model.Subject
&& m.Message == model.Body
&& m.To == "Test@test.com"), It.IsAny<EmailNotificationSettings>()), Times.Once);
}
[Test]
public async Task SendMassEmail_MultipleUsers()
{
var model = new MassEmailModel
{
Body = "Test",
Subject = "Subject",
Users = new List<OmbiUser>
{
new OmbiUser
{
Id = "a"
},
new OmbiUser
{
Id = "b"
}
}
};
_mocker.Setup<OmbiUserManager, IQueryable<OmbiUser>>(x => x.Users).Returns(new List<OmbiUser>
{
new OmbiUser
{
Id = "a",
Email = "Test@test.com"
},
new OmbiUser
{
Id = "b",
Email = "b@test.com"
}
}.AsQueryable().BuildMock().Object);
var result = await _subject.SendMassEmail(model);
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.Is<NotificationMessage>(m => m.Subject == model.Subject
&& m.Message == model.Body
&& m.To == "Test@test.com"), It.IsAny<EmailNotificationSettings>()), Times.Once);
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.Is<NotificationMessage>(m => m.Subject == model.Subject
&& m.Message == model.Body
&& m.To == "b@test.com"), It.IsAny<EmailNotificationSettings>()), Times.Once);
}
[Test]
public async Task SendMassEmail_UserNoEmail()
{
var model = new MassEmailModel
{
Body = "Test",
Subject = "Subject",
Users = new List<OmbiUser>
{
new OmbiUser
{
Id = "a"
}
}
};
_mocker.Setup<OmbiUserManager, IQueryable<OmbiUser>>(x => x.Users).Returns(new List<OmbiUser>
{
new OmbiUser
{
Id = "a",
}
}.AsQueryable().BuildMock().Object);
var result = await _subject.SendMassEmail(model);
_mocker.Verify<ILogger<MassEmailSender>>(
x => x.Log(
LogLevel.Information,
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
Times.Once);
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.IsAny<NotificationMessage>(), It.IsAny<EmailNotificationSettings>()), Times.Never);
}
[Test]
public async Task SendMassEmail_Bcc()
{
var model = new MassEmailModel
{
Body = "Test",
Subject = "Subject",
Bcc = true,
Users = new List<OmbiUser>
{
new OmbiUser
{
Id = "a"
},
new OmbiUser
{
Id = "b"
}
}
};
_mocker.Setup<OmbiUserManager, IQueryable<OmbiUser>>(x => x.Users).Returns(new List<OmbiUser>
{
new OmbiUser
{
Id = "a",
Email = "Test@test.com"
},
new OmbiUser
{
Id = "b",
Email = "b@test.com"
}
}.AsQueryable().BuildMock().Object);
var result = await _subject.SendMassEmail(model);
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.Is<NotificationMessage>(m => m.Subject == model.Subject
&& m.Message == model.Body
&& m.Other["bcc"] == "Test@test.com,b@test.com"), It.IsAny<EmailNotificationSettings>()), Times.Once);
}
[Test]
public async Task SendMassEmail_Bcc_NoEmails()
{
var model = new MassEmailModel
{
Body = "Test",
Subject = "Subject",
Bcc = true,
Users = new List<OmbiUser>
{
new OmbiUser
{
Id = "a"
},
new OmbiUser
{
Id = "b"
}
}
};
_mocker.Setup<OmbiUserManager, IQueryable<OmbiUser>>(x => x.Users).Returns(new List<OmbiUser>
{
new OmbiUser
{
Id = "a",
},
new OmbiUser
{
Id = "b",
}
}.AsQueryable().BuildMock().Object);
var result = await _subject.SendMassEmail(model);
_mocker.Verify<IEmailProvider>(x => x.SendAdHoc(It.IsAny<NotificationMessage>(), It.IsAny<EmailNotificationSettings>()), Times.Never);
}
}
}

@ -78,6 +78,32 @@ namespace Ombi.Core.Engine
return _dbTv;
}
protected async Task<RequestEngineResult> CheckCanManageRequest(BaseRequest request) {
var errorResult = new RequestEngineResult {
Result = false,
ErrorCode = ErrorCode.NoPermissions
};
var successResult = new RequestEngineResult { Result = true };
// Admins can always manage requests
var isAdmin = await IsInRole(OmbiRoles.PowerUser) || await IsInRole(OmbiRoles.Admin);
if (isAdmin) {
return successResult;
}
// Users with 'ManageOwnRequests' can only manage their own requests
var canManageOwnRequests = await IsInRole(OmbiRoles.ManageOwnRequests);
if (!canManageOwnRequests) {
return errorResult;
}
var isRequestedBySameUser = ( await GetUser() ).Id == request.RequestedUser?.Id;
if (isRequestedBySameUser) {
return successResult;
}
return errorResult;
}
public RequestCountModel RequestCount()
{
var movieQuery = MovieRepository.GetAll();

@ -18,7 +18,7 @@ namespace Ombi.Core.Engine
Task<int> GetTotal();
Task<RequestEngineResult> MarkAvailable(int modelId);
Task<RequestEngineResult> MarkUnavailable(int modelId);
Task RemoveAlbumRequest(int requestId);
Task<RequestEngineResult> RemoveAlbumRequest(int requestId);
Task<RequestEngineResult> RequestAlbum(MusicAlbumRequestViewModel model);
Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search);
Task<bool> UserHasRequest(string userId);

@ -14,7 +14,7 @@ namespace Ombi.Core.Engine.Interfaces
Task<IEnumerable<MovieRequests>> SearchMovieRequest(string search);
Task<RequestEngineResult> RequestCollection(int collectionId, CancellationToken cancellationToken);
Task RemoveMovieRequest(int requestId);
Task<RequestEngineResult> RemoveMovieRequest(int requestId);
Task RemoveAllMovieRequests();
Task<MovieRequests> GetRequest(int requestId);
Task<MovieRequests> UpdateMovieRequest(MovieRequests request);

@ -20,7 +20,7 @@ namespace Ombi.Core.Engine.Interfaces
Task<TvRequests> UpdateTvRequest(TvRequests request);
Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId);
Task<ChildRequests> UpdateChildRequest(ChildRequests request);
Task RemoveTvChild(int requestId);
Task<RequestEngineResult> RemoveTvChild(int requestId);
Task<RequestEngineResult> ApproveChildRequest(int id);
Task<IEnumerable<TvRequests>> GetRequestsLite();
Task UpdateQualityProfile(int requestId, int profileId);

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Search.V2;
@ -10,6 +11,7 @@ namespace Ombi.Core
{
Task<SearchFullInfoTvShowViewModel> GetShowInformation(string tvdbid, CancellationToken token);
Task<SearchFullInfoTvShowViewModel> GetShowByRequest(int requestId, CancellationToken token);
Task<ActorCredits> GetTvByActor(int actorId, string langCode);
Task<IEnumerable<StreamingData>> GetStreamInformation(int movieDbId, CancellationToken cancellationToken);
Task<IEnumerable<SearchTvShowViewModel>> Popular(int currentlyLoaded, int amountToLoad, string langCustomCode = null);
Task<IEnumerable<SearchTvShowViewModel>> Anticipated(int currentlyLoaded, int amountToLoad);

@ -527,11 +527,12 @@ namespace Ombi.Core.Engine
request.Denied = true;
request.DeniedReason = denyReason;
// We are denying a request
await NotificationHelper.Notify(request, NotificationType.RequestDeclined);
await MovieRepository.Update(request);
await _mediaCacheService.Purge();
// We are denying a request
await NotificationHelper.Notify(request, NotificationType.RequestDeclined);
return new RequestEngineResult
{
Result = true,
@ -654,11 +655,20 @@ namespace Ombi.Core.Engine
/// </summary>
/// <param name="requestId">The request identifier.</param>
/// <returns></returns>
public async Task RemoveMovieRequest(int requestId)
public async Task<RequestEngineResult> RemoveMovieRequest(int requestId)
{
var request = await MovieRepository.GetAll().FirstOrDefaultAsync(x => x.Id == requestId);
var result = await CheckCanManageRequest(request);
if (result.IsError)
return result;
await MovieRepository.Delete(request);
await _mediaCacheService.Purge();
return new RequestEngineResult
{
Result = true,
};
}
public async Task RemoveAllMovieRequests()

@ -404,10 +404,20 @@ namespace Ombi.Core.Engine
/// </summary>
/// <param name="requestId">The request identifier.</param>
/// <returns></returns>
public async Task RemoveAlbumRequest(int requestId)
public async Task<RequestEngineResult> RemoveAlbumRequest(int requestId)
{
var request = await MusicRepository.GetAll().FirstOrDefaultAsync(x => x.Id == requestId);
var result = await CheckCanManageRequest(request);
if (result.IsError)
return result;
await MusicRepository.Delete(request);
return new RequestEngineResult
{
Result = true,
};
}
public async Task<bool> UserHasRequest(string userId)

@ -7,7 +7,7 @@ namespace Ombi.Core.Engine
{
public bool Result { get; set; }
public string Message { get; set; }
public bool IsError => !string.IsNullOrEmpty(ErrorMessage);
public bool IsError => ( !string.IsNullOrEmpty(ErrorMessage) || ErrorCode != null );
public string ErrorMessage { get; set; }
public ErrorCode? ErrorCode { get; set; }
public int RequestId { get; set; }

@ -710,7 +710,11 @@ namespace Ombi.Core.Engine
if (request.Approved)
{
await NotificationHelper.Notify(request, NotificationType.RequestApproved);
var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification, string.Empty);
if (canNotify.Success)
{
await NotificationHelper.Notify(request, NotificationType.RequestApproved);
}
// Autosend
await TvSender.Send(request);
}
@ -749,10 +753,14 @@ namespace Ombi.Core.Engine
return request;
}
public async Task RemoveTvChild(int requestId)
public async Task<RequestEngineResult> RemoveTvChild(int requestId)
{
var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);
var result = await CheckCanManageRequest(request);
if (result.IsError)
return result;
TvRepository.Db.ChildRequests.Remove(request);
var all = TvRepository.Db.TvRequests.Include(x => x.ChildRequests);
var parent = all.FirstOrDefault(x => x.Id == request.ParentRequestId);
@ -766,6 +774,11 @@ namespace Ombi.Core.Engine
await TvRepository.Db.SaveChangesAsync();
await _mediaCacheService.Purge();
return new RequestEngineResult
{
Result = true,
};
}
public async Task RemoveTvRequest(int requestId)
@ -948,7 +961,11 @@ namespace Ombi.Core.Engine
if (model.Approved)
{
// Autosend
await NotificationHelper.Notify(model, NotificationType.RequestApproved);
var canNotify = await RunSpecificRule(model, SpecificRules.CanSendNotification, string.Empty);
if (canNotify.Success)
{
await NotificationHelper.Notify(model, NotificationType.RequestApproved);
}
var result = await TvSender.Send(model);
if (result.Success)
{

@ -323,6 +323,7 @@ namespace Ombi.Core.Engine.V2
public async Task<ActorCredits> GetMoviesByActor(int actorId, string langCode)
{
langCode = await DefaultLanguageCode(langCode);
var result = await Cache.GetOrAddAsync(nameof(GetMoviesByActor) + actorId + langCode,
() => MovieApi.GetActorMovieCredits(actorId, langCode), DateTimeOffset.Now.AddHours(12));
// Later we run this through the rules engine

@ -89,7 +89,7 @@ namespace Ombi.Core.Engine.V2
foreach (var tvSeason in show.seasons.Where(x => x.season_number != 0)) // skip the first season
{
var seasonEpisodes = (await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, token));
var seasonEpisodes = (await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, token, langCode));
MapSeasons(mapped.SeasonRequests, tvSeason, seasonEpisodes);
}
@ -147,6 +147,13 @@ namespace Ombi.Core.Engine.V2
return await processed;
}
public async Task<ActorCredits> GetTvByActor(int actorId, string langCode)
{
langCode = await DefaultLanguageCode(langCode);
var result = await Cache.GetOrAddAsync(nameof(GetTvByActor) + actorId + langCode,
() => _movieApi.GetActorTvCredits(actorId, langCode), DateTimeOffset.Now.AddHours(12));
return result;
}
public async Task<IEnumerable<StreamingData>> GetStreamInformation(int movieDbId, CancellationToken cancellationToken)
{

@ -35,6 +35,8 @@ namespace Ombi.Core.Models
public string Subject { get; set; }
public string Body { get; set; }
public bool Bcc { get; set; }
public List<OmbiUser> Users { get; set; }
}
}

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -13,8 +13,8 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="10.0.0" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="6.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0" />
<PackageReference Include="MusicBrainzAPI" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />

@ -85,11 +85,18 @@ namespace Ombi.Core.Rule.Rules.Search
{
request.FullyAvailable = true;
}
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available && e.AirDate > DateTime.MinValue)))
if (request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.Available && e.AirDate > DateTime.MinValue && e.AirDate <= DateTime.UtcNow)))
{
request.PartlyAvailable = true;
}
var hasUnairedRequests = request.SeasonRequests.Any() && request.SeasonRequests.All(x => x.Episodes.Any(e => e.AirDate >= DateTime.UtcNow));
if (request.FullyAvailable)
{
request.PartlyAvailable = hasUnairedRequests;
}
return Success();
}
if (obj.Type == RequestType.Album)

@ -25,7 +25,9 @@
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -64,6 +66,63 @@ namespace Ombi.Core.Senders
var customization = await _customizationService.GetSettingsAsync();
var email = await _emailService.GetSettingsAsync();
var messagesSent = new List<Task>();
if (model.Bcc)
{
await SendBccMails(model, customization, email, messagesSent);
}
else
{
await SendIndividualEmails(model, customization, email, messagesSent);
}
await Task.WhenAll(messagesSent);
return true;
}
private async Task SendBccMails(MassEmailModel model, CustomizationSettings customization, EmailNotificationSettings email, List<Task> messagesSent)
{
var resolver = new NotificationMessageResolver();
var curlys = new NotificationMessageCurlys();
var validUsers = new List<OmbiUser>();
foreach (var user in model.Users)
{
var fullUser = await _userManager.Users.FirstOrDefaultAsync(x => x.Id == user.Id);
if (!fullUser.Email.HasValue())
{
_log.LogInformation("User {0} has no email, cannot send mass email to this user", fullUser.UserName);
continue;
}
validUsers.Add(fullUser);
}
if (!validUsers.Any())
{
return;
}
var firstUser = validUsers.FirstOrDefault();
var bccAddress = string.Join(',', validUsers.Select(x => x.Email));
curlys.Setup(firstUser, customization);
var template = new NotificationTemplates() { Message = model.Body, Subject = model.Subject };
var content = resolver.ParseMessage(template, curlys);
var msg = new NotificationMessage
{
Message = content.Message,
Subject = content.Subject,
Other = new Dictionary<string, string> { { "bcc", bccAddress } }
};
messagesSent.Add(_email.SendAdHoc(msg, email));
}
private async Task SendIndividualEmails(MassEmailModel model, CustomizationSettings customization, EmailNotificationSettings email, List<Task> messagesSent)
{
var resolver = new NotificationMessageResolver();
var curlys = new NotificationMessageCurlys();
foreach (var user in model.Users)
{
var fullUser = await _userManager.Users.FirstOrDefaultAsync(x => x.Id == user.Id);
@ -72,8 +131,6 @@ namespace Ombi.Core.Senders
_log.LogInformation("User {0} has no email, cannot send mass email to this user", fullUser.UserName);
continue;
}
var resolver = new NotificationMessageResolver();
var curlys = new NotificationMessageCurlys();
curlys.Setup(fullUser, customization);
var template = new NotificationTemplates() { Message = model.Body, Subject = model.Subject };
var content = resolver.ParseMessage(template, curlys);
@ -83,13 +140,19 @@ namespace Ombi.Core.Senders
To = fullUser.Email,
Subject = content.Subject
};
messagesSent.Add(_email.SendAdHoc(msg, email));
messagesSent.Add(DelayEmail(msg, email));
_log.LogInformation("Sent mass email to user {0} @ {1}", fullUser.UserName, fullUser.Email);
}
}
await Task.WhenAll(messagesSent);
return true;
/// <summary>
/// This will add a 2 second delay, this is to help with concurrent connection limits
/// <see href="https://github.com/Ombi-app/Ombi/issues/4377"/>
/// </summary>
private async Task DelayEmail(NotificationMessage msg, EmailNotificationSettings email)
{
await Task.Delay(2000);
await _email.SendAdHoc(msg, email);
}
}
}

@ -309,6 +309,22 @@ namespace Ombi.Core.Senders
private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s)
{
// Check to ensure we have the all the seasons, ensure the Sonarr metadata has grabbed all the data
Season existingSeason = null;
foreach (var season in model.SeasonRequests)
{
var attempt = 0;
existingSeason = result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
while (existingSeason == null && attempt < 5)
{
attempt++;
Logger.LogInformation("There was no season numer {0} in Sonarr for title {1}. Will try again as the metadata did not get created", season.SeasonNumber, model.ParentRequest.Title);
result = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri);
existingSeason = result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
await Task.Delay(500);
}
}
var episodesToUpdate = new List<Episode>();
// Ok, now let's sort out the episodes.
@ -327,24 +343,20 @@ namespace Ombi.Core.Senders
await Task.Delay(500);
}
var seriesChanges = false;
foreach (var req in model.SeasonRequests)
foreach (var season in model.SeasonRequests)
{
foreach (var ep in req.Episodes)
foreach (var ep in season.Episodes)
{
var sonarrEp = sonarrEpList.FirstOrDefault(x =>
x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber);
x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == season.SeasonNumber);
if (sonarrEp != null && !sonarrEp.monitored)
{
sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp);
}
}
}
var seriesChanges = false;
foreach (var season in model.SeasonRequests)
{
var sonarrEpisodeList = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber).ToList();
var sonarrEpCount = sonarrEpisodeList.Count;
var ourRequestCount = season.Episodes.Count;
@ -356,15 +368,7 @@ namespace Ombi.Core.Senders
//// 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);
var existingSeason =
result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
if (existingSeason == null)
{
Logger.LogError("There was no season numer {0} in Sonarr for title {1}", season.SeasonNumber, model.ParentRequest.Title);
continue;
}
//var missingEpisodes = Enumerable.Range(distinctEpisodes.Min(), distinctEpisodes.Count).Except(distinctEpisodes);
if (sonarrEpCount == ourRequestCount /*|| !missingEpisodes.Any()*/)

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -11,10 +11,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
</ItemGroup>
<ItemGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="nunit" Version="3.11.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
</ItemGroup>

@ -3,6 +3,7 @@
public class JobDataKeys
{
public const string RecentlyAddedSearch = "recentlyAddedSearch";
public const string EmbyRecentlyAddedSearch = nameof(EmbyRecentlyAddedSearch);
public const string NotificationOptions = nameof(NotificationOptions);
}
}

@ -6,16 +6,6 @@ namespace Ombi.Helpers
{
public static class LinqHelpers
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
HashSet<TKey> knownKeys = new HashSet<TKey>();
foreach (TSource source1 in source)
{
if (knownKeys.Add(keySelector(source1)))
yield return source1;
}
}
public static HashSet<T> ToHashSet<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer = null)

@ -53,17 +53,18 @@ namespace Ombi.Helpers
_memoryCache.Set(CacheKey, mediaServiceCache);
}
public async Task Purge()
public Task Purge()
{
var keys = _memoryCache.Get<List<string>>(CacheKey);
if (keys == null)
{
return;
return Task.CompletedTask;
}
foreach (var key in keys)
{
base.Remove(key);
}
return Task.CompletedTask;
}
}

@ -0,0 +1,18 @@
namespace Ombi.Helpers
{
public static class NotificationSubstitues
{
public const string Title = nameof(Title);
public const string IssueDescription = nameof(IssueDescription);
public const string IssueCategory = nameof(IssueCategory);
public const string IssueStatus = nameof(IssueStatus);
public const string IssueSubject = nameof(IssueSubject);
public const string IssueUser = nameof(IssueUser);
public const string IssueUserAlias = nameof(IssueUserAlias);
public const string RequestType = nameof(RequestType);
public const string PosterPath = nameof(PosterPath);
public const string NewIssueComment = nameof(NewIssueComment);
public const string IssueId = nameof(IssueId);
public const string AdminComment = nameof(AdminComment);
}
}

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -13,8 +13,8 @@
<ItemGroup>
<PackageReference Include="EasyCrypto" Version="3.3.2" />
<PackageReference Include="LazyCache.AspNetCore" Version="2.1.3" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.0" />
<PackageReference Include="Quartz" Version="3.1.0" />

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>8.0</LangVersion>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates
if (string.IsNullOrEmpty(_templateLocation))
{
#if DEBUG
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net5.0", "Templates",
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net6.0", "Templates",
"BasicTemplate.html");
#else
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html");

@ -13,7 +13,7 @@ namespace Ombi.Notifications.Templates
if (string.IsNullOrEmpty(_templateLocation))
{
#if DEBUG
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net5.0", "Templates", "NewsletterTemplate.html");
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "net6.0", "Templates", "NewsletterTemplate.html");
#else
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "NewsletterTemplate.html");
#endif

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -76,6 +76,7 @@ namespace Ombi.Notifications.Tests
{ "IssueUser", "User" },
{ "IssueUserAlias", "alias" },
{ "RequestType", "Movie" },
{ "PosterPath", "aaaaa" }
}
};
var req = F.Build<MovieRequests>()
@ -94,6 +95,39 @@ namespace Ombi.Notifications.Tests
Assert.That("Movie", Is.EqualTo(sut.Type));
}
[Test]
public void IssueNotificationTests_NoRequest()
{
var notificationOptions = new NotificationOptions
{
Substitutes = new Dictionary<string, string>
{
{ "IssueDescription", "Desc" },
{ "IssueCategory", "Cat" },
{ "IssueStatus", "state" },
{ "IssueSubject", "sub" },
{ "NewIssueComment", "a" },
{ "IssueUser", "User" },
{ "IssueUserAlias", "alias" },
{ "RequestType", "Movie" },
{ "PosterPath", "aaaaa" }
}
};
var customization = new CustomizationSettings();
var userPrefs = new UserNotificationPreferences();
sut.Setup(notificationOptions, (MovieRequests)null, customization, userPrefs);
Assert.That("Desc", Is.EqualTo(sut.IssueDescription));
Assert.That("Cat", Is.EqualTo(sut.IssueCategory));
Assert.That("state", Is.EqualTo(sut.IssueStatus));
Assert.That("a", Is.EqualTo(sut.NewIssueComment));
Assert.That("User", Is.EqualTo(sut.UserName));
Assert.That("alias", Is.EqualTo(sut.Alias));
Assert.That("Movie", Is.EqualTo(sut.Type));
Assert.That("https://image.tmdb.org/t/p/w300/aaaaa", Is.EqualTo(sut.PosterImage));
}
[Test]
public void MovieNotificationUserPreferences()
{

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>
@ -10,7 +10,7 @@
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.8.0"></packagereference>
<PackageReference Include="Moq" Version="4.10.0" />
</ItemGroup>

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Ombi.Api.Discord;
using Ombi.Api.Discord.Models;
@ -21,8 +22,8 @@ namespace Ombi.Notifications.Agents
public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn,
ILogger<DiscordNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref)
: base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um)
: base(sn, r, m, t, s, log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MailKit.Net.Smtp;
@ -22,7 +23,7 @@ namespace Ombi.Notifications.Agents
{
public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService<CustomizationSettings> c,
ILogger<EmailNotification> log, UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(settings, r, m, t, c, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref) : base(settings, r, m, t, c, log, sub, music, userPref, um)
{
EmailProvider = prov;
Logger = log;
@ -114,7 +115,15 @@ namespace Ombi.Notifications.Agents
var plaintext = await LoadPlainTextMessage(NotificationType.NewRequest, model, settings);
message.Other.Add("PlainTextBody", plaintext);
await Send(message, settings);
foreach (var recipient in (await GetPrivilegedUsers()).DistinctBy(x => x.Email))
{
if (recipient.Email.IsNullOrEmpty())
{
continue;
}
message.To = recipient.Email;
await Send(message, settings);
}
}
protected override async Task NewIssue(NotificationOptions model, EmailNotificationSettings settings)

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Ombi.Api.Gotify;
using Ombi.Core.Settings;
@ -17,7 +18,7 @@ namespace Ombi.Notifications.Agents
{
public GotifyNotification(IGotifyApi api, ISettingsService<GotifySettings> sn, ILogger<GotifyNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -23,7 +23,7 @@ namespace Ombi.Notifications.Agents
public LegacyMobileNotification(IOneSignalApi api, ISettingsService<MobileNotificationSettings> sn, ILogger<LegacyMobileNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<NotificationUserId> notification,
UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music, IRepository<Issues> issueRepository,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{
_api = api;
_logger = log;
@ -59,7 +59,7 @@ namespace Ombi.Notifications.Agents
};
// Get admin devices
var playerIds = await GetAdmins(NotificationType.NewRequest);
var playerIds = await GetPrivilegedUsersPlayerIds();
await Send(playerIds, notification, settings, model, true);
}
@ -77,7 +77,7 @@ namespace Ombi.Notifications.Agents
};
// Get admin devices
var playerIds = await GetAdmins(NotificationType.Issue);
var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model);
}
@ -106,7 +106,7 @@ namespace Ombi.Notifications.Agents
else
{
// Send to admin
var playerIds = await GetAdmins(NotificationType.IssueComment);
var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model);
}
}
@ -147,7 +147,7 @@ namespace Ombi.Notifications.Agents
};
// Get admin devices
var playerIds = await GetAdmins(NotificationType.Test);
var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model);
}
@ -241,15 +241,25 @@ namespace Ombi.Notifications.Agents
await Send(playerIds, notification, settings, model);
}
private async Task<List<string>> GetAdmins(NotificationType type)
private async Task<List<string>> GetAdmins()
{
var adminUsers = (await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin)).Select(x => x.Id).ToList();
return await GetNotificationRecipients(await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin));
}
private async Task<List<string>> GetPrivilegedUsersPlayerIds()
{
return await GetNotificationRecipients(await GetPrivilegedUsers());
}
private async Task<List<string>> GetNotificationRecipients(IEnumerable<OmbiUser> users)
{
var adminUsers = users.Select(x => x.Id).ToList();
var notificationUsers = _notifications.GetAll().Include(x => x.User).Where(x => adminUsers.Contains(x.UserId));
var playerIds = await notificationUsers.Select(x => x.PlayerId).ToListAsync();
if (!playerIds.Any())
{
_logger.LogInformation(
$"there are no admins to send a notification for {type}, for agent {NotificationAgent.Mobile}");
$"there are no users to send a notification for agent {NotificationAgent.Mobile}");
return null;
}
return playerIds;

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Ombi.Api.Mattermost;
using Ombi.Api.Mattermost.Models;
@ -19,7 +20,7 @@ namespace Ombi.Notifications.Agents
{
public MattermostNotification(IMattermostApi api, ISettingsService<MattermostNotificationSettings> sn, ILogger<MattermostNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -23,7 +23,7 @@ namespace Ombi.Notifications.Agents
public MobileNotification(ICloudMobileNotification api, ISettingsService<MobileNotificationSettings> sn, ILogger<MobileNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<MobileDevices> notification,
UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music, IRepository<Issues> issueRepository,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{
_api = api;
_logger = log;
@ -62,7 +62,7 @@ namespace Ombi.Notifications.Agents
};
// Get admin devices
var playerIds = await GetAdmins(NotificationType.NewRequest);
var playerIds = await GetPrivilegedUsersPlayerIds();
await Send(playerIds, notification, settings, model, true);
}
@ -82,7 +82,7 @@ namespace Ombi.Notifications.Agents
};
// Get admin devices
var playerIds = await GetAdmins(NotificationType.Issue);
var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model);
}
@ -112,7 +112,7 @@ namespace Ombi.Notifications.Agents
else
{
// Send to admin
var playerIds = await GetAdmins(NotificationType.IssueComment);
var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model);
}
}
@ -157,7 +157,7 @@ namespace Ombi.Notifications.Agents
};
// Get admin devices
var playerIds = await GetAdmins(NotificationType.Test);
var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model);
}
@ -279,15 +279,25 @@ namespace Ombi.Notifications.Agents
await Send(playerIds, notification, settings, model);
}
private async Task<List<string>> GetAdmins(NotificationType type)
private async Task<List<string>> GetAdmins()
{
var adminUsers = (await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin)).Select(x => x.Id).ToList();
return await GetNotificationRecipients(await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin));
}
private async Task<List<string>> GetPrivilegedUsersPlayerIds()
{
return await GetNotificationRecipients(await GetPrivilegedUsers());
}
private async Task<List<string>> GetNotificationRecipients(IEnumerable<OmbiUser> users)
{
var adminUsers = users.Select(x => x.Id).ToList();
var notificationUsers = _notifications.GetAll().Include(x => x.User).Where(x => adminUsers.Contains(x.UserId));
var playerIds = await notificationUsers.Select(x => x.Token).ToListAsync();
if (!playerIds.Any())
{
_logger.LogInformation(
$"there are no admins to send a notification for {type}, for agent {NotificationAgent.Mobile}");
$"there are no users to send a notification for agent {NotificationAgent.Mobile}");
return null;
}
return playerIds;
@ -377,7 +387,7 @@ namespace Ombi.Notifications.Agents
};
// Get admin devices
var playerIds = await GetAdmins(NotificationType.PartiallyAvailable);
var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model, true);
}
}

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Ombi.Api.Pushbullet;
using Ombi.Core.Settings;
@ -17,7 +18,7 @@ namespace Ombi.Notifications.Agents
{
public PushbulletNotification(IPushbulletApi api, ISettingsService<PushbulletSettings> sn, ILogger<PushbulletNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Ombi.Api.Pushbullet;
using Ombi.Api.Pushover;
@ -18,7 +19,7 @@ namespace Ombi.Notifications.Agents
{
public PushoverNotification(IPushoverApi api, ISettingsService<PushoverSettings> sn, ILogger<PushoverNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Ombi.Api.Slack;
using Ombi.Api.Slack.Models;
@ -18,7 +19,7 @@ namespace Ombi.Notifications.Agents
{
public SlackNotification(ISlackApi api, ISettingsService<SlackNotificationSettings> sn, ILogger<SlackNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -10,6 +10,7 @@ using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using Ombi.Api.Telegram;
using Microsoft.AspNetCore.Identity;
namespace Ombi.Notifications.Agents
{
@ -19,7 +20,7 @@ namespace Ombi.Notifications.Agents
INotificationTemplatesRepository r, IMovieRequestRepository m,
ITvRequestRepository t, ISettingsService<CustomizationSettings> s
, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t,s,log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t,s,log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Ombi.Api.Webhook;
using Ombi.Core.Settings;
@ -18,7 +19,7 @@ namespace Ombi.Notifications.Agents
{
public WebhookNotification(IWebhookApi api, ISettingsService<WebhookSettings> sn, ILogger<WebhookNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -10,6 +10,7 @@ using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using Ombi.Api.Twilio;
using Microsoft.AspNetCore.Identity;
namespace Ombi.Notifications.Agents
{
@ -19,7 +20,7 @@ namespace Ombi.Notifications.Agents
INotificationTemplatesRepository r, IMovieRequestRepository m,
ITvRequestRepository t, ISettingsService<CustomizationSettings> s
, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t,s,log, sub, music, userPref)
IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t,s,log, sub, music, userPref, um)
{
Api = api;
Logger = log;

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Core.Settings;
@ -19,7 +21,7 @@ namespace Ombi.Notifications
{
protected BaseNotification(ISettingsService<T> settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv,
ISettingsService<CustomizationSettings> customization, ILogger<BaseNotification<T>> log, IRepository<RequestSubscription> sub, IMusicRequestRepository album,
IRepository<UserNotificationPreferences> notificationUserPreferences)
IRepository<UserNotificationPreferences> notificationUserPreferences, UserManager<OmbiUser> um)
{
Settings = settings;
TemplateRepository = templateRepo;
@ -30,6 +32,7 @@ namespace Ombi.Notifications
_log = log;
AlbumRepository = album;
UserNotificationPreferences = notificationUserPreferences;
_userManager = um;
Settings.ClearCache();
}
@ -43,6 +46,7 @@ namespace Ombi.Notifications
protected IRepository<UserNotificationPreferences> UserNotificationPreferences { get; set; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private readonly ILogger<BaseNotification<T>> _log;
private readonly UserManager<OmbiUser> _userManager;
protected ChildRequests TvRequest { get; set; }
@ -210,6 +214,13 @@ namespace Ombi.Notifications
.FirstOrDefault(x => x.Agent == agent && x.UserId == userId);
}
protected async Task<IEnumerable<OmbiUser>> GetPrivilegedUsers()
{
IEnumerable<OmbiUser> recipients = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin);
recipients = recipients.Concat(await _userManager.GetUsersInRoleAsync(OmbiRoles.PowerUser));
return recipients;
}
private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template, NotificationAgent agent)
{
var resolver = new NotificationMessageResolver();

@ -64,7 +64,20 @@ namespace Ombi.Notifications
MessageId = messageId
};
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
message.To.Add(new MailboxAddress(model.To, model.To));
if (model.To.HasValue())
{
message.To.Add(new MailboxAddress(model.To, model.To));
}
// Check for BCC
if (model.Other.TryGetValue("bcc", out var bcc))
{
var bccList = bcc.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (var item in bccList)
{
message.Bcc.Add(new MailboxAddress(item, item));
}
}
using (var client = new SmtpClient())
{

@ -39,7 +39,22 @@ namespace Ombi.Notifications
Year = req?.ReleaseDate.Year.ToString();
Overview = req?.Overview;
AdditionalInformation = opts?.AdditionalInformation ?? string.Empty;
PosterImage = $"https://image.tmdb.org/t/p/w300/{req?.PosterPath?.TrimStart('/') ?? string.Empty}";
var img = req?.PosterPath ?? string.Empty;
if (img.HasValue())
{
if (img.StartsWith("http"))
{
// This means it's a legacy request from when we used TvMaze as a provider.
// The poster url is the fully qualified address, so just use it
PosterImage = img;
}
else
{
PosterImage =
$"https://image.tmdb.org/t/p/w300/{img?.TrimStart('/') ?? string.Empty}";
}
}
CalculateRequestStatus(req);
}
@ -53,8 +68,21 @@ namespace Ombi.Notifications
Year = req?.ParentRequest?.ReleaseDate.Year.ToString();
Overview = req?.ParentRequest?.Overview;
AdditionalInformation = opts.AdditionalInformation;
PosterImage =
$"https://image.tmdb.org/t/p/w300/{req?.ParentRequest?.PosterPath?.TrimStart('/') ?? string.Empty}";
var img = req?.ParentRequest?.PosterPath ?? string.Empty;
if (img.HasValue())
{
if (img.StartsWith("http"))
{
// This means it's a legacy request from when we used TvMaze as a provider.
// The poster url is the fully qualified address, so just use it
PosterImage = img;
}
else
{
PosterImage =
$"https://image.tmdb.org/t/p/w300/{img?.TrimStart('/') ?? string.Empty}";
}
}
// Generate episode list.
StringBuilder epSb = new StringBuilder();
@ -94,16 +122,24 @@ namespace Ombi.Notifications
private void LoadIssues(NotificationOptions opts)
{
IssueDescription = opts.Substitutes.TryGetValue("IssueDescription", out string val) ? val : string.Empty;
IssueCategory = opts.Substitutes.TryGetValue("IssueCategory", out val) ? val : string.Empty;
IssueStatus = opts.Substitutes.TryGetValue("IssueStatus", out val) ? val : string.Empty;
IssueSubject = opts.Substitutes.TryGetValue("IssueSubject", out val) ? val : string.Empty;
NewIssueComment = opts.Substitutes.TryGetValue("NewIssueComment", out val) ? val : string.Empty;
UserName = opts.Substitutes.TryGetValue("IssueUser", out val) ? val : string.Empty;
Alias = opts.Substitutes.TryGetValue("IssueUserAlias", out val) ? val : string.Empty;
Type = opts.Substitutes.TryGetValue("RequestType", out val) && Enum.TryParse(val, out RequestType type)
IssueDescription = opts.Substitutes.TryGetValue(NotificationSubstitues.IssueDescription, out string val) ? val : string.Empty;
IssueCategory = opts.Substitutes.TryGetValue(NotificationSubstitues.IssueCategory, out val) ? val : string.Empty;
IssueStatus = opts.Substitutes.TryGetValue(NotificationSubstitues.IssueStatus, out val) ? val : string.Empty;
IssueSubject = opts.Substitutes.TryGetValue(NotificationSubstitues.IssueSubject, out val) ? val : string.Empty;
NewIssueComment = opts.Substitutes.TryGetValue(NotificationSubstitues.NewIssueComment, out val) ? val : string.Empty;
UserName = opts.Substitutes.TryGetValue(NotificationSubstitues.IssueUser, out val) ? val : string.Empty;
Alias = opts.Substitutes.TryGetValue(NotificationSubstitues.IssueUserAlias, out val) ? val : string.Empty;
Type = opts.Substitutes.TryGetValue(NotificationSubstitues.RequestType, out val) && Enum.TryParse(val, out RequestType type)
? HumanizeReturnType(type)
: string.Empty;
if (opts.Substitutes.TryGetValue(NotificationSubstitues.PosterPath, out val) && val.HasValue())
{
PosterImage = $"https://image.tmdb.org/t/p/w300/{val.TrimStart('/')}";
}
else
{
PosterImage = string.Empty;
}
}
private void LoadCommon(BaseRequest req, CustomizationSettings s, UserNotificationPreferences pref, NotificationOptions opts)

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release;NonUiBuild</Configurations>
</PropertyGroup>
@ -11,7 +11,7 @@
<PackageReference Include="Moq" Version="4.15.1" />
<PackageReference Include="Moq.AutoMock" Version="0.4.0" />
<PackageReference Include="Nunit" Version="3.11.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="16.11.0"></packagereference>

@ -5,6 +5,8 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using Ombi.Api.Emby;
using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Media.Tv;
using Ombi.Api.Emby.Models.Movie;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
@ -36,24 +38,33 @@ namespace Ombi.Schedule.Jobs.Emby
private readonly IEmbyContentRepository _repo;
private readonly IHubContext<NotificationHub> _notification;
private const int AmountToTake = 100;
private IEmbyApi Api { get; set; }
public async Task Execute(IJobExecutionContext job)
public async Task Execute(IJobExecutionContext context)
{
JobDataMap dataMap = context.JobDetail.JobDataMap;
var recentlyAddedSearch = false;
if (dataMap.TryGetValue(JobDataKeys.EmbyRecentlyAddedSearch, out var recentlyAddedObj))
{
recentlyAddedSearch = Convert.ToBoolean(recentlyAddedObj);
}
var embySettings = await _settings.GetSettingsAsync();
if (!embySettings.Enable)
return;
Api = _apiFactory.CreateClient(embySettings);
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Emby Content Sync Started");
.SendAsync(NotificationHub.NotificationEvent, recentlyAddedSearch ? "Emby Recently Added Started" : "Emby Content Sync Started");
foreach (var server in embySettings.Servers)
{
try
{
await StartServerCache(server, embySettings);
await StartServerCache(server, recentlyAddedSearch);
}
catch (Exception e)
{
@ -67,11 +78,12 @@ namespace Ombi.Schedule.Jobs.Emby
.SendAsync(NotificationHub.NotificationEvent, "Emby Content Sync Finished");
// Episodes
await OmbiQuartz.TriggerJob(nameof(IEmbyEpisodeSync), "Emby");
await OmbiQuartz.Scheduler.TriggerJob(new JobKey(nameof(IEmbyEpisodeSync), "Emby"), new JobDataMap(new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, recentlyAddedSearch.ToString() } }));
}
private async Task StartServerCache(EmbyServers server, EmbySettings settings)
private async Task StartServerCache(EmbyServers server, bool recentlyAdded)
{
if (!ValidateSettings(server))
return;
@ -86,14 +98,14 @@ namespace Ombi.Schedule.Jobs.Emby
foreach (var movieParentIdFilder in movieLibsToFilter)
{
_logger.LogInformation($"Scanning Lib '{movieParentIdFilder.Title}'");
await ProcessMovies(server, movieParentIdFilder.Key);
await ProcessMovies(server, recentlyAdded, movieParentIdFilder.Key);
}
var tvLibsToFilter = server.EmbySelectedLibraries.Where(x => x.Enabled && x.CollectionType == "tvshows");
foreach (var tvParentIdFilter in tvLibsToFilter)
{
_logger.LogInformation($"Scanning Lib '{tvParentIdFilter.Title}'");
await ProcessTv(server, tvParentIdFilter.Key);
await ProcessTv(server, recentlyAdded, tvParentIdFilter.Key);
}
@ -101,68 +113,74 @@ namespace Ombi.Schedule.Jobs.Emby
foreach (var m in mixedLibs)
{
_logger.LogInformation($"Scanning Lib '{m.Title}'");
await ProcessTv(server, m.Key);
await ProcessMovies(server, m.Key);
await ProcessTv(server, recentlyAdded, m.Key);
await ProcessMovies(server, recentlyAdded, m.Key);
}
}
else
{
await ProcessMovies(server);
await ProcessTv(server);
await ProcessMovies(server, recentlyAdded);
await ProcessTv(server, recentlyAdded);
}
}
private async Task ProcessTv(EmbyServers server, string parentId = default)
private async Task ProcessTv(EmbyServers server, bool recentlyAdded, string parentId = default)
{
// TV Time
var mediaToAdd = new HashSet<EmbyContent>();
var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
EmbyItemContainer<EmbySeries> tv;
if (recentlyAdded)
{
var recentlyAddedAmountToTake = AmountToTake / 2;
tv = await Api.RecentlyAddedShows(server.ApiKey, parentId, 0, recentlyAddedAmountToTake, server.AdministratorId, server.FullUri);
if (tv.TotalRecordCount > recentlyAddedAmountToTake)
{
tv.TotalRecordCount = recentlyAddedAmountToTake;
}
}
else
{
tv = await Api.GetAllShows(server.ApiKey, parentId, 0, AmountToTake, server.AdministratorId, server.FullUri);
}
var totalTv = tv.TotalRecordCount;
var processed = 1;
var processed = 0;
while (processed < totalTv)
{
foreach (var tvShow in tv.Items)
{
try
processed++;
if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
{
_logger.LogInformation("Provider Id on tv {0} is null", tvShow.Name);
continue;
}
processed++;
if (string.IsNullOrEmpty(tvShow.ProviderIds?.Tvdb))
{
_logger.LogInformation("Provider Id on tv {0} is null", tvShow.Name);
continue;
}
var existingTv = await _repo.GetByEmbyId(tvShow.Id);
if (existingTv == null)
{
_logger.LogDebug("Adding new TV Show {0}", tvShow.Name);
mediaToAdd.Add(new EmbyContent
{
TvDbId = tvShow.ProviderIds?.Tvdb,
ImdbId = tvShow.ProviderIds?.Imdb,
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
Title = tvShow.Name,
Type = EmbyMediaType.Series,
EmbyId = tvShow.Id,
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow
});
}
else
var existingTv = await _repo.GetByEmbyId(tvShow.Id);
if (existingTv == null)
{
_logger.LogDebug("Adding new TV Show {0}", tvShow.Name);
mediaToAdd.Add(new EmbyContent
{
_logger.LogDebug("We already have TV Show {0}", tvShow.Name);
}
TvDbId = tvShow.ProviderIds?.Tvdb,
ImdbId = tvShow.ProviderIds?.Imdb,
TheMovieDbId = tvShow.ProviderIds?.Tmdb,
Title = tvShow.Name,
Type = EmbyMediaType.Series,
EmbyId = tvShow.Id,
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id, server?.ServerId, server.ServerHostname),
AddedAt = DateTime.UtcNow
});
}
catch (Exception)
else
{
throw;
_logger.LogDebug("We already have TV Show {0}", tvShow.Name);
}
}
// Get the next batch
tv = await Api.GetAllShows(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri);
if (!recentlyAdded)
{
tv = await Api.GetAllShows(server.ApiKey, parentId, processed, AmountToTake, server.AdministratorId, server.FullUri);
}
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
@ -171,11 +189,25 @@ namespace Ombi.Schedule.Jobs.Emby
await _repo.AddRange(mediaToAdd);
}
private async Task ProcessMovies(EmbyServers server, string parentId = default)
private async Task ProcessMovies(EmbyServers server, bool recentlyAdded, string parentId = default)
{
var movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
EmbyItemContainer<EmbyMovie> movies;
if (recentlyAdded)
{
var recentlyAddedAmountToTake = AmountToTake / 2;
movies = await Api.RecentlyAddedMovies(server.ApiKey, parentId, 0, recentlyAddedAmountToTake, server.AdministratorId, server.FullUri);
// Setting this so we don't attempt to grab more than we need
if (movies.TotalRecordCount > recentlyAddedAmountToTake)
{
movies.TotalRecordCount = recentlyAddedAmountToTake;
}
}
else
{
movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, AmountToTake, server.AdministratorId, server.FullUri);
}
var totalCount = movies.TotalRecordCount;
var processed = 1;
var processed = 0;
var mediaToAdd = new HashSet<EmbyContent>();
while (processed < totalCount)
{
@ -189,22 +221,24 @@ namespace Ombi.Schedule.Jobs.Emby
{
await ProcessMovies(item, mediaToAdd, server);
}
processed++;
}
else
{
processed++;
// Regular movie
await ProcessMovies(movie, mediaToAdd, server);
}
processed++;
}
// Get the next batch
movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, 200, server.AdministratorId, server.FullUri);
// Recently Added should never be checked as the TotalRecords should equal the amount to take
if (!recentlyAdded)
{
movies = await Api.GetAllMovies(server.ApiKey, parentId, processed, AmountToTake, server.AdministratorId, server.FullUri);
}
await _repo.AddRange(mediaToAdd);
mediaToAdd.Clear();
}
}

@ -40,6 +40,8 @@ using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Quartz;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Media.Tv;
namespace Ombi.Schedule.Jobs.Emby
{
@ -61,13 +63,22 @@ namespace Ombi.Schedule.Jobs.Emby
private readonly IEmbyContentRepository _repo;
private readonly IHubContext<NotificationHub> _notification;
private const int AmountToTake = 100;
private IEmbyApi Api { get; set; }
public async Task Execute(IJobExecutionContext job)
public async Task Execute(IJobExecutionContext context)
{
JobDataMap dataMap = context.MergedJobDataMap;
var recentlyAddedSearch = false;
if (dataMap.TryGetValue(JobDataKeys.EmbyRecentlyAddedSearch, out var recentlyAddedObj))
{
recentlyAddedSearch = Convert.ToBoolean(recentlyAddedObj);
}
var settings = await _settings.GetSettingsAsync();
Api = _apiFactory.CreateClient(settings);
await _notification.Clients.Clients(NotificationHub.AdminConnectionIds)
.SendAsync(NotificationHub.NotificationEvent, "Emby Episode Sync Started");
@ -79,12 +90,12 @@ namespace Ombi.Schedule.Jobs.Emby
foreach (var tvParentIdFilter in tvLibsToFilter)
{
_logger.LogInformation($"Scanning Lib for episodes '{tvParentIdFilter.Title}'");
await CacheEpisodes(server, tvParentIdFilter.Key);
await CacheEpisodes(server, recentlyAddedSearch, tvParentIdFilter.Key);
}
}
else
{
await CacheEpisodes(server, string.Empty);
await CacheEpisodes(server, recentlyAddedSearch, string.Empty);
}
}
@ -94,11 +105,24 @@ namespace Ombi.Schedule.Jobs.Emby
await OmbiQuartz.TriggerJob(nameof(IRefreshMetadata), "System");
}
private async Task CacheEpisodes(EmbyServers server, string parentIdFilter)
private async Task CacheEpisodes(EmbyServers server, bool recentlyAdded, string parentIdFilter)
{
var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, 200, server.AdministratorId, server.FullUri);
EmbyItemContainer<EmbyEpisodes> allEpisodes;
if (recentlyAdded)
{
var recentlyAddedAmountToTake = AmountToTake;
allEpisodes = await Api.RecentlyAddedEpisodes(server.ApiKey, parentIdFilter, 0, recentlyAddedAmountToTake, server.AdministratorId, server.FullUri);
if (allEpisodes.TotalRecordCount > recentlyAddedAmountToTake)
{
allEpisodes.TotalRecordCount = recentlyAddedAmountToTake;
}
}
else
{
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, AmountToTake, server.AdministratorId, server.FullUri);
}
var total = allEpisodes.TotalRecordCount;
var processed = 1;
var processed = 0;
var epToAdd = new HashSet<EmbyEpisode>();
while (processed < total)
{
@ -163,7 +187,10 @@ namespace Ombi.Schedule.Jobs.Emby
await _repo.AddRange(epToAdd);
epToAdd.Clear();
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, processed, 200, server.AdministratorId, server.FullUri);
if (!recentlyAdded)
{
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, processed, AmountToTake, server.AdministratorId, server.FullUri);
}
}
if (epToAdd.Any())

@ -118,7 +118,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
var mediaToAdd = new HashSet<JellyfinContent>();
var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
var totalTv = tv.TotalRecordCount;
var processed = 1;
var processed = 0;
while (processed < totalTv)
{
foreach (var tvShow in tv.Items)
@ -177,7 +177,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
{
var movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
var totalCount = movies.TotalRecordCount;
var processed = 1;
var processed = 0;
var mediaToAdd = new HashSet<JellyfinContent>();
while (processed < totalCount)
{

@ -98,7 +98,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
{
var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, 200, server.AdministratorId, server.FullUri);
var total = allEpisodes.TotalRecordCount;
var processed = 1;
var processed = 0;
var epToAdd = new HashSet<JellyfinEpisode>();
while (processed < total)
{

@ -16,14 +16,16 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly ISettingsService<OmbiSettings> _ombiSettings;
private readonly IMovieRequestRepository _movieRequests;
private readonly ITvRequestRepository _tvRequestRepository;
private readonly IMusicRequestRepository _musicRequestRepository;
private readonly ILogger<AutoDeleteRequests> _logger;
public AutoDeleteRequests(ISettingsService<OmbiSettings> ombiSettings, IMovieRequestRepository movieRequest,
ILogger<AutoDeleteRequests> logger, ITvRequestRepository tvRequestRepository)
ILogger<AutoDeleteRequests> logger, ITvRequestRepository tvRequestRepository, IMusicRequestRepository musicRequestRepository)
{
_ombiSettings = ombiSettings;
_movieRequests = movieRequest;
_tvRequestRepository = tvRequestRepository;
_musicRequestRepository = _musicRequestRepository;
_logger = logger;
}
@ -37,6 +39,7 @@ namespace Ombi.Schedule.Jobs.Ombi
var date = DateTime.UtcNow.AddDays(-settings.AutoDeleteAfterDays).Date;
await ProcessMovieRequests(date);
await ProcessTvRequests(date);
await ProcessMusicRequests(date);
}
private async Task ProcessMovieRequests(DateTime date)
@ -66,6 +69,20 @@ namespace Ombi.Schedule.Jobs.Ombi
await _tvRequestRepository.DeleteRange(parentRequests);
}
private async Task ProcessMusicRequests(DateTime date)
{
var requestsToDelete = await _musicRequestRepository.GetAll().Where(x => x.Available && x.MarkedAsAvailable.HasValue && x.MarkedAsAvailable.Value < date).ToListAsync();
_logger.LogInformation($"Deleting {requestsToDelete.Count} music requests that have now been scheduled for deletion, All available requests before {date::MM/dd/yyyy} will be deleted");
foreach (var r in requestsToDelete)
{
_logger.LogInformation($"Deleting music title {r.Title} as it was approved on {r.MarkedAsApproved:MM/dd/yyyy hh:mm tt}");
}
await _musicRequestRepository.DeleteRange(requestsToDelete);
}
private bool _disposed;
protected virtual void Dispose(bool disposing)

@ -321,7 +321,9 @@ namespace Ombi.Schedule.Jobs.Ombi
public async Task DownloadAsync(string requestUri, string filename)
{
Logger.LogDebug(LoggingEvents.Updater, "Starting the DownloadAsync");
#pragma warning disable SYSLIB0014 // Type or member is obsolete
using (var client = new WebClient())
#pragma warning restore SYSLIB0014 // Type or member is obsolete
{
await client.DownloadFileTaskAsync(requestUri, filename);
}

@ -108,7 +108,7 @@ namespace Ombi.Schedule.Jobs.Sonarr
foreach (var s in ids)
{
if (!s.Monitored || s.EpisodeFileCount == 0) // We have files
if (!s.Monitored && s.EpisodeFileCount == 0) // We have files
{
continue;
}

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -11,10 +11,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Quartz" Version="3.1.0" />
<PackageReference Include="Serilog" Version="2.8.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="SharpCompress" Version="0.30.0" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.6.13" />
<PackageReference Include="Markdig" Version="0.14.8" />

@ -96,6 +96,7 @@ namespace Ombi.Schedule
private static async Task AddEmby(JobSettings s)
{
await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync), "Emby", JobSettingsHelper.EmbyContent(s));
await OmbiQuartz.Instance.AddJob<IEmbyContentSync>(nameof(IEmbyContentSync) + "RecentlyAdded", "Emby", JobSettingsHelper.EmbyRecentlyAddedSync(s), new Dictionary<string, string> { { JobDataKeys.EmbyRecentlyAddedSearch, "true" } });
await OmbiQuartz.Instance.AddJob<IEmbyEpisodeSync>(nameof(IEmbyEpisodeSync), "Emby", null);
await OmbiQuartz.Instance.AddJob<IEmbyAvaliabilityChecker>(nameof(IEmbyAvaliabilityChecker), "Emby", null);
await OmbiQuartz.Instance.AddJob<IEmbyUserImporter>(nameof(IEmbyUserImporter), "Emby", JobSettingsHelper.UserImporter(s));

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup><TargetFramework>net5.0</TargetFramework>
<PropertyGroup><TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="nunit" Version="3.11.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="5.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
</ItemGroup>

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Quartz" Version="3.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save