mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 13:23:20 -07:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
11839deee8
580 changed files with 48720 additions and 14077 deletions
48
.github/ISSUE_TEMPLATE.md
vendored
48
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,48 +0,0 @@
|
||||||
<!---
|
|
||||||
|
|
||||||
!! Please use the Support / bug report template, otherwise we will close the Github issue !!
|
|
||||||
|
|
||||||
Version 2.X is not supported anymore. Please don't open a issue for the 2.x version.
|
|
||||||
See https://github.com/tidusjar/Ombi/issues/1455 for more information.
|
|
||||||
|
|
||||||
(Pleas submit a feature request over here: http://feathub.com/tidusjar/Ombi)
|
|
||||||
|
|
||||||
|
|
||||||
--->
|
|
||||||
|
|
||||||
#### Ombi build Version:
|
|
||||||
|
|
||||||
V 3.0.XX
|
|
||||||
|
|
||||||
#### Update Branch:
|
|
||||||
|
|
||||||
Open Beta
|
|
||||||
|
|
||||||
#### Media Sever:
|
|
||||||
|
|
||||||
Plex/Emby
|
|
||||||
|
|
||||||
#### Media Server Version:
|
|
||||||
|
|
||||||
<!-- If appropriate --->
|
|
||||||
|
|
||||||
#### Operating System:
|
|
||||||
|
|
||||||
(Place text here)
|
|
||||||
|
|
||||||
|
|
||||||
#### Ombi Applicable Logs (from `/logs/` directory or the Admin page):
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
(Logs go here. Don't remove the ' tags for showing your logs correctly. Please make sure you remove any personal information from the logs)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Problem Description:
|
|
||||||
|
|
||||||
(Place text here)
|
|
||||||
|
|
||||||
#### Reproduction Steps:
|
|
||||||
|
|
||||||
Please include any steps to reproduce the issue, this the request that is causing the problem etc.
|
|
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
37
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Logs (Logs directory where Ombi is located)**
|
||||||
|
If applicable, a snippet of the logs that seems relevant to the bug if present.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. iOS]
|
||||||
|
|
||||||
|
**Ombi Version (please complete the following information):**
|
||||||
|
- Version [e.g. 3.0.1158]
|
||||||
|
- Media Server [e.g. Plex]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -243,3 +243,6 @@ _Pvt_Extensions
|
||||||
# CAKE - C# Make
|
# CAKE - C# Make
|
||||||
/Tools/*
|
/Tools/*
|
||||||
*.db-journal
|
*.db-journal
|
||||||
|
|
||||||
|
# Ignore local vscode config
|
||||||
|
*.vscode
|
||||||
|
|
950
CHANGELOG.md
950
CHANGELOG.md
|
@ -1,6 +1,954 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## (unreleased)
|
## v3.0.4256 (2019-02-18)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update discord link to follow the scheme of other links. [Tom McClellan]
|
||||||
|
|
||||||
|
- Update issue templates. [Jamie]
|
||||||
|
|
||||||
|
- Update README.md. [Jamie]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
- Added the functionality to remove a user from getting notifications to their mobile device #2780. [tidusjar]
|
||||||
|
|
||||||
|
- Added a demo mode, this will only show movies and shows that are in the public domain. Dam that stupid fruit company. [tidusjar]
|
||||||
|
|
||||||
|
- Added Actor Searching for Movies! [TidusJar]
|
||||||
|
|
||||||
|
- Added the ability to change where the View on Emby link goes to #2730. [TidusJar]
|
||||||
|
|
||||||
|
- Added the request queue to the notifications UI so you can turn it off per notification agent #2747. [TidusJar]
|
||||||
|
|
||||||
|
- Added new classes to the posters #2732. [TidusJar]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fix: src/Ombi/package.json to reduce vulnerabilities. [snyk-bot]
|
||||||
|
|
||||||
|
- Fixed #2801 this is when a season is not correctly monitored in sonarr when approved by an admin. [tidusjar]
|
||||||
|
|
||||||
|
- Small improvements to try and mitigate #2750. [tidusjar]
|
||||||
|
|
||||||
|
- Removed some areas where we clear out the cache. This should help with DB locking #2750. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed #2810. [tidusjar]
|
||||||
|
|
||||||
|
- Cannot create an issue comment with the API #2811. [tidusjar]
|
||||||
|
|
||||||
|
- Set the local domain if the Application URL is set for the HELO or EHLO commands. #2636. [tidusjar]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- Delete ISSUE_TEMPLATE.md. [Jamie]
|
||||||
|
|
||||||
|
- More minor grammatical edits. [Andrew Metzger]
|
||||||
|
|
||||||
|
- Minor grammatical edits. [Andrew Metzger]
|
||||||
|
|
||||||
|
- Fixed #2802 the issue where "Issues" were not being deleted correctly. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed #2797. [tidusjar]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2786. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed #2756. [tidusjar]
|
||||||
|
|
||||||
|
- Ignore the UserName header as part of the Api is the value is an empty string. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed #2759. [tidusjar]
|
||||||
|
|
||||||
|
- Did #2756. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed the exception that sometimes makes ombi fallover. [TidusJar]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- Log the error to the ui to figure out what's going on with #2755. [tidusjar]
|
||||||
|
|
||||||
|
- Small fix when denying a request with a reason, we wasn't updating the ui. [TidusJar]
|
||||||
|
|
||||||
|
- Make sure we can only set the ApiAlias when using the API Key. [tidusjar]
|
||||||
|
|
||||||
|
- #2363 Added the ability to pass any username into the API using the ApiAlias header. [tidusjar]
|
||||||
|
|
||||||
|
- Removed the Add user to Plex from Ombi. [tidusjar]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.4119 (2019-01-09)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
- Added a page where the admin can write/style/basically do whatever they want with e.g. FAQ for the users #2715 This needs to be enabled in the Customization Settings and then it's all configured on the page. [TidusJar]
|
||||||
|
|
||||||
|
- Updated the AspnetCore.App package to remove the CVE-2019-0564 vulnerability. [TidusJar]
|
||||||
|
|
||||||
|
- Added a global language flag that now applies to the search by default. [tidusjar]
|
||||||
|
|
||||||
|
- Updated the frontend packages (Using Angular 7 now) [TidusJar]
|
||||||
|
|
||||||
|
- Added capture of anonymous analytical data. [tidusjar]
|
||||||
|
|
||||||
|
- Added {AvailableDate} as a Notification Variable, this is the date the request was marked as available. See here: https://github.com/tidusjar/Ombi/wiki/Notification-Template-Variables. [tidusjar]
|
||||||
|
|
||||||
|
- Added the ability to search movies via the movie db with a different language! [tidusjar]
|
||||||
|
|
||||||
|
- Added the ability to specify a year when searching for movies. [tidusjar]
|
||||||
|
|
||||||
|
- Update NewsletterTemplate.html. [d1slact0r]
|
||||||
|
|
||||||
|
- Update NewsletterTemplate.html. [d1slact0r]
|
||||||
|
|
||||||
|
- Update NewsletterTemplate.html. [d1slact0r]
|
||||||
|
|
||||||
|
- Update HtmlTemplateGenerator.cs. [d1slact0r]
|
||||||
|
|
||||||
|
- Update NewsletterTemplate.html. [d1slact0r]
|
||||||
|
|
||||||
|
- Update HtmlTemplateGenerator.cs. [d1slact0r]
|
||||||
|
|
||||||
|
- Update NewsletterTemplate.html. [d1slact0r]
|
||||||
|
|
||||||
|
- Update NewsletterTemplate.html. [d1slact0r]
|
||||||
|
|
||||||
|
- Update NewsletterTemplate.html. [d1slact0r]
|
||||||
|
|
||||||
|
- Update HtmlTemplateGenerator.cs. [d1slact0r]
|
||||||
|
|
||||||
|
- Updated boostrap #2694. [Jamie]
|
||||||
|
|
||||||
|
- Added the ability to deny a request with a reason. [TidusJar]
|
||||||
|
|
||||||
|
- Update EmbyEpisodeSync.cs. [Jamie]
|
||||||
|
|
||||||
|
- Updated to .net core 2.2 and included a linux-arm64 build. [TidusJar]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- There is now a new Job in ombi that will clear out the Plex/Emby data and recache. This will prevent the issues going forward that we have when Ombi and the Media server fall out of sync with deletions/updates #2641 #2362 #1566. [TidusJar]
|
||||||
|
|
||||||
|
- Potentially fix #2726. [TidusJar]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2725 and #2721. [TidusJar]
|
||||||
|
|
||||||
|
- Made the newsletter use the default lanuage code set in the Ombi settings for movie information. [TidusJar]
|
||||||
|
|
||||||
|
- Save the language code against the request so we can use it later e.g. Sending to the DVR apps. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed #2716. [tidusjar]
|
||||||
|
|
||||||
|
- Make the newsletter BCC the users rather than creating a million newsletters (Hopefully will stop SMTP providers from marking as spam). This does mean that the custom user customization in the newsletter will no longer work. [TidusJar]
|
||||||
|
|
||||||
|
- If we don't know the Plex agent, then see if it's a ImdbId, if it's not check the string for any episode and season hints #2695. [tidusjar]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- Made the search results the language specified in the search refinement. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed #2704. [tidusjar]
|
||||||
|
|
||||||
|
- Now it is fixed :) [d1slact0r]
|
||||||
|
|
||||||
|
- Android please be nice now. [d1slact0r]
|
||||||
|
|
||||||
|
- Fixed title bit better. [d1slact0r]
|
||||||
|
|
||||||
|
- Fixed titles. [d1slact0r]
|
||||||
|
|
||||||
|
- This should fix the build for sure (stupid quotes) [d1slact0r]
|
||||||
|
|
||||||
|
- Fixes build. [d1slact0r]
|
||||||
|
|
||||||
|
- Rewritten the whole newsletter template. [d1slact0r]
|
||||||
|
|
||||||
|
- Fixed #2697. [tidusjar]
|
||||||
|
|
||||||
|
- Add linux-arm runtime identifier. [aptalca]
|
||||||
|
|
||||||
|
- Add back arm packages. [aptalca]
|
||||||
|
|
||||||
|
- Add arm32 package. [aptalca]
|
||||||
|
|
||||||
|
- Fixed #2691. [tidusjar]
|
||||||
|
|
||||||
|
- Fixed linting. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed the Plex OAuth when going through the wizard. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed #2678. [TidusJar]
|
||||||
|
|
||||||
|
- Deny reason for movie requests. [TidusJar]
|
||||||
|
|
||||||
|
- Set the landing and login pages background refresh to 15 seconds rather than 10 and 7. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed a bug with us thinking future dated emby episodes are not available, Consoldated the emby and plex search rules (since they have the same logic) [TidusJar]
|
||||||
|
|
||||||
|
- Fixed build. [TidusJar]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.4036 (2018-12-11)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Changelog. [Jamie]
|
||||||
|
|
||||||
|
- Added Sonarr v3 #2359. [TidusJar]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- !changelog. [Jamie]
|
||||||
|
|
||||||
|
- Fixed a missing translation. [Jamie]
|
||||||
|
|
||||||
|
- Fixed a potential security vulnerability. [Jamie]
|
||||||
|
|
||||||
|
- Sorted out some of the settings pages, trying to make it consistent. [Jamie]
|
||||||
|
|
||||||
|
- #2669 Fixed missing translations. [TidusJar]
|
||||||
|
|
||||||
|
- Maps alias email variable for welcome emails. [Victor Usoltsev]
|
||||||
|
|
||||||
|
- Increased the logo size on the landing page to match the container below it. [Jamie]
|
||||||
|
|
||||||
|
- Think the request queue is done! [Jamie]
|
||||||
|
|
||||||
|
- Finished off the job. [TidusJar]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3988 (2018-11-23)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Updated the emby api since we no longer need the extra parameters to send to emby to log in a local user #2546. [Jamie]
|
||||||
|
|
||||||
|
- Added the ability to get the ombi user via a Plex Token #2591. [Jamie]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- !changelog. [Jamie]
|
||||||
|
|
||||||
|
- Made the subscribe/unsubscribe button more obvious on the UI #2309. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2603. [Jamie]
|
||||||
|
|
||||||
|
- Fixed the issue with the user overrides #2646. [Jamie]
|
||||||
|
|
||||||
|
- Fixed the issue where we could sometimes allow the request of a whole series when the user shouldn't be able to. [Jamie]
|
||||||
|
|
||||||
|
- Fixed the issue where we were marking episodes as available with the Emby connection when they have not yet aired #2417 #2623. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed the issue where we were marking the whole season as wanted in Sonarr rather than the individual episode #2629. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed #2623. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2633. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed #2639. [Jamie]
|
||||||
|
|
||||||
|
- Show the TV show as available when we have all the episodes but future episodes have not aired. #2585. [Jamie]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3945 (2018-10-25)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update Readme for Lidarr. [Qstick]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- Fixed the issue with mobile notifications. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2514. [Jamie]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3923 (2018-10-19)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fixed #2601. [Jamie]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3919 (2018-10-17)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Added automation tests for the voting feature. [TidusJar]
|
||||||
|
|
||||||
|
- Update LidarrAvailabilityChecker.cs. [Jamie]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
- Changes language selector to always show native language name. [Victor Usoltsev]
|
||||||
|
|
||||||
|
- Updated test dependancies. [TidusJar]
|
||||||
|
|
||||||
|
- Added in the external repo so we can rip out external stuff. [TidusJar]
|
||||||
|
|
||||||
|
- Added the ability to purge/remove issues. [TidusJar]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- When a users requests content and the voting is enabled, the user who requested is an automatic +1 vote. [TidusJar]
|
||||||
|
|
||||||
|
- Revert, no idea how this happened. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed the build. Thanks Matt! [TidusJar]
|
||||||
|
|
||||||
|
- Fixes untickable mass email checkboxes in Safari. [Victor Usoltsev]
|
||||||
|
|
||||||
|
- [ImgBot] optimizes images. [ImgBotApp]
|
||||||
|
|
||||||
|
- Revert "Feature/purge issues" [Jamie]
|
||||||
|
|
||||||
|
- Fixed the issue where user preferences was not being inported into some notifications. [TidusJar]
|
||||||
|
|
||||||
|
- New role to enable users to remove their own requests. [Anojh]
|
||||||
|
|
||||||
|
- Users can now remove their own requests. [Anojh]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- Fixed lidarr newsletter bug. [Jamie]
|
||||||
|
|
||||||
|
- Potentially fix the user profiles issue. [Jamie]
|
||||||
|
|
||||||
|
- Hides Radarr options on movie requests page if only 1 option available. [Victor Usoltsev]
|
||||||
|
|
||||||
|
- Hides Sonarr options on tv requests page if only 1 option available. [Victor Usoltsev]
|
||||||
|
|
||||||
|
- Fixed the issue where we could not delete users #2558. [TidusJar]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- Subscribe the user to the request when they vote on it. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed #2555. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2549. [Jamie]
|
||||||
|
|
||||||
|
- Removed the pinID from the OAuth url #2548. [Jamie]
|
||||||
|
|
||||||
|
- Put the issue purge limit on the issues page. [Jamie]
|
||||||
|
|
||||||
|
- Date and times are now in the local users date time. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed the migration. [TidusJar]
|
||||||
|
|
||||||
|
- ExternalContext migrations. [TidusJar]
|
||||||
|
|
||||||
|
- The settings have now been split out of the main db. [TidusJar]
|
||||||
|
|
||||||
|
- Search for the Lidarr Album when it's a new artist. [TidusJar]
|
||||||
|
|
||||||
|
- The album in Lidarr does not need to be marked as monitored for us to pick up it's available. Fixes #2536. [Jamie]
|
||||||
|
|
||||||
|
- Truncate the request title. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2535. [Jamie]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3795 (2018-09-23)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fixed the issue with notifications not sending. [Jamie]
|
||||||
|
|
||||||
|
- Removes Legacy command result variables. [Qstick]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3786 (2018-09-22)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- Fix #2529 - Change data type to long. [Anojh]
|
||||||
|
|
||||||
|
- Fix #2527 - Music request not triggering search and failing. [Anojh]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3776 (2018-09-21)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Update settingsmenu.component.html. [Jamie]
|
||||||
|
|
||||||
|
- Added the request limits in the ui for music. [Jamie]
|
||||||
|
|
||||||
|
- Added the root folders and qualities per user! [Jamie]
|
||||||
|
|
||||||
|
- Updated all the MS packages. [TidusJar]
|
||||||
|
|
||||||
|
- Update the .net core packages to fix "CVE-2018-8409: ASP.NET Core Denial Of Service Vulnerability" [TidusJar]
|
||||||
|
|
||||||
|
- Change way remainingrequests component is notified. [Kenton Royal]
|
||||||
|
|
||||||
|
- Added the music request limits. [TidusJar]
|
||||||
|
|
||||||
|
- Added the Notification Preferences to the user. [TidusJar]
|
||||||
|
|
||||||
|
- Added the API to add user notification preferences. [TidusJar]
|
||||||
|
|
||||||
|
- Added more logging into the updater. [Jamie]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fixed #2518. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed #2522. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed #2485. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed #2516. [TidusJar]
|
||||||
|
|
||||||
|
- Fix bug in which requested TV wasn't logging for some users. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add to translations. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add html for displaying remaining requests on users page. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add quota fields to user view model. [Kenton Royal]
|
||||||
|
|
||||||
|
- Users can now see the music search tab #2493. [TidusJar]
|
||||||
|
|
||||||
|
- Add href to a tags so that a pointer cursor shows on requests page. [Stephen Panzer]
|
||||||
|
|
||||||
|
- Allow Lidarr to specify if we should search for the album. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed the issue if in Radarr we only want to add and monitor, if the movie already exists we search for it. [TidusJar]
|
||||||
|
|
||||||
|
- Fix bug causing wrong time to be displayed for next request. [Kenton Royal]
|
||||||
|
|
||||||
|
- Bodge fix test to prevent compile error. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix displaying year in issue dialog. [Stephen Panzer]
|
||||||
|
|
||||||
|
- Add clearfix class. Closes #2486. [Stephen Panzer]
|
||||||
|
|
||||||
|
- Correct path of lidarr component import for unix systems. [Kenton Royal]
|
||||||
|
|
||||||
|
- Refactor code. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix formatting error. [Kenton Royal]
|
||||||
|
|
||||||
|
- Revert "Revert request.service.ts to version on upstream/develop" [Kenton Royal]
|
||||||
|
|
||||||
|
- Revert request.service.ts to version on upstream/develop. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix lint errors. [Kenton Royal]
|
||||||
|
|
||||||
|
- Move logic for notifying when reuqest is complete. [Kenton Royal]
|
||||||
|
|
||||||
|
- Remove import. [Kenton Royal]
|
||||||
|
|
||||||
|
- Remove unused module. [Kenton Royal]
|
||||||
|
|
||||||
|
- Refactor code. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add text to translation file. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix query for fetching requested tv shows. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add vscode to gitignore. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix lint errors. [Kenton Royal]
|
||||||
|
|
||||||
|
- Remove unused methods from SearchController. [Kenton Royal]
|
||||||
|
|
||||||
|
- Remove local vscode files. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix bug when submitting requests for multiple episodes accross multiple seasons. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix bug with TV requests in which requesting a seasion would treat request as single episode. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix issues with remaining count updating. [Kenton Royal]
|
||||||
|
|
||||||
|
- Trigger update of request limit on new request. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add logic for movie request count. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add logic for retriving request information. [Kenton Royal]
|
||||||
|
|
||||||
|
- Move to seperate component and display for both TV and movies. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add dummy for request counter. [Kenton Royal]
|
||||||
|
|
||||||
|
- Fix scss import for unix systems. [Kenton Royal]
|
||||||
|
|
||||||
|
- Add methods to interface and add model class. [Kenton Royal]
|
||||||
|
|
||||||
|
- !fixed lint. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed #2481. [TidusJar]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2475. [Jamie]
|
||||||
|
|
||||||
|
- Stript out certain characters when sending a pushover message #2385. [TidusJar]
|
||||||
|
|
||||||
|
- Add default values for Priority and Sound. [David Pooley]
|
||||||
|
|
||||||
|
- Allow for the ability to set Pushover notification sound and priority from within Ombi. [David Pooley]
|
||||||
|
|
||||||
|
- It works now when we request an album when we do not have the artist in Lidarr. Waiting on https://github.com/lidarr/Lidarr/issues/459 to do when we have the artist. [Jamie]
|
||||||
|
|
||||||
|
- Fix non-Windows builds. Fixes #2453. [Joe Groocock]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3587 (2018-08-19)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Added the ability to invite Plex Friends from the user management screen. [Jamie]
|
||||||
|
|
||||||
|
- Added rich notifications for mobile. [Jamie]
|
||||||
|
|
||||||
|
- Updater fixes. [Jamie]
|
||||||
|
|
||||||
|
- Added updater test mode. [Jamie Rees]
|
||||||
|
|
||||||
|
- Added a new API method to delete issue comments. [TidusJar]
|
||||||
|
|
||||||
|
- Updated @ngu/carousel to beta version to remove rxjs-compat dependency. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Update to Angular 6/Webpack 4. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Update CHANGELOG.md. [Jamie]
|
||||||
|
|
||||||
|
- Updated the way we create the wizard user, errors show now be fed back to the user. [Jamie]
|
||||||
|
|
||||||
|
- Added Brazillian Portuguese as a language and also Polish. [Jamie]
|
||||||
|
|
||||||
|
- Updated swagger. [Jamie]
|
||||||
|
|
||||||
|
- Updated to 2.1.1. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Now include the release year in the issue title #2381. [TidusJar]
|
||||||
|
|
||||||
|
- Made the OAuth a Popout to work with Org. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2418. [TidusJar]
|
||||||
|
|
||||||
|
- #2408 Added the feature to delete comments on issues. [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2440. [TidusJar]
|
||||||
|
|
||||||
|
- Delete cake.config. [Chris Pritchard]
|
||||||
|
|
||||||
|
- Initial attempt at getting anime seriestype working. [Chris Pritchard]
|
||||||
|
|
||||||
|
- Add cake.config. [Chris Pritchard]
|
||||||
|
|
||||||
|
- Fixed the issue where we wouldn't correctly mark some shows as available when there was no provider id #2429. [Jamie]
|
||||||
|
|
||||||
|
- Fixed the 'loop' in the cacher #2429. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2427. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2424. [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2409. [Jamie]
|
||||||
|
|
||||||
|
- More updater. [Jamie]
|
||||||
|
|
||||||
|
- Humanize the request type enum in notifications e.g. TvShow will now appear as "Tv Show" #2416. [TidusJar]
|
||||||
|
|
||||||
|
- Made the quality override and root folder override load when we load the show (It will now appear) [Jamie]
|
||||||
|
|
||||||
|
- Fixed #2415 where power users could not set the Sonarr Quality Override or Root Folder Override. [Jamie]
|
||||||
|
|
||||||
|
- #2371 Fixed the issue where certain actions would not setup the series correctly in Sonarr. [Jamie]
|
||||||
|
|
||||||
|
- Tightened up the security from an API perspecitve. [TidusJar]
|
||||||
|
|
||||||
|
- Stop the root folder and profile calls from erroring. [TidusJar]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- Fixed all linting. [TidusJar]
|
||||||
|
|
||||||
|
- Comment out envparam stuff. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Fixed prod build issue. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Missed a tiny bit. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Fix test. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Fix test build. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Linting + remove debug. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Switch to Yarn and disable auto publish in release mode. [Matt Jeanes]
|
||||||
|
|
||||||
|
- Fix for #2409. [TidusJar]
|
||||||
|
|
||||||
|
- New translations en.json (Swedish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Spanish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Polish) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Norwegian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Italian) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (German) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (French) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Dutch) [Jamie]
|
||||||
|
|
||||||
|
- New translations en.json (Danish) [Jamie]
|
||||||
|
|
||||||
|
- Possible fix for #2298. [D34DC3N73R]
|
||||||
|
|
||||||
|
- Fixed the text for #2370. [Jamie]
|
||||||
|
|
||||||
|
- Fixed where you couldn't bulk edit the limits to 0 #2318. [Jamie]
|
||||||
|
|
||||||
|
- Upgraded to .net 2.1.2 (Includes security fixes) [Jamie]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3477 (2018-07-18)
|
||||||
|
|
||||||
|
### **New Features**
|
||||||
|
|
||||||
|
- Updated the Emby availability checker to bring it more in line with what we do with Plex. [TidusJar]
|
||||||
|
|
||||||
|
- Added the ability to impersonate a user when using the API Key. This allows people to use the API and request as a certain user. #2363. [Jamie Rees]
|
||||||
|
|
||||||
|
- Added more background images and it will loop through the available ones. [Jamie Rees]
|
||||||
|
|
||||||
|
- Added chunk hashing to resolve #2330. [Jamie Rees]
|
||||||
|
|
||||||
|
- Added API at /api/v1/status/info to get branch and version information #2331. [Jamie Rees]
|
||||||
|
|
||||||
|
- Update to .net 2.1.1. [Jamie]
|
||||||
|
|
||||||
|
### **Fixes**
|
||||||
|
|
||||||
|
- Fix #2322 caused by continue statement inside try catch block. [Anojh]
|
||||||
|
|
||||||
|
- Fixed #2367. [TidusJar]
|
||||||
|
|
||||||
|
- Fixed the issue where you could not delete a user #2365. [TidusJar]
|
||||||
|
|
||||||
|
- Another attempt to fix #2366. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed the Plex OAuth warning. [Jamie]
|
||||||
|
|
||||||
|
- Revert "Fixed Plex OAuth, should no longer show Insecure warning" [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed Plex OAuth, should no longer show Insecure warning. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed the View On Emby URL since the Link changed #2368. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed the issue where episodes were not being marked as available in the search #2367. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed #2371. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed collection issues in Emby #2366. [Jamie Rees]
|
||||||
|
|
||||||
|
- Do not delete the Emby Information every time we run, let's keep the content now. [Jamie Rees]
|
||||||
|
|
||||||
|
- Emby Improvements: Batch up the amount we get from the server. [Jamie Rees]
|
||||||
|
|
||||||
|
- Log errors when they are uncaught. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fix unclosed table tags causing overflow #2322. [Anojh]
|
||||||
|
|
||||||
|
- This should now fix #2350. [Jamie]
|
||||||
|
|
||||||
|
- Improve the validation around the Application URL. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed #2341. [Jamie Rees]
|
||||||
|
|
||||||
|
- Stop spamming errors when FanArt doesn't have the image. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed #2338. [Jamie Rees]
|
||||||
|
|
||||||
|
- Removed some logging statements. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed the api key being case sensative #2350. [Jamie Rees]
|
||||||
|
|
||||||
|
- Improved the Emby API #2230 Thanks Luke! [Jamie Rees]
|
||||||
|
|
||||||
|
- Revert. [Jamie Rees]
|
||||||
|
|
||||||
|
- Fixed a small error in the Mobile Notification Provider. [Jamie Rees]
|
||||||
|
|
||||||
|
- Minor style tweaks. [Randall Bruder]
|
||||||
|
|
||||||
|
- Downgrade to .net core 2.0. [Jamie Rees]
|
||||||
|
|
||||||
|
- Downgrade Microsoft.AspNetCore.All package back to 2.0.8. [Jamie Rees]
|
||||||
|
|
||||||
|
- Removed old code. [Jamie Rees]
|
||||||
|
|
||||||
|
- Swap out the old way of validating the API key with a real middlewear this time. [Jamie Rees]
|
||||||
|
|
||||||
|
|
||||||
|
## v3.0.3421 (2018-06-23)
|
||||||
|
|
||||||
### **New Features**
|
### **New Features**
|
||||||
|
|
||||||
|
|
11
README.md
11
README.md
|
@ -32,7 +32,7 @@ ___
|
||||||
# Features
|
# Features
|
||||||
Here are some of the features Ombi V3 has:
|
Here are some of the features Ombi V3 has:
|
||||||
* Now working without crashes on Linux.
|
* Now working without crashes on Linux.
|
||||||
* Lets users request Movies and TV Shows (whether it being the entire series, an entire season, or even single episodes.)
|
* Lets users request Movies, Music, and TV Shows (whether it being the entire series, an entire season, or even single episodes.)
|
||||||
* Easily manage your requests
|
* Easily manage your requests
|
||||||
* User management system (supports plex.tv, Emby and local accounts)
|
* User management system (supports plex.tv, Emby and local accounts)
|
||||||
* A landing page that will give you the availability of your Plex/Emby server and also add custom notification text to inform your users of downtime.
|
* A landing page that will give you the availability of your Plex/Emby server and also add custom notification text to inform your users of downtime.
|
||||||
|
@ -50,6 +50,7 @@ We integrate with the following applications:
|
||||||
* Emby
|
* Emby
|
||||||
* Sonarr
|
* Sonarr
|
||||||
* Radarr
|
* Radarr
|
||||||
|
* Lidarr
|
||||||
* DogNzb
|
* DogNzb
|
||||||
* Couch Potato
|
* Couch Potato
|
||||||
|
|
||||||
|
@ -87,6 +88,7 @@ We are planning to bring back these features in V3 but for now you can find a li
|
||||||
| DogNzb | Yes | No |
|
| DogNzb | Yes | No |
|
||||||
| Issues | Yes | Yes |
|
| Issues | Yes | Yes |
|
||||||
| Headphones | No | Yes |
|
| Headphones | No | Yes |
|
||||||
|
| Lidarr | Yes | No |
|
||||||
|
|
||||||
# Feature Requests
|
# Feature Requests
|
||||||
Feature requests are handled on FeatHub.
|
Feature requests are handled on FeatHub.
|
||||||
|
@ -115,13 +117,12 @@ Please feel free to submit a pull request!
|
||||||
# Donation
|
# Donation
|
||||||
If you feel like donating you can donate with the below buttons!
|
If you feel like donating you can donate with the below buttons!
|
||||||
|
|
||||||
[](https://patreon.com/tidusjar/Ombi)
|
|
||||||
[](https://paypal.me/PlexRequestsNet)
|
[](https://patreon.com/tidusjar/Ombi)
|
||||||
|
[](https://paypal.me/PlexRequestsNet)
|
||||||
|
|
||||||
### A massive thanks to everyone for all their help!
|
### A massive thanks to everyone for all their help!
|
||||||
|
|
||||||
## Stats
|
|
||||||
[](https://waffle.io/tidusjar/PlexRequests.Net/metrics/throughput)
|
|
||||||
|
|
||||||
### Sponsors ###
|
### Sponsors ###
|
||||||
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
|
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
|
||||||
|
|
24
appveyor.yml
24
appveyor.yml
|
@ -3,34 +3,46 @@ configuration: Release
|
||||||
os: Visual Studio 2017
|
os: Visual Studio 2017
|
||||||
environment:
|
environment:
|
||||||
nodejs_version: "9.8.0"
|
nodejs_version: "9.8.0"
|
||||||
|
typescript_version: "3.0.1"
|
||||||
|
github_auth_token:
|
||||||
|
secure: H/7uCrjmWHGJxgN3l9fbhhdVjvvWI8VVF4ZzQqeXuJwAf+PgSNBdxv4SS+rMQ+RH
|
||||||
|
sonarrcloudtoken:
|
||||||
|
secure: WGkIog4wuMSx1q5vmSOgIBXMtI/leMpLbZhi9MJnJdBBuDfcv12zwXg3LQwY0WbE
|
||||||
|
|
||||||
install:
|
install:
|
||||||
# Get the latest stable version of Node.js or io.js
|
# Get the latest stable version of Node.js or io.js
|
||||||
- ps: Install-Product node $env:nodejs_version
|
- ps: Install-Product node $env:nodejs_version
|
||||||
|
|
||||||
|
- cmd: set path=%programfiles(x86)%\\Microsoft SDKs\TypeScript\3.0;%path%
|
||||||
|
- cmd: tsc -v
|
||||||
build_script:
|
build_script:
|
||||||
|
# - dotnet tool install --global dotnet-sonarscanner
|
||||||
|
#- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER) { dotnet sonarscanner begin /k:"tidusjar_Ombi" /d:sonar.organization="tidusjar-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$env.sonarrcloud_token" /d:sonar.analysis.mode="preview" /d:sonar.github.pullRequest="$env:APPVEYOR_PULL_REQUEST_NUMBER" /d:sonar.github.repository="https://github.com/tidusjar/ombi" /d:sonar.github.oauth="$env.github_auth_token" }
|
||||||
|
# - ps: if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER) { dotnet sonarscanner begin /k:"tidusjar_Ombi" /d:sonar.organization="tidusjar-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login="$env.SONARRCLOUDTOKEN" }
|
||||||
- ps: ./build.ps1 --settings_skipverification=true
|
- ps: ./build.ps1 --settings_skipverification=true
|
||||||
|
# - dotnet sonarscanner end /d:sonar.login="%sonarrcloudtoken%"
|
||||||
|
|
||||||
test: off
|
test: off
|
||||||
|
|
||||||
after_build:
|
after_build:
|
||||||
- cmd: >-
|
- cmd: >-
|
||||||
|
|
||||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows.zip"
|
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows.zip"
|
||||||
|
|
||||||
|
|
||||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\osx.tar.gz"
|
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\osx.tar.gz"
|
||||||
|
|
||||||
|
|
||||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux.tar.gz"
|
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux.tar.gz"
|
||||||
|
|
||||||
|
|
||||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux-arm.tar.gz"
|
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm.tar.gz"
|
||||||
|
|
||||||
|
|
||||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows-32bit.zip"
|
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows-32bit.zip"
|
||||||
|
|
||||||
|
|
||||||
# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz"
|
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm64.tar.gz"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
assets/music-placeholder.psd
Normal file
BIN
assets/music-placeholder.psd
Normal file
Binary file not shown.
48
build.cake
48
build.cake
|
@ -1,10 +1,10 @@
|
||||||
|
|
||||||
#tool "nuget:?package=GitVersion.CommandLine"
|
#tool "nuget:?package=GitVersion.CommandLine"
|
||||||
#addin "Cake.Gulp"
|
#addin "Cake.Gulp"
|
||||||
#addin "nuget:?package=Cake.Npm&version=0.13.0"
|
|
||||||
#addin "SharpZipLib"
|
#addin "SharpZipLib"
|
||||||
#addin nuget:?package=Cake.Compression&version=0.1.4
|
#addin nuget:?package=Cake.Compression&version=0.1.4
|
||||||
#addin "Cake.Incubator"
|
#addin "Cake.Incubator"
|
||||||
|
#addin "Cake.Yarn"
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// ARGUMENTS
|
// ARGUMENTS
|
||||||
|
@ -26,7 +26,7 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj
|
||||||
var solutionFile = "Ombi.sln"; // Solution file if needed
|
var solutionFile = "Ombi.sln"; // Solution file if needed
|
||||||
GitVersion versionInfo = null;
|
GitVersion versionInfo = null;
|
||||||
|
|
||||||
var frameworkVer = "netcoreapp2.1";
|
var frameworkVer = "netcoreapp2.2";
|
||||||
|
|
||||||
var buildSettings = new DotNetCoreBuildSettings
|
var buildSettings = new DotNetCoreBuildSettings
|
||||||
{
|
{
|
||||||
|
@ -81,9 +81,9 @@ Task("SetVersionInfo")
|
||||||
|
|
||||||
versionInfo = GitVersion(settings);
|
versionInfo = GitVersion(settings);
|
||||||
|
|
||||||
Information("GitResults -> {0}", versionInfo.Dump());
|
// Information("GitResults -> {0}", versionInfo.Dump());
|
||||||
|
|
||||||
Information(@"Build:{0}",AppVeyor.Environment.Build.Dump());
|
//Information(@"Build:{0}",AppVeyor.Environment.Build.Dump());
|
||||||
|
|
||||||
var buildVersion = string.Empty;
|
var buildVersion = string.Empty;
|
||||||
if(string.IsNullOrEmpty(AppVeyor.Environment.Build.Version))
|
if(string.IsNullOrEmpty(AppVeyor.Environment.Build.Version))
|
||||||
|
@ -122,36 +122,19 @@ Task("SetVersionInfo")
|
||||||
|
|
||||||
Task("NPM")
|
Task("NPM")
|
||||||
.Does(() => {
|
.Does(() => {
|
||||||
var settings = new NpmInstallSettings {
|
Yarn.FromPath(webProjDir).Install();
|
||||||
LogLevel = NpmLogLevel.Silent,
|
|
||||||
WorkingDirectory = webProjDir,
|
|
||||||
Production = true
|
|
||||||
};
|
|
||||||
|
|
||||||
NpmInstall(settings);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Gulp Publish")
|
Task("Gulp Publish")
|
||||||
.IsDependentOn("NPM")
|
.IsDependentOn("NPM")
|
||||||
.Does(() => {
|
.Does(() => {
|
||||||
|
Yarn.FromPath(webProjDir).RunScript("publish");
|
||||||
var runScriptSettings = new NpmRunScriptSettings {
|
|
||||||
ScriptName="publish",
|
|
||||||
WorkingDirectory = webProjDir,
|
|
||||||
};
|
|
||||||
|
|
||||||
NpmRunScript(runScriptSettings);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("TSLint")
|
Task("TSLint")
|
||||||
.Does(() =>
|
.Does(() =>
|
||||||
{
|
{
|
||||||
var settings = new NpmRunScriptSettings {
|
Yarn.FromPath(webProjDir).RunScript("lint");
|
||||||
WorkingDirectory = webProjDir,
|
|
||||||
ScriptName = "lint"
|
|
||||||
};
|
|
||||||
|
|
||||||
NpmRunScript(settings);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("PrePublish")
|
Task("PrePublish")
|
||||||
|
@ -168,7 +151,7 @@ Task("Package")
|
||||||
GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz");
|
GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz");
|
||||||
GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz");
|
GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz");
|
||||||
GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz");
|
GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz");
|
||||||
//GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz");
|
GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz");
|
||||||
});
|
});
|
||||||
|
|
||||||
Task("Publish")
|
Task("Publish")
|
||||||
|
@ -178,7 +161,7 @@ Task("Publish")
|
||||||
.IsDependentOn("Publish-OSX")
|
.IsDependentOn("Publish-OSX")
|
||||||
.IsDependentOn("Publish-Linux")
|
.IsDependentOn("Publish-Linux")
|
||||||
.IsDependentOn("Publish-Linux-ARM")
|
.IsDependentOn("Publish-Linux-ARM")
|
||||||
//.IsDependentOn("Publish-Linux-ARM-64Bit")
|
.IsDependentOn("Publish-Linux-ARM-64Bit")
|
||||||
.IsDependentOn("Package");
|
.IsDependentOn("Package");
|
||||||
|
|
||||||
Task("Publish-Windows")
|
Task("Publish-Windows")
|
||||||
|
@ -189,6 +172,8 @@ Task("Publish-Windows")
|
||||||
|
|
||||||
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
|
||||||
CopyFile(buildDir + "/"+frameworkVer+"/win10-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x64/published/Swagger.xml");
|
CopyFile(buildDir + "/"+frameworkVer+"/win10-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x64/published/Swagger.xml");
|
||||||
|
|
||||||
|
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x64/published/updater");
|
||||||
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -200,6 +185,9 @@ Task("Publish-Windows-32bit")
|
||||||
|
|
||||||
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
|
||||||
CopyFile(buildDir + "/"+frameworkVer+"/win10-x86/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x86/published/Swagger.xml");
|
CopyFile(buildDir + "/"+frameworkVer+"/win10-x86/Swagger.xml", buildDir + "/"+frameworkVer+"/win10-x86/published/Swagger.xml");
|
||||||
|
|
||||||
|
|
||||||
|
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/win10-x86/published/updater");
|
||||||
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -211,6 +199,8 @@ Task("Publish-OSX")
|
||||||
|
|
||||||
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
|
||||||
CopyFile(buildDir + "/"+frameworkVer+"/osx-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/osx-x64/published/Swagger.xml");
|
CopyFile(buildDir + "/"+frameworkVer+"/osx-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/osx-x64/published/Swagger.xml");
|
||||||
|
|
||||||
|
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/osx-x64/published/updater");
|
||||||
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -222,6 +212,8 @@ Task("Publish-Linux")
|
||||||
|
|
||||||
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
|
||||||
CopyFile(buildDir + "/"+frameworkVer+"/linux-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-x64/published/Swagger.xml");
|
CopyFile(buildDir + "/"+frameworkVer+"/linux-x64/Swagger.xml", buildDir + "/"+frameworkVer+"/linux-x64/published/Swagger.xml");
|
||||||
|
|
||||||
|
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-x64/published/updater");
|
||||||
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -235,6 +227,8 @@ Task("Publish-Linux-ARM")
|
||||||
CopyFile(
|
CopyFile(
|
||||||
buildDir + "/"+frameworkVer+"/linux-arm/Swagger.xml",
|
buildDir + "/"+frameworkVer+"/linux-arm/Swagger.xml",
|
||||||
buildDir + "/"+frameworkVer+"/linux-arm/published/Swagger.xml");
|
buildDir + "/"+frameworkVer+"/linux-arm/published/Swagger.xml");
|
||||||
|
|
||||||
|
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-arm/published/updater");
|
||||||
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -248,6 +242,8 @@ Task("Publish-Linux-ARM-64Bit")
|
||||||
CopyFile(
|
CopyFile(
|
||||||
buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml",
|
buildDir + "/"+frameworkVer+"/linux-arm64/Swagger.xml",
|
||||||
buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml");
|
buildDir + "/"+frameworkVer+"/linux-arm64/published/Swagger.xml");
|
||||||
|
|
||||||
|
publishSettings.OutputDirectory = Directory(buildDir) + Directory(frameworkVer +"/linux-arm64/published/updater");
|
||||||
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore.Internal;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Ombi.Api.Emby.Models;
|
using Ombi.Api.Emby.Models;
|
||||||
using Ombi.Api.Emby.Models.Media.Tv;
|
using Ombi.Api.Emby.Models.Media.Tv;
|
||||||
|
@ -52,8 +53,6 @@ namespace Ombi.Api.Emby
|
||||||
{
|
{
|
||||||
username,
|
username,
|
||||||
pw = password,
|
pw = password,
|
||||||
password = password.GetSha1Hash().ToLower(),
|
|
||||||
passwordMd5 = password.CalcuateMd5Hash()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
request.AddJsonBody(body);
|
request.AddJsonBody(body);
|
||||||
|
@ -90,27 +89,31 @@ namespace Ombi.Api.Emby
|
||||||
request.AddContentHeader("Content-Type", "application/json");
|
request.AddContentHeader("Content-Type", "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
|
public async Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request($"emby/users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get);
|
var request = new Request($"emby/users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get);
|
||||||
AddHeaders(request, apiKey);
|
AddHeaders(request, apiKey);
|
||||||
|
|
||||||
return await Api.Request<EmbyItemContainer<MovieInformation>>(request);
|
request.AddQueryString("Fields", "ProviderIds,Overview");
|
||||||
|
|
||||||
|
request.AddQueryString("IsVirtualItem", "False");
|
||||||
|
|
||||||
|
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri)
|
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||||
{
|
{
|
||||||
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri);
|
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri)
|
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||||
{
|
{
|
||||||
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri);
|
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri)
|
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
|
||||||
{
|
{
|
||||||
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri);
|
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
|
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
|
||||||
|
@ -129,20 +132,40 @@ namespace Ombi.Api.Emby
|
||||||
private async Task<T> GetInformation<T>(string mediaId, string apiKey, string userId, string baseUrl)
|
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);
|
var request = new Request($"emby/users/{userId}/items/{mediaId}", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
AddHeaders(request, apiKey);
|
AddHeaders(request, apiKey);
|
||||||
var response = await Api.RequestContent(request);
|
var response = await Api.RequestContent(request);
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<T>(response);
|
return JsonConvert.DeserializeObject<T>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview = false)
|
||||||
|
|
||||||
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri)
|
|
||||||
{
|
{
|
||||||
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
|
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
|
||||||
|
|
||||||
request.AddQueryString("Recursive", true.ToString());
|
request.AddQueryString("Recursive", true.ToString());
|
||||||
request.AddQueryString("IncludeItemTypes", type);
|
request.AddQueryString("IncludeItemTypes", type);
|
||||||
|
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
|
||||||
|
|
||||||
|
request.AddQueryString("IsVirtualItem", "False");
|
||||||
|
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
|
||||||
|
|
||||||
|
var obj = await Api.Request<EmbyItemContainer<T>>(request);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
|
||||||
|
{
|
||||||
|
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("IsVirtualItem", "False");
|
||||||
|
|
||||||
AddHeaders(request, apiKey);
|
AddHeaders(request, apiKey);
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,17 @@ namespace Ombi.Api.Emby
|
||||||
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
|
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
|
||||||
Task<EmbyConnectUser> LoginConnectUser(string username, string password);
|
Task<EmbyConnectUser> LoginConnectUser(string username, string password);
|
||||||
|
|
||||||
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri);
|
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId,
|
||||||
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri);
|
string baseUri);
|
||||||
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri);
|
|
||||||
|
|
||||||
Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId,
|
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
|
||||||
string baseUrl);
|
string baseUri);
|
||||||
|
|
||||||
|
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId,
|
||||||
|
string baseUri);
|
||||||
|
|
||||||
|
Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,
|
||||||
|
string apiKey, string userId, string baseUrl);
|
||||||
|
|
||||||
Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
||||||
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);
|
||||||
|
|
|
@ -28,5 +28,7 @@ namespace Ombi.Api.Emby.Models.Movie
|
||||||
public string MediaType { get; set; }
|
public string MediaType { get; set; }
|
||||||
public bool HasSubtitles { get; set; }
|
public bool HasSubtitles { get; set; }
|
||||||
public int CriticRating { get; set; }
|
public int CriticRating { get; set; }
|
||||||
|
public string Overview { get; set; }
|
||||||
|
public EmbyProviderids ProviderIds { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -39,5 +39,6 @@ namespace Ombi.Api.Emby.Models.Media.Tv
|
||||||
public string LocationType { get; set; }
|
public string LocationType { get; set; }
|
||||||
public string MediaType { get; set; }
|
public string MediaType { get; set; }
|
||||||
public bool HasSubtitles { get; set; }
|
public bool HasSubtitles { get; set; }
|
||||||
|
public EmbyProviderids ProviderIds { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,5 +26,7 @@ namespace Ombi.Api.Emby.Models.Media.Tv
|
||||||
public string[] BackdropImageTags { get; set; }
|
public string[] BackdropImageTags { get; set; }
|
||||||
public string LocationType { get; set; }
|
public string LocationType { get; set; }
|
||||||
public DateTime EndDate { get; set; }
|
public DateTime EndDate { get; set; }
|
||||||
|
|
||||||
|
public EmbyProviderids ProviderIds { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,6 +21,7 @@ namespace Ombi.Api.FanartTv
|
||||||
{
|
{
|
||||||
var request = new Request($"tv/{tvdbId}", Endpoint, HttpMethod.Get);
|
var request = new Request($"tv/{tvdbId}", Endpoint, HttpMethod.Get);
|
||||||
request.AddHeader("api-key", token);
|
request.AddHeader("api-key", token);
|
||||||
|
request.IgnoreErrors = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await Api.Request<TvResult>(request);
|
return await Api.Request<TvResult>(request);
|
||||||
|
@ -36,6 +37,7 @@ namespace Ombi.Api.FanartTv
|
||||||
{
|
{
|
||||||
var request = new Request($"movies/{movieOrImdbId}", Endpoint, HttpMethod.Get);
|
var request = new Request($"movies/{movieOrImdbId}", Endpoint, HttpMethod.Get);
|
||||||
request.AddHeader("api-key", token);
|
request.AddHeader("api-key", token);
|
||||||
|
request.IgnoreErrors = true;
|
||||||
|
|
||||||
return await Api.Request<MovieResult>(request);
|
return await Api.Request<MovieResult>(request);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,5 @@ namespace Ombi.Api.Github
|
||||||
request.AddHeader("User-Agent", "Ombi");
|
request.AddHeader("User-Agent", "Ombi");
|
||||||
return await _api.Request<List<CakeThemes>>(request);
|
return await _api.Request<List<CakeThemes>>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetThemesRawContent(string url)
|
|
||||||
{
|
|
||||||
var sections = url.Split('/');
|
|
||||||
var lastPart = sections.Last();
|
|
||||||
url = url.Replace(lastPart, string.Empty);
|
|
||||||
var request = new Request(lastPart, url, HttpMethod.Get);
|
|
||||||
request.AddHeader("Accept", "application/vnd.github.v3+json");
|
|
||||||
request.AddHeader("User-Agent", "Ombi");
|
|
||||||
return await _api.RequestContent(request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,5 @@ namespace Ombi.Api.Github
|
||||||
public interface IGithubApi
|
public interface IGithubApi
|
||||||
{
|
{
|
||||||
Task<List<CakeThemes>> GetCakeThemes();
|
Task<List<CakeThemes>> GetCakeThemes();
|
||||||
Task<string> GetThemesRawContent(string url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
27
src/Ombi.Api.Lidarr/ILidarrApi.cs
Normal file
27
src/Ombi.Api.Lidarr/ILidarrApi.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Api.Lidarr.Models;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr
|
||||||
|
{
|
||||||
|
public interface ILidarrApi
|
||||||
|
{
|
||||||
|
Task<List<AlbumLookup>> AlbumLookup(string searchTerm, string apiKey, string baseUrl);
|
||||||
|
Task<List<ArtistLookup>> ArtistLookup(string searchTerm, string apiKey, string baseUrl);
|
||||||
|
Task<List<LidarrProfile>> GetProfiles(string apiKey, string baseUrl);
|
||||||
|
Task<List<LidarrRootFolder>> GetRootFolders(string apiKey, string baseUrl);
|
||||||
|
Task<ArtistResult> GetArtist(int artistId, string apiKey, string baseUrl);
|
||||||
|
Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl);
|
||||||
|
Task<AlbumByArtistResponse> GetAlbumsByArtist(string foreignArtistId);
|
||||||
|
Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl);
|
||||||
|
Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl);
|
||||||
|
Task<List<AlbumResponse>> GetAllAlbums(string apiKey, string baseUrl);
|
||||||
|
Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl);
|
||||||
|
Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl);
|
||||||
|
Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl);
|
||||||
|
Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl);
|
||||||
|
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
|
||||||
|
Task<LidarrStatus> Status(string apiKey, string baseUrl);
|
||||||
|
Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl);
|
||||||
|
}
|
||||||
|
}
|
170
src/Ombi.Api.Lidarr/LidarrApi.cs
Normal file
170
src/Ombi.Api.Lidarr/LidarrApi.cs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ombi.Api.Lidarr.Models;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr
|
||||||
|
{
|
||||||
|
public class LidarrApi : ILidarrApi
|
||||||
|
{
|
||||||
|
public LidarrApi(ILogger<LidarrApi> logger, IApi api)
|
||||||
|
{
|
||||||
|
Api = api;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IApi Api { get; }
|
||||||
|
private ILogger Logger { get; }
|
||||||
|
|
||||||
|
private const string ApiVersion = "/api/v1";
|
||||||
|
|
||||||
|
public Task<List<LidarrProfile>> GetProfiles(string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/qualityprofile", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<List<LidarrProfile>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<LidarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/rootfolder", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<List<LidarrRootFolder>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<ArtistLookup>> ArtistLookup(string searchTerm, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/Artist/lookup", baseUrl, HttpMethod.Get);
|
||||||
|
request.AddQueryString("term", searchTerm);
|
||||||
|
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return await Api.Request<List<ArtistLookup>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<AlbumLookup>> AlbumLookup(string searchTerm, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/Album/lookup", baseUrl, HttpMethod.Get);
|
||||||
|
request.AddQueryString("term", searchTerm);
|
||||||
|
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<List<AlbumLookup>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ArtistResult> GetArtist(int artistId, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/artist/{artistId}", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<ArtistResult>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
request.AddQueryString("term", $"lidarr:{foreignArtistId}");
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return (await Api.Request<List<ArtistResult>>(request)).FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/album/lookup", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
request.AddQueryString("term", $"lidarr:{foreignArtistId}");
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
var albums = await Api.Request<List<AlbumLookup>>(request);
|
||||||
|
return albums.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<AlbumByArtistResponse> GetAlbumsByArtist(string foreignArtistId)
|
||||||
|
{
|
||||||
|
var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.3/artist/{foreignArtistId}",
|
||||||
|
HttpMethod.Get) {IgnoreBaseUrlAppend = true};
|
||||||
|
return Api.Request<AlbumByArtistResponse>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<List<ArtistResult>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<AlbumResponse>> GetAllAlbums(string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
|
||||||
|
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<List<AlbumResponse>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post);
|
||||||
|
request.AddJsonBody(artist);
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<ArtistResult>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/album/monitor", baseUrl, HttpMethod.Put);
|
||||||
|
request.AddJsonBody(new
|
||||||
|
{
|
||||||
|
albumIds = new[] { albumId },
|
||||||
|
monitored = true
|
||||||
|
});
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return (await Api.Request<List<AlbumResponse>>(request)).FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
|
||||||
|
request.AddQueryString("artistId", artistId.ToString());
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<List<AlbumResponse>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/languageprofile", baseUrl, HttpMethod.Get);
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<List<LanguageProfiles>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get);
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<List<MetadataProfile>>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<LidarrStatus> Status(string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/system/status", baseUrl, HttpMethod.Get);
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<LidarrStatus>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiVersion}/command/", baseUrl, HttpMethod.Post);
|
||||||
|
request.AddJsonBody(new { name = "AlbumSearch", albumIds });
|
||||||
|
AddHeaders(request, apiKey);
|
||||||
|
return Api.Request<CommandResult>(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddHeaders(Request request, string key)
|
||||||
|
{
|
||||||
|
request.AddHeader("X-Api-Key", key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs
Normal file
34
src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class AlbumByArtistResponse
|
||||||
|
{
|
||||||
|
public Album[] Albums { get; set; }
|
||||||
|
public string ArtistName { get; set; }
|
||||||
|
public string Disambiguation { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
public Image[] Images { get; set; }
|
||||||
|
public Link[] Links { get; set; }
|
||||||
|
public string Overview { get; set; }
|
||||||
|
public Rating Rating { get; set; }
|
||||||
|
public string SortName { get; set; }
|
||||||
|
public string Status { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Rating
|
||||||
|
{
|
||||||
|
public int Count { get; set; }
|
||||||
|
public decimal Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Album
|
||||||
|
{
|
||||||
|
public string Disambiguation { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string ReleaseDate { get; set; }
|
||||||
|
public string[] ReleaseStatuses { get; set; }
|
||||||
|
public string[] SecondaryTypes { get; set; }
|
||||||
|
public string Title { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
25
src/Ombi.Api.Lidarr/Models/AlbumLookup.cs
Normal file
25
src/Ombi.Api.Lidarr/Models/AlbumLookup.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class AlbumLookup
|
||||||
|
{
|
||||||
|
public string title { get; set; }
|
||||||
|
public int artistId { get; set; }
|
||||||
|
public string foreignAlbumId { get; set; }
|
||||||
|
public bool monitored { get; set; }
|
||||||
|
public int profileId { get; set; }
|
||||||
|
public int duration { get; set; }
|
||||||
|
public string albumType { get; set; }
|
||||||
|
public string[] secondaryTypes { get; set; }
|
||||||
|
public int mediumCount { get; set; }
|
||||||
|
public Ratings ratings { get; set; }
|
||||||
|
public DateTime releaseDate { get; set; }
|
||||||
|
//public object[] releases { get; set; }
|
||||||
|
public object[] genres { get; set; }
|
||||||
|
//public object[] media { get; set; }
|
||||||
|
public Artist artist { get; set; }
|
||||||
|
public Image[] images { get; set; }
|
||||||
|
public string remoteCover { get; set; }
|
||||||
|
}
|
||||||
|
}
|
27
src/Ombi.Api.Lidarr/Models/AlbumResponse.cs
Normal file
27
src/Ombi.Api.Lidarr/Models/AlbumResponse.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class AlbumResponse
|
||||||
|
{
|
||||||
|
public string title { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public int artistId { get; set; }
|
||||||
|
public string foreignAlbumId { get; set; }
|
||||||
|
public bool monitored { get; set; }
|
||||||
|
public int profileId { get; set; }
|
||||||
|
public int duration { get; set; }
|
||||||
|
public string albumType { get; set; }
|
||||||
|
public object[] secondaryTypes { get; set; }
|
||||||
|
public int mediumCount { get; set; }
|
||||||
|
public Ratings ratings { get; set; }
|
||||||
|
public DateTime releaseDate { get; set; }
|
||||||
|
public Currentrelease currentRelease { get; set; }
|
||||||
|
public Release[] releases { get; set; }
|
||||||
|
public object[] genres { get; set; }
|
||||||
|
public Medium[] media { get; set; }
|
||||||
|
public Image[] images { get; set; }
|
||||||
|
public Statistics statistics { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
25
src/Ombi.Api.Lidarr/Models/Artist.cs
Normal file
25
src/Ombi.Api.Lidarr/Models/Artist.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class Artist
|
||||||
|
{
|
||||||
|
public string status { get; set; }
|
||||||
|
public bool ended { get; set; }
|
||||||
|
public string artistName { get; set; }
|
||||||
|
public string foreignArtistId { get; set; }
|
||||||
|
public int tadbId { get; set; }
|
||||||
|
public int discogsId { get; set; }
|
||||||
|
public object[] links { get; set; }
|
||||||
|
public object[] images { get; set; }
|
||||||
|
public int qualityProfileId { get; set; }
|
||||||
|
public int languageProfileId { get; set; }
|
||||||
|
public int metadataProfileId { get; set; }
|
||||||
|
public bool albumFolder { get; set; }
|
||||||
|
public bool monitored { get; set; }
|
||||||
|
public object[] genres { get; set; }
|
||||||
|
public object[] tags { get; set; }
|
||||||
|
public DateTime added { get; set; }
|
||||||
|
public Statistics statistics { get; set; }
|
||||||
|
}
|
||||||
|
}
|
49
src/Ombi.Api.Lidarr/Models/ArtistAdd.cs
Normal file
49
src/Ombi.Api.Lidarr/Models/ArtistAdd.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using System;
|
||||||
|
using System.Net.Mime;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class ArtistAdd
|
||||||
|
{
|
||||||
|
public string status { get; set; }
|
||||||
|
public bool ended { get; set; }
|
||||||
|
public string artistName { get; set; }
|
||||||
|
public string foreignArtistId { get; set; }
|
||||||
|
public int tadbId { get; set; }
|
||||||
|
public int discogsId { get; set; }
|
||||||
|
public string overview { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public Link[] links { get; set; }
|
||||||
|
public Image[] images { get; set; }
|
||||||
|
public string remotePoster { get; set; }
|
||||||
|
public int qualityProfileId { get; set; }
|
||||||
|
public int languageProfileId { get; set; }
|
||||||
|
public int metadataProfileId { get; set; }
|
||||||
|
public bool albumFolder { get; set; }
|
||||||
|
public bool monitored { get; set; }
|
||||||
|
public string cleanName { get; set; }
|
||||||
|
public string sortName { get; set; }
|
||||||
|
public object[] tags { get; set; }
|
||||||
|
public DateTime added { get; set; }
|
||||||
|
public Ratings ratings { get; set; }
|
||||||
|
public Statistics statistics { get; set; }
|
||||||
|
public Addoptions addOptions { get; set; }
|
||||||
|
public string rootFolderPath { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Addoptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Future = 1
|
||||||
|
/// Missing = 2
|
||||||
|
/// Existing = 3
|
||||||
|
/// First = 5
|
||||||
|
/// Latest = 4
|
||||||
|
/// None = 6
|
||||||
|
/// </summary>
|
||||||
|
public int selectedOption { get; set; }
|
||||||
|
public bool monitored { get; set; }
|
||||||
|
public bool searchForMissingAlbums { get; set; }
|
||||||
|
public string[] AlbumsToMonitor { get; set; } // Uses the MusicBrainzAlbumId!
|
||||||
|
}
|
||||||
|
}
|
32
src/Ombi.Api.Lidarr/Models/ArtistLookup.cs
Normal file
32
src/Ombi.Api.Lidarr/Models/ArtistLookup.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using System;
|
||||||
|
using System.Net.Mime;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class ArtistLookup
|
||||||
|
{
|
||||||
|
public string status { get; set; }
|
||||||
|
public bool ended { get; set; }
|
||||||
|
public string artistName { get; set; }
|
||||||
|
public string foreignArtistId { get; set; }
|
||||||
|
public int tadbId { get; set; }
|
||||||
|
public int discogsId { get; set; }
|
||||||
|
public string overview { get; set; }
|
||||||
|
public string artistType { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public Link[] links { get; set; }
|
||||||
|
public Image[] images { get; set; }
|
||||||
|
public string remotePoster { get; set; }
|
||||||
|
public int qualityProfileId { get; set; }
|
||||||
|
public int languageProfileId { get; set; }
|
||||||
|
public int metadataProfileId { get; set; }
|
||||||
|
public bool albumFolder { get; set; }
|
||||||
|
public bool monitored { get; set; }
|
||||||
|
public string cleanName { get; set; }
|
||||||
|
public string sortName { get; set; }
|
||||||
|
public object[] tags { get; set; }
|
||||||
|
public DateTime added { get; set; }
|
||||||
|
public Ratings ratings { get; set; }
|
||||||
|
public Statistics statistics { get; set; }
|
||||||
|
}
|
||||||
|
}
|
93
src/Ombi.Api.Lidarr/Models/ArtistResult.cs
Normal file
93
src/Ombi.Api.Lidarr/Models/ArtistResult.cs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
public class ArtistResult
|
||||||
|
{
|
||||||
|
public string status { get; set; }
|
||||||
|
public bool ended { get; set; }
|
||||||
|
public DateTime lastInfoSync { get; set; }
|
||||||
|
public string artistName { get; set; }
|
||||||
|
public string foreignArtistId { get; set; }
|
||||||
|
public int tadbId { get; set; }
|
||||||
|
public int discogsId { get; set; }
|
||||||
|
public string overview { get; set; }
|
||||||
|
public string artistType { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public Link[] links { get; set; }
|
||||||
|
public Nextalbum nextAlbum { get; set; }
|
||||||
|
public Image[] images { get; set; }
|
||||||
|
public string path { get; set; }
|
||||||
|
public int qualityProfileId { get; set; }
|
||||||
|
public int languageProfileId { get; set; }
|
||||||
|
public int metadataProfileId { get; set; }
|
||||||
|
public bool albumFolder { get; set; }
|
||||||
|
public bool monitored { get; set; }
|
||||||
|
public object[] genres { get; set; }
|
||||||
|
public string cleanName { get; set; }
|
||||||
|
public string sortName { get; set; }
|
||||||
|
public object[] tags { get; set; }
|
||||||
|
public DateTime added { get; set; }
|
||||||
|
public Ratings ratings { get; set; }
|
||||||
|
public Statistics statistics { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Nextalbum
|
||||||
|
{
|
||||||
|
public string foreignAlbumId { get; set; }
|
||||||
|
public int artistId { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public string cleanTitle { get; set; }
|
||||||
|
public DateTime releaseDate { get; set; }
|
||||||
|
public int profileId { get; set; }
|
||||||
|
public int duration { get; set; }
|
||||||
|
public bool monitored { get; set; }
|
||||||
|
public object[] images { get; set; }
|
||||||
|
public object[] genres { get; set; }
|
||||||
|
public Medium[] media { get; set; }
|
||||||
|
public DateTime lastInfoSync { get; set; }
|
||||||
|
public DateTime added { get; set; }
|
||||||
|
public string albumType { get; set; }
|
||||||
|
public object[] secondaryTypes { get; set; }
|
||||||
|
public Ratings ratings { get; set; }
|
||||||
|
public Release[] releases { get; set; }
|
||||||
|
public Currentrelease currentRelease { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Currentrelease
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public DateTime releaseDate { get; set; }
|
||||||
|
public int trackCount { get; set; }
|
||||||
|
public int mediaCount { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public string[] country { get; set; }
|
||||||
|
public string format { get; set; }
|
||||||
|
public string[] label { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Medium
|
||||||
|
{
|
||||||
|
public int number { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
public string format { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Release
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public DateTime releaseDate { get; set; }
|
||||||
|
public int trackCount { get; set; }
|
||||||
|
public int mediaCount { get; set; }
|
||||||
|
public string disambiguation { get; set; }
|
||||||
|
public string[] country { get; set; }
|
||||||
|
public string format { get; set; }
|
||||||
|
public string[] label { get; set; }
|
||||||
|
}
|
||||||
|
}
|
15
src/Ombi.Api.Lidarr/Models/CommandResult.cs
Normal file
15
src/Ombi.Api.Lidarr/Models/CommandResult.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
public class CommandResult
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public DateTime queued { get; set; }
|
||||||
|
public DateTime stateChangeTime { get; set; }
|
||||||
|
public bool sendUpdatesToClient { get; set; }
|
||||||
|
public string status { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/Ombi.Api.Lidarr/Models/Image.cs
Normal file
8
src/Ombi.Api.Lidarr/Models/Image.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class Image
|
||||||
|
{
|
||||||
|
public string coverType { get; set; }
|
||||||
|
public string url { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs
Normal file
8
src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class LanguageProfiles
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
23
src/Ombi.Api.Lidarr/Models/LidarrProfile.cs
Normal file
23
src/Ombi.Api.Lidarr/Models/LidarrProfile.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class Quality
|
||||||
|
{
|
||||||
|
public int id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Item
|
||||||
|
{
|
||||||
|
public Quality quality { get; set; }
|
||||||
|
public bool allowed { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LidarrProfile
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public List<Item> items { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
11
src/Ombi.Api.Lidarr/Models/LidarrRootFolder.cs
Normal file
11
src/Ombi.Api.Lidarr/Models/LidarrRootFolder.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class LidarrRootFolder
|
||||||
|
{
|
||||||
|
public string path { get; set; }
|
||||||
|
public long freeSpace { get; set; }
|
||||||
|
public object[] unmappedFolders { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/Ombi.Api.Lidarr/Models/LidarrStatus.cs
Normal file
31
src/Ombi.Api.Lidarr/Models/LidarrStatus.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class LidarrStatus
|
||||||
|
{
|
||||||
|
public string version { get; set; }
|
||||||
|
public DateTime buildTime { get; set; }
|
||||||
|
public bool isDebug { get; set; }
|
||||||
|
public bool isProduction { get; set; }
|
||||||
|
public bool isAdmin { get; set; }
|
||||||
|
public bool isUserInteractive { get; set; }
|
||||||
|
public string startupPath { get; set; }
|
||||||
|
public string appData { get; set; }
|
||||||
|
public string osName { get; set; }
|
||||||
|
public string osVersion { get; set; }
|
||||||
|
public bool isMonoRuntime { get; set; }
|
||||||
|
public bool isMono { get; set; }
|
||||||
|
public bool isLinux { get; set; }
|
||||||
|
public bool isOsx { get; set; }
|
||||||
|
public bool isWindows { get; set; }
|
||||||
|
public string mode { get; set; }
|
||||||
|
public string branch { get; set; }
|
||||||
|
public string authentication { get; set; }
|
||||||
|
public string sqliteVersion { get; set; }
|
||||||
|
public int migrationVersion { get; set; }
|
||||||
|
public string urlBase { get; set; }
|
||||||
|
public string runtimeVersion { get; set; }
|
||||||
|
public string runtimeName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/Ombi.Api.Lidarr/Models/Link.cs
Normal file
8
src/Ombi.Api.Lidarr/Models/Link.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class Link
|
||||||
|
{
|
||||||
|
public string url { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/Ombi.Api.Lidarr/Models/MetadataProfile.cs
Normal file
8
src/Ombi.Api.Lidarr/Models/MetadataProfile.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class MetadataProfile
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/Ombi.Api.Lidarr/Models/Ratings.cs
Normal file
8
src/Ombi.Api.Lidarr/Models/Ratings.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class Ratings
|
||||||
|
{
|
||||||
|
public int votes { get; set; }
|
||||||
|
public decimal value { get; set; }
|
||||||
|
}
|
||||||
|
}
|
12
src/Ombi.Api.Lidarr/Models/Statistics.cs
Normal file
12
src/Ombi.Api.Lidarr/Models/Statistics.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Ombi.Api.Lidarr.Models
|
||||||
|
{
|
||||||
|
public class Statistics
|
||||||
|
{
|
||||||
|
public int albumCount { get; set; }
|
||||||
|
public int trackFileCount { get; set; }
|
||||||
|
public int trackCount { get; set; }
|
||||||
|
public int totalTrackCount { get; set; }
|
||||||
|
public long sizeOnDisk { get; set; }
|
||||||
|
public decimal percentOfEpisodes { get; set; }
|
||||||
|
}
|
||||||
|
}
|
11
src/Ombi.Api.Lidarr/Ombi.Api.Lidarr.csproj
Normal file
11
src/Ombi.Api.Lidarr/Ombi.Api.Lidarr.csproj
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -9,7 +9,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -6,6 +6,6 @@ namespace Ombi.Api.Notifications
|
||||||
{
|
{
|
||||||
public interface IOneSignalApi
|
public interface IOneSignalApi
|
||||||
{
|
{
|
||||||
Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message);
|
Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message, bool isAdminNotification, int requestId, int requestType);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,18 +4,22 @@
|
||||||
{
|
{
|
||||||
public string app_id { get; set; }
|
public string app_id { get; set; }
|
||||||
public string[] include_player_ids { get; set; }
|
public string[] include_player_ids { get; set; }
|
||||||
public Data data { get; set; }
|
public object data { get; set; }
|
||||||
|
public Button[] buttons { get; set; }
|
||||||
public Contents contents { get; set; }
|
public Contents contents { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Data
|
|
||||||
{
|
|
||||||
public string foo { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Contents
|
public class Contents
|
||||||
{
|
{
|
||||||
public string en { get; set; }
|
public string en { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Button
|
||||||
|
{
|
||||||
|
public string id { get; set; }
|
||||||
|
public string text { get; set; }
|
||||||
|
//public string icon { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,6 +4,10 @@
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Humanizer.Core" Version="2.4.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
|
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -20,13 +20,13 @@ namespace Ombi.Api.Notifications
|
||||||
private readonly IApplicationConfigRepository _appConfig;
|
private readonly IApplicationConfigRepository _appConfig;
|
||||||
private const string ApiUrl = "https://onesignal.com/api/v1/notifications";
|
private const string ApiUrl = "https://onesignal.com/api/v1/notifications";
|
||||||
|
|
||||||
public async Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message)
|
public async Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message, bool isAdminNotification, int requestId, int requestType)
|
||||||
{
|
{
|
||||||
if (!playerIds.Any())
|
if (!playerIds.Any())
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var id = await _appConfig.Get(ConfigurationTypes.Notification);
|
var id = await _appConfig.GetAsync(ConfigurationTypes.Notification);
|
||||||
var request = new Request(string.Empty, ApiUrl, HttpMethod.Post);
|
var request = new Request(string.Empty, ApiUrl, HttpMethod.Post);
|
||||||
|
|
||||||
var body = new OneSignalNotificationBody
|
var body = new OneSignalNotificationBody
|
||||||
|
@ -39,6 +39,17 @@ namespace Ombi.Api.Notifications
|
||||||
include_player_ids = playerIds.ToArray()
|
include_player_ids = playerIds.ToArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isAdminNotification)
|
||||||
|
{
|
||||||
|
// Add the action buttons
|
||||||
|
body.data = new { requestid = requestId, requestType = requestType};
|
||||||
|
body.buttons = new[]
|
||||||
|
{
|
||||||
|
new Button {id = "approve", text = "Approve Request"},
|
||||||
|
new Button {id = "deny", text = "Deny Request"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
request.AddJsonBody(body);
|
request.AddJsonBody(body);
|
||||||
|
|
||||||
var result = await _api.Request<OneSignalNotificationResponse>(request);
|
var result = await _api.Request<OneSignalNotificationResponse>(request);
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace Ombi.Api.Plex
|
||||||
public interface IPlexApi
|
public interface IPlexApi
|
||||||
{
|
{
|
||||||
Task<PlexStatus> GetStatus(string authToken, string uri);
|
Task<PlexStatus> GetStatus(string authToken, string uri);
|
||||||
|
Task<PlexLibrariesForMachineId> GetLibrariesForMachineId(string authToken, string machineId);
|
||||||
Task<PlexAuthentication> SignIn(UserRequest user);
|
Task<PlexAuthentication> SignIn(UserRequest user);
|
||||||
Task<PlexServer> GetServer(string authToken);
|
Task<PlexServer> GetServer(string authToken);
|
||||||
Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost);
|
Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost);
|
||||||
|
@ -22,8 +23,8 @@ namespace Ombi.Api.Plex
|
||||||
Task<PlexFriends> GetUsers(string authToken);
|
Task<PlexFriends> GetUsers(string authToken);
|
||||||
Task<PlexAccount> GetAccount(string authToken);
|
Task<PlexAccount> GetAccount(string authToken);
|
||||||
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
|
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
|
||||||
Task<OAuthPin> CreatePin();
|
|
||||||
Task<OAuthPin> GetPin(int pinId);
|
Task<OAuthPin> GetPin(int pinId);
|
||||||
Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard);
|
Task<Uri> GetOAuthUrl(string code, string applicationUrl);
|
||||||
|
Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs);
|
||||||
}
|
}
|
||||||
}
|
}
|
84
src/Ombi.Api.Plex/Models/PlexAdd.cs
Normal file
84
src/Ombi.Api.Plex/Models/PlexAdd.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Plex.Models
|
||||||
|
{
|
||||||
|
[XmlRoot(ElementName = "Section")]
|
||||||
|
public class Section
|
||||||
|
{
|
||||||
|
[XmlAttribute(AttributeName = "id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "key")]
|
||||||
|
public string Key { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "shared")]
|
||||||
|
public string Shared { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot(ElementName = "SharedServer")]
|
||||||
|
public class SharedServer
|
||||||
|
{
|
||||||
|
[XmlElement(ElementName = "Section")]
|
||||||
|
public List<Section> Section { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "username")]
|
||||||
|
public string Username { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "email")]
|
||||||
|
public string Email { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "userID")]
|
||||||
|
public string UserID { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "accessToken")]
|
||||||
|
public string AccessToken { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "acceptedAt")]
|
||||||
|
public string AcceptedAt { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "invitedAt")]
|
||||||
|
public string InvitedAt { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "allowSync")]
|
||||||
|
public string AllowSync { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "allowCameraUpload")]
|
||||||
|
public string AllowCameraUpload { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "allowChannels")]
|
||||||
|
public string AllowChannels { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "allowTuners")]
|
||||||
|
public string AllowTuners { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "owned")]
|
||||||
|
public string Owned { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot(ElementName = "MediaContainer")]
|
||||||
|
public class PlexAdd
|
||||||
|
{
|
||||||
|
[XmlElement(ElementName = "SharedServer")]
|
||||||
|
public SharedServer SharedServer { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "friendlyName")]
|
||||||
|
public string FriendlyName { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "identifier")]
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "machineIdentifier")]
|
||||||
|
public string MachineIdentifier { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "size")]
|
||||||
|
public string Size { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot(ElementName = "Response")]
|
||||||
|
public class AddUserError
|
||||||
|
{
|
||||||
|
[XmlAttribute(AttributeName = "code")]
|
||||||
|
public string Code { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "status")]
|
||||||
|
public string Status { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlexAddWrapper
|
||||||
|
{
|
||||||
|
public PlexAdd Add { get; set; }
|
||||||
|
public AddUserError Error { get; set; }
|
||||||
|
public bool HasError => Error != null;
|
||||||
|
}
|
||||||
|
}
|
66
src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs
Normal file
66
src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
namespace Ombi.Api.Plex.Models
|
||||||
|
{
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Xml.Serialization;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
[XmlRoot(ElementName = "Section")]
|
||||||
|
public class SectionLite
|
||||||
|
{
|
||||||
|
[XmlAttribute(AttributeName = "id")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "key")]
|
||||||
|
public string Key { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "title")]
|
||||||
|
public string Title { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot(ElementName = "Server")]
|
||||||
|
public class ServerLib
|
||||||
|
{
|
||||||
|
[XmlElement(ElementName = "Section")]
|
||||||
|
public List<SectionLite> Section { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "address")]
|
||||||
|
public string Address { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "port")]
|
||||||
|
public string Port { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "version")]
|
||||||
|
public string Version { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "scheme")]
|
||||||
|
public string Scheme { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "host")]
|
||||||
|
public string Host { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "localAddresses")]
|
||||||
|
public string LocalAddresses { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "machineIdentifier")]
|
||||||
|
public string MachineIdentifier { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "createdAt")]
|
||||||
|
public string CreatedAt { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "updatedAt")]
|
||||||
|
public string UpdatedAt { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "owned")]
|
||||||
|
public string Owned { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "synced")]
|
||||||
|
public string Synced { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[XmlRoot(ElementName = "MediaContainer")]
|
||||||
|
public class PlexLibrariesForMachineId
|
||||||
|
{
|
||||||
|
[XmlElement(ElementName = "Server")]
|
||||||
|
public ServerLib Server { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "friendlyName")]
|
||||||
|
public string FriendlyName { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "identifier")]
|
||||||
|
public string Identifier { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "machineIdentifier")]
|
||||||
|
public string MachineIdentifier { get; set; }
|
||||||
|
[XmlAttribute(AttributeName = "size")]
|
||||||
|
public string Size { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,14 +16,16 @@ namespace Ombi.Api.Plex
|
||||||
{
|
{
|
||||||
public class PlexApi : IPlexApi
|
public class PlexApi : IPlexApi
|
||||||
{
|
{
|
||||||
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings)
|
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings, ISettingsService<PlexSettings> p)
|
||||||
{
|
{
|
||||||
Api = api;
|
Api = api;
|
||||||
_custom = settings;
|
_custom = settings;
|
||||||
|
_plexSettings = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IApi Api { get; }
|
private IApi Api { get; }
|
||||||
private readonly ISettingsService<CustomizationSettings> _custom;
|
private readonly ISettingsService<CustomizationSettings> _custom;
|
||||||
|
private readonly ISettingsService<PlexSettings> _plexSettings;
|
||||||
|
|
||||||
private string _app;
|
private string _app;
|
||||||
private string ApplicationName
|
private string ApplicationName
|
||||||
|
@ -39,7 +41,18 @@ namespace Ombi.Api.Plex
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_app = settings.ApplicationName;
|
// Check for non-ascii characters (New .Net Core HTTPLib does not allow this)
|
||||||
|
var chars = settings.ApplicationName.ToCharArray();
|
||||||
|
var hasNonAscii = false;
|
||||||
|
foreach (var c in chars)
|
||||||
|
{
|
||||||
|
if (c > 128)
|
||||||
|
{
|
||||||
|
hasNonAscii = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_app = hasNonAscii ? "Ombi" : settings.ApplicationName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _app;
|
return _app;
|
||||||
|
@ -69,7 +82,7 @@ namespace Ombi.Api.Plex
|
||||||
};
|
};
|
||||||
var request = new Request(SignInUri, string.Empty, HttpMethod.Post);
|
var request = new Request(SignInUri, string.Empty, HttpMethod.Post);
|
||||||
|
|
||||||
AddHeaders(request);
|
await AddHeaders(request);
|
||||||
request.AddJsonBody(userModel);
|
request.AddJsonBody(userModel);
|
||||||
|
|
||||||
var obj = await Api.Request<PlexAuthentication>(request);
|
var obj = await Api.Request<PlexAuthentication>(request);
|
||||||
|
@ -80,14 +93,14 @@ namespace Ombi.Api.Plex
|
||||||
public async Task<PlexStatus> GetStatus(string authToken, string uri)
|
public async Task<PlexStatus> GetStatus(string authToken, string uri)
|
||||||
{
|
{
|
||||||
var request = new Request(uri, string.Empty, HttpMethod.Get);
|
var request = new Request(uri, string.Empty, HttpMethod.Get);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
return await Api.Request<PlexStatus>(request);
|
return await Api.Request<PlexStatus>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PlexAccount> GetAccount(string authToken)
|
public async Task<PlexAccount> GetAccount(string authToken)
|
||||||
{
|
{
|
||||||
var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get);
|
var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
return await Api.Request<PlexAccount>(request);
|
return await Api.Request<PlexAccount>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +108,7 @@ namespace Ombi.Api.Plex
|
||||||
{
|
{
|
||||||
var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml);
|
var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml);
|
||||||
|
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
|
|
||||||
return await Api.Request<PlexServer>(request);
|
return await Api.Request<PlexServer>(request);
|
||||||
}
|
}
|
||||||
|
@ -103,17 +116,24 @@ namespace Ombi.Api.Plex
|
||||||
public async Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost)
|
public async Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost)
|
||||||
{
|
{
|
||||||
var request = new Request("library/sections", plexFullHost, HttpMethod.Get);
|
var request = new Request("library/sections", plexFullHost, HttpMethod.Get);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
return await Api.Request<PlexContainer>(request);
|
return await Api.Request<PlexContainer>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PlexContainer> GetLibrary(string authToken, string plexFullHost, string libraryId)
|
public async Task<PlexContainer> GetLibrary(string authToken, string plexFullHost, string libraryId)
|
||||||
{
|
{
|
||||||
var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get);
|
var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
return await Api.Request<PlexContainer>(request);
|
return await Api.Request<PlexContainer>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PlexLibrariesForMachineId> GetLibrariesForMachineId(string authToken, string machineId)
|
||||||
|
{
|
||||||
|
var request = new Request("", $"https://plex.tv/api/servers/{machineId}", HttpMethod.Get, ContentType.Xml);
|
||||||
|
await AddHeaders(request, authToken);
|
||||||
|
return await Api.Request<PlexLibrariesForMachineId>(request);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
// 192.168.1.69:32400/library/metadata/3662/allLeaves
|
// 192.168.1.69:32400/library/metadata/3662/allLeaves
|
||||||
// The metadata ratingkey should be in the Cache
|
// The metadata ratingkey should be in the Cache
|
||||||
|
@ -128,21 +148,21 @@ namespace Ombi.Api.Plex
|
||||||
public async Task<PlexMetadata> GetEpisodeMetaData(string authToken, string plexFullHost, int ratingKey)
|
public async Task<PlexMetadata> GetEpisodeMetaData(string authToken, string plexFullHost, int ratingKey)
|
||||||
{
|
{
|
||||||
var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get);
|
var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
return await Api.Request<PlexMetadata>(request);
|
return await Api.Request<PlexMetadata>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, int itemId)
|
public async Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, int itemId)
|
||||||
{
|
{
|
||||||
var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get);
|
var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
return await Api.Request<PlexMetadata>(request);
|
return await Api.Request<PlexMetadata>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, int ratingKey)
|
public async Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, int ratingKey)
|
||||||
{
|
{
|
||||||
var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get);
|
var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
return await Api.Request<PlexMetadata>(request);
|
return await Api.Request<PlexMetadata>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +181,7 @@ namespace Ombi.Api.Plex
|
||||||
|
|
||||||
request.AddQueryString("type", "4");
|
request.AddQueryString("type", "4");
|
||||||
AddLimitHeaders(request, start, retCount);
|
AddLimitHeaders(request, start, retCount);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
|
|
||||||
return await Api.Request<PlexContainer>(request);
|
return await Api.Request<PlexContainer>(request);
|
||||||
}
|
}
|
||||||
|
@ -174,8 +194,8 @@ namespace Ombi.Api.Plex
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<PlexFriends> GetUsers(string authToken)
|
public async Task<PlexFriends> GetUsers(string authToken)
|
||||||
{
|
{
|
||||||
var request = new Request(string.Empty,FriendsUri, HttpMethod.Get, ContentType.Xml);
|
var request = new Request(string.Empty, FriendsUri, HttpMethod.Get, ContentType.Xml);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
|
|
||||||
return await Api.Request<PlexFriends>(request);
|
return await Api.Request<PlexFriends>(request);
|
||||||
}
|
}
|
||||||
|
@ -183,43 +203,35 @@ namespace Ombi.Api.Plex
|
||||||
public async Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId)
|
public async Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId)
|
||||||
{
|
{
|
||||||
var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get);
|
var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get);
|
||||||
AddHeaders(request, authToken);
|
await AddHeaders(request, authToken);
|
||||||
AddLimitHeaders(request, 0, 50);
|
AddLimitHeaders(request, 0, 50);
|
||||||
|
|
||||||
return await Api.Request<PlexMetadata>(request);
|
return await Api.Request<PlexMetadata>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OAuthPin> CreatePin()
|
|
||||||
{
|
|
||||||
var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post);
|
|
||||||
request.AddQueryString("strong", "true");
|
|
||||||
AddHeaders(request);
|
|
||||||
|
|
||||||
return await Api.Request<OAuthPin>(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<OAuthPin> GetPin(int pinId)
|
public async Task<OAuthPin> GetPin(int pinId)
|
||||||
{
|
{
|
||||||
var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get);
|
var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get);
|
||||||
AddHeaders(request);
|
await AddHeaders(request);
|
||||||
|
|
||||||
return await Api.Request<OAuthPin>(request);
|
return await Api.Request<OAuthPin>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard)
|
public async Task<Uri> GetOAuthUrl(string code, string applicationUrl)
|
||||||
{
|
{
|
||||||
var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get);
|
var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get);
|
||||||
AddHeaders(request);
|
await AddHeaders(request);
|
||||||
var forwardUrl = wizard
|
|
||||||
? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get)
|
|
||||||
: new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get);
|
|
||||||
|
|
||||||
request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString());
|
|
||||||
request.AddQueryString("pinID", pinId.ToString());
|
|
||||||
request.AddQueryString("code", code);
|
request.AddQueryString("code", code);
|
||||||
request.AddQueryString("context[device][product]", "Ombi");
|
request.AddQueryString("context[device][product]", ApplicationName);
|
||||||
request.AddQueryString("context[device][environment]", "bundled");
|
request.AddQueryString("context[device][environment]", "bundled");
|
||||||
request.AddQueryString("clientID", $"OmbiV3");
|
request.AddQueryString("context[device][layout]", "desktop");
|
||||||
|
request.AddQueryString("context[device][platform]", "Web");
|
||||||
|
request.AddQueryString("context[device][device]", "Ombi (Web)");
|
||||||
|
|
||||||
|
var s = await GetSettings();
|
||||||
|
await CheckInstallId(s);
|
||||||
|
request.AddQueryString("clientID", s.InstallId.ToString("N"));
|
||||||
|
|
||||||
if (request.FullUri.Fragment.Equals("#"))
|
if (request.FullUri.Fragment.Equals("#"))
|
||||||
{
|
{
|
||||||
|
@ -233,26 +245,58 @@ namespace Ombi.Api.Plex
|
||||||
return request.FullUri;
|
return request.FullUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs)
|
||||||
|
{
|
||||||
|
var request = new Request(string.Empty, $"https://plex.tv/api/servers/{serverId}/shared_servers", HttpMethod.Post, ContentType.Xml);
|
||||||
|
await AddHeaders(request, authToken);
|
||||||
|
request.AddJsonBody(new
|
||||||
|
{
|
||||||
|
server_id = serverId,
|
||||||
|
shared_server = new
|
||||||
|
{
|
||||||
|
library_section_ids = libs.Length > 0 ? libs : new int[]{},
|
||||||
|
invited_email = emailAddress
|
||||||
|
},
|
||||||
|
sharing_settings = new { }
|
||||||
|
});
|
||||||
|
var result = await Api.RequestContent(request);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var add = Api.DeserializeXml<PlexAdd>(result);
|
||||||
|
return new PlexAddWrapper{Add = add};
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
var error = Api.DeserializeXml<AddUserError>(result);
|
||||||
|
return new PlexAddWrapper{Error = error};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the required headers and also the authorization header
|
/// Adds the required headers and also the authorization header
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request"></param>
|
/// <param name="request"></param>
|
||||||
/// <param name="authToken"></param>
|
/// <param name="authToken"></param>
|
||||||
private void AddHeaders(Request request, string authToken)
|
private async Task AddHeaders(Request request, string authToken)
|
||||||
{
|
{
|
||||||
request.AddHeader("X-Plex-Token", authToken);
|
request.AddHeader("X-Plex-Token", authToken);
|
||||||
AddHeaders(request);
|
await AddHeaders(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the main required headers to the Plex Request
|
/// Adds the main required headers to the Plex Request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request"></param>
|
/// <param name="request"></param>
|
||||||
private void AddHeaders(Request request)
|
private async Task AddHeaders(Request request)
|
||||||
{
|
{
|
||||||
request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3");
|
var s = await GetSettings();
|
||||||
|
await CheckInstallId(s);
|
||||||
|
request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N"));
|
||||||
request.AddHeader("X-Plex-Product", ApplicationName);
|
request.AddHeader("X-Plex-Product", ApplicationName);
|
||||||
request.AddHeader("X-Plex-Version", "3");
|
request.AddHeader("X-Plex-Version", "3");
|
||||||
|
request.AddHeader("X-Plex-Device", "Ombi (Web)");
|
||||||
|
request.AddHeader("X-Plex-Platform", "Web");
|
||||||
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
|
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
|
||||||
request.AddHeader("Accept", "application/json");
|
request.AddHeader("Accept", "application/json");
|
||||||
}
|
}
|
||||||
|
@ -262,5 +306,19 @@ namespace Ombi.Api.Plex
|
||||||
request.AddHeader("X-Plex-Container-Start", from.ToString());
|
request.AddHeader("X-Plex-Container-Start", from.ToString());
|
||||||
request.AddHeader("X-Plex-Container-Size", to.ToString());
|
request.AddHeader("X-Plex-Container-Size", to.ToString());
|
||||||
}
|
}
|
||||||
|
private async Task CheckInstallId(PlexSettings s)
|
||||||
|
{
|
||||||
|
if (s.InstallId == null || s.InstallId == Guid.Empty)
|
||||||
|
{
|
||||||
|
s.InstallId = Guid.NewGuid();
|
||||||
|
await _plexSettings.SaveSettingsAsync(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PlexSettings _settings;
|
||||||
|
private async Task<PlexSettings> GetSettings()
|
||||||
|
{
|
||||||
|
return _settings ?? (_settings = await _plexSettings.GetSettingsAsync());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,6 @@ namespace Ombi.Api.Pushover
|
||||||
{
|
{
|
||||||
public interface IPushoverApi
|
public interface IPushoverApi
|
||||||
{
|
{
|
||||||
Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken);
|
Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,13 +16,13 @@ namespace Ombi.Api.Pushover
|
||||||
private readonly IApi _api;
|
private readonly IApi _api;
|
||||||
private const string PushoverEndpoint = "https://api.pushover.net/1";
|
private const string PushoverEndpoint = "https://api.pushover.net/1";
|
||||||
|
|
||||||
public async Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken)
|
public async Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound)
|
||||||
{
|
{
|
||||||
if (message.Contains("'"))
|
if (message.Contains("'"))
|
||||||
{
|
{
|
||||||
message = message.Replace("'", "'");
|
message = message.Replace("'", "'");
|
||||||
}
|
}
|
||||||
var request = new Request($"messages.json?token={accessToken}&user={userToken}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
|
var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
|
||||||
|
|
||||||
var result = await _api.Request<PushoverResponse>(request);
|
var result = await _api.Request<PushoverResponse>(request);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -79,7 +79,7 @@ namespace Ombi.Api.Radarr
|
||||||
tmdbId = tmdbId,
|
tmdbId = tmdbId,
|
||||||
qualityProfileId = qualityId,
|
qualityProfileId = qualityId,
|
||||||
rootFolderPath = rootPath,
|
rootFolderPath = rootPath,
|
||||||
titleSlug = title,
|
titleSlug = title + year,
|
||||||
monitored = true,
|
monitored = true,
|
||||||
year = year,
|
year = year,
|
||||||
minimumAvailability = minimumAvailability
|
minimumAvailability = minimumAvailability
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
13
src/Ombi.Api.Sonarr/ISonarrV3Api.cs
Normal file
13
src/Ombi.Api.Sonarr/ISonarrV3Api.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Api.Sonarr.Models;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Ombi.Api.Sonarr.Models.V3;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Sonarr
|
||||||
|
{
|
||||||
|
public interface ISonarrV3Api : ISonarrApi
|
||||||
|
{
|
||||||
|
Task<IEnumerable<LanguageProfiles>> LanguageProfiles(string apiKey, string baseUrl);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,30 @@ namespace Ombi.Api.Sonarr.Models
|
||||||
{
|
{
|
||||||
public class Episode
|
public class Episode
|
||||||
{
|
{
|
||||||
|
public Episode()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Episode(Episode ep)
|
||||||
|
{
|
||||||
|
seriesId = ep.seriesId;
|
||||||
|
episodeFileId = ep.episodeFileId;
|
||||||
|
seasonNumber = ep.seasonNumber;
|
||||||
|
episodeNumber = ep.episodeNumber;
|
||||||
|
title = ep.title;
|
||||||
|
airDate = ep.airDate;
|
||||||
|
airDateUtc = ep.airDateUtc;
|
||||||
|
overview = ep.overview;
|
||||||
|
hasFile = ep.hasFile;
|
||||||
|
monitored = ep.monitored;
|
||||||
|
unverifiedSceneNumbering = ep.unverifiedSceneNumbering;
|
||||||
|
id = ep.id;
|
||||||
|
absoluteEpisodeNumber = ep.absoluteEpisodeNumber;
|
||||||
|
sceneAbsoluteEpisodeNumber = ep.sceneAbsoluteEpisodeNumber;
|
||||||
|
sceneEpisodeNumber = ep.sceneEpisodeNumber;
|
||||||
|
sceneSeasonNumber = ep.sceneSeasonNumber;
|
||||||
|
}
|
||||||
public int seriesId { get; set; }
|
public int seriesId { get; set; }
|
||||||
public int episodeFileId { get; set; }
|
public int episodeFileId { get; set; }
|
||||||
public int seasonNumber { get; set; }
|
public int seasonNumber { get; set; }
|
||||||
|
@ -27,6 +51,24 @@ namespace Ombi.Api.Sonarr.Models
|
||||||
|
|
||||||
public class Episodefile
|
public class Episodefile
|
||||||
{
|
{
|
||||||
|
public Episodefile()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Episodefile(Episodefile e)
|
||||||
|
{
|
||||||
|
seriesId = e.seriesId;
|
||||||
|
seasonNumber = e.seasonNumber;
|
||||||
|
relativePath = e.relativePath;
|
||||||
|
path = e.path;
|
||||||
|
size = e.size;
|
||||||
|
dateAdded = e.dateAdded;
|
||||||
|
sceneName = e.sceneName;
|
||||||
|
quality = new EpisodeQuality(e.quality);
|
||||||
|
qualityCutoffNotMet = e.qualityCutoffNotMet;
|
||||||
|
id = e.id;
|
||||||
|
}
|
||||||
public int seriesId { get; set; }
|
public int seriesId { get; set; }
|
||||||
public int seasonNumber { get; set; }
|
public int seasonNumber { get; set; }
|
||||||
public string relativePath { get; set; }
|
public string relativePath { get; set; }
|
||||||
|
@ -41,12 +83,32 @@ namespace Ombi.Api.Sonarr.Models
|
||||||
|
|
||||||
public class EpisodeQuality
|
public class EpisodeQuality
|
||||||
{
|
{
|
||||||
|
public EpisodeQuality()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public EpisodeQuality(EpisodeQuality e)
|
||||||
|
{
|
||||||
|
quality = new Quality(e.quality);
|
||||||
|
revision = new Revision(e.revision);
|
||||||
|
}
|
||||||
public Quality quality { get; set; }
|
public Quality quality { get; set; }
|
||||||
public Revision revision { get; set; }
|
public Revision revision { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Revision
|
public class Revision
|
||||||
{
|
{
|
||||||
|
public Revision()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Revision(Revision r)
|
||||||
|
{
|
||||||
|
version = r.version;
|
||||||
|
real = r.real;
|
||||||
|
}
|
||||||
public int version { get; set; }
|
public int version { get; set; }
|
||||||
public int real { get; set; }
|
public int real { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,13 @@ namespace Ombi.Api.Sonarr.Models
|
||||||
public string cleanTitle { get; set; }
|
public string cleanTitle { get; set; }
|
||||||
public string imdbId { get; set; }
|
public string imdbId { get; set; }
|
||||||
public string titleSlug { get; set; }
|
public string titleSlug { get; set; }
|
||||||
|
public string seriesType { get; set; }
|
||||||
public int id { get; set; }
|
public int id { get; set; }
|
||||||
public List<SonarrImage> images { get; set; }
|
public List<SonarrImage> images { get; set; }
|
||||||
|
|
||||||
|
// V3 Property
|
||||||
|
public int languageProfileId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is for us
|
/// This is for us
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -2,6 +2,16 @@ namespace Ombi.Api.Sonarr.Models
|
||||||
{
|
{
|
||||||
public class Quality
|
public class Quality
|
||||||
{
|
{
|
||||||
|
public Quality()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Quality(Quality q)
|
||||||
|
{
|
||||||
|
id = q.id;
|
||||||
|
name = q.name;
|
||||||
|
}
|
||||||
public int id { get; set; }
|
public int id { get; set; }
|
||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ namespace Ombi.Api.Sonarr.Models
|
||||||
public DateTime added { get; set; }
|
public DateTime added { get; set; }
|
||||||
public Ratings ratings { get; set; }
|
public Ratings ratings { get; set; }
|
||||||
public int qualityProfileId { get; set; }
|
public int qualityProfileId { get; set; }
|
||||||
|
public int languageProfileId { get; set; }
|
||||||
public int id { get; set; }
|
public int id { get; set; }
|
||||||
public DateTime nextAiring { get; set; }
|
public DateTime nextAiring { get; set; }
|
||||||
}
|
}
|
||||||
|
|
30
src/Ombi.Api.Sonarr/Models/V3/LanguageProfiles.cs
Normal file
30
src/Ombi.Api.Sonarr/Models/V3/LanguageProfiles.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
namespace Ombi.Api.Sonarr.Models.V3
|
||||||
|
{
|
||||||
|
public class LanguageProfiles
|
||||||
|
{
|
||||||
|
public string name { get; set; }
|
||||||
|
public bool upgradeAllowed { get; set; }
|
||||||
|
public Cutoff cutoff { get; set; }
|
||||||
|
public Languages[] languages { get; set; }
|
||||||
|
public int id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Cutoff
|
||||||
|
{
|
||||||
|
public int id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Languages
|
||||||
|
{
|
||||||
|
public Language languages { get; set; }
|
||||||
|
public bool allowed { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Language
|
||||||
|
{
|
||||||
|
public int id { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,18 +16,19 @@ namespace Ombi.Api.Sonarr
|
||||||
Api = api;
|
Api = api;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IApi Api { get; }
|
protected IApi Api { get; }
|
||||||
|
protected virtual string ApiBaseUrl => "/api/";
|
||||||
|
|
||||||
public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl)
|
public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request("/api/profile", baseUrl, HttpMethod.Get);
|
var request = new Request($"{ApiBaseUrl}profile", baseUrl, HttpMethod.Get);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
return await Api.Request<List<SonarrProfile>>(request);
|
return await Api.Request<List<SonarrProfile>>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
|
public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request("/api/rootfolder", baseUrl, HttpMethod.Get);
|
var request = new Request($"{ApiBaseUrl}rootfolder", baseUrl, HttpMethod.Get);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
return await Api.Request<List<SonarrRootFolder>>(request);
|
return await Api.Request<List<SonarrRootFolder>>(request);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +41,7 @@ namespace Ombi.Api.Sonarr
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<SonarrSeries>> GetSeries(string apiKey, string baseUrl)
|
public async Task<IEnumerable<SonarrSeries>> GetSeries(string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request("/api/series", baseUrl, HttpMethod.Get);
|
var request = new Request($"{ApiBaseUrl}series", baseUrl, HttpMethod.Get);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
var results = await Api.Request<List<SonarrSeries>>(request);
|
var results = await Api.Request<List<SonarrSeries>>(request);
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ namespace Ombi.Api.Sonarr
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<SonarrSeries> GetSeriesById(int id, string apiKey, string baseUrl)
|
public async Task<SonarrSeries> GetSeriesById(int id, string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get);
|
var request = new Request($"{ApiBaseUrl}series/{id}", baseUrl, HttpMethod.Get);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
var result = await Api.Request<SonarrSeries>(request);
|
var result = await Api.Request<SonarrSeries>(request);
|
||||||
if (result?.seasons?.Length > 0)
|
if (result?.seasons?.Length > 0)
|
||||||
|
@ -82,7 +83,7 @@ namespace Ombi.Api.Sonarr
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<SonarrSeries> UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl)
|
public async Task<SonarrSeries> UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request("/api/series/", baseUrl, HttpMethod.Put);
|
var request = new Request($"{ApiBaseUrl}series/", baseUrl, HttpMethod.Put);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
request.AddJsonBody(updated);
|
request.AddJsonBody(updated);
|
||||||
return await Api.Request<SonarrSeries>(request);
|
return await Api.Request<SonarrSeries>(request);
|
||||||
|
@ -94,7 +95,7 @@ namespace Ombi.Api.Sonarr
|
||||||
{
|
{
|
||||||
return new NewSeries { ErrorMessages = new List<string> { seriesToAdd.Validate() } };
|
return new NewSeries { ErrorMessages = new List<string> { seriesToAdd.Validate() } };
|
||||||
}
|
}
|
||||||
var request = new Request("/api/series/", baseUrl, HttpMethod.Post);
|
var request = new Request($"{ApiBaseUrl}series/", baseUrl, HttpMethod.Post);
|
||||||
|
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
request.AddJsonBody(seriesToAdd);
|
request.AddJsonBody(seriesToAdd);
|
||||||
|
@ -120,7 +121,7 @@ namespace Ombi.Api.Sonarr
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<Episode>> GetEpisodes(int seriesId, string apiKey, string baseUrl)
|
public async Task<IEnumerable<Episode>> GetEpisodes(int seriesId, string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request($"/api/Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get);
|
var request = new Request($"{ApiBaseUrl}Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
return await Api.Request<List<Episode>>(request);
|
return await Api.Request<List<Episode>>(request);
|
||||||
}
|
}
|
||||||
|
@ -134,14 +135,14 @@ namespace Ombi.Api.Sonarr
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<Episode> GetEpisodeById(int episodeId, string apiKey, string baseUrl)
|
public async Task<Episode> GetEpisodeById(int episodeId, string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request($"/api/Episode/{episodeId}", baseUrl, HttpMethod.Get);
|
var request = new Request($"{ApiBaseUrl}Episode/{episodeId}", baseUrl, HttpMethod.Get);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
return await Api.Request<Episode>(request);
|
return await Api.Request<Episode>(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EpisodeUpdateResult> UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl)
|
public async Task<EpisodeUpdateResult> UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request($"/api/Episode/", baseUrl, HttpMethod.Put);
|
var request = new Request($"{ApiBaseUrl}Episode/", baseUrl, HttpMethod.Put);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
request.AddJsonBody(episodeToUpdate);
|
request.AddJsonBody(episodeToUpdate);
|
||||||
return await Api.Request<EpisodeUpdateResult>(request);
|
return await Api.Request<EpisodeUpdateResult>(request);
|
||||||
|
@ -189,7 +190,7 @@ namespace Ombi.Api.Sonarr
|
||||||
|
|
||||||
private async Task<CommandResult> Command(string apiKey, string baseUrl, object body)
|
private async Task<CommandResult> Command(string apiKey, string baseUrl, object body)
|
||||||
{
|
{
|
||||||
var request = new Request($"/api/Command/", baseUrl, HttpMethod.Post);
|
var request = new Request($"{ApiBaseUrl}Command/", baseUrl, HttpMethod.Post);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
request.AddJsonBody(body);
|
request.AddJsonBody(body);
|
||||||
return await Api.Request<CommandResult>(request);
|
return await Api.Request<CommandResult>(request);
|
||||||
|
@ -197,7 +198,7 @@ namespace Ombi.Api.Sonarr
|
||||||
|
|
||||||
public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl)
|
public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl)
|
||||||
{
|
{
|
||||||
var request = new Request("/api/system/status", baseUrl, HttpMethod.Get);
|
var request = new Request($"{ApiBaseUrl}system/status", baseUrl, HttpMethod.Get);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
|
|
||||||
return await Api.Request<SystemStatus>(request);
|
return await Api.Request<SystemStatus>(request);
|
||||||
|
@ -217,7 +218,7 @@ namespace Ombi.Api.Sonarr
|
||||||
ignoreEpisodesWithoutFiles = false,
|
ignoreEpisodesWithoutFiles = false,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var request = new Request("/api/seasonpass", baseUrl, HttpMethod.Post);
|
var request = new Request($"{ApiBaseUrl}seasonpass", baseUrl, HttpMethod.Post);
|
||||||
request.AddHeader("X-Api-Key", apiKey);
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
request.AddJsonBody(seasonPass);
|
request.AddJsonBody(seasonPass);
|
||||||
|
|
||||||
|
|
25
src/Ombi.Api.Sonarr/SonarrV3Api.cs
Normal file
25
src/Ombi.Api.Sonarr/SonarrV3Api.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Api.Sonarr.Models.V3;
|
||||||
|
|
||||||
|
namespace Ombi.Api.Sonarr
|
||||||
|
{
|
||||||
|
public class SonarrV3Api : SonarrApi, ISonarrV3Api
|
||||||
|
{
|
||||||
|
public SonarrV3Api(IApi api) : base(api)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string ApiBaseUrl => "/api/v3/";
|
||||||
|
|
||||||
|
public async Task<IEnumerable<LanguageProfiles>> LanguageProfiles(string apiKey, string baseUrl)
|
||||||
|
{
|
||||||
|
var request = new Request($"{ApiBaseUrl}languageprofile", baseUrl, HttpMethod.Get);
|
||||||
|
request.AddHeader("X-Api-Key", apiKey);
|
||||||
|
|
||||||
|
return await Api.Request<List<LanguageProfiles>>(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,11 @@ namespace Ombi.Api
|
||||||
|
|
||||||
if (!httpResponseMessage.IsSuccessStatusCode)
|
if (!httpResponseMessage.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
LogError(request, httpResponseMessage);
|
if (!request.IgnoreErrors)
|
||||||
|
{
|
||||||
|
await LogError(request, httpResponseMessage);
|
||||||
|
}
|
||||||
|
|
||||||
if (request.Retry)
|
if (request.Retry)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -76,15 +80,20 @@ namespace Ombi.Api
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// XML
|
// XML
|
||||||
XmlSerializer serializer = new XmlSerializer(typeof(T));
|
return DeserializeXml<T>(receivedString);
|
||||||
StringReader reader = new StringReader(receivedString);
|
|
||||||
var value = (T)serializer.Deserialize(reader);
|
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public T DeserializeXml<T>(string receivedString)
|
||||||
|
{
|
||||||
|
XmlSerializer serializer = new XmlSerializer(typeof(T));
|
||||||
|
StringReader reader = new StringReader(receivedString);
|
||||||
|
var value = (T) serializer.Deserialize(reader);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string> RequestContent(Request request)
|
public async Task<string> RequestContent(Request request)
|
||||||
{
|
{
|
||||||
using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri))
|
using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri))
|
||||||
|
@ -94,7 +103,10 @@ namespace Ombi.Api
|
||||||
var httpResponseMessage = await _client.SendAsync(httpRequestMessage);
|
var httpResponseMessage = await _client.SendAsync(httpRequestMessage);
|
||||||
if (!httpResponseMessage.IsSuccessStatusCode)
|
if (!httpResponseMessage.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
LogError(request, httpResponseMessage);
|
if (!request.IgnoreErrors)
|
||||||
|
{
|
||||||
|
await LogError(request, httpResponseMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// do something with the response
|
// do something with the response
|
||||||
var data = httpResponseMessage.Content;
|
var data = httpResponseMessage.Content;
|
||||||
|
@ -112,7 +124,10 @@ namespace Ombi.Api
|
||||||
var httpResponseMessage = await _client.SendAsync(httpRequestMessage);
|
var httpResponseMessage = await _client.SendAsync(httpRequestMessage);
|
||||||
if (!httpResponseMessage.IsSuccessStatusCode)
|
if (!httpResponseMessage.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
LogError(request, httpResponseMessage);
|
if (!request.IgnoreErrors)
|
||||||
|
{
|
||||||
|
await LogError(request, httpResponseMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,10 +149,15 @@ namespace Ombi.Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogError(Request request, HttpResponseMessage httpResponseMessage)
|
private async Task LogError(Request request, HttpResponseMessage httpResponseMessage)
|
||||||
{
|
{
|
||||||
Logger.LogError(LoggingEvents.Api,
|
Logger.LogError(LoggingEvents.Api,
|
||||||
$"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}, RequestUri: {request.FullUri}");
|
$"StatusCode: {httpResponseMessage.StatusCode}, Reason: {httpResponseMessage.ReasonPhrase}, RequestUri: {request.FullUri}");
|
||||||
|
if (Logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var content = await httpResponseMessage.Content.ReadAsStringAsync();
|
||||||
|
Logger.LogDebug(content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,6 @@ namespace Ombi.Api
|
||||||
Task Request(Request request);
|
Task Request(Request request);
|
||||||
Task<T> Request<T>(Request request);
|
Task<T> Request<T>(Request request);
|
||||||
Task<string> RequestContent(Request request);
|
Task<string> RequestContent(Request request);
|
||||||
|
T DeserializeXml<T>(string receivedString);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,9 +9,9 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||||
<PackageReference Include="Polly" Version="5.8.0" />
|
<PackageReference Include="Polly" Version="6.1.0" />
|
||||||
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
|
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,10 @@ namespace Ombi.Api
|
||||||
public string Endpoint { get; }
|
public string Endpoint { get; }
|
||||||
public string BaseUrl { get; }
|
public string BaseUrl { get; }
|
||||||
public HttpMethod HttpMethod { get; }
|
public HttpMethod HttpMethod { get; }
|
||||||
|
public bool IgnoreErrors { get; set; }
|
||||||
public bool Retry { get; set; }
|
public bool Retry { get; set; }
|
||||||
public List<HttpStatusCode> StatusCodeToRetry { get; set; } = new List<HttpStatusCode>();
|
public List<HttpStatusCode> StatusCodeToRetry { get; set; } = new List<HttpStatusCode>();
|
||||||
|
public bool IgnoreBaseUrlAppend { get; set; }
|
||||||
|
|
||||||
public Action<string> OnBeforeDeserialization { get; set; }
|
public Action<string> OnBeforeDeserialization { get; set; }
|
||||||
|
|
||||||
|
@ -38,7 +39,7 @@ namespace Ombi.Api
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
if (!string.IsNullOrEmpty(BaseUrl))
|
if (!string.IsNullOrEmpty(BaseUrl))
|
||||||
{
|
{
|
||||||
sb.Append(!BaseUrl.EndsWith("/") ? string.Format("{0}/", BaseUrl) : BaseUrl);
|
sb.Append(!BaseUrl.EndsWith("/") && !IgnoreBaseUrlAppend ? string.Format("{0}/", BaseUrl) : BaseUrl);
|
||||||
}
|
}
|
||||||
sb.Append(Endpoint.StartsWith("/") ? Endpoint.Remove(0, 1) : Endpoint);
|
sb.Append(Endpoint.StartsWith("/") ? Endpoint.Remove(0, 1) : Endpoint);
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
|
73
src/Ombi.Core.Tests/Engine/VoteEngineTests.cs
Normal file
73
src/Ombi.Core.Tests/Engine/VoteEngineTests.cs
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AutoFixture;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Engine;
|
||||||
|
using Ombi.Core.Engine.Interfaces;
|
||||||
|
using Ombi.Core.Rule.Interfaces;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Tests.Engine
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class VoteEngineTests
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
F = new Fixture();
|
||||||
|
VoteRepository = new Mock<IRepository<Votes>>();
|
||||||
|
VoteSettings = new Mock<ISettingsService<VoteSettings>>();
|
||||||
|
MusicRequestEngine = new Mock<IMusicRequestEngine>();
|
||||||
|
TvRequestEngine = new Mock<ITvRequestEngine>();
|
||||||
|
MovieRequestEngine = new Mock<IMovieRequestEngine>();
|
||||||
|
MovieRequestEngine = new Mock<IMovieRequestEngine>();
|
||||||
|
User = new Mock<IPrincipal>();
|
||||||
|
UserManager = new Mock<OmbiUserManager>();
|
||||||
|
UserManager.Setup(x => x.Users)
|
||||||
|
.Returns(new EnumerableQuery<OmbiUser>(new List<OmbiUser> {new OmbiUser {Id = "abc"}}));
|
||||||
|
Rule = new Mock<IRuleEvaluator>();
|
||||||
|
Engine = new VoteEngine(VoteRepository.Object, User.Object, UserManager.Object, Rule.Object, VoteSettings.Object, MusicRequestEngine.Object,
|
||||||
|
TvRequestEngine.Object, MovieRequestEngine.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Fixture F { get; set; }
|
||||||
|
public VoteEngine Engine { get; set; }
|
||||||
|
public Mock<IPrincipal> User { get; set; }
|
||||||
|
public Mock<OmbiUserManager> UserManager { get; set; }
|
||||||
|
public Mock<IRuleEvaluator> Rule { get; set; }
|
||||||
|
public Mock<IRepository<Votes>> VoteRepository { get; set; }
|
||||||
|
public Mock<ISettingsService<VoteSettings>> VoteSettings { get; set; }
|
||||||
|
public Mock<IMusicRequestEngine> MusicRequestEngine { get; set; }
|
||||||
|
public Mock<ITvRequestEngine> TvRequestEngine { get; set; }
|
||||||
|
public Mock<IMovieRequestEngine> MovieRequestEngine { get; set; }
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
[Ignore("Need to mock the user manager")]
|
||||||
|
public async Task New_Upvote()
|
||||||
|
{
|
||||||
|
VoteSettings.Setup(x => x.GetSettingsAsync()).ReturnsAsync(new VoteSettings());
|
||||||
|
var votes = F.CreateMany<Votes>().ToList();
|
||||||
|
votes.Add(new Votes
|
||||||
|
{
|
||||||
|
RequestId = 1,
|
||||||
|
RequestType = RequestType.Movie,
|
||||||
|
UserId = "abc"
|
||||||
|
});
|
||||||
|
VoteRepository.Setup(x => x.GetAll()).Returns(new EnumerableQuery<Votes>(votes));
|
||||||
|
var result = await Engine.UpVote(1, RequestType.Movie);
|
||||||
|
|
||||||
|
Assert.That(result.Result, Is.True);
|
||||||
|
VoteRepository.Verify(x => x.Add(It.Is<Votes>(c => c.UserId == "abc" && c.VoteType == VoteType.Upvote)), Times.Once);
|
||||||
|
VoteRepository.Verify(x => x.Delete(It.IsAny<Votes>()), Times.Once);
|
||||||
|
MovieRequestEngine.Verify(x => x.ApproveMovieById(1), Times.Never);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,16 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Moq" Version="4.7.99" />
|
<PackageReference Include="AutoFixture" Version="4.5.0" />
|
||||||
<PackageReference Include="Nunit" Version="3.8.1" />
|
<PackageReference Include="Moq" Version="4.10.0" />
|
||||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
|
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
|
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.7.2"></packagereference>
|
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||||
|
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -18,13 +18,13 @@ namespace Ombi.Core.Tests.Rule.Search
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
ContextMock = new Mock<IRepository<CouchPotatoCache>>();
|
ContextMock = new Mock<IExternalRepository<CouchPotatoCache>>();
|
||||||
Rule = new CouchPotatoCacheRule(ContextMock.Object);
|
Rule = new CouchPotatoCacheRule(ContextMock.Object);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CouchPotatoCacheRule Rule { get; set; }
|
private CouchPotatoCacheRule Rule { get; set; }
|
||||||
private Mock<IRepository<CouchPotatoCache>> ContextMock { get; set; }
|
private Mock<IExternalRepository<CouchPotatoCache>> ContextMock { get; set; }
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Should_ReturnApproved_WhenMovieIsInCouchPotato()
|
public async Task Should_ReturnApproved_WhenMovieIsInCouchPotato()
|
||||||
|
|
|
@ -29,7 +29,10 @@ namespace Ombi.Core.Tests.Rule.Search
|
||||||
{
|
{
|
||||||
ProviderId = "123"
|
ProviderId = "123"
|
||||||
});
|
});
|
||||||
var search = new SearchMovieViewModel();
|
var search = new SearchMovieViewModel()
|
||||||
|
{
|
||||||
|
TheMovieDbId = "123",
|
||||||
|
};
|
||||||
var result = await Rule.Execute(search);
|
var result = await Rule.Execute(search);
|
||||||
|
|
||||||
Assert.True(result.Success);
|
Assert.True(result.Success);
|
||||||
|
|
|
@ -19,12 +19,14 @@ namespace Ombi.Core.Tests.Rule.Search
|
||||||
|
|
||||||
MovieMock = new Mock<IMovieRequestRepository>();
|
MovieMock = new Mock<IMovieRequestRepository>();
|
||||||
TvMock = new Mock<ITvRequestRepository>();
|
TvMock = new Mock<ITvRequestRepository>();
|
||||||
Rule = new ExistingRule(MovieMock.Object, TvMock.Object);
|
MusicMock = new Mock<IMusicRequestRepository>();
|
||||||
|
Rule = new ExistingRule(MovieMock.Object, TvMock.Object, MusicMock.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExistingRule Rule { get; set; }
|
private ExistingRule Rule { get; set; }
|
||||||
private Mock<IMovieRequestRepository> MovieMock { get; set; }
|
private Mock<IMovieRequestRepository> MovieMock { get; set; }
|
||||||
private Mock<ITvRequestRepository> TvMock { get; set; }
|
private Mock<ITvRequestRepository> TvMock { get; set; }
|
||||||
|
private Mock<IMusicRequestRepository> MusicMock { get; set; }
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Ombi.Core.Models.Search;
|
using Ombi.Core.Models.Search;
|
||||||
|
@ -14,7 +15,7 @@ namespace Ombi.Core.Tests.Rule.Search
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
ContextMock = new Mock<IPlexContentRepository>();
|
ContextMock = new Mock<IPlexContentRepository>();
|
||||||
Rule = new PlexAvailabilityRule(ContextMock.Object);
|
Rule = new PlexAvailabilityRule(ContextMock.Object, new Mock<ILogger<PlexAvailabilityRule>>().Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlexAvailabilityRule Rule { get; set; }
|
private PlexAvailabilityRule Rule { get; set; }
|
||||||
|
|
|
@ -15,13 +15,13 @@ namespace Ombi.Core.Tests.Rule.Search
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
ContextMock = new Mock<IRepository<RadarrCache>>();
|
ContextMock = new Mock<IExternalRepository<RadarrCache>>();
|
||||||
Rule = new RadarrCacheRule(ContextMock.Object);
|
Rule = new RadarrCacheRule(ContextMock.Object);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RadarrCacheRule Rule { get; set; }
|
private RadarrCacheRule Rule { get; set; }
|
||||||
private Mock<IRepository<RadarrCache>> ContextMock { get; set; }
|
private Mock<IExternalRepository<RadarrCache>> ContextMock { get; set; }
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Should_ReturnApproved_WhenMovieIsInRadarr()
|
public async Task Should_ReturnApproved_WhenMovieIsInRadarr()
|
||||||
|
|
26
src/Ombi.Core.Tests/StringHelperTests.cs
Normal file
26
src/Ombi.Core.Tests/StringHelperTests.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class StringHelperTests
|
||||||
|
{
|
||||||
|
[TestCaseSource(nameof(StripCharsData))]
|
||||||
|
public string StripCharacters(string str, char[] chars)
|
||||||
|
{
|
||||||
|
return str.StripCharacters(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<TestCaseData> StripCharsData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new TestCaseData("this!is^a*string",new []{'!','^','*'}).Returns("thisisastring").SetName("Basic Strip Multipe Chars");
|
||||||
|
yield return new TestCaseData("What is this madness'",new []{'\'','^','*'}).Returns("What is this madness").SetName("Basic Strip Multipe Chars");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Ombi.Api.Emby;
|
using Ombi.Api.Emby;
|
||||||
|
@ -101,6 +102,22 @@ namespace Ombi.Core.Authentication
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<OmbiUser> GetOmbiUserFromPlexToken(string plexToken)
|
||||||
|
{
|
||||||
|
var plexAccount = await _plexApi.GetAccount(plexToken);
|
||||||
|
|
||||||
|
// Check for a ombi user
|
||||||
|
if (plexAccount?.user != null)
|
||||||
|
{
|
||||||
|
var potentialOmbiUser = await Users.FirstOrDefaultAsync(x =>
|
||||||
|
x.ProviderUserId == plexAccount.user.id);
|
||||||
|
return potentialOmbiUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sign the user into plex and make sure we can get the authentication token.
|
/// Sign the user into plex and make sure we can get the authentication token.
|
||||||
/// <remarks>We do not check if the user is in the owners "friends" since they must have a local user account to get this far</remarks>
|
/// <remarks>We do not check if the user is in the owners "friends" since they must have a local user account to get this far</remarks>
|
||||||
|
|
|
@ -20,12 +20,6 @@ namespace Ombi.Core.Authentication
|
||||||
private readonly IPlexApi _api;
|
private readonly IPlexApi _api;
|
||||||
private readonly ISettingsService<CustomizationSettings> _customizationSettingsService;
|
private readonly ISettingsService<CustomizationSettings> _customizationSettingsService;
|
||||||
|
|
||||||
public async Task<OAuthPin> RequestPin()
|
|
||||||
{
|
|
||||||
var pin = await _api.CreatePin();
|
|
||||||
return pin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GetAccessTokenFromPin(int pinId)
|
public async Task<string> GetAccessTokenFromPin(int pinId)
|
||||||
{
|
{
|
||||||
var pin = await _api.GetPin(pinId);
|
var pin = await _api.GetPin(pinId);
|
||||||
|
@ -34,19 +28,6 @@ namespace Ombi.Core.Authentication
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pin.authToken.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
// Looks like we do not have a pin yet, we should retry a few times.
|
|
||||||
var retryCount = 0;
|
|
||||||
var retryMax = 5;
|
|
||||||
var retryWaitMs = 1000;
|
|
||||||
while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax)
|
|
||||||
{
|
|
||||||
retryCount++;
|
|
||||||
await Task.Delay(retryWaitMs);
|
|
||||||
pin = await _api.GetPin(pinId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pin.authToken;
|
return pin.authToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,17 +36,17 @@ namespace Ombi.Core.Authentication
|
||||||
return await _api.GetAccount(accessToken);
|
return await _api.GetAccount(accessToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null)
|
public async Task<Uri> GetOAuthUrl(string code, string websiteAddress = null)
|
||||||
{
|
{
|
||||||
var settings = await _customizationSettingsService.GetSettingsAsync();
|
var settings = await _customizationSettingsService.GetSettingsAsync();
|
||||||
var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false);
|
var url = await _api.GetOAuthUrl(code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl);
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress)
|
public async Task<Uri> GetWizardOAuthUrl(string code, string websiteAddress)
|
||||||
{
|
{
|
||||||
var url = _api.GetOAuthUrl(pinId, code, websiteAddress, true);
|
var url = await _api.GetOAuthUrl(code, websiteAddress);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ namespace Ombi.Core.Engine
|
||||||
protected IRequestServiceMain RequestService { get; }
|
protected IRequestServiceMain RequestService { get; }
|
||||||
protected IMovieRequestRepository MovieRepository => RequestService.MovieRequestService;
|
protected IMovieRequestRepository MovieRepository => RequestService.MovieRequestService;
|
||||||
protected ITvRequestRepository TvRepository => RequestService.TvRequestService;
|
protected ITvRequestRepository TvRepository => RequestService.TvRequestService;
|
||||||
|
protected IMusicRequestRepository MusicRepository => RequestService.MusicRequestRepository;
|
||||||
protected readonly ICacheService Cache;
|
protected readonly ICacheService Cache;
|
||||||
protected readonly ISettingsService<OmbiSettings> OmbiSettings;
|
protected readonly ISettingsService<OmbiSettings> OmbiSettings;
|
||||||
protected readonly IRepository<RequestSubscription> _subscriptionRepository;
|
protected readonly IRepository<RequestSubscription> _subscriptionRepository;
|
||||||
|
@ -156,6 +157,24 @@ namespace Ombi.Core.Engine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string defaultLangCode;
|
||||||
|
protected async Task<string> DefaultLanguageCode(string currentCode)
|
||||||
|
{
|
||||||
|
if (currentCode.HasValue())
|
||||||
|
{
|
||||||
|
return currentCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
var s = await GetOmbiSettings();
|
||||||
|
return s.DefaultLanguageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private OmbiSettings ombiSettings;
|
||||||
|
protected async Task<OmbiSettings> GetOmbiSettings()
|
||||||
|
{
|
||||||
|
return ombiSettings ?? (ombiSettings = await OmbiSettings.GetSettingsAsync());
|
||||||
|
}
|
||||||
|
|
||||||
public class HideResult
|
public class HideResult
|
||||||
{
|
{
|
||||||
public bool Hide { get; set; }
|
public bool Hide { get; set; }
|
||||||
|
|
106
src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs
Normal file
106
src/Ombi.Core/Engine/Demo/DemoMovieSearchEngine.cs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Ombi.Api.TheMovieDb;
|
||||||
|
using Ombi.Api.TheMovieDb.Models;
|
||||||
|
using Ombi.Config;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Core.Models.Search;
|
||||||
|
using Ombi.Core.Rule.Interfaces;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine.Demo
|
||||||
|
{
|
||||||
|
public class DemoMovieSearchEngine : MovieSearchEngine, IDemoMovieSearchEngine
|
||||||
|
{
|
||||||
|
public DemoMovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
|
||||||
|
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s,
|
||||||
|
IRepository<RequestSubscription> sub, IOptions<DemoLists> lists)
|
||||||
|
: base(identity, service, movApi, mapper, logger, r, um, mem, s, sub)
|
||||||
|
{
|
||||||
|
_demoLists = lists.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly DemoLists _demoLists;
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
|
||||||
|
{
|
||||||
|
var result = await MovieApi.SearchMovie(search, null, "en");
|
||||||
|
|
||||||
|
for (var i = 0; i < result.Count; i++)
|
||||||
|
{
|
||||||
|
if (!_demoLists.Movies.Contains(result[i].Id))
|
||||||
|
{
|
||||||
|
result.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(result.Count > 0)
|
||||||
|
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
|
||||||
|
{
|
||||||
|
var rand = new Random();
|
||||||
|
var responses = new List<SearchMovieViewModel>();
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var item = rand.Next(_demoLists.Movies.Length);
|
||||||
|
var movie = _demoLists.Movies[item];
|
||||||
|
if (responses.Any(x => x.Id == movie))
|
||||||
|
{
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var movieResult = await MovieApi.GetMovieInformationWithExtraInfo(movie);
|
||||||
|
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieResult);
|
||||||
|
|
||||||
|
responses.Add(await ProcessSingleMovie(viewMovie));
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
|
||||||
|
{
|
||||||
|
return await NowPlayingMovies();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
|
||||||
|
{
|
||||||
|
return await NowPlayingMovies();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
|
||||||
|
{
|
||||||
|
|
||||||
|
return await NowPlayingMovies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDemoMovieSearchEngine
|
||||||
|
{
|
||||||
|
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
|
||||||
|
|
||||||
|
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
|
||||||
|
|
||||||
|
Task<IEnumerable<SearchMovieViewModel>> Search(string search);
|
||||||
|
|
||||||
|
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
|
||||||
|
|
||||||
|
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
96
src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs
Normal file
96
src/Ombi.Core/Engine/Demo/DemoTvSearchEngine.cs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Ombi.Api.Trakt;
|
||||||
|
using Ombi.Api.TvMaze;
|
||||||
|
using Ombi.Config;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Core.Models.Search;
|
||||||
|
using Ombi.Core.Rule.Interfaces;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Core.Settings.Models.External;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine.Demo
|
||||||
|
{
|
||||||
|
public class DemoTvSearchEngine : TvSearchEngine, IDemoTvSearchEngine
|
||||||
|
{
|
||||||
|
|
||||||
|
public DemoTvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper,
|
||||||
|
ISettingsService<PlexSettings> plexSettings, ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo,
|
||||||
|
IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um, ICacheService memCache,
|
||||||
|
ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub, IOptions<DemoLists> lists)
|
||||||
|
: base(identity, service, tvMaze, mapper, plexSettings, embySettings, repo, embyRepo, trakt, r, um, memCache, s, sub)
|
||||||
|
{
|
||||||
|
_demoLists = lists.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly DemoLists _demoLists;
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string search)
|
||||||
|
{
|
||||||
|
var searchResult = await TvMazeApi.Search(search);
|
||||||
|
|
||||||
|
for (var i = 0; i < searchResult.Count; i++)
|
||||||
|
{
|
||||||
|
if (!_demoLists.TvShows.Contains(searchResult[i].show?.externals?.thetvdb ?? 0))
|
||||||
|
{
|
||||||
|
searchResult.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchResult != null)
|
||||||
|
{
|
||||||
|
var retVal = new List<SearchTvShowViewModel>();
|
||||||
|
foreach (var tvMazeSearch in searchResult)
|
||||||
|
{
|
||||||
|
if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
retVal.Add(ProcessResult(tvMazeSearch));
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchTvShowViewModel>> NowPlayingMovies()
|
||||||
|
{
|
||||||
|
var rand = new Random();
|
||||||
|
var responses = new List<SearchTvShowViewModel>();
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var item = rand.Next(_demoLists.TvShows.Length);
|
||||||
|
var tv = _demoLists.TvShows[item];
|
||||||
|
if (responses.Any(x => x.Id == tv))
|
||||||
|
{
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var movieResult = await TvMazeApi.ShowLookup(tv);
|
||||||
|
responses.Add(ProcessResult(movieResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IDemoTvSearchEngine
|
||||||
|
{
|
||||||
|
Task<IEnumerable<SearchTvShowViewModel>> Search(string search);
|
||||||
|
Task<IEnumerable<SearchTvShowViewModel>> NowPlayingMovies();
|
||||||
|
}
|
||||||
|
}
|
27
src/Ombi.Core/Engine/IMusicRequestEngine.cs
Normal file
27
src/Ombi.Core/Engine/IMusicRequestEngine.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Core.Models.UI;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Entities.Requests;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public interface IMusicRequestEngine
|
||||||
|
{
|
||||||
|
Task<RequestEngineResult>ApproveAlbum(AlbumRequest request);
|
||||||
|
Task<RequestEngineResult> ApproveAlbumById(int requestId);
|
||||||
|
Task<RequestEngineResult> DenyAlbumById(int modelId, string reason);
|
||||||
|
Task<IEnumerable<AlbumRequest>> GetRequests();
|
||||||
|
Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, OrderFilterModel orderFilter);
|
||||||
|
Task<int> GetTotal();
|
||||||
|
Task<RequestEngineResult> MarkAvailable(int modelId);
|
||||||
|
Task<RequestEngineResult> MarkUnavailable(int modelId);
|
||||||
|
Task RemoveAlbumRequest(int requestId);
|
||||||
|
Task<RequestEngineResult> RequestAlbum(MusicAlbumRequestViewModel model);
|
||||||
|
Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search);
|
||||||
|
Task<bool> UserHasRequest(string userId);
|
||||||
|
Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user = null);
|
||||||
|
}
|
||||||
|
}
|
19
src/Ombi.Core/Engine/IVoteEngine.cs
Normal file
19
src/Ombi.Core/Engine/IVoteEngine.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
using Ombi.Core.Models.UI;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public interface IVoteEngine
|
||||||
|
{
|
||||||
|
Task<VoteEngineResult> DownVote(int requestId, RequestType requestType);
|
||||||
|
Task<Votes> GetVoteForUser(int requestId, string userId);
|
||||||
|
IQueryable<Votes> GetVotes(int requestId, RequestType requestType);
|
||||||
|
Task RemoveCurrentVote(Votes currentVote);
|
||||||
|
Task<VoteEngineResult> UpVote(int requestId, RequestType requestType);
|
||||||
|
Task<List<VoteViewModel>> GetMovieViewModel();
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,11 +7,8 @@ using Ombi.Core.Models.Search;
|
||||||
using Ombi.Core.Rule.Interfaces;
|
using Ombi.Core.Rule.Interfaces;
|
||||||
using Ombi.Store.Entities.Requests;
|
using Ombi.Store.Entities.Requests;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Ombi.Core.Authentication;
|
using Ombi.Core.Authentication;
|
||||||
using Ombi.Helpers;
|
|
||||||
|
|
||||||
namespace Ombi.Core.Engine.Interfaces
|
namespace Ombi.Core.Engine.Interfaces
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,14 +10,15 @@ namespace Ombi.Core
|
||||||
|
|
||||||
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
|
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
|
||||||
|
|
||||||
Task<IEnumerable<SearchMovieViewModel>> Search(string search);
|
Task<IEnumerable<SearchMovieViewModel>> Search(string search, int? year, string languageCode);
|
||||||
|
|
||||||
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
|
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
|
||||||
|
|
||||||
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
|
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
|
||||||
|
|
||||||
Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId);
|
Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId, string langCode = null);
|
||||||
|
|
||||||
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId);
|
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode);
|
||||||
|
Task<IEnumerable<SearchMovieViewModel>> SearchActor(string search, string langaugeCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,12 +12,11 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
Task<IEnumerable<MovieRequests>> SearchMovieRequest(string search);
|
Task<IEnumerable<MovieRequests>> SearchMovieRequest(string search);
|
||||||
|
|
||||||
Task RemoveMovieRequest(int requestId);
|
Task RemoveMovieRequest(int requestId);
|
||||||
|
Task RemoveAllMovieRequests();
|
||||||
|
|
||||||
Task<MovieRequests> UpdateMovieRequest(MovieRequests request);
|
Task<MovieRequests> UpdateMovieRequest(MovieRequests request);
|
||||||
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
|
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
|
||||||
Task<RequestEngineResult> ApproveMovieById(int requestId);
|
Task<RequestEngineResult> ApproveMovieById(int requestId);
|
||||||
Task<RequestEngineResult> DenyMovieById(int modelId);
|
Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
16
src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs
Normal file
16
src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Api.Lidarr.Models;
|
||||||
|
using Ombi.Core.Models.Search;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public interface IMusicSearchEngine
|
||||||
|
{
|
||||||
|
Task<ArtistResult> GetAlbumArtist(string foreignArtistId);
|
||||||
|
Task<ArtistResult> GetArtist(int artistId);
|
||||||
|
Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId);
|
||||||
|
Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search);
|
||||||
|
Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Models;
|
||||||
using Ombi.Core.Models.Requests;
|
using Ombi.Core.Models.Requests;
|
||||||
using Ombi.Core.Models.UI;
|
using Ombi.Core.Models.UI;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
|
@ -22,5 +23,6 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
Task<int> GetTotal();
|
Task<int> GetTotal();
|
||||||
Task UnSubscribeRequest(int requestId, RequestType type);
|
Task UnSubscribeRequest(int requestId, RequestType type);
|
||||||
Task SubscribeToRequest(int requestId, RequestType type);
|
Task SubscribeToRequest(int requestId, RequestType type);
|
||||||
|
Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -12,16 +12,16 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
Task RemoveTvRequest(int requestId);
|
Task RemoveTvRequest(int requestId);
|
||||||
Task<TvRequests> GetTvRequest(int requestId);
|
Task<TvRequests> GetTvRequest(int requestId);
|
||||||
Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
|
Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
|
||||||
Task<RequestEngineResult> DenyChildRequest(int requestId);
|
Task<RequestEngineResult> DenyChildRequest(int requestId, string reason);
|
||||||
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
|
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
|
||||||
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
|
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
|
||||||
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search);
|
|
||||||
Task<TvRequests> UpdateTvRequest(TvRequests request);
|
Task<TvRequests> UpdateTvRequest(TvRequests request);
|
||||||
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position);
|
|
||||||
Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId);
|
Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId);
|
||||||
Task<ChildRequests> UpdateChildRequest(ChildRequests request);
|
Task<ChildRequests> UpdateChildRequest(ChildRequests request);
|
||||||
Task RemoveTvChild(int requestId);
|
Task RemoveTvChild(int requestId);
|
||||||
Task<RequestEngineResult> ApproveChildRequest(int id);
|
Task<RequestEngineResult> ApproveChildRequest(int id);
|
||||||
Task<IEnumerable<TvRequests>> GetRequestsLite();
|
Task<IEnumerable<TvRequests>> GetRequestsLite();
|
||||||
|
Task UpdateQualityProfile(int requestId, int profileId);
|
||||||
|
Task UpdateRootPath(int requestId, int rootPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,16 +7,10 @@ namespace Ombi.Core.Engine.Interfaces
|
||||||
public interface ITvSearchEngine
|
public interface ITvSearchEngine
|
||||||
{
|
{
|
||||||
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
|
Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm);
|
||||||
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm);
|
|
||||||
Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid);
|
|
||||||
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
|
Task<SearchTvShowViewModel> GetShowInformation(int tvdbid);
|
||||||
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree();
|
|
||||||
Task<IEnumerable<SearchTvShowViewModel>> Popular();
|
Task<IEnumerable<SearchTvShowViewModel>> Popular();
|
||||||
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree();
|
|
||||||
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
|
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
|
||||||
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree();
|
|
||||||
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
|
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
|
||||||
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree();
|
|
||||||
Task<IEnumerable<SearchTvShowViewModel>> Trending();
|
Task<IEnumerable<SearchTvShowViewModel>> Trending();
|
||||||
}
|
}
|
||||||
}
|
}
|
9
src/Ombi.Core/Engine/Interfaces/IUserStatsEngine.cs
Normal file
9
src/Ombi.Core/Engine/Interfaces/IUserStatsEngine.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public interface IUserStatsEngine
|
||||||
|
{
|
||||||
|
Task<UserStatsSummary> GetSummary(SummaryRequest request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ using Ombi.Core.Settings;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Store.Entities.Requests;
|
using Ombi.Store.Entities.Requests;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
|
||||||
namespace Ombi.Core.Engine
|
namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
|
@ -50,7 +51,7 @@ namespace Ombi.Core.Engine
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)
|
public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)
|
||||||
{
|
{
|
||||||
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId);
|
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId, model.LanguageCode);
|
||||||
if (movieInfo == null || movieInfo.Id == 0)
|
if (movieInfo == null || movieInfo.Id == 0)
|
||||||
{
|
{
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
|
@ -81,7 +82,9 @@ namespace Ombi.Core.Engine
|
||||||
RequestedDate = DateTime.UtcNow,
|
RequestedDate = DateTime.UtcNow,
|
||||||
Approved = false,
|
Approved = false,
|
||||||
RequestedUserId = userDetails.Id,
|
RequestedUserId = userDetails.Id,
|
||||||
Background = movieInfo.BackdropPath
|
Background = movieInfo.BackdropPath,
|
||||||
|
LangCode = model.LanguageCode,
|
||||||
|
RequestedByAlias = model.RequestedByAlias
|
||||||
};
|
};
|
||||||
|
|
||||||
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
||||||
|
@ -304,7 +307,7 @@ namespace Ombi.Core.Engine
|
||||||
return await ApproveMovie(request);
|
return await ApproveMovie(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestEngineResult> DenyMovieById(int modelId)
|
public async Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason)
|
||||||
{
|
{
|
||||||
var request = await MovieRepository.Find(modelId);
|
var request = await MovieRepository.Find(modelId);
|
||||||
if (request == null)
|
if (request == null)
|
||||||
|
@ -316,12 +319,14 @@ namespace Ombi.Core.Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Denied = true;
|
request.Denied = true;
|
||||||
|
request.DeniedReason = denyReason;
|
||||||
// We are denying a request
|
// We are denying a request
|
||||||
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
|
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
|
||||||
await MovieRepository.Update(request);
|
await MovieRepository.Update(request);
|
||||||
|
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
{
|
{
|
||||||
|
Result = true,
|
||||||
Message = "Request successfully deleted",
|
Message = "Request successfully deleted",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -336,6 +341,7 @@ namespace Ombi.Core.Engine
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request.MarkedAsApproved = DateTime.Now;
|
||||||
request.Approved = true;
|
request.Approved = true;
|
||||||
request.Denied = false;
|
request.Denied = false;
|
||||||
await MovieRepository.Update(request);
|
await MovieRepository.Update(request);
|
||||||
|
@ -414,6 +420,12 @@ namespace Ombi.Core.Engine
|
||||||
await MovieRepository.Delete(request);
|
await MovieRepository.Delete(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task RemoveAllMovieRequests()
|
||||||
|
{
|
||||||
|
var request = MovieRepository.GetAll();
|
||||||
|
await MovieRepository.DeleteRange(request);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> UserHasRequest(string userId)
|
public async Task<bool> UserHasRequest(string userId)
|
||||||
{
|
{
|
||||||
return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId);
|
return await MovieRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId);
|
||||||
|
@ -452,6 +464,7 @@ namespace Ombi.Core.Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Available = true;
|
request.Available = true;
|
||||||
|
request.MarkedAsAvailable = DateTime.Now;
|
||||||
NotificationHelper.Notify(request, NotificationType.RequestAvailable);
|
NotificationHelper.Notify(request, NotificationType.RequestAvailable);
|
||||||
await MovieRepository.Update(request);
|
await MovieRepository.Update(request);
|
||||||
|
|
||||||
|
@ -480,7 +493,51 @@ namespace Ombi.Core.Engine
|
||||||
RequestType = RequestType.Movie,
|
RequestType = RequestType.Movie,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"};
|
return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!", RequestId = model.Id};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
user = await GetUser();
|
||||||
|
|
||||||
|
// If user is still null after attempting to get the logged in user, return null.
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = user.MovieRequestLimit ?? 0;
|
||||||
|
|
||||||
|
if (limit <= 0)
|
||||||
|
{
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = false,
|
||||||
|
Limit = 0,
|
||||||
|
Remaining = 0,
|
||||||
|
NextRequest = DateTime.Now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
IQueryable<RequestLog> log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.Movie);
|
||||||
|
|
||||||
|
int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7));
|
||||||
|
|
||||||
|
DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7))
|
||||||
|
.OrderBy(x => x.RequestDate)
|
||||||
|
.Select(x => x.RequestDate)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = true,
|
||||||
|
Limit = limit,
|
||||||
|
Remaining = count,
|
||||||
|
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,23 +1,22 @@
|
||||||
using System;
|
using AutoMapper;
|
||||||
using AutoMapper;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Api.TheMovieDb;
|
using Ombi.Api.TheMovieDb;
|
||||||
using Ombi.Api.TheMovieDb.Models;
|
using Ombi.Api.TheMovieDb.Models;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
using Ombi.Core.Models.Requests;
|
using Ombi.Core.Models.Requests;
|
||||||
using Ombi.Core.Models.Search;
|
using Ombi.Core.Models.Search;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Principal;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Ombi.Core.Rule.Interfaces;
|
using Ombi.Core.Rule.Interfaces;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Ombi.Core.Authentication;
|
|
||||||
using Ombi.Core.Settings;
|
using Ombi.Core.Settings;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ombi.Core.Engine
|
namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
|
@ -32,18 +31,21 @@ namespace Ombi.Core.Engine
|
||||||
Logger = logger;
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IMovieDbApi MovieApi { get; }
|
protected IMovieDbApi MovieApi { get; }
|
||||||
private IMapper Mapper { get; }
|
protected IMapper Mapper { get; }
|
||||||
private ILogger<MovieSearchEngine> Logger { get; }
|
private ILogger<MovieSearchEngine> Logger { get; }
|
||||||
|
|
||||||
|
protected const int MovieLimit = 10;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lookups the imdb information.
|
/// Lookups the imdb information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="theMovieDbId">The movie database identifier.</param>
|
/// <param name="theMovieDbId">The movie database identifier.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId)
|
public async Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId, string langCode = null)
|
||||||
{
|
{
|
||||||
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId);
|
langCode = await DefaultLanguageCode(langCode);
|
||||||
|
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId, langCode);
|
||||||
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo);
|
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo);
|
||||||
|
|
||||||
return await ProcessSingleMovie(viewMovie, true);
|
return await ProcessSingleMovie(viewMovie, true);
|
||||||
|
@ -52,32 +54,58 @@ namespace Ombi.Core.Engine
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Searches the specified movie.
|
/// Searches the specified movie.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="search">The search.</param>
|
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search, int? year, string langaugeCode)
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
|
|
||||||
{
|
{
|
||||||
var result = await MovieApi.SearchMovie(search);
|
langaugeCode = await DefaultLanguageCode(langaugeCode);
|
||||||
|
var result = await MovieApi.SearchMovie(search, year, langaugeCode);
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Search Result: {result}", result);
|
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<SearchMovieViewModel>> SearchActor(string search, string langaugeCode)
|
||||||
|
{
|
||||||
|
langaugeCode = await DefaultLanguageCode(langaugeCode);
|
||||||
|
var people = await MovieApi.SearchByActor(search, langaugeCode);
|
||||||
|
var person = people?.results?.Count > 0 ? people.results.FirstOrDefault() : null;
|
||||||
|
|
||||||
|
var resultSet = new List<SearchMovieViewModel>();
|
||||||
|
if (person == null)
|
||||||
|
{
|
||||||
|
return resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get this person movie credits
|
||||||
|
var credits = await MovieApi.GetActorMovieCredits(person.id, langaugeCode);
|
||||||
|
// Grab results from both cast and crew, prefer items in cast. we can handle directors like this.
|
||||||
|
var movieResults = (from role in credits.cast select new { Id = role.id, Title = role.title, ReleaseDate = role.release_date }).ToList();
|
||||||
|
movieResults.AddRange((from job in credits.crew select new { Id = job.id, Title = job.title, ReleaseDate = job.release_date }).ToList());
|
||||||
|
|
||||||
|
movieResults = movieResults.Take(10).ToList();
|
||||||
|
foreach (var movieResult in movieResults)
|
||||||
|
{
|
||||||
|
resultSet.Add(await LookupImdbInformation(movieResult.Id, langaugeCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSet;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get similar movies to the id passed in
|
/// Get similar movies to the id passed in
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="theMovieDbId"></param>
|
/// <param name="theMovieDbId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId)
|
public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode)
|
||||||
{
|
{
|
||||||
var result = await MovieApi.SimilarMovies(theMovieDbId);
|
langCode = await DefaultLanguageCode(langCode);
|
||||||
|
var result = await MovieApi.SimilarMovies(theMovieDbId, langCode);
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Search Result: {result}", result);
|
Logger.LogDebug("Search Result: {result}", result);
|
||||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -88,11 +116,15 @@ namespace Ombi.Core.Engine
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
|
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
|
||||||
{
|
{
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () => await MovieApi.PopularMovies(), DateTime.Now.AddHours(12));
|
|
||||||
|
var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () =>
|
||||||
|
{
|
||||||
|
var langCode = await DefaultLanguageCode(null);
|
||||||
|
return await MovieApi.PopularMovies(langCode);
|
||||||
|
}, DateTime.Now.AddHours(12));
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Search Result: {result}", result);
|
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -103,11 +135,14 @@ namespace Ombi.Core.Engine
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
|
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
|
||||||
{
|
{
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () => await MovieApi.TopRated(), DateTime.Now.AddHours(12));
|
var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () =>
|
||||||
|
{
|
||||||
|
var langCode = await DefaultLanguageCode(null);
|
||||||
|
return await MovieApi.TopRated(langCode);
|
||||||
|
}, DateTime.Now.AddHours(12));
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Search Result: {result}", result);
|
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -118,11 +153,15 @@ namespace Ombi.Core.Engine
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
|
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
|
||||||
{
|
{
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () => await MovieApi.Upcoming(), DateTime.Now.AddHours(12));
|
var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () =>
|
||||||
|
{
|
||||||
|
var langCode = await DefaultLanguageCode(null);
|
||||||
|
return await MovieApi.Upcoming(langCode);
|
||||||
|
}, DateTime.Now.AddHours(12));
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Search Result: {result}", result);
|
Logger.LogDebug("Search Result: {result}", result);
|
||||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -133,16 +172,19 @@ namespace Ombi.Core.Engine
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
|
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
|
||||||
{
|
{
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => await MovieApi.NowPlaying(), DateTime.Now.AddHours(12));
|
var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () =>
|
||||||
|
{
|
||||||
|
var langCode = await DefaultLanguageCode(null);
|
||||||
|
return await MovieApi.NowPlaying(langCode);
|
||||||
|
}, DateTime.Now.AddHours(12));
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Search Result: {result}", result);
|
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
|
protected async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(
|
||||||
IEnumerable<MovieSearchResult> movies)
|
IEnumerable<MovieSearchResult> movies)
|
||||||
{
|
{
|
||||||
var viewMovies = new List<SearchMovieViewModel>();
|
var viewMovies = new List<SearchMovieViewModel>();
|
||||||
|
@ -153,16 +195,17 @@ namespace Ombi.Core.Engine
|
||||||
return viewMovies;
|
return viewMovies;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
|
protected async Task<SearchMovieViewModel> ProcessSingleMovie(SearchMovieViewModel viewMovie, bool lookupExtraInfo = false)
|
||||||
{
|
{
|
||||||
if (lookupExtraInfo)
|
if (lookupExtraInfo && viewMovie.ImdbId.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id);
|
var showInfo = await MovieApi.GetMovieInformation(viewMovie.Id);
|
||||||
viewMovie.Id = showInfo.Id; // TheMovieDbId
|
viewMovie.Id = showInfo.Id; // TheMovieDbId
|
||||||
viewMovie.ImdbId = showInfo.ImdbId;
|
viewMovie.ImdbId = showInfo.ImdbId;
|
||||||
|
}
|
||||||
|
|
||||||
var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
var usDates = viewMovie.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
||||||
viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate;
|
viewMovie.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate;
|
||||||
}
|
|
||||||
|
|
||||||
viewMovie.TheMovieDbId = viewMovie.Id.ToString();
|
viewMovie.TheMovieDbId = viewMovie.Id.ToString();
|
||||||
|
|
||||||
|
@ -178,9 +221,13 @@ namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
// Check if this user requested it
|
// Check if this user requested it
|
||||||
var user = await GetUser();
|
var user = await GetUser();
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
var request = await RequestService.MovieRequestService.GetAll()
|
var request = await RequestService.MovieRequestService.GetAll()
|
||||||
.AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id);
|
.AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id);
|
||||||
if (request)
|
if (request || viewModel.Available)
|
||||||
{
|
{
|
||||||
viewModel.ShowSubscribe = false;
|
viewModel.ShowSubscribe = false;
|
||||||
}
|
}
|
||||||
|
|
505
src/Ombi.Core/Engine/MusicRequestEngine.cs
Normal file
505
src/Ombi.Core/Engine/MusicRequestEngine.cs
Normal file
|
@ -0,0 +1,505 @@
|
||||||
|
using Ombi.Api.TheMovieDb;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ombi.Api.Lidarr;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Engine.Interfaces;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
using Ombi.Core.Models.UI;
|
||||||
|
using Ombi.Core.Rule.Interfaces;
|
||||||
|
using Ombi.Core.Senders;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Settings.Settings.Models.External;
|
||||||
|
using Ombi.Store.Entities.Requests;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public class MusicRequestEngine : BaseMediaEngine, IMusicRequestEngine
|
||||||
|
{
|
||||||
|
public MusicRequestEngine(IRequestServiceMain requestService, IPrincipal user,
|
||||||
|
INotificationHelper helper, IRuleEvaluator r, ILogger<MusicRequestEngine> log,
|
||||||
|
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache,
|
||||||
|
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub, ILidarrApi lidarr,
|
||||||
|
ISettingsService<LidarrSettings> lidarrSettings, IMusicSender sender)
|
||||||
|
: base(user, requestService, r, manager, cache, ombiSettings, sub)
|
||||||
|
{
|
||||||
|
NotificationHelper = helper;
|
||||||
|
_musicSender = sender;
|
||||||
|
Logger = log;
|
||||||
|
_requestLog = rl;
|
||||||
|
_lidarrApi = lidarr;
|
||||||
|
_lidarrSettings = lidarrSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
private INotificationHelper NotificationHelper { get; }
|
||||||
|
//private IMovieSender Sender { get; }
|
||||||
|
private ILogger Logger { get; }
|
||||||
|
private readonly IRepository<RequestLog> _requestLog;
|
||||||
|
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
|
||||||
|
private readonly ILidarrApi _lidarrApi;
|
||||||
|
private readonly IMusicSender _musicSender;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Requests the Album.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<RequestEngineResult> RequestAlbum(MusicAlbumRequestViewModel model)
|
||||||
|
{
|
||||||
|
var s = await _lidarrSettings.GetSettingsAsync();
|
||||||
|
var album = await _lidarrApi.GetAlbumByForeignId(model.ForeignAlbumId, s.ApiKey, s.FullUri);
|
||||||
|
if (album == null)
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Result = false,
|
||||||
|
Message = "There was an issue adding this album!",
|
||||||
|
ErrorMessage = "Please try again later"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var userDetails = await GetUser();
|
||||||
|
|
||||||
|
var requestModel = new AlbumRequest
|
||||||
|
{
|
||||||
|
ForeignAlbumId = model.ForeignAlbumId,
|
||||||
|
ArtistName = album.artist?.artistName,
|
||||||
|
ReleaseDate = album.releaseDate,
|
||||||
|
RequestedDate = DateTime.Now,
|
||||||
|
RequestType = RequestType.Album,
|
||||||
|
Rating = album.ratings?.value ?? 0m,
|
||||||
|
RequestedUserId = userDetails.Id,
|
||||||
|
Title = album.title,
|
||||||
|
Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
|
||||||
|
Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url,
|
||||||
|
ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty,
|
||||||
|
RequestedByAlias = model.RequestedByAlias
|
||||||
|
};
|
||||||
|
if (requestModel.Cover.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
requestModel.Cover = album.remoteCover;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ruleResults = (await RunRequestRules(requestModel)).ToList();
|
||||||
|
if (ruleResults.Any(x => !x.Success))
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
ErrorMessage = ruleResults.FirstOrDefault(x => x.Message.HasValue()).Message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestModel.Approved) // The rules have auto approved this
|
||||||
|
{
|
||||||
|
var requestEngineResult = await AddAlbumRequest(requestModel);
|
||||||
|
if (requestEngineResult.Result)
|
||||||
|
{
|
||||||
|
var result = await ApproveAlbum(requestModel);
|
||||||
|
if (result.IsError)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Tried auto sending Album but failed. Message: {0}", result.Message);
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Message = result.Message,
|
||||||
|
ErrorMessage = result.Message,
|
||||||
|
Result = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestEngineResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no providers then it's successful but album has not been sent
|
||||||
|
}
|
||||||
|
|
||||||
|
return await AddAlbumRequest(requestModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the requests.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="count">The count.</param>
|
||||||
|
/// <param name="position">The position.</param>
|
||||||
|
/// <param name="orderFilter">The order/filter type.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position,
|
||||||
|
OrderFilterModel orderFilter)
|
||||||
|
{
|
||||||
|
var shouldHide = await HideFromOtherUsers();
|
||||||
|
IQueryable<AlbumRequest> allRequests;
|
||||||
|
if (shouldHide.Hide)
|
||||||
|
{
|
||||||
|
allRequests =
|
||||||
|
MusicRepository.GetWithUser(shouldHide
|
||||||
|
.UserId); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allRequests =
|
||||||
|
MusicRepository
|
||||||
|
.GetWithUser(); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (orderFilter.AvailabilityFilter)
|
||||||
|
{
|
||||||
|
case FilterType.None:
|
||||||
|
break;
|
||||||
|
case FilterType.Available:
|
||||||
|
allRequests = allRequests.Where(x => x.Available);
|
||||||
|
break;
|
||||||
|
case FilterType.NotAvailable:
|
||||||
|
allRequests = allRequests.Where(x => !x.Available);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (orderFilter.StatusFilter)
|
||||||
|
{
|
||||||
|
case FilterType.None:
|
||||||
|
break;
|
||||||
|
case FilterType.Approved:
|
||||||
|
allRequests = allRequests.Where(x => x.Approved);
|
||||||
|
break;
|
||||||
|
case FilterType.Processing:
|
||||||
|
allRequests = allRequests.Where(x => x.Approved && !x.Available);
|
||||||
|
break;
|
||||||
|
case FilterType.PendingApproval:
|
||||||
|
allRequests = allRequests.Where(x => !x.Approved && !x.Available && !(x.Denied ?? false));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var total = allRequests.Count();
|
||||||
|
|
||||||
|
var requests = await (OrderAlbums(allRequests, orderFilter.OrderType)).Skip(position).Take(count)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
requests.ForEach(async x =>
|
||||||
|
{
|
||||||
|
await CheckForSubscription(shouldHide, x);
|
||||||
|
});
|
||||||
|
return new RequestsViewModel<AlbumRequest>
|
||||||
|
{
|
||||||
|
Collection = requests,
|
||||||
|
Total = total
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IQueryable<AlbumRequest> OrderAlbums(IQueryable<AlbumRequest> allRequests, OrderType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case OrderType.RequestedDateAsc:
|
||||||
|
return allRequests.OrderBy(x => x.RequestedDate);
|
||||||
|
case OrderType.RequestedDateDesc:
|
||||||
|
return allRequests.OrderByDescending(x => x.RequestedDate);
|
||||||
|
case OrderType.TitleAsc:
|
||||||
|
return allRequests.OrderBy(x => x.Title);
|
||||||
|
case OrderType.TitleDesc:
|
||||||
|
return allRequests.OrderByDescending(x => x.Title);
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> GetTotal()
|
||||||
|
{
|
||||||
|
var shouldHide = await HideFromOtherUsers();
|
||||||
|
if (shouldHide.Hide)
|
||||||
|
{
|
||||||
|
return await MusicRepository.GetWithUser(shouldHide.UserId).CountAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return await MusicRepository.GetWithUser().CountAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the requests.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IEnumerable<AlbumRequest>> GetRequests()
|
||||||
|
{
|
||||||
|
var shouldHide = await HideFromOtherUsers();
|
||||||
|
List<AlbumRequest> allRequests;
|
||||||
|
if (shouldHide.Hide)
|
||||||
|
{
|
||||||
|
allRequests = await MusicRepository.GetWithUser(shouldHide.UserId).ToListAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allRequests = await MusicRepository.GetWithUser().ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
allRequests.ForEach(async x =>
|
||||||
|
{
|
||||||
|
await CheckForSubscription(shouldHide, x);
|
||||||
|
});
|
||||||
|
return allRequests;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckForSubscription(HideResult shouldHide, AlbumRequest x)
|
||||||
|
{
|
||||||
|
if (shouldHide.UserId == x.RequestedUserId)
|
||||||
|
{
|
||||||
|
x.ShowSubscribe = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
x.ShowSubscribe = true;
|
||||||
|
var sub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(s =>
|
||||||
|
s.UserId == shouldHide.UserId && s.RequestId == x.Id && s.RequestType == RequestType.Album);
|
||||||
|
x.Subscribed = sub != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches the album request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="search">The search.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IEnumerable<AlbumRequest>> SearchAlbumRequest(string search)
|
||||||
|
{
|
||||||
|
var shouldHide = await HideFromOtherUsers();
|
||||||
|
List<AlbumRequest> allRequests;
|
||||||
|
if (shouldHide.Hide)
|
||||||
|
{
|
||||||
|
allRequests = await MusicRepository.GetWithUser(shouldHide.UserId).ToListAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allRequests = await MusicRepository.GetWithUser().ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList();
|
||||||
|
results.ForEach(async x =>
|
||||||
|
{
|
||||||
|
await CheckForSubscription(shouldHide, x);
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestEngineResult> ApproveAlbumById(int requestId)
|
||||||
|
{
|
||||||
|
var request = await MusicRepository.Find(requestId);
|
||||||
|
return await ApproveAlbum(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestEngineResult> DenyAlbumById(int modelId, string reason)
|
||||||
|
{
|
||||||
|
var request = await MusicRepository.Find(modelId);
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request does not exist"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Denied = true;
|
||||||
|
request.DeniedReason = reason;
|
||||||
|
// We are denying a request
|
||||||
|
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
|
||||||
|
await MusicRepository.Update(request);
|
||||||
|
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Message = "Request successfully deleted",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestEngineResult> ApproveAlbum(AlbumRequest request)
|
||||||
|
{
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request does not exist"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
request.MarkedAsApproved = DateTime.Now;
|
||||||
|
request.Approved = true;
|
||||||
|
request.Denied = false;
|
||||||
|
await MusicRepository.Update(request);
|
||||||
|
|
||||||
|
|
||||||
|
var canNotify = await RunSpecificRule(request, SpecificRules.CanSendNotification);
|
||||||
|
if (canNotify.Success)
|
||||||
|
{
|
||||||
|
NotificationHelper.Notify(request, NotificationType.RequestApproved);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.Approved)
|
||||||
|
{
|
||||||
|
var result = await _musicSender.Send(request);
|
||||||
|
if (result.Success && result.Sent)
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Result = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
Logger.LogWarning("Tried auto sending album but failed. Message: {0}", result.Message);
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Message = result.Message,
|
||||||
|
ErrorMessage = result.Message,
|
||||||
|
Result = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no providers then it's successful but movie has not been sent
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Result = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the Album request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestId">The request identifier.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task RemoveAlbumRequest(int requestId)
|
||||||
|
{
|
||||||
|
var request = await MusicRepository.GetAll().FirstOrDefaultAsync(x => x.Id == requestId);
|
||||||
|
await MusicRepository.Delete(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> UserHasRequest(string userId)
|
||||||
|
{
|
||||||
|
return await MusicRepository.GetAll().AnyAsync(x => x.RequestedUserId == userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestEngineResult> MarkUnavailable(int modelId)
|
||||||
|
{
|
||||||
|
var request = await MusicRepository.Find(modelId);
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request does not exist"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Available = false;
|
||||||
|
await MusicRepository.Update(request);
|
||||||
|
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Message = "Request is now unavailable",
|
||||||
|
Result = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
user = await GetUser();
|
||||||
|
|
||||||
|
// If user is still null after attempting to get the logged in user, return null.
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = user.MusicRequestLimit ?? 0;
|
||||||
|
|
||||||
|
if (limit <= 0)
|
||||||
|
{
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = false,
|
||||||
|
Limit = 0,
|
||||||
|
Remaining = 0,
|
||||||
|
NextRequest = DateTime.Now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
IQueryable<RequestLog> log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.Album);
|
||||||
|
|
||||||
|
int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7));
|
||||||
|
|
||||||
|
DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7))
|
||||||
|
.OrderBy(x => x.RequestDate)
|
||||||
|
.Select(x => x.RequestDate)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = true,
|
||||||
|
Limit = limit,
|
||||||
|
Remaining = count,
|
||||||
|
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestEngineResult> MarkAvailable(int modelId)
|
||||||
|
{
|
||||||
|
var request = await MusicRepository.Find(modelId);
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
ErrorMessage = "Request does not exist"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Available = true;
|
||||||
|
request.MarkedAsAvailable = DateTime.Now;
|
||||||
|
NotificationHelper.Notify(request, NotificationType.RequestAvailable);
|
||||||
|
await MusicRepository.Update(request);
|
||||||
|
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
Message = "Request is now available",
|
||||||
|
Result = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RequestEngineResult> AddAlbumRequest(AlbumRequest model)
|
||||||
|
{
|
||||||
|
await MusicRepository.Add(model);
|
||||||
|
|
||||||
|
var result = await RunSpecificRule(model, SpecificRules.CanSendNotification);
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
NotificationHelper.NewRequest(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
await _requestLog.Add(new RequestLog
|
||||||
|
{
|
||||||
|
UserId = (await GetUser()).Id,
|
||||||
|
RequestDate = DateTime.UtcNow,
|
||||||
|
RequestId = model.Id,
|
||||||
|
RequestType = RequestType.Album,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new RequestEngineResult { Result = true, Message = $"{model.Title} has been successfully added!", RequestId = model.Id };
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
219
src/Ombi.Core/Engine/MusicSearchEngine.cs
Normal file
219
src/Ombi.Core/Engine/MusicSearchEngine.cs
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
using System;
|
||||||
|
using AutoMapper;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ombi.Api.TheMovieDb;
|
||||||
|
using Ombi.Api.TheMovieDb.Models;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Core.Models.Search;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Core.Rule.Interfaces;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Ombi.Api.Lidarr;
|
||||||
|
using Ombi.Api.Lidarr.Models;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Settings.Settings.Models.External;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public class MusicSearchEngine : BaseMediaEngine, IMusicSearchEngine
|
||||||
|
{
|
||||||
|
public MusicSearchEngine(IPrincipal identity, IRequestServiceMain service, ILidarrApi lidarrApi, IMapper mapper,
|
||||||
|
ILogger<MusicSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub,
|
||||||
|
ISettingsService<LidarrSettings> lidarrSettings)
|
||||||
|
: base(identity, service, r, um, mem, s, sub)
|
||||||
|
{
|
||||||
|
_lidarrApi = lidarrApi;
|
||||||
|
_lidarrSettings = lidarrSettings;
|
||||||
|
Mapper = mapper;
|
||||||
|
Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ILidarrApi _lidarrApi;
|
||||||
|
private IMapper Mapper { get; }
|
||||||
|
private ILogger Logger { get; }
|
||||||
|
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches the specified album.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="search">The search.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search)
|
||||||
|
{
|
||||||
|
var settings = await GetSettings();
|
||||||
|
var result = await _lidarrApi.AlbumLookup(search, settings.ApiKey, settings.FullUri);
|
||||||
|
var vm = new List<SearchAlbumViewModel>();
|
||||||
|
foreach (var r in result)
|
||||||
|
{
|
||||||
|
vm.Add(await MapIntoAlbumVm(r, settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Searches the specified artist
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="search">The search.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search)
|
||||||
|
{
|
||||||
|
var settings = await GetSettings();
|
||||||
|
var result = await _lidarrApi.ArtistLookup(search, settings.ApiKey, settings.FullUri);
|
||||||
|
|
||||||
|
var vm = new List<SearchArtistViewModel>();
|
||||||
|
foreach (var r in result)
|
||||||
|
{
|
||||||
|
vm.Add(await MapIntoArtistVm(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all albums by the specified artist
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="foreignArtistId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId)
|
||||||
|
{
|
||||||
|
var settings = await GetSettings();
|
||||||
|
var result = await _lidarrApi.GetAlbumsByArtist(foreignArtistId);
|
||||||
|
// We do not want any Singles (This will include EP's)
|
||||||
|
var albumsOnly =
|
||||||
|
result.Albums.Where(x => !x.Type.Equals("Single", StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
var vm = new List<SearchAlbumViewModel>();
|
||||||
|
foreach (var album in albumsOnly)
|
||||||
|
{
|
||||||
|
vm.Add(await MapIntoAlbumVm(album, result.Id, result.ArtistName, settings));
|
||||||
|
}
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the artist that produced the album
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="foreignArtistId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<ArtistResult> GetAlbumArtist(string foreignArtistId)
|
||||||
|
{
|
||||||
|
var settings = await GetSettings();
|
||||||
|
return await _lidarrApi.GetArtistByForeignId(foreignArtistId, settings.ApiKey, settings.FullUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ArtistResult> GetArtist(int artistId)
|
||||||
|
{
|
||||||
|
var settings = await GetSettings();
|
||||||
|
return await _lidarrApi.GetArtist(artistId, settings.ApiKey, settings.FullUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<SearchArtistViewModel> MapIntoArtistVm(ArtistLookup a)
|
||||||
|
{
|
||||||
|
var vm = new SearchArtistViewModel
|
||||||
|
{
|
||||||
|
ArtistName = a.artistName,
|
||||||
|
ArtistType = a.artistType,
|
||||||
|
Banner = a.images?.FirstOrDefault(x => x.coverType.Equals("banner"))?.url,
|
||||||
|
Logo = a.images?.FirstOrDefault(x => x.coverType.Equals("logo"))?.url,
|
||||||
|
CleanName = a.cleanName,
|
||||||
|
Disambiguation = a.disambiguation,
|
||||||
|
ForignArtistId = a.foreignArtistId,
|
||||||
|
Links = a.links,
|
||||||
|
Overview = a.overview,
|
||||||
|
};
|
||||||
|
|
||||||
|
var poster = a.images?.FirstOrDefault(x => x.coverType.Equals("poaster"));
|
||||||
|
if (poster == null)
|
||||||
|
{
|
||||||
|
vm.Poster = a.remotePoster;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist);
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(AlbumLookup a, LidarrSettings settings)
|
||||||
|
{
|
||||||
|
var vm = new SearchAlbumViewModel
|
||||||
|
{
|
||||||
|
ForeignAlbumId = a.foreignAlbumId,
|
||||||
|
Monitored = a.monitored,
|
||||||
|
Rating = a.ratings?.value ?? 0m,
|
||||||
|
ReleaseDate = a.releaseDate,
|
||||||
|
Title = a.title,
|
||||||
|
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url
|
||||||
|
};
|
||||||
|
if (a.artistId > 0)
|
||||||
|
{
|
||||||
|
//TODO THEY HAVE FIXED THIS IN DEV
|
||||||
|
// The JSON is different for some stupid reason
|
||||||
|
// Need to lookup the artist now and all the images -.-"
|
||||||
|
var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri);
|
||||||
|
vm.ArtistName = artist.artistName;
|
||||||
|
vm.ForeignArtistId = artist.foreignArtistId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
vm.ForeignArtistId = a.artist?.foreignArtistId;
|
||||||
|
vm.ArtistName = a.artist?.artistName;
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url;
|
||||||
|
if (vm.Cover.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
vm.Cover = a.remoteCover;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
|
||||||
|
|
||||||
|
await RunSearchRules(vm);
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(Album a, string artistId, string artistName, LidarrSettings settings)
|
||||||
|
{
|
||||||
|
var fullAlbum = await _lidarrApi.GetAlbumByForeignId(a.Id, settings.ApiKey, settings.FullUri);
|
||||||
|
var vm = new SearchAlbumViewModel
|
||||||
|
{
|
||||||
|
ForeignAlbumId = a.Id,
|
||||||
|
Monitored = fullAlbum.monitored,
|
||||||
|
Rating = fullAlbum.ratings?.value ?? 0m,
|
||||||
|
ReleaseDate = fullAlbum.releaseDate,
|
||||||
|
Title = a.Title,
|
||||||
|
Disk = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
|
||||||
|
ForeignArtistId = artistId,
|
||||||
|
ArtistName = artistName,
|
||||||
|
Cover = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url
|
||||||
|
};
|
||||||
|
|
||||||
|
if (vm.Cover.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
vm.Cover = fullAlbum.remoteCover;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
|
||||||
|
|
||||||
|
await RunSearchRules(vm);
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LidarrSettings _settings;
|
||||||
|
private async Task<LidarrSettings> GetSettings()
|
||||||
|
{
|
||||||
|
return _settings ?? (_settings = await _lidarrSettings.GetSettingsAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,5 +6,6 @@
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public bool IsError => !string.IsNullOrEmpty(ErrorMessage);
|
public bool IsError => !string.IsNullOrEmpty(ErrorMessage);
|
||||||
public string ErrorMessage { get; set; }
|
public string ErrorMessage { get; set; }
|
||||||
|
public int RequestId { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ombi.Core.Engine
|
|
||||||
{
|
|
||||||
|
|
||||||
public class TreeNode<T>
|
|
||||||
{
|
|
||||||
public string Label { get; set; }
|
|
||||||
public T Data { get; set; }
|
|
||||||
public List<TreeNode<T>> Children { get; set; }
|
|
||||||
public bool Leaf { get; set; }
|
|
||||||
public bool Expanded { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TreeNode<T,U>
|
|
||||||
{
|
|
||||||
public string Label { get; set; }
|
|
||||||
public T Data { get; set; }
|
|
||||||
public List<TreeNode<U>> Children { get; set; }
|
|
||||||
public bool Leaf { get; set; }
|
|
||||||
public bool Expanded { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,6 +23,7 @@ using Ombi.Core.Settings;
|
||||||
using Ombi.Settings.Settings.Models;
|
using Ombi.Settings.Settings.Models;
|
||||||
using Ombi.Store.Entities.Requests;
|
using Ombi.Store.Entities.Requests;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
|
||||||
namespace Ombi.Core.Engine
|
namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
|
@ -115,6 +116,7 @@ namespace Ombi.Core.Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the ID since this is a new child
|
// Remove the ID since this is a new child
|
||||||
|
// This was a TVDBID for the request rules to run
|
||||||
tvBuilder.ChildRequest.Id = 0;
|
tvBuilder.ChildRequest.Id = 0;
|
||||||
if (!tvBuilder.ChildRequest.SeasonRequests.Any())
|
if (!tvBuilder.ChildRequest.SeasonRequests.Any())
|
||||||
{
|
{
|
||||||
|
@ -143,7 +145,7 @@ namespace Ombi.Core.Engine
|
||||||
.Include(x => x.ChildRequests)
|
.Include(x => x.ChildRequests)
|
||||||
.ThenInclude(x => x.SeasonRequests)
|
.ThenInclude(x => x.SeasonRequests)
|
||||||
.ThenInclude(x => x.Episodes)
|
.ThenInclude(x => x.Episodes)
|
||||||
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
|
.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
|
||||||
.Skip(position).Take(count).ToListAsync();
|
.Skip(position).Take(count).ToListAsync();
|
||||||
|
|
||||||
// Filter out children
|
// Filter out children
|
||||||
|
@ -156,8 +158,9 @@ namespace Ombi.Core.Engine
|
||||||
.Include(x => x.ChildRequests)
|
.Include(x => x.ChildRequests)
|
||||||
.ThenInclude(x => x.SeasonRequests)
|
.ThenInclude(x => x.SeasonRequests)
|
||||||
.ThenInclude(x => x.Episodes)
|
.ThenInclude(x => x.Episodes)
|
||||||
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
|
.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
|
||||||
.Skip(position).Take(count).ToListAsync();
|
.Skip(position).Take(count).ToListAsync();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
|
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
|
||||||
|
@ -171,24 +174,30 @@ namespace Ombi.Core.Engine
|
||||||
public async Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type)
|
public async Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type)
|
||||||
{
|
{
|
||||||
var shouldHide = await HideFromOtherUsers();
|
var shouldHide = await HideFromOtherUsers();
|
||||||
List<TvRequests> allRequests;
|
List<TvRequests> allRequests = null;
|
||||||
if (shouldHide.Hide)
|
if (shouldHide.Hide)
|
||||||
{
|
{
|
||||||
allRequests = await TvRepository.GetLite(shouldHide.UserId)
|
var tv = TvRepository.GetLite(shouldHide.UserId);
|
||||||
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
|
if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
|
||||||
.Skip(position).Take(count).ToListAsync();
|
{
|
||||||
|
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
// Filter out children
|
// Filter out children
|
||||||
|
|
||||||
FilterChildren(allRequests, shouldHide);
|
FilterChildren(allRequests, shouldHide);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
allRequests = await TvRepository.GetLite()
|
var tv = TvRepository.GetLite();
|
||||||
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
|
if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
|
||||||
.Skip(position).Take(count).ToListAsync();
|
{
|
||||||
|
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allRequests == null)
|
||||||
|
{
|
||||||
|
return new RequestsViewModel<TvRequests>();
|
||||||
}
|
}
|
||||||
|
|
||||||
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
|
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
|
||||||
|
|
||||||
return new RequestsViewModel<TvRequests>
|
return new RequestsViewModel<TvRequests>
|
||||||
|
@ -196,38 +205,6 @@ namespace Ombi.Core.Engine
|
||||||
Collection = allRequests
|
Collection = allRequests
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position)
|
|
||||||
{
|
|
||||||
var shouldHide = await HideFromOtherUsers();
|
|
||||||
List<TvRequests> allRequests;
|
|
||||||
if (shouldHide.Hide)
|
|
||||||
{
|
|
||||||
allRequests = await TvRepository.Get(shouldHide.UserId)
|
|
||||||
.Include(x => x.ChildRequests)
|
|
||||||
.ThenInclude(x => x.SeasonRequests)
|
|
||||||
.ThenInclude(x => x.Episodes)
|
|
||||||
.Where(x => x.ChildRequests.Any())
|
|
||||||
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
|
|
||||||
.Skip(position).Take(count).ToListAsync();
|
|
||||||
|
|
||||||
FilterChildren(allRequests, shouldHide);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
allRequests = await TvRepository.Get()
|
|
||||||
.Include(x => x.ChildRequests)
|
|
||||||
.ThenInclude(x => x.SeasonRequests)
|
|
||||||
.ThenInclude(x => x.Episodes)
|
|
||||||
.Where(x => x.ChildRequests.Any())
|
|
||||||
.OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate))
|
|
||||||
.Skip(position).Take(count).ToListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
|
|
||||||
return ParseIntoTreeNode(allRequests);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<TvRequests>> GetRequests()
|
public async Task<IEnumerable<TvRequests>> GetRequests()
|
||||||
{
|
{
|
||||||
var shouldHide = await HideFromOtherUsers();
|
var shouldHide = await HideFromOtherUsers();
|
||||||
|
@ -288,6 +265,10 @@ namespace Ombi.Core.Engine
|
||||||
|
|
||||||
private static void FilterChildren(IEnumerable<TvRequests> allRequests, HideResult shouldHide)
|
private static void FilterChildren(IEnumerable<TvRequests> allRequests, HideResult shouldHide)
|
||||||
{
|
{
|
||||||
|
if (allRequests == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Filter out children
|
// Filter out children
|
||||||
foreach (var t in allRequests)
|
foreach (var t in allRequests)
|
||||||
{
|
{
|
||||||
|
@ -350,21 +331,22 @@ namespace Ombi.Core.Engine
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search)
|
public async Task UpdateRootPath(int requestId, int rootPath)
|
||||||
{
|
{
|
||||||
var shouldHide = await HideFromOtherUsers();
|
var allRequests = TvRepository.Get();
|
||||||
IQueryable<TvRequests> allRequests;
|
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
|
||||||
if (shouldHide.Hide)
|
results.RootFolder = rootPath;
|
||||||
{
|
|
||||||
allRequests = TvRepository.Get(shouldHide.UserId);
|
await TvRepository.Update(results);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
public async Task UpdateQualityProfile(int requestId, int profileId)
|
||||||
{
|
{
|
||||||
allRequests = TvRepository.Get();
|
var allRequests = TvRepository.Get();
|
||||||
}
|
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
|
||||||
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();
|
results.QualityOverride = profileId;
|
||||||
results.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
|
|
||||||
return ParseIntoTreeNode(results);
|
await TvRepository.Update(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
|
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
|
||||||
|
@ -403,6 +385,7 @@ namespace Ombi.Core.Engine
|
||||||
foreach (var ep in s.Episodes)
|
foreach (var ep in s.Episodes)
|
||||||
{
|
{
|
||||||
ep.Approved = true;
|
ep.Approved = true;
|
||||||
|
ep.Requested = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +404,7 @@ namespace Ombi.Core.Engine
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestEngineResult> DenyChildRequest(int requestId)
|
public async Task<RequestEngineResult> DenyChildRequest(int requestId, string reason)
|
||||||
{
|
{
|
||||||
var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);
|
var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);
|
||||||
if (request == null)
|
if (request == null)
|
||||||
|
@ -432,6 +415,7 @@ namespace Ombi.Core.Engine
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
request.Denied = true;
|
request.Denied = true;
|
||||||
|
request.DeniedReason = reason;
|
||||||
await TvRepository.UpdateChild(request);
|
await TvRepository.UpdateChild(request);
|
||||||
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
|
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
|
@ -516,6 +500,7 @@ namespace Ombi.Core.Engine
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
request.Available = true;
|
request.Available = true;
|
||||||
|
request.MarkedAsAvailable = DateTime.Now;
|
||||||
foreach (var season in request.SeasonRequests)
|
foreach (var season in request.SeasonRequests)
|
||||||
{
|
{
|
||||||
foreach (var e in season.Episodes)
|
foreach (var e in season.Episodes)
|
||||||
|
@ -585,28 +570,6 @@ namespace Ombi.Core.Engine
|
||||||
return await AfterRequest(model.ChildRequests.FirstOrDefault());
|
return await AfterRequest(model.ChildRequests.FirstOrDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<TreeNode<TvRequests, List<ChildRequests>>> ParseIntoTreeNode(IEnumerable<TvRequests> result)
|
|
||||||
{
|
|
||||||
var node = new List<TreeNode<TvRequests, List<ChildRequests>>>();
|
|
||||||
|
|
||||||
foreach (var value in result)
|
|
||||||
{
|
|
||||||
node.Add(new TreeNode<TvRequests, List<ChildRequests>>
|
|
||||||
{
|
|
||||||
Data = value,
|
|
||||||
Children = new List<TreeNode<List<ChildRequests>>>
|
|
||||||
{
|
|
||||||
new TreeNode<List<ChildRequests>>
|
|
||||||
{
|
|
||||||
Data = SortEpisodes(value.ChildRequests),
|
|
||||||
Leaf = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
|
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
|
||||||
{
|
{
|
||||||
foreach (var value in items)
|
foreach (var value in items)
|
||||||
|
@ -628,6 +591,15 @@ namespace Ombi.Core.Engine
|
||||||
NotificationHelper.NewRequest(model);
|
NotificationHelper.NewRequest(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _requestLog.Add(new RequestLog
|
||||||
|
{
|
||||||
|
UserId = (await GetUser()).Id,
|
||||||
|
RequestDate = DateTime.UtcNow,
|
||||||
|
RequestId = model.Id,
|
||||||
|
RequestType = RequestType.TvShow,
|
||||||
|
EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(),
|
||||||
|
});
|
||||||
|
|
||||||
if (model.Approved)
|
if (model.Approved)
|
||||||
{
|
{
|
||||||
// Autosend
|
// Autosend
|
||||||
|
@ -635,23 +607,67 @@ namespace Ombi.Core.Engine
|
||||||
var result = await TvSender.Send(model);
|
var result = await TvSender.Send(model);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
return new RequestEngineResult { Result = true };
|
return new RequestEngineResult { Result = true, RequestId = model.Id};
|
||||||
}
|
}
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
{
|
{
|
||||||
ErrorMessage = result.Message
|
ErrorMessage = result.Message,
|
||||||
|
RequestId = model.Id
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await _requestLog.Add(new RequestLog
|
return new RequestEngineResult { Result = true, RequestId = model.Id };
|
||||||
{
|
}
|
||||||
UserId = (await GetUser()).Id,
|
|
||||||
RequestDate = DateTime.UtcNow,
|
|
||||||
RequestId = model.Id,
|
|
||||||
RequestType = RequestType.TvShow,
|
|
||||||
});
|
|
||||||
|
|
||||||
return new RequestEngineResult { Result = true };
|
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
|
||||||
|
{
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
user = await GetUser();
|
||||||
|
|
||||||
|
// If user is still null after attempting to get the logged in user, return null.
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = user.EpisodeRequestLimit ?? 0;
|
||||||
|
|
||||||
|
if (limit <= 0)
|
||||||
|
{
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = false,
|
||||||
|
Limit = 0,
|
||||||
|
Remaining = 0,
|
||||||
|
NextRequest = DateTime.Now,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
IQueryable<RequestLog> log = _requestLog.GetAll()
|
||||||
|
.Where(x => x.UserId == user.Id
|
||||||
|
&& x.RequestType == RequestType.TvShow
|
||||||
|
&& x.RequestDate >= DateTime.UtcNow.AddDays(-7));
|
||||||
|
|
||||||
|
// Needed, due to a bug which would cause all episode counts to be 0
|
||||||
|
int zeroEpisodeCount = await log.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync();
|
||||||
|
|
||||||
|
int episodeCount = await log.Where(x => x.EpisodeCount != 0).Select(x => x.EpisodeCount).SumAsync();
|
||||||
|
|
||||||
|
int count = limit - (zeroEpisodeCount + episodeCount);
|
||||||
|
|
||||||
|
DateTime oldestRequestedAt = await log.OrderBy(x => x.RequestDate)
|
||||||
|
.Select(x => x.RequestDate)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
return new RequestQuotaCountModel()
|
||||||
|
{
|
||||||
|
HasLimit = true,
|
||||||
|
Limit = limit,
|
||||||
|
Remaining = count,
|
||||||
|
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -40,8 +40,8 @@ namespace Ombi.Core.Engine
|
||||||
EmbyContentRepo = embyRepo;
|
EmbyContentRepo = embyRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ITvMazeApi TvMazeApi { get; }
|
protected ITvMazeApi TvMazeApi { get; }
|
||||||
private IMapper Mapper { get; }
|
protected IMapper Mapper { get; }
|
||||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||||
private ISettingsService<EmbySettings> EmbySettings { get; }
|
private ISettingsService<EmbySettings> EmbySettings { get; }
|
||||||
private IPlexContentRepository PlexContentRepo { get; }
|
private IPlexContentRepository PlexContentRepo { get; }
|
||||||
|
@ -54,16 +54,20 @@ namespace Ombi.Core.Engine
|
||||||
|
|
||||||
if (searchResult != null)
|
if (searchResult != null)
|
||||||
{
|
{
|
||||||
return await ProcessResults(searchResult);
|
var retVal = new List<SearchTvShowViewModel>();
|
||||||
|
foreach (var tvMazeSearch in searchResult)
|
||||||
|
{
|
||||||
|
if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
retVal.Add(ProcessResult(tvMazeSearch));
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm)
|
|
||||||
{
|
|
||||||
var result = await Search(searchTerm);
|
|
||||||
return result.Select(ParseIntoTreeNode).ToList();
|
|
||||||
}
|
|
||||||
public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid)
|
public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid)
|
||||||
{
|
{
|
||||||
var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
|
var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
|
||||||
|
@ -95,7 +99,7 @@ namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
Url = e.url,
|
Url = e.url,
|
||||||
Title = e.name,
|
Title = e.name,
|
||||||
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()),
|
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
|
||||||
EpisodeNumber = e.number,
|
EpisodeNumber = e.number,
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -108,7 +112,7 @@ namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
Url = e.url,
|
Url = e.url,
|
||||||
Title = e.name,
|
Title = e.name,
|
||||||
AirDate = DateTime.Parse(e.airstamp ?? DateTime.MinValue.ToString()),
|
AirDate = e.airstamp.HasValue() ? DateTime.Parse(e.airstamp) : DateTime.MinValue,
|
||||||
EpisodeNumber = e.number,
|
EpisodeNumber = e.number,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -116,94 +120,50 @@ namespace Ombi.Core.Engine
|
||||||
return await ProcessResult(mapped);
|
return await ProcessResult(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid)
|
|
||||||
{
|
|
||||||
var result = await GetShowInformation(tvdbid);
|
|
||||||
return ParseIntoTreeNode(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree()
|
|
||||||
{
|
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
|
|
||||||
var processed = await ProcessResults(result);
|
|
||||||
return processed.Select(ParseIntoTreeNode).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
|
public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
|
||||||
{
|
{
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
|
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
|
||||||
var processed = await ProcessResults(result);
|
var processed = ProcessResults(result);
|
||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree()
|
|
||||||
{
|
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
|
|
||||||
var processed = await ProcessResults(result);
|
|
||||||
return processed.Select(ParseIntoTreeNode).ToList();
|
|
||||||
}
|
|
||||||
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
|
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
|
||||||
{
|
{
|
||||||
|
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
|
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
|
||||||
var processed = await ProcessResults(result);
|
var processed = ProcessResults(result);
|
||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree()
|
|
||||||
{
|
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
|
|
||||||
var processed = await ProcessResults(result);
|
|
||||||
return processed.Select(ParseIntoTreeNode).ToList();
|
|
||||||
}
|
|
||||||
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
|
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
|
||||||
{
|
{
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
|
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
|
||||||
var processed = await ProcessResults(result);
|
var processed = ProcessResults(result);
|
||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree()
|
|
||||||
{
|
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
|
|
||||||
var processed = await ProcessResults(result);
|
|
||||||
return processed.Select(ParseIntoTreeNode).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
|
public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
|
||||||
{
|
{
|
||||||
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
|
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
|
||||||
var processed = await ProcessResults(result);
|
var processed = ProcessResults(result);
|
||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TreeNode<SearchTvShowViewModel> ParseIntoTreeNode(SearchTvShowViewModel result)
|
protected IEnumerable<SearchTvShowViewModel> ProcessResults<T>(IEnumerable<T> items)
|
||||||
{
|
|
||||||
return new TreeNode<SearchTvShowViewModel>
|
|
||||||
{
|
|
||||||
Data = result,
|
|
||||||
Children = new List<TreeNode<SearchTvShowViewModel>>
|
|
||||||
{
|
|
||||||
new TreeNode<SearchTvShowViewModel>
|
|
||||||
{
|
|
||||||
Data = result, Leaf = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Leaf = false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
|
|
||||||
{
|
{
|
||||||
var retVal = new List<SearchTvShowViewModel>();
|
var retVal = new List<SearchTvShowViewModel>();
|
||||||
foreach (var tvMazeSearch in items)
|
foreach (var tvMazeSearch in items)
|
||||||
{
|
{
|
||||||
var viewT = Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
|
retVal.Add(ProcessResult(tvMazeSearch));
|
||||||
retVal.Add(await ProcessResult(viewT));
|
|
||||||
}
|
}
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected SearchTvShowViewModel ProcessResult<T>(T tvMazeSearch)
|
||||||
|
{
|
||||||
|
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<SearchTvShowViewModel> ProcessResult(SearchTvShowViewModel item)
|
private async Task<SearchTvShowViewModel> ProcessResult(SearchTvShowViewModel item)
|
||||||
{
|
{
|
||||||
item.TheTvDbId = item.Id.ToString();
|
item.TheTvDbId = item.Id.ToString();
|
||||||
|
|
77
src/Ombi.Core/Engine/UserStatsEngine.cs
Normal file
77
src/Ombi.Core/Engine/UserStatsEngine.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository.Requests;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public class UserStatsEngine : IUserStatsEngine
|
||||||
|
{
|
||||||
|
public UserStatsEngine(OmbiUserManager um, IMovieRequestRepository movieRequest, ITvRequestRepository tvRequest)
|
||||||
|
{
|
||||||
|
_userManager = um;
|
||||||
|
_movieRequest = movieRequest;
|
||||||
|
_tvRequest = tvRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly OmbiUserManager _userManager;
|
||||||
|
private readonly IMovieRequestRepository _movieRequest;
|
||||||
|
private readonly ITvRequestRepository _tvRequest;
|
||||||
|
|
||||||
|
public async Task<UserStatsSummary> GetSummary(SummaryRequest request)
|
||||||
|
{
|
||||||
|
// get all movie requests
|
||||||
|
var movies = _movieRequest.GetWithUser();
|
||||||
|
var filteredMovies = movies.Where(x => x.RequestedDate >= request.From && x.RequestedDate <= request.To);
|
||||||
|
var tv = _tvRequest.GetLite();
|
||||||
|
var children = tv.SelectMany(x =>
|
||||||
|
x.ChildRequests.Where(c => c.RequestedDate >= request.From && c.RequestedDate <= request.To));
|
||||||
|
|
||||||
|
var moviesCount = filteredMovies.CountAsync();
|
||||||
|
var childrenCount = children.CountAsync();
|
||||||
|
var availableMovies =
|
||||||
|
movies.Select(x => x.MarkedAsAvailable >= request.From && x.MarkedAsAvailable <= request.To).CountAsync();
|
||||||
|
var availableChildren = tv.SelectMany(x =>
|
||||||
|
x.ChildRequests.Where(c => c.MarkedAsAvailable >= request.From && c.MarkedAsAvailable <= request.To)).CountAsync();
|
||||||
|
|
||||||
|
var userMovie = filteredMovies.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
|
||||||
|
var userTv = children.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
|
||||||
|
return new UserStatsSummary
|
||||||
|
{
|
||||||
|
TotalMovieRequests = await moviesCount,
|
||||||
|
TotalTvRequests = await childrenCount,
|
||||||
|
CompletedRequestsTv = await availableChildren,
|
||||||
|
CompletedRequestsMovies = await availableMovies,
|
||||||
|
MostRequestedUserMovie = (await userMovie).FirstOrDefault().RequestedUser,
|
||||||
|
MostRequestedUserTv = (await userTv).FirstOrDefault().RequestedUser,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SummaryRequest
|
||||||
|
{
|
||||||
|
public DateTime From { get; set; }
|
||||||
|
public DateTime To { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserStatsSummary
|
||||||
|
{
|
||||||
|
public int TotalRequests => TotalTvRequests + TotalMovieRequests;
|
||||||
|
public int TotalMovieRequests { get; set; }
|
||||||
|
public int TotalTvRequests { get; set; }
|
||||||
|
public int TotalIssues { get; set; }
|
||||||
|
public int CompletedRequestsMovies { get; set; }
|
||||||
|
public int CompletedRequestsTv { get; set; }
|
||||||
|
public int CompletedRequests => CompletedRequestsMovies + CompletedRequestsTv;
|
||||||
|
public OmbiUser MostRequestedUserMovie { get; set; }
|
||||||
|
public OmbiUser MostRequestedUserTv { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
263
src/Ombi.Core/Engine/VoteEngine.cs
Normal file
263
src/Ombi.Core/Engine/VoteEngine.cs
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
using Ombi.Core.Engine.Interfaces;
|
||||||
|
using Ombi.Core.Models;
|
||||||
|
using Ombi.Core.Models.UI;
|
||||||
|
using Ombi.Core.Rule.Interfaces;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public class VoteEngine : BaseEngine, IVoteEngine
|
||||||
|
{
|
||||||
|
public VoteEngine(IRepository<Votes> votes, IPrincipal user, OmbiUserManager um, IRuleEvaluator r, ISettingsService<VoteSettings> voteSettings,
|
||||||
|
IMusicRequestEngine musicRequestEngine, ITvRequestEngine tvRequestEngine, IMovieRequestEngine movieRequestEngine) : base(user, um, r)
|
||||||
|
{
|
||||||
|
_voteRepository = votes;
|
||||||
|
_voteSettings = voteSettings;
|
||||||
|
_movieRequestEngine = movieRequestEngine;
|
||||||
|
_musicRequestEngine = musicRequestEngine;
|
||||||
|
_tvRequestEngine = tvRequestEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly IRepository<Votes> _voteRepository;
|
||||||
|
private readonly ISettingsService<VoteSettings> _voteSettings;
|
||||||
|
private readonly IMusicRequestEngine _musicRequestEngine;
|
||||||
|
private readonly ITvRequestEngine _tvRequestEngine;
|
||||||
|
private readonly IMovieRequestEngine _movieRequestEngine;
|
||||||
|
|
||||||
|
public async Task<List<VoteViewModel>> GetMovieViewModel()
|
||||||
|
{
|
||||||
|
var vm = new List<VoteViewModel>();
|
||||||
|
var movieRequests = await _movieRequestEngine.GetRequests();
|
||||||
|
var tvRequestsTask = _tvRequestEngine.GetRequests();
|
||||||
|
var musicRequestsTask = _musicRequestEngine.GetRequests();
|
||||||
|
var user = await GetUser();
|
||||||
|
foreach (var r in movieRequests)
|
||||||
|
{
|
||||||
|
if (r.Available || r.Approved || (r.Denied ?? false))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Make model
|
||||||
|
var votes = GetVotes(r.Id, RequestType.Movie);
|
||||||
|
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
|
||||||
|
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
|
||||||
|
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
|
||||||
|
|
||||||
|
vm.Add(new VoteViewModel
|
||||||
|
{
|
||||||
|
Upvotes = upVotes,
|
||||||
|
Downvotes = downVotes,
|
||||||
|
RequestId = r.Id,
|
||||||
|
RequestType = RequestType.Movie,
|
||||||
|
Title = r.Title,
|
||||||
|
Image = $"https://image.tmdb.org/t/p/w500/{r.PosterPath}",
|
||||||
|
Background = $"https://image.tmdb.org/t/p/w1280{r.Background}",
|
||||||
|
Description = r.Overview,
|
||||||
|
AlreadyVoted = myVote != null,
|
||||||
|
MyVote = myVote?.VoteType ?? VoteType.Downvote
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var r in await musicRequestsTask)
|
||||||
|
{
|
||||||
|
if (r.Available || r.Approved || (r.Denied ?? false))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Make model
|
||||||
|
var votes = GetVotes(r.Id, RequestType.Album);
|
||||||
|
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
|
||||||
|
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
|
||||||
|
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
|
||||||
|
vm.Add(new VoteViewModel
|
||||||
|
{
|
||||||
|
Upvotes = upVotes,
|
||||||
|
Downvotes = downVotes,
|
||||||
|
RequestId = r.Id,
|
||||||
|
RequestType = RequestType.Album,
|
||||||
|
Title = r.Title,
|
||||||
|
Image = r.Cover,
|
||||||
|
Background = r.Cover,
|
||||||
|
Description = r.ArtistName,
|
||||||
|
AlreadyVoted = myVote != null,
|
||||||
|
MyVote = myVote?.VoteType ?? VoteType.Downvote
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var r in await tvRequestsTask)
|
||||||
|
{
|
||||||
|
|
||||||
|
foreach (var childRequests in r.ChildRequests)
|
||||||
|
{
|
||||||
|
var finalsb = new StringBuilder();
|
||||||
|
if (childRequests.Available || childRequests.Approved || (childRequests.Denied ?? false))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var votes = GetVotes(childRequests.Id, RequestType.TvShow);
|
||||||
|
// Make model
|
||||||
|
var upVotes = await votes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
|
||||||
|
var downVotes = await votes.Where(x => x.VoteType == VoteType.Downvote).CountAsync();
|
||||||
|
var myVote = await votes.FirstOrDefaultAsync(x => x.UserId == user.Id && !x.Deleted);
|
||||||
|
foreach (var epInformation in childRequests.SeasonRequests.OrderBy(x => x.SeasonNumber))
|
||||||
|
{
|
||||||
|
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||||
|
var episodeString = StringHelper.BuildEpisodeList(orderedEpisodes.Select(x => x.EpisodeNumber));
|
||||||
|
finalsb.Append($"Season: {epInformation.SeasonNumber} - Episodes: {episodeString}");
|
||||||
|
finalsb.Append("<br />");
|
||||||
|
}
|
||||||
|
vm.Add(new VoteViewModel
|
||||||
|
{
|
||||||
|
Upvotes = upVotes,
|
||||||
|
Downvotes = downVotes,
|
||||||
|
RequestId = childRequests.Id,
|
||||||
|
RequestType = RequestType.TvShow,
|
||||||
|
Title = r.Title,
|
||||||
|
Image = r.PosterPath,
|
||||||
|
Background = r.Background,
|
||||||
|
Description = finalsb.ToString(),
|
||||||
|
AlreadyVoted = myVote != null,
|
||||||
|
MyVote = myVote?.VoteType ?? VoteType.Downvote
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQueryable<Votes> GetVotes(int requestId, RequestType requestType)
|
||||||
|
{
|
||||||
|
return _voteRepository.GetAll().Where(x => x.RequestType == requestType && requestId == x.RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Votes> GetVoteForUser(int requestId, string userId)
|
||||||
|
{
|
||||||
|
return _voteRepository.GetAll().FirstOrDefaultAsync(x => x.RequestId == requestId && x.UserId == userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<VoteEngineResult> UpVote(int requestId, RequestType requestType)
|
||||||
|
{
|
||||||
|
var voteSettings = await _voteSettings.GetSettingsAsync();
|
||||||
|
if (!voteSettings.Enabled)
|
||||||
|
{
|
||||||
|
return new VoteEngineResult {Result = true};
|
||||||
|
}
|
||||||
|
// How many votes does this have?!
|
||||||
|
var currentVotes = GetVotes(requestId, requestType);
|
||||||
|
|
||||||
|
var user = await GetUser();
|
||||||
|
|
||||||
|
// Does this user have a downvote? If so we should revert it and make it an upvote
|
||||||
|
var currentVote = await GetVoteForUser(requestId, user.Id);
|
||||||
|
if (currentVote != null && currentVote.VoteType == VoteType.Upvote)
|
||||||
|
{
|
||||||
|
return new VoteEngineResult { ErrorMessage = "You have already voted!" };
|
||||||
|
}
|
||||||
|
await RemoveCurrentVote(currentVote);
|
||||||
|
await _movieRequestEngine.SubscribeToRequest(requestId, requestType);
|
||||||
|
|
||||||
|
await _voteRepository.Add(new Votes
|
||||||
|
{
|
||||||
|
Date = DateTime.UtcNow,
|
||||||
|
RequestId = requestId,
|
||||||
|
RequestType = requestType,
|
||||||
|
UserId = user.Id,
|
||||||
|
VoteType = VoteType.Upvote
|
||||||
|
});
|
||||||
|
|
||||||
|
var upVotes = await currentVotes.Where(x => x.VoteType == VoteType.Upvote).CountAsync();
|
||||||
|
var downVotes = -(await currentVotes.Where(x => x.VoteType == VoteType.Downvote).CountAsync());
|
||||||
|
|
||||||
|
var totalVotes = upVotes + downVotes;
|
||||||
|
RequestEngineResult result = null;
|
||||||
|
switch (requestType)
|
||||||
|
{
|
||||||
|
case RequestType.TvShow:
|
||||||
|
if (totalVotes >= voteSettings.TvShowVoteMax)
|
||||||
|
{
|
||||||
|
result = await _tvRequestEngine.ApproveChildRequest(requestId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RequestType.Movie:
|
||||||
|
if (totalVotes >= voteSettings.MovieVoteMax)
|
||||||
|
{
|
||||||
|
result = await _movieRequestEngine.ApproveMovieById(requestId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RequestType.Album:
|
||||||
|
if (totalVotes >= voteSettings.MusicVoteMax)
|
||||||
|
{
|
||||||
|
result = await _musicRequestEngine.ApproveAlbumById(requestId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(requestType), requestType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != null && !result.Result)
|
||||||
|
{
|
||||||
|
return new VoteEngineResult
|
||||||
|
{
|
||||||
|
ErrorMessage = "Voted succesfully but could not approve!"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VoteEngineResult
|
||||||
|
{
|
||||||
|
Result = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<VoteEngineResult> DownVote(int requestId, RequestType requestType)
|
||||||
|
{
|
||||||
|
var voteSettings = await _voteSettings.GetSettingsAsync();
|
||||||
|
if (!voteSettings.Enabled)
|
||||||
|
{
|
||||||
|
return new VoteEngineResult { Result = true };
|
||||||
|
}
|
||||||
|
var user = await GetUser();
|
||||||
|
var currentVote = await GetVoteForUser(requestId, user.Id);
|
||||||
|
if (currentVote != null && currentVote.VoteType == VoteType.Downvote)
|
||||||
|
{
|
||||||
|
return new VoteEngineResult { ErrorMessage = "You have already voted!" };
|
||||||
|
}
|
||||||
|
await RemoveCurrentVote(currentVote);
|
||||||
|
|
||||||
|
await _movieRequestEngine.UnSubscribeRequest(requestId, requestType);
|
||||||
|
|
||||||
|
await _voteRepository.Add(new Votes
|
||||||
|
{
|
||||||
|
Date = DateTime.UtcNow,
|
||||||
|
RequestId = requestId,
|
||||||
|
RequestType = requestType,
|
||||||
|
UserId = user.Id,
|
||||||
|
VoteType = VoteType.Downvote
|
||||||
|
});
|
||||||
|
|
||||||
|
return new VoteEngineResult
|
||||||
|
{
|
||||||
|
Result = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveCurrentVote(Votes currentVote)
|
||||||
|
{
|
||||||
|
if (currentVote != null)
|
||||||
|
{
|
||||||
|
await _voteRepository.Delete(currentVote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,18 @@ namespace Ombi.Core
|
||||||
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
|
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void NewRequest(AlbumRequest model)
|
||||||
|
{
|
||||||
|
var notificationModel = new NotificationOptions
|
||||||
|
{
|
||||||
|
RequestId = model.Id,
|
||||||
|
DateTime = DateTime.Now,
|
||||||
|
NotificationType = NotificationType.NewRequest,
|
||||||
|
RequestType = model.RequestType
|
||||||
|
};
|
||||||
|
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Notify(MovieRequests model, NotificationType type)
|
public void Notify(MovieRequests model, NotificationType type)
|
||||||
{
|
{
|
||||||
|
@ -66,5 +78,19 @@ namespace Ombi.Core
|
||||||
};
|
};
|
||||||
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
|
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Notify(AlbumRequest model, NotificationType type)
|
||||||
|
{
|
||||||
|
var notificationModel = new NotificationOptions
|
||||||
|
{
|
||||||
|
RequestId = model.Id,
|
||||||
|
DateTime = DateTime.Now,
|
||||||
|
NotificationType = type,
|
||||||
|
RequestType = model.RequestType,
|
||||||
|
Recipient = model.RequestedUser?.Email ?? string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
|
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -41,7 +41,7 @@ namespace Ombi.Core.Helpers
|
||||||
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
|
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
|
||||||
Results = await MovieDbApi.SearchTv(ShowInfo.name);
|
Results = await MovieDbApi.SearchTv(ShowInfo.name);
|
||||||
foreach (TvSearchResult result in Results) {
|
foreach (TvSearchResult result in Results) {
|
||||||
if (result.Name == ShowInfo.name)
|
if (result.Name.Equals(ShowInfo.name, StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
var showIds = await MovieDbApi.GetTvExternals(result.Id);
|
var showIds = await MovieDbApi.GetTvExternals(result.Id);
|
||||||
ShowInfo.externals.imdb = showIds.imdb_id;
|
ShowInfo.externals.imdb = showIds.imdb_id;
|
||||||
|
@ -64,14 +64,16 @@ namespace Ombi.Core.Helpers
|
||||||
{
|
{
|
||||||
ChildRequest = new ChildRequests
|
ChildRequest = new ChildRequests
|
||||||
{
|
{
|
||||||
Id = model.TvDbId,
|
Id = model.TvDbId, // This is set to 0 after the request rules have run, the request rules needs it to identify the request
|
||||||
RequestType = RequestType.TvShow,
|
RequestType = RequestType.TvShow,
|
||||||
RequestedDate = DateTime.UtcNow,
|
RequestedDate = DateTime.UtcNow,
|
||||||
Approved = false,
|
Approved = false,
|
||||||
RequestedUserId = userId,
|
RequestedUserId = userId,
|
||||||
SeasonRequests = new List<SeasonRequests>(),
|
SeasonRequests = new List<SeasonRequests>(),
|
||||||
Title = ShowInfo.name,
|
Title = ShowInfo.name,
|
||||||
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.OrdinalIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
|
ReleaseYear = FirstAir,
|
||||||
|
RequestedByAlias = model.RequestedByAlias,
|
||||||
|
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.InvariantCultureIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
|
||||||
};
|
};
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Ombi.Api.Plex.Models;
|
using Ombi.Api.Plex.Models;
|
||||||
using Ombi.Api.Plex.Models.OAuth;
|
|
||||||
|
|
||||||
namespace Ombi.Core.Authentication
|
namespace Ombi.Core.Authentication
|
||||||
{
|
{
|
||||||
public interface IPlexOAuthManager
|
public interface IPlexOAuthManager
|
||||||
{
|
{
|
||||||
Task<string> GetAccessTokenFromPin(int pinId);
|
Task<string> GetAccessTokenFromPin(int pinId);
|
||||||
Task<OAuthPin> RequestPin();
|
Task<Uri> GetOAuthUrl(string code, string websiteAddress = null);
|
||||||
Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null);
|
Task<Uri> GetWizardOAuthUrl(string code, string websiteAddress);
|
||||||
Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
|
|
||||||
Task<PlexAccount> GetAccount(string accessToken);
|
Task<PlexAccount> GetAccount(string accessToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
15
src/Ombi.Core/Models/RequestQuotaCountModel.cs
Normal file
15
src/Ombi.Core/Models/RequestQuotaCountModel.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Models
|
||||||
|
{
|
||||||
|
public class RequestQuotaCountModel
|
||||||
|
{
|
||||||
|
public bool HasLimit { get; set; }
|
||||||
|
|
||||||
|
public int Limit { get; set; }
|
||||||
|
|
||||||
|
public int Remaining { get; set; }
|
||||||
|
|
||||||
|
public DateTime NextRequest { get; set; }
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue