Merge branch 'develop' into Translations

This commit is contained in:
Jamie 2019-10-19 00:01:40 +01:00 committed by GitHub
commit 2681c857cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
618 changed files with 50075 additions and 14208 deletions

8
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,8 @@
# These are supported funding model platforms
github: [tidusjar]
patreon: tidusjar
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
custom: https://paypal.me/PlexRequestsNet

View file

@ -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
View 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.

23
.github/stale.yml vendored Normal file
View file

@ -0,0 +1,23 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 60
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
- bug / issue
- help wanted
- possible feature
- planned
- in progress
- enhancement
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

18
.github/workflows/aspnetcore.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: ASP.NET Core CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 2.2.108
- name: Build Backend
run: ./build.sh --settings_skipverification=true

9
.github/workflows/test.workflow vendored Normal file
View file

@ -0,0 +1,9 @@
workflow "New workflow" {
on = "push"
resolves = [".NET Core CLI"]
}
action ".NET Core CLI" {
uses = "baruchiro/github-actions@0.0.1"
args = "build src/Ombi.sln"
}

3
.gitignore vendored
View file

@ -243,3 +243,6 @@ _Pvt_Extensions
# CAKE - C# Make # CAKE - C# Make
/Tools/* /Tools/*
*.db-journal *.db-journal
# Ignore local vscode config
*.vscode

File diff suppressed because it is too large Load diff

View file

@ -9,11 +9,19 @@ ____
[![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi) [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi)
[![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet) [![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet)
___
[![Twitter](https://img.shields.io/twitter/follow/tidusjar.svg?style=social)](https://twitter.com/intent/follow?screen_name=tidusjar)
Follow me developing Ombi!
[![Twitch](https://img.shields.io/badge/Twitch-Watch-blue.svg?style=flat-square&logo=twitch)](https://www.twitch.tv/tidusjar)
___ ___
<a href='https://play.google.com/store/apps/details?id=com.tidusjar.Ombi&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img width="150" alt='Get it on Google Play' src='https://play.google.com/intl/en_gb/badges/images/generic/en_badge_web_generic.png'/></a> <a href='https://play.google.com/store/apps/details?id=com.tidusjar.Ombi&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img width="150" alt='Get it on Google Play' src='https://play.google.com/intl/en_gb/badges/images/generic/en_badge_web_generic.png'/></a>
<br>
<a href='https://itunes.apple.com/us/app/ombi/id1335260043?ls=1&mt=8'><img width="150" alt='Get it on App Store' src='https://i.imgur.com/cJFa0M4.png'/></a> _**Note:** There is no longer an iOS app due to complications outside of our control._
___ ___
@ -32,8 +40,9 @@ ___
# 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
* Allows you to set specific users to automatically have requests approved and added to the relevant service (Sonarr/Radarr/Lidarr/Couchpotato etc)
* 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.
* Allows your users to get custom notifications! * Allows your users to get custom notifications!
@ -41,7 +50,7 @@ Here are some of the features Ombi V3 has:
* Will show if the request is already on plex or even if it's already monitored. * Will show if the request is already on plex or even if it's already monitored.
* Automatically updates the status of requests when they are available on Plex/Emby * Automatically updates the status of requests when they are available on Plex/Emby
* Slick, responsive and mobile friendly UI * Slick, responsive and mobile friendly UI
* Ombi will automatically update itself :) * Ombi will automatically update itself :) (YMMV)
* Very fast! * Very fast!
### Integration ### Integration
@ -50,6 +59,7 @@ We integrate with the following applications:
* Emby * Emby
* Sonarr * Sonarr
* Radarr * Radarr
* Lidarr
* DogNzb * DogNzb
* Couch Potato * Couch Potato
@ -58,6 +68,7 @@ We integrate with the following applications:
Supported notifications: Supported notifications:
* SMTP Notifications (Email) * SMTP Notifications (Email)
* Discord * Discord
* Gotify
* Slack * Slack
* Pushbullet * Pushbullet
* Pushover * Pushover
@ -87,6 +98,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 +127,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!
[![Patreon](https://www.ombi.io/img/patreondonate.svg)](https://patreon.com/tidusjar/Ombi)
[![Paypal](https://www.ombi.io/img/paypaldonate.svg)](https://paypal.me/PlexRequestsNet) [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi)
[![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet)
### A massive thanks to everyone for all their help! ### A massive thanks to everyone for all their help!
## Stats
[![Throughput Graph](https://graphs.waffle.io/tidusjar/PlexRequests.Net/throughput.svg)](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

View file

@ -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"

Binary file not shown.

View file

@ -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&version=3.1.0"
#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);
}); });

View file

@ -46,6 +46,17 @@ namespace Ombi.Api.Emby
return obj; return obj;
} }
public async Task<PublicInfo> GetPublicInformation(string baseUrl)
{
var request = new Request("emby/System/Info/public", baseUrl, HttpMethod.Get);
AddHeaders(request, string.Empty);
var obj = await Api.Request<PublicInfo>(request);
return obj;
}
public async Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri) public async Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri)
{ {
var request = new Request("emby/users/authenticatebyname", baseUri, HttpMethod.Post); var request = new Request("emby/users/authenticatebyname", baseUri, HttpMethod.Post);
@ -53,8 +64,6 @@ namespace Ombi.Api.Emby
{ {
username, username,
pw = password, pw = password,
password = password.GetSha1Hash().ToLower(),
passwordMd5 = password.CalcuateMd5Hash()
}; };
request.AddJsonBody(body); request.AddJsonBody(body);
@ -98,7 +107,7 @@ namespace Ombi.Api.Emby
request.AddQueryString("Fields", "ProviderIds,Overview"); request.AddQueryString("Fields", "ProviderIds,Overview");
request.AddQueryString("VirtualItem", "False"); request.AddQueryString("IsVirtualItem", "False");
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request); return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
} }
@ -126,6 +135,7 @@ namespace Ombi.Api.Emby
{ {
return await GetInformation<MovieInformation>(mediaId, apiKey, userId, baseUrl); return await GetInformation<MovieInformation>(mediaId, apiKey, userId, baseUrl);
} }
public async Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl) public async Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl)
{ {
return await GetInformation<EpisodeInformation>(mediaId, apiKey, userId, baseUrl); return await GetInformation<EpisodeInformation>(mediaId, apiKey, userId, baseUrl);
@ -149,7 +159,7 @@ namespace Ombi.Api.Emby
request.AddQueryString("IncludeItemTypes", type); request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("VirtualItem", "False"); request.AddQueryString("IsVirtualItem", "False");
AddHeaders(request, apiKey); AddHeaders(request, apiKey);
@ -167,7 +177,7 @@ namespace Ombi.Api.Emby
request.AddQueryString("startIndex", startIndex.ToString()); request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString()); request.AddQueryString("limit", count.ToString());
request.AddQueryString("VirtualItem", "False"); request.AddQueryString("IsVirtualItem", "False");
AddHeaders(request, apiKey); AddHeaders(request, apiKey);

View file

@ -29,5 +29,6 @@ namespace Ombi.Api.Emby
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);
Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl); Task<EpisodeInformation> GetEpisodeInformation(string mediaId, string apiKey, string userId, string baseUrl);
Task<PublicInfo> GetPublicInformation(string baseUrl);
} }
} }

View file

@ -16,6 +16,7 @@ namespace Ombi.Api.Emby.Models.Media.Tv
public int ProductionYear { get; set; } public int ProductionYear { get; set; }
public bool IsPlaceHolder { get; set; } public bool IsPlaceHolder { get; set; }
public int IndexNumber { get; set; } public int IndexNumber { get; set; }
public int? IndexNumberEnd { get; set; }
public int ParentIndexNumber { get; set; } public int ParentIndexNumber { get; set; }
public bool IsHD { get; set; } public bool IsHD { get; set; }
public bool IsFolder { get; set; } public bool IsFolder { get; set; }

View file

@ -0,0 +1,19 @@
namespace Ombi.Api.Emby.Models
{
public class PublicInfo
{
public string LocalAddress { get; set; }
public string ServerName { get; set; }
public string Version { get; set; }
/// <summary>
/// Only populated for Jellyfin
/// </summary>
public string ProductName { get; set; }
public bool IsJellyfin => !string.IsNullOrEmpty(ProductName) && ProductName.Contains("Jellyfin");
public string OperatingSystem { get; set; }
public string Id { get; set; }
}
}

View file

@ -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);
}
} }
} }

View file

@ -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);
} }
} }

View file

@ -0,0 +1,36 @@
using System.Net.Http;
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public class GotifyApi : IGotifyApi
{
public GotifyApi(IApi api)
{
_api = api;
}
private readonly IApi _api;
public async Task PushAsync(string baseUrl, string accessToken, string subject, string body, sbyte priority)
{
var request = new Request("/message", baseUrl, HttpMethod.Post);
request.AddQueryString("token", accessToken);
request.AddHeader("Access-Token", accessToken);
request.ApplicationJsonContentType();
var jsonBody = new
{
message = body,
title = subject,
priority = priority
};
request.AddJsonBody(jsonBody);
await _api.Request(request);
}
}
}

View file

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public interface IGotifyApi
{
Task PushAsync(string endpoint, string accessToken, string subject, string body, sbyte priority);
}
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
<PackageVersion></PackageVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

View 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<LidarrStatus> Status(string apiKey, string baseUrl);
Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl);
Task<AlbumByForeignId> AlbumInformation(string albumId, string apiKey, string baseUrl);
}
}

View file

@ -0,0 +1,188 @@
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.4/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 async Task<AlbumByForeignId> AlbumInformation(string albumId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
request.AddQueryString("foreignAlbumId", albumId);
AddHeaders(request, apiKey);
var albums = await Api.Request<List<AlbumByForeignId>>(request);
return albums.FirstOrDefault();
}
/// <summary>
/// THIS ONLY SUPPORTS ALBUMS THAT THE ARTIST IS IN LIDARR
/// </summary>
/// <param name="albumId"></param>
/// <param name="apiKey"></param>
/// <param name="baseUrl"></param>
/// <returns></returns>
public Task<List<LidarrTrack>> GetTracksForAlbum(int albumId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
request.AddQueryString("albumId", albumId.ToString());
AddHeaders(request, apiKey);
return Api.Request<List<LidarrTrack>>(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<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);
}
}
}

View 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; }
}
}

View file

@ -0,0 +1,31 @@
using System;
using System.Net.Mime;
namespace Ombi.Api.Lidarr.Models
{
public class AlbumByForeignId
{
public string title { get; set; }
public string disambiguation { get; set; }
public string overview { get; set; }
public int artistId { get; set; }
public string foreignAlbumId { get; set; }
public bool monitored { get; set; }
public bool anyReleaseOk { 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 Release[] releases { get; set; }
public object[] genres { get; set; }
public Medium[] media { get; set; }
public Artist artist { get; set; }
public Image[] images { get; set; }
public Link[] links { get; set; }
public Statistics statistics { get; set; }
public int id { get; set; }
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace Ombi.Api.Lidarr.Models
{
public class AlbumLookup
{
public string title { get; set; }
public string status { get; set; }
public string artistType { get; set; }
public string disambiguation { get; set; }
public List<LidarrLinks> links { 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; }
}
}

View 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; }
}
}

View 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; }
}
}

View file

@ -0,0 +1,48 @@
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 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!
}
}

View 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; }
}
}

View 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; }
}
}

View 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; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class Image
{
public string coverType { get; set; }
public string url { get; set; }
}
}

View file

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Api.Lidarr.Models
{
public class LidarrLinks
{
public string url { get; set; }
public string name { get; set; }
}
}

View 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; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class LidarrRatings
{
public int votes { get; set; }
public decimal value { get; set; }
}
}

View 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; }
}
}

View 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; }
}
}

View file

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Api.Lidarr.Models
{
public class LidarrTrack
{
public int artistId { get; set; }
public int trackFileId { get; set; }
public int albumId { get; set; }
public bool _explicit { get; set; }
public int absoluteTrackNumber { get; set; }
public string trackNumber { get; set; }
public string title { get; set; }
public int duration { get; set; }
public int mediumNumber { get; set; }
public bool hasFile { get; set; }
public bool monitored { get; set; }
public int id { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class Link
{
public string url { get; set; }
public string name { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class MetadataProfile
{
public string name { get; set; }
public int id { get; set; }
}
}

View file

@ -0,0 +1,8 @@
namespace Ombi.Api.Lidarr.Models
{
public class Ratings
{
public int votes { get; set; }
public decimal value { get; set; }
}
}

View 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; }
}
}

View 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>

View file

@ -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>

View file

@ -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);
} }
} }

View file

@ -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; }
}
} }

View file

@ -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>

View file

@ -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);

View file

@ -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);
@ -23,6 +24,7 @@ namespace Ombi.Api.Plex
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> GetPin(int pinId); Task<OAuthPin> GetPin(int pinId);
Task<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);
} }
} }

View 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;
}
}

View 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; }
}
}

View file

@ -41,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;
@ -116,6 +127,13 @@ namespace Ombi.Api.Plex
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
@ -199,22 +217,17 @@ namespace Ombi.Api.Plex
return await Api.Request<OAuthPin>(request); return await Api.Request<OAuthPin>(request);
} }
public async Task<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);
await 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]", ApplicationName); request.AddQueryString("context[device][product]", ApplicationName);
request.AddQueryString("context[device][environment]", "bundled"); request.AddQueryString("context[device][environment]", "bundled");
request.AddQueryString("context[device][layout]", "desktop"); request.AddQueryString("context[device][layout]", "desktop");
request.AddQueryString("context[device][platform]", "Web"); request.AddQueryString("context[device][platform]", "Web");
request.AddQueryString("context[device][device]", "Ombi (Web)"); request.AddQueryString("context[device][device]", "Ombi");
var s = await GetSettings(); var s = await GetSettings();
await CheckInstallId(s); await CheckInstallId(s);
@ -232,6 +245,34 @@ 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>
@ -254,7 +295,7 @@ namespace Ombi.Api.Plex
request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N")); 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-Device", "Ombi");
request.AddHeader("X-Plex-Platform", "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");

View file

@ -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);
} }
} }

View file

@ -2,6 +2,7 @@
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
using Ombi.Api.Pushover.Models; using Ombi.Api.Pushover.Models;
namespace Ombi.Api.Pushover namespace Ombi.Api.Pushover
@ -16,13 +17,9 @@ 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("'")) var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.UrlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
{
message = message.Replace("'", "&#39;");
}
var request = new Request($"messages.json?token={accessToken}&user={userToken}&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;

View file

@ -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>

View file

@ -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

View file

@ -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>

View 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);
}
}

View file

@ -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; }
} }

View file

@ -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>

View file

@ -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; }
} }

View file

@ -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; }
} }

View 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; }
}
}

View file

@ -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);

View 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);
}
}
}

View file

@ -41,7 +41,7 @@ namespace Ombi.Api
{ {
if (!request.IgnoreErrors) if (!request.IgnoreErrors)
{ {
LogError(request, httpResponseMessage); await LogError(request, httpResponseMessage);
} }
if (request.Retry) if (request.Retry)
@ -72,6 +72,7 @@ namespace Ombi.Api
// do something with the response // do something with the response
var receivedString = await httpResponseMessage.Content.ReadAsStringAsync(); var receivedString = await httpResponseMessage.Content.ReadAsStringAsync();
LogDebugContent(receivedString);
if (request.ContentType == ContentType.Json) if (request.ContentType == ContentType.Json)
{ {
request.OnBeforeDeserialization?.Invoke(receivedString); request.OnBeforeDeserialization?.Invoke(receivedString);
@ -80,14 +81,19 @@ namespace Ombi.Api
else else
{ {
// XML // XML
return DeserializeXml<T>(receivedString);
}
}
}
public T DeserializeXml<T>(string receivedString)
{
XmlSerializer serializer = new XmlSerializer(typeof(T)); XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(receivedString); StringReader reader = new StringReader(receivedString);
var value = (T) serializer.Deserialize(reader); var value = (T) serializer.Deserialize(reader);
return value; return value;
} }
}
}
public async Task<string> RequestContent(Request request) public async Task<string> RequestContent(Request request)
{ {
@ -100,12 +106,12 @@ namespace Ombi.Api
{ {
if (!request.IgnoreErrors) if (!request.IgnoreErrors)
{ {
LogError(request, httpResponseMessage); await LogError(request, httpResponseMessage);
} }
} }
// do something with the response // do something with the response
var data = httpResponseMessage.Content; var data = httpResponseMessage.Content;
await LogDebugContent(httpResponseMessage);
return await data.ReadAsStringAsync(); return await data.ReadAsStringAsync();
} }
@ -117,21 +123,23 @@ namespace Ombi.Api
{ {
AddHeadersBody(request, httpRequestMessage); AddHeadersBody(request, httpRequestMessage);
var httpResponseMessage = await _client.SendAsync(httpRequestMessage); var httpResponseMessage = await _client.SendAsync(httpRequestMessage);
await LogDebugContent(httpResponseMessage);
if (!httpResponseMessage.IsSuccessStatusCode) if (!httpResponseMessage.IsSuccessStatusCode)
{ {
if (!request.IgnoreErrors) if (!request.IgnoreErrors)
{ {
LogError(request, httpResponseMessage); await LogError(request, httpResponseMessage);
} }
} }
} }
} }
private static void AddHeadersBody(Request request, HttpRequestMessage httpRequestMessage) private void AddHeadersBody(Request request, HttpRequestMessage httpRequestMessage)
{ {
// Add the Json Body // Add the Json Body
if (request.JsonBody != null) if (request.JsonBody != null)
{ {
LogDebugContent("REQUEST: " + request.JsonBody);
httpRequestMessage.Content = new JsonContent(request.JsonBody); httpRequestMessage.Content = new JsonContent(request.JsonBody);
httpRequestMessage.Content.Headers.ContentType = httpRequestMessage.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/json"); // Emby connect fails if we have the charset in the header new MediaTypeHeaderValue("application/json"); // Emby connect fails if we have the charset in the header
@ -144,10 +152,28 @@ 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}");
await LogDebugContent(httpResponseMessage);
}
private async Task LogDebugContent(HttpResponseMessage message)
{
if (Logger.IsEnabled(LogLevel.Debug))
{
var content = await message.Content.ReadAsStringAsync();
Logger.LogDebug(content);
}
}
private void LogDebugContent(string message)
{
if (Logger.IsEnabled(LogLevel.Debug))
{
Logger.LogDebug(message);
}
} }
} }
} }

View file

@ -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);
} }
} }

View file

@ -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="6.1.0" /> <PackageReference Include="Polly" Version="7.1.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup> </ItemGroup>

View file

@ -28,6 +28,7 @@ namespace Ombi.Api
public bool IgnoreErrors { get; set; } 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();

View 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);
}
}
}

View file

@ -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.8.0"></packagereference> <PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -4,6 +4,7 @@ using Moq;
using Ombi.Core.Rule.Rules.Request; using Ombi.Core.Rule.Rules.Request;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
using NUnit.Framework; using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Helpers; using Ombi.Helpers;
namespace Ombi.Core.Tests.Rule.Request namespace Ombi.Core.Tests.Rule.Request
@ -16,7 +17,7 @@ namespace Ombi.Core.Tests.Rule.Request
{ {
PrincipalMock = new Mock<IPrincipal>(); PrincipalMock = new Mock<IPrincipal>();
Rule = new AutoApproveRule(PrincipalMock.Object); Rule = new AutoApproveRule(PrincipalMock.Object, null);
} }

View file

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using Ombi.Core.Rule.Rules; using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Store.Entities.Requests; using Ombi.Store.Entities.Requests;
@ -15,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Request
{ {
PrincipalMock = new Mock<IPrincipal>(); PrincipalMock = new Mock<IPrincipal>();
Rule = new CanRequestRule(PrincipalMock.Object); Rule = new CanRequestRule(PrincipalMock.Object, null);
} }

View file

@ -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()

View file

@ -16,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup() public void Setup()
{ {
ContextMock = new Mock<IEmbyContentRepository>(); ContextMock = new Mock<IEmbyContentRepository>();
Rule = new EmbyAvailabilityRule(ContextMock.Object); Rule = new EmbyAvailabilityRule(ContextMock.Object, null);
} }
private EmbyAvailabilityRule Rule { get; set; } private EmbyAvailabilityRule Rule { get; set; }
@ -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);

View file

@ -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]

View file

@ -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; }

View file

@ -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()

View 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");
}
}
}
}

View file

@ -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>

View file

@ -28,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;
} }
@ -49,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 = await _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 async Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress) public async Task<Uri> GetWizardOAuthUrl(string code, string websiteAddress)
{ {
var url = await _api.GetOAuthUrl(pinId, code, websiteAddress, true); var url = await _api.GetOAuthUrl(code, websiteAddress);
return url; return url;
} }
} }

View file

@ -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; }

View 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();
}
}

View 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();
}
}

View 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);
}
}

View 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();
}
}

View file

@ -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
{ {

View file

@ -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);
} }
} }

View file

@ -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);
} }
} }

View file

@ -0,0 +1,17 @@
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);
Task<SearchAlbumViewModel> GetAlbumInformation(string foreignAlbumId);
}
}

View file

@ -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);
} }
} }

View file

@ -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);
} }
} }

View file

@ -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();
} }
} }

View file

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Core.Engine
{
public interface IUserStatsEngine
{
Task<UserStatsSummary> GetSummary(SummaryRequest request);
}
}

View file

@ -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),
};
} }
} }
} }

View file

@ -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,31 +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)
{ {
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;
} }
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;
} }
@ -87,10 +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)
{ {
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;
} }
@ -101,10 +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)
{ {
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;
} }
@ -115,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;
} }
@ -130,15 +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)
{ {
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;
} }
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>();
@ -149,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();
@ -174,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;
} }

View 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 };
}
}
}

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