Merge pull request #2539 from tidusjar/develop

Develop
pull/2542/head
Jamie 6 years ago committed by GitHub
commit 6f02c38a20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

3
.gitignore vendored

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

File diff suppressed because it is too large Load Diff

@ -1,18 +1,28 @@
![](http://i.imgur.com/qQsN78U.png)
____
[![Discord](https://img.shields.io/discord/102860784329052160.svg)](https://discord.gg/KxYZ64w)
[![Discord](https://img.shields.io/discord/270828201473736705.svg)](https://discord.gg/Sa7wNWb)
[![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/ombi.svg)](https://hub.docker.com/r/linuxserver/ombi/)
[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/Ombi/total.svg)](https://github.com/tidusjar/Ombi)
[![firsttimersonly](http://img.shields.io/badge/first--timers--only-friendly-blue.svg?style=flat-square)](http://www.firsttimersonly.com/)
[![firsttimersonly](http://img.shields.io/badge/first--timers--only-friendly-blue.svg)](http://www.firsttimersonly.com/)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/ombi/localized.svg)](https://crowdin.com/project/ombi)
[![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 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://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>
[![Report a bug](http://i.imgur.com/xSpw482.png)](https://forums.ombi.io/viewforum.php?f=10) [![Feature request](http://i.imgur.com/mFO0OuX.png)](https://forums.ombi.io/posting.php?mode=post&f=20)
___
We also now have merch up on Teespring!
[EU Store](https://teespring.com/stores/ombi-eu)
[US Store](https://teespring.com/stores/ombi-us)
___
| Service | Stable | Develop |
@ -56,7 +66,7 @@ Supported notifications:
### The difference between Version 3 and 2
Over the last 8 months, we focused on the main functions on Ombi, a complete rewrite while making it better, faster and more stable.
Over the last year, we focused on the main functions on Ombi, a complete rewrite while making it better, faster and more stable.
We have already done most of the work, but some features are still be missing in this first version.
We are planning to bring back these features in V3 but for now you can find a list below with a quick comparison of features between v2 and v3.
@ -70,13 +80,13 @@ We are planning to bring back these features in V3 but for now you can find a li
| Landing page | Yes (brand new) | Yes |
| Login page | Yes (brand new) | Yes |
| Custom Notification Messages | Yes | No |
| Sending newsletters | Planned | Yes |
| Sending newsletters | Yes | Yes |
| Send a Mass Email | Yes | Yes |
| SickRage | Yes | Yes |
| CouchPotato | Yes | Yes |
| DogNzb | Yes | No |
| Issues | Yes | Yes |
| Headphones | No (support dropped) | Yes |
| Headphones | No | Yes |
# Feature Requests
Feature requests are handled on FeatHub.

@ -2,7 +2,7 @@ version: 3.0.{build}
configuration: Release
os: Visual Studio 2017
environment:
nodejs_version: "7.8.0"
nodejs_version: "9.8.0"
install:
# Get the latest stable version of Node.js or io.js
@ -15,19 +15,19 @@ test: off
after_build:
- cmd: >-
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\osx.tar.gz"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux.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.0\linux-arm.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.0\windows-32bit.zip"
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows-32bit.zip"
# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz"

@ -1,10 +1,10 @@
#tool "nuget:?package=GitVersion.CommandLine"
#addin "Cake.Gulp"
#addin "Cake.Npm"
#addin "SharpZipLib"
#addin nuget:?package=Cake.Compression&version=0.1.4
#addin "Cake.Incubator"
#addin "Cake.Yarn"
//////////////////////////////////////////////////////////////////////
// ARGUMENTS
@ -26,7 +26,7 @@ var csProj = "./src/Ombi/Ombi.csproj"; // Path to the project.csproj
var solutionFile = "Ombi.sln"; // Solution file if needed
GitVersion versionInfo = null;
var frameworkVer = "netcoreapp2.0";
var frameworkVer = "netcoreapp2.1";
var buildSettings = new DotNetCoreBuildSettings
{
@ -122,36 +122,19 @@ Task("SetVersionInfo")
Task("NPM")
.Does(() => {
var settings = new NpmInstallSettings {
LogLevel = NpmLogLevel.Silent,
WorkingDirectory = webProjDir,
Production = true
};
NpmInstall(settings);
Yarn.FromPath(webProjDir).Install();
});
Task("Gulp Publish")
.IsDependentOn("NPM")
.Does(() => {
var runScriptSettings = new NpmRunScriptSettings {
ScriptName="publish",
WorkingDirectory = webProjDir,
};
NpmRunScript(runScriptSettings);
.Does(() => {
Yarn.FromPath(webProjDir).RunScript("publish");
});
Task("TSLint")
.Does(() =>
{
var settings = new NpmRunScriptSettings {
WorkingDirectory = webProjDir,
ScriptName = "lint"
};
NpmRunScript(settings);
Yarn.FromPath(webProjDir).RunScript("lint");
});
Task("PrePublish")
@ -178,7 +161,7 @@ Task("Publish")
.IsDependentOn("Publish-OSX")
.IsDependentOn("Publish-Linux")
.IsDependentOn("Publish-Linux-ARM")
//.IsDependentOn("Publish-Linux-ARM-64Bit")
.IsDependentOn("Publish-Linux-ARM-64Bit")
.IsDependentOn("Package");
Task("Publish-Windows")
@ -189,6 +172,8 @@ Task("Publish-Windows")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
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);
});
@ -200,6 +185,9 @@ Task("Publish-Windows-32bit")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
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);
});
@ -211,6 +199,8 @@ Task("Publish-OSX")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
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);
});
@ -222,6 +212,8 @@ Task("Publish-Linux")
DotNetCorePublish("./src/Ombi/Ombi.csproj", publishSettings);
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);
});
@ -235,6 +227,8 @@ Task("Publish-Linux-ARM")
CopyFile(
buildDir + "/"+frameworkVer+"/linux-arm/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);
});
@ -248,6 +242,8 @@ Task("Publish-Linux-ARM-64Bit")
CopyFile(
buildDir + "/"+frameworkVer+"/linux-arm64/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);
});

Binary file not shown.

291
package-lock.json generated

@ -0,0 +1,291 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "1.0.3"
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"requires": {
"chalk": "1.1.3",
"esutils": "2.0.2",
"js-tokens": "3.0.2"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
}
}
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"builtin-modules": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
},
"chalk": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.4.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": {
"color-convert": "1.9.1"
}
},
"supports-color": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"color-convert": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"diff": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
},
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
},
"js-yaml": {
"version": "3.12.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
"integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
"requires": {
"argparse": "1.0.10",
"esprima": "4.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.11"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"path-parse": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
},
"resolve": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz",
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
"requires": {
"path-parse": "1.0.5"
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
},
"tslib": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz",
"integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw=="
},
"tslint": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz",
"integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=",
"requires": {
"babel-code-frame": "6.26.0",
"builtin-modules": "1.1.1",
"chalk": "2.4.1",
"commander": "2.15.1",
"diff": "3.5.0",
"glob": "7.1.2",
"js-yaml": "3.12.0",
"minimatch": "3.0.4",
"resolve": "1.7.1",
"semver": "5.5.0",
"tslib": "1.9.2",
"tsutils": "2.27.1"
}
},
"tsutils": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz",
"integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==",
"requires": {
"tslib": "1.9.2"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Internal;
using Newtonsoft.Json;
using Ombi.Api.Emby.Models;
using Ombi.Api.Emby.Models.Media.Tv;
@ -77,34 +78,44 @@ namespace Ombi.Api.Emby
request.AddJsonBody(body);
request.AddHeader("Accept", "application/json");
request.AddContentHeader("Content-Type", "application/json");
AddEmbyHeaders(request);
var obj = await Api.Request<EmbyConnectUser>(request);
return obj;
}
public async Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
private static void AddEmbyHeaders(Request request)
{
request.AddHeader("Accept", "application/json");
request.AddHeader("X-Application", $"Ombi/{AssemblyHelper.GetRuntimeVersion()}");
request.AddContentHeader("Content-Type", "application/json");
}
public async Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId, string apiKey, string userId, string baseUrl)
{
var request = new Request($"emby/users/{userId}/items?parentId={mediaId}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<EmbyItemContainer<MovieInformation>>(request);
request.AddQueryString("Fields", "ProviderIds,Overview");
request.AddQueryString("VirtualItem", "False");
return await Api.Request<EmbyItemContainer<EmbyMovie>>(request);
}
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri);
return await GetAll<EmbyMovie>("Movie", apiKey, userId, baseUri, true, startIndex, count);
}
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri);
return await GetAll<EmbyEpisodes>("Episode", apiKey, userId, baseUri, false, startIndex, count);
}
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri)
public async Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri)
{
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri);
return await GetAll<EmbySeries>("Series", apiKey, userId, baseUri, false, startIndex, count);
}
public async Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl)
@ -123,20 +134,40 @@ namespace Ombi.Api.Emby
private async Task<T> GetInformation<T>(string mediaId, string apiKey, string userId, string baseUrl)
{
var request = new Request($"emby/users/{userId}/items/{mediaId}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
var response = await Api.RequestContent(request);
return JsonConvert.DeserializeObject<T>(response);
}
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview = false)
{
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("VirtualItem", "False");
AddHeaders(request, apiKey);
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri)
var obj = await Api.Request<EmbyItemContainer<T>>(request);
return obj;
}
private async Task<EmbyItemContainer<T>> GetAll<T>(string type, string apiKey, string userId, string baseUri, bool includeOverview, int startIndex, int count)
{
var request = new Request($"emby/users/{userId}/items", baseUri, HttpMethod.Get);
request.AddQueryString("Recursive", true.ToString());
request.AddQueryString("IncludeItemTypes", type);
request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds");
request.AddQueryString("startIndex", startIndex.ToString());
request.AddQueryString("limit", count.ToString());
request.AddQueryString("VirtualItem", "False");
AddHeaders(request, apiKey);

@ -14,12 +14,17 @@ namespace Ombi.Api.Emby
Task<EmbyUser> LogIn(string username, string password, string apiKey, string baseUri);
Task<EmbyConnectUser> LoginConnectUser(string username, string password);
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, string userId, string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetAllMovies(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<MovieInformation>> GetCollection(string mediaId, string apiKey, string userId,
string baseUrl);
Task<EmbyItemContainer<EmbyEpisodes>> GetAllEpisodes(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<EmbySeries>> GetAllShows(string apiKey, int startIndex, int count, string userId,
string baseUri);
Task<EmbyItemContainer<EmbyMovie>> GetCollection(string mediaId,
string apiKey, string userId, string baseUrl);
Task<SeriesInformation> GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl);
Task<MovieInformation> GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl);

@ -28,5 +28,7 @@ namespace Ombi.Api.Emby.Models.Movie
public string MediaType { get; set; }
public bool HasSubtitles { get; set; }
public int CriticRating { get; set; }
public string Overview { get; set; }
public EmbyProviderids ProviderIds { get; set; }
}
}

@ -39,5 +39,6 @@ namespace Ombi.Api.Emby.Models.Media.Tv
public string LocationType { get; set; }
public string MediaType { get; set; }
public bool HasSubtitles { get; set; }
public EmbyProviderids ProviderIds { get; set; }
}
}

@ -26,5 +26,7 @@ namespace Ombi.Api.Emby.Models.Media.Tv
public string[] BackdropImageTags { get; set; }
public string LocationType { get; set; }
public DateTime EndDate { get; set; }
public EmbyProviderids ProviderIds { get; set; }
}
}

@ -21,6 +21,7 @@ namespace Ombi.Api.FanartTv
{
var request = new Request($"tv/{tvdbId}", Endpoint, HttpMethod.Get);
request.AddHeader("api-key", token);
request.IgnoreErrors = true;
try
{
return await Api.Request<TvResult>(request);
@ -36,6 +37,7 @@ namespace Ombi.Api.FanartTv
{
var request = new Request($"movies/{movieOrImdbId}", Endpoint, HttpMethod.Get);
request.AddHeader("api-key", token);
request.IgnoreErrors = true;
return await Api.Request<MovieResult>(request);
}

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Lidarr.Models;
namespace Ombi.Api.Lidarr
{
public interface ILidarrApi
{
Task<List<AlbumLookup>> AlbumLookup(string searchTerm, string apiKey, string baseUrl);
Task<List<ArtistLookup>> ArtistLookup(string searchTerm, string apiKey, string baseUrl);
Task<List<LidarrProfile>> GetProfiles(string apiKey, string baseUrl);
Task<List<LidarrRootFolder>> GetRootFolders(string apiKey, string baseUrl);
Task<ArtistResult> GetArtist(int artistId, string apiKey, string baseUrl);
Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl);
Task<AlbumByArtistResponse> GetAlbumsByArtist(string foreignArtistId);
Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl);
Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl);
Task<List<AlbumResponse>> GetAllAlbums(string apiKey, string baseUrl);
Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl);
Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl);
Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl);
Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl);
Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl);
Task<LidarrStatus> Status(string apiKey, string baseUrl);
Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl);
}
}

@ -0,0 +1,170 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Lidarr.Models;
namespace Ombi.Api.Lidarr
{
public class LidarrApi : ILidarrApi
{
public LidarrApi(ILogger<LidarrApi> logger, IApi api)
{
Api = api;
Logger = logger;
}
private IApi Api { get; }
private ILogger Logger { get; }
private const string ApiVersion = "/api/v1";
public Task<List<LidarrProfile>> GetProfiles(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/qualityprofile", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<LidarrProfile>>(request);
}
public Task<List<LidarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/rootfolder", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<LidarrRootFolder>>(request);
}
public async Task<List<ArtistLookup>> ArtistLookup(string searchTerm, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/Artist/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", searchTerm);
AddHeaders(request, apiKey);
return await Api.Request<List<ArtistLookup>>(request);
}
public Task<List<AlbumLookup>> AlbumLookup(string searchTerm, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/Album/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", searchTerm);
AddHeaders(request, apiKey);
return Api.Request<List<AlbumLookup>>(request);
}
public Task<ArtistResult> GetArtist(int artistId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist/{artistId}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<ArtistResult>(request);
}
public async Task<ArtistResult> GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", $"lidarr:{foreignArtistId}");
AddHeaders(request, apiKey);
return (await Api.Request<List<ArtistResult>>(request)).FirstOrDefault();
}
public async Task<AlbumLookup> GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album/lookup", baseUrl, HttpMethod.Get);
request.AddQueryString("term", $"lidarr:{foreignArtistId}");
AddHeaders(request, apiKey);
var albums = await Api.Request<List<AlbumLookup>>(request);
return albums.FirstOrDefault();
}
public Task<AlbumByArtistResponse> GetAlbumsByArtist(string foreignArtistId)
{
var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.3/artist/{foreignArtistId}",
HttpMethod.Get) {IgnoreBaseUrlAppend = true};
return Api.Request<AlbumByArtistResponse>(request);
}
public Task<List<ArtistResult>> GetArtists(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<ArtistResult>>(request);
}
public Task<List<AlbumResponse>> GetAllAlbums(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<AlbumResponse>>(request);
}
public Task<ArtistResult> AddArtist(ArtistAdd artist, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post);
request.AddJsonBody(artist);
AddHeaders(request, apiKey);
return Api.Request<ArtistResult>(request);
}
public async Task<AlbumResponse> MontiorAlbum(int albumId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album/monitor", baseUrl, HttpMethod.Put);
request.AddJsonBody(new
{
albumIds = new[] { albumId },
monitored = true
});
AddHeaders(request, apiKey);
return (await Api.Request<List<AlbumResponse>>(request)).FirstOrDefault();
}
public Task<List<AlbumResponse>> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get);
request.AddQueryString("artistId", artistId.ToString());
AddHeaders(request, apiKey);
return Api.Request<List<AlbumResponse>>(request);
}
public Task<List<LanguageProfiles>> GetLanguageProfile(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/languageprofile", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<LanguageProfiles>>(request);
}
public Task<List<MetadataProfile>> GetMetadataProfile(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<List<MetadataProfile>>(request);
}
public Task<LidarrStatus> Status(string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/system/status", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return Api.Request<LidarrStatus>(request);
}
public Task<CommandResult> AlbumSearch(int[] albumIds, string apiKey, string baseUrl)
{
var request = new Request($"{ApiVersion}/command/", baseUrl, HttpMethod.Post);
request.AddJsonBody(new { name = "AlbumSearch", albumIds });
AddHeaders(request, apiKey);
return Api.Request<CommandResult>(request);
}
private void AddHeaders(Request request, string key)
{
request.AddHeader("X-Api-Key", key);
}
}
}

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

@ -0,0 +1,25 @@
using System;
namespace Ombi.Api.Lidarr.Models
{
public class AlbumLookup
{
public string title { get; set; }
public int artistId { get; set; }
public string foreignAlbumId { get; set; }
public bool monitored { get; set; }
public int profileId { get; set; }
public int duration { get; set; }
public string albumType { get; set; }
public string[] secondaryTypes { get; set; }
public int mediumCount { get; set; }
public Ratings ratings { get; set; }
public DateTime releaseDate { get; set; }
//public object[] releases { get; set; }
public object[] genres { get; set; }
//public object[] media { get; set; }
public Artist artist { get; set; }
public Image[] images { get; set; }
public string remoteCover { get; set; }
}
}

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

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

@ -0,0 +1,49 @@
using System;
using System.Net.Mime;
namespace Ombi.Api.Lidarr.Models
{
public class ArtistAdd
{
public string status { get; set; }
public bool ended { get; set; }
public string artistName { get; set; }
public string foreignArtistId { get; set; }
public int tadbId { get; set; }
public int discogsId { get; set; }
public string overview { get; set; }
public string disambiguation { get; set; }
public Link[] links { get; set; }
public Image[] images { get; set; }
public string remotePoster { get; set; }
public int qualityProfileId { get; set; }
public int languageProfileId { get; set; }
public int metadataProfileId { get; set; }
public bool albumFolder { get; set; }
public bool monitored { get; set; }
public string cleanName { get; set; }
public string sortName { get; set; }
public object[] tags { get; set; }
public DateTime added { get; set; }
public Ratings ratings { get; set; }
public Statistics statistics { get; set; }
public Addoptions addOptions { get; set; }
public string rootFolderPath { get; set; }
}
public class Addoptions
{
/// <summary>
/// Future = 1
/// Missing = 2
/// Existing = 3
/// First = 5
/// Latest = 4
/// None = 6
/// </summary>
public int selectedOption { get; set; }
public bool monitored { get; set; }
public bool searchForMissingAlbums { get; set; }
public string[] AlbumsToMonitor { get; set; } // Uses the MusicBrainzAlbumId!
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -120,8 +120,10 @@ namespace Ombi.Api.Mattermost.Models
var attIndex = outMessages[msgCount].Attachments.Count - 1;
//Get the text lines
lines = att.Text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
if (!String.IsNullOrEmpty(att.Text))
{
lines = att.Text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None);
}
foreach (var line in lines)
{
//Get the total length of all attachments on the current outgoing message
@ -153,8 +155,9 @@ namespace Ombi.Api.Mattermost.Models
foreach (var msg in outMessages)
{
var request = new Request("", _webhookUrl.ToString(), HttpMethod.Post);
var request = new Request(_webhookUrl.ToString(), "", HttpMethod.Post);
request.AddJsonBody(msg);
request.AddHeader("Host", _webhookUrl.Host);
await api.Request(request);
}
}

@ -33,7 +33,7 @@ namespace Ombi.Api.Mattermost.Models
/// Bot/User Icon
/// </summary>
[JsonProperty(PropertyName = "icon_url")]
public Uri IconUrl { get; set; }
public string IconUrl { get; set; }
/// <summary>
/// Message body. Supports Markdown
@ -142,7 +142,7 @@ namespace Ombi.Api.Mattermost.Models
/// Large images are resized to a maximum width of 400px or a maximum height of 300px, while still maintaining the original aspect ratio.
/// </summary>
[JsonProperty(PropertyName = "image_url")]
public Uri ImageUrl { get; set; }
public string ImageUrl { get; set; }
/// <summary>
/// An optional URL to an image file(GIF, JPEG, PNG, or BMP) that is displayed as a 75x75 pixel thumbnail on the right side of an attachment.

@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup>
<ItemGroup>

@ -6,6 +6,6 @@ namespace Ombi.Api.Notifications
{
public interface IOneSignalApi
{
Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message);
Task<OneSignalNotificationResponse> PushNotification(List<string> playerIds, string message, bool isAdminNotification, int requestId, int requestType);
}
}

@ -4,18 +4,22 @@
{
public string app_id { get; set; }
public string[] 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 class Data
{
public string foo { get; set; }
}
public class Contents
{
public string en { get; set; }
}
public class Button
{
public string id { get; set; }
public string text { get; set; }
//public string icon { get; set; }
}
}

@ -4,6 +4,10 @@
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.4.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>

@ -20,7 +20,7 @@ namespace Ombi.Api.Notifications
private readonly IApplicationConfigRepository _appConfig;
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())
{
@ -39,6 +39,17 @@ namespace Ombi.Api.Notifications
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);
var result = await _api.Request<OneSignalNotificationResponse>(request);

@ -1,6 +1,8 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Friends;
using Ombi.Api.Plex.Models.OAuth;
using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status;
@ -9,6 +11,7 @@ namespace Ombi.Api.Plex
public interface IPlexApi
{
Task<PlexStatus> GetStatus(string authToken, string uri);
Task<PlexLibrariesForMachineId> GetLibrariesForMachineId(string authToken, string machineId);
Task<PlexAuthentication> SignIn(UserRequest user);
Task<PlexServer> GetServer(string authToken);
Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost);
@ -19,5 +22,9 @@ namespace Ombi.Api.Plex
Task<PlexContainer> GetAllEpisodes(string authToken, string host, string section, int start, int retCount);
Task<PlexFriends> GetUsers(string authToken);
Task<PlexAccount> GetAccount(string authToken);
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
Task<OAuthPin> GetPin(int pinId);
Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl);
Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs);
}
}

@ -11,20 +11,20 @@ namespace Ombi.Api.Plex.Models
public string summary { get; set; }
public int index { get; set; }
public float rating { get; set; }
public int viewCount { get; set; }
public int lastViewedAt { get; set; }
//public int viewCount { get; set; }
//public int lastViewedAt { get; set; }
public int year { get; set; }
public string thumb { get; set; }
public string art { get; set; }
public string banner { get; set; }
public string theme { get; set; }
public string duration { get; set; }
public string originallyAvailableAt { get; set; }
//public string duration { get; set; }
//public string originallyAvailableAt { get; set; }
public int leafCount { get; set; }
public int viewedLeafCount { get; set; }
public int childCount { get; set; }
public long addedAt { get; set; }
public int updatedAt { get; set; }
//public long addedAt { get; set; }
//public int updatedAt { get; set; }
public Genre[] Genre { get; set; }
//public Role[] Role { get; set; }
public string primaryExtraKey { get; set; }

@ -0,0 +1,27 @@
using System;
namespace Ombi.Api.Plex.Models.OAuth
{
public class OAuthPin
{
public int id { get; set; }
public string code { get; set; }
public bool trusted { get; set; }
public string clientIdentifier { get; set; }
public Location location { get; set; }
public int expiresIn { get; set; }
public DateTime createdAt { get; set; }
public DateTime expiresAt { get; set; }
public string authToken { get; set; }
}
public class Location
{
public string code { get; set; }
public string country { get; set; }
public string city { get; set; }
public string subdivisions { get; set; }
public string coordinates { get; set; }
}
}

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

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

@ -1,20 +1,66 @@
using System.Net.Http;
using System;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Friends;
using Ombi.Api.Plex.Models.OAuth;
using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
namespace Ombi.Api.Plex
{
public class PlexApi : IPlexApi
{
public PlexApi(IApi api)
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings, ISettingsService<PlexSettings> p)
{
Api = api;
_custom = settings;
_plexSettings = p;
}
private IApi Api { get; }
private readonly ISettingsService<CustomizationSettings> _custom;
private readonly ISettingsService<PlexSettings> _plexSettings;
private string _app;
private string ApplicationName
{
get
{
if (string.IsNullOrEmpty(_app))
{
var settings = _custom.GetSettings();
if (settings.ApplicationName.IsNullOrEmpty())
{
_app = "Ombi";
}
else
{
// 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;
}
}
private const string SignInUri = "https://plex.tv/users/sign_in.json";
private const string FriendsUri = "https://plex.tv/pms/friends/all";
@ -36,7 +82,7 @@ namespace Ombi.Api.Plex
};
var request = new Request(SignInUri, string.Empty, HttpMethod.Post);
AddHeaders(request);
await AddHeaders(request);
request.AddJsonBody(userModel);
var obj = await Api.Request<PlexAuthentication>(request);
@ -47,14 +93,14 @@ namespace Ombi.Api.Plex
public async Task<PlexStatus> GetStatus(string authToken, string uri)
{
var request = new Request(uri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexStatus>(request);
}
public async Task<PlexAccount> GetAccount(string authToken)
{
var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexAccount>(request);
}
@ -62,7 +108,7 @@ namespace Ombi.Api.Plex
{
var request = new Request(ServerUri, string.Empty, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexServer>(request);
}
@ -70,17 +116,24 @@ namespace Ombi.Api.Plex
public async Task<PlexContainer> GetLibrarySections(string authToken, string plexFullHost)
{
var request = new Request("library/sections", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request);
}
public async Task<PlexContainer> GetLibrary(string authToken, string plexFullHost, string libraryId)
{
var request = new Request($"library/sections/{libraryId}/all", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
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>
// 192.168.1.69:32400/library/metadata/3662/allLeaves
// The metadata ratingkey should be in the Cache
@ -95,21 +148,21 @@ namespace Ombi.Api.Plex
public async Task<PlexMetadata> GetEpisodeMetaData(string authToken, string plexFullHost, int ratingKey)
{
var request = new Request($"/library/metadata/{ratingKey}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
public async Task<PlexMetadata> GetMetadata(string authToken, string plexFullHost, int itemId)
{
var request = new Request($"library/metadata/{itemId}", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
public async Task<PlexMetadata> GetSeasons(string authToken, string plexFullHost, int ratingKey)
{
var request = new Request($"library/metadata/{ratingKey}/children", plexFullHost, HttpMethod.Get);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexMetadata>(request);
}
@ -128,9 +181,9 @@ namespace Ombi.Api.Plex
request.AddQueryString("type", "4");
AddLimitHeaders(request, start, retCount);
AddHeaders(request, authToken);
await AddHeaders(request, authToken);
return await Api.Request<PlexContainer>(request);
return await Api.Request<PlexContainer>(request);
}
/// <summary>
@ -141,32 +194,110 @@ namespace Ombi.Api.Plex
/// <returns></returns>
public async Task<PlexFriends> GetUsers(string authToken)
{
var request = new Request(string.Empty,FriendsUri, HttpMethod.Get, ContentType.Xml);
AddHeaders(request, authToken);
var request = new Request(string.Empty, FriendsUri, HttpMethod.Get, ContentType.Xml);
await AddHeaders(request, authToken);
return await Api.Request<PlexFriends>(request);
}
public async Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId)
{
var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get);
await AddHeaders(request, authToken);
AddLimitHeaders(request, 0, 50);
return await Api.Request<PlexMetadata>(request);
}
public async Task<OAuthPin> GetPin(int pinId)
{
var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get);
await AddHeaders(request);
return await Api.Request<OAuthPin>(request);
}
public async Task<Uri> GetOAuthUrl(int pinId, string code, string applicationUrl)
{
var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get);
await AddHeaders(request);
request.AddQueryString("pinID", pinId.ToString());
request.AddQueryString("code", code);
request.AddQueryString("context[device][product]", ApplicationName);
request.AddQueryString("context[device][environment]", "bundled");
request.AddQueryString("context[device][layout]", "desktop");
request.AddQueryString("context[device][platform]", "Web");
request.AddQueryString("context[device][device]", "Ombi (Web)");
var s = await GetSettings();
await CheckInstallId(s);
request.AddQueryString("clientID", s.InstallId.ToString("N"));
if (request.FullUri.Fragment.Equals("#"))
{
var uri = request.FullUri.ToString();
var withoutEnd = uri.Remove(uri.Length - 1, 1);
var startOfQueryLocation = withoutEnd.IndexOf('?');
var better = withoutEnd.Insert(startOfQueryLocation, "#");
request.FullUri = new Uri(better);
}
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>
/// Adds the required headers and also the authorization header
/// </summary>
/// <param name="request"></param>
/// <param name="authToken"></param>
private void AddHeaders(Request request, string authToken)
private async Task AddHeaders(Request request, string authToken)
{
request.AddHeader("X-Plex-Token", authToken);
AddHeaders(request);
await AddHeaders(request);
}
/// <summary>
/// Adds the main required headers to the Plex Request
/// </summary>
/// <param name="request"></param>
private void AddHeaders(Request request)
private async Task AddHeaders(Request request)
{
request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3");
request.AddHeader("X-Plex-Product", "Ombi");
var s = await GetSettings();
await CheckInstallId(s);
request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N"));
request.AddHeader("X-Plex-Product", ApplicationName);
request.AddHeader("X-Plex-Version", "3");
request.AddHeader("X-Plex-Device", "Ombi (Web)");
request.AddHeader("X-Plex-Platform", "Web");
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
request.AddHeader("Accept", "application/json");
}
@ -176,5 +307,19 @@ namespace Ombi.Api.Plex
request.AddHeader("X-Plex-Container-Start", from.ToString());
request.AddHeader("X-Plex-Container-Size", to.ToString());
}
private async Task CheckInstallId(PlexSettings s)
{
if (s.InstallId == null || s.InstallId == Guid.Empty)
{
s.InstallId = Guid.NewGuid();
await _plexSettings.SaveSettingsAsync(s);
}
}
private PlexSettings _settings;
private async Task<PlexSettings> GetSettings()
{
return _settings ?? (_settings = await _plexSettings.GetSettingsAsync());
}
}
}

@ -5,6 +5,6 @@ namespace Ombi.Api.Pushover
{
public interface IPushoverApi
{
Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken);
Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound);
}
}

@ -1,4 +1,5 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Ombi.Api.Pushover.Models;
@ -15,9 +16,13 @@ namespace Ombi.Api.Pushover
private readonly IApi _api;
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)
{
var request = new Request($"messages.json?token={accessToken}&user={userToken}&message={message}", PushoverEndpoint, HttpMethod.Post);
if (message.Contains("'"))
{
message = message.Replace("'", "&#39;");
}
var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post);
var result = await _api.Request<PushoverResponse>(request);
return result;

@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

@ -79,7 +79,7 @@ namespace Ombi.Api.Radarr
tmdbId = tmdbId,
qualityProfileId = qualityId,
rootFolderPath = rootPath,
titleSlug = title,
titleSlug = title + year,
monitored = true,
year = year,
minimumAvailability = minimumAvailability

@ -11,7 +11,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" />
</ItemGroup>
<ItemGroup>

@ -6,6 +6,30 @@ namespace Ombi.Api.Sonarr.Models
{
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 episodeFileId { get; set; }
public int seasonNumber { get; set; }
@ -27,6 +51,24 @@ namespace Ombi.Api.Sonarr.Models
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 seasonNumber { get; set; }
public string relativePath { get; set; }
@ -41,12 +83,32 @@ namespace Ombi.Api.Sonarr.Models
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 Revision revision { get; set; }
}
public class Revision
{
public Revision()
{
}
public Revision(Revision r)
{
version = r.version;
real = r.real;
}
public int version { get; set; }
public int real { get; set; }
}

@ -23,6 +23,7 @@ namespace Ombi.Api.Sonarr.Models
public string cleanTitle { get; set; }
public string imdbId { get; set; }
public string titleSlug { get; set; }
public string seriesType { get; set; }
public int id { get; set; }
public List<SonarrImage> images { get; set; }

@ -2,6 +2,16 @@ namespace Ombi.Api.Sonarr.Models
{
public class Quality
{
public Quality()
{
}
public Quality(Quality q)
{
id = q.id;
name = q.name;
}
public int id { get; set; }
public string name { get; set; }
}

@ -39,7 +39,11 @@ namespace Ombi.Api
if (!httpResponseMessage.IsSuccessStatusCode)
{
LogError(request, httpResponseMessage);
if (!request.IgnoreErrors)
{
LogError(request, httpResponseMessage);
}
if (request.Retry)
{
@ -76,15 +80,20 @@ namespace Ombi.Api
else
{
// XML
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(receivedString);
var value = (T)serializer.Deserialize(reader);
return value;
return DeserializeXml<T>(receivedString);
}
}
}
public T DeserializeXml<T>(string receivedString)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
StringReader reader = new StringReader(receivedString);
var value = (T) serializer.Deserialize(reader);
return value;
}
public async Task<string> RequestContent(Request request)
{
using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri))
@ -94,7 +103,10 @@ namespace Ombi.Api
var httpResponseMessage = await _client.SendAsync(httpRequestMessage);
if (!httpResponseMessage.IsSuccessStatusCode)
{
LogError(request, httpResponseMessage);
if (!request.IgnoreErrors)
{
LogError(request, httpResponseMessage);
}
}
// do something with the response
var data = httpResponseMessage.Content;
@ -112,7 +124,10 @@ namespace Ombi.Api
var httpResponseMessage = await _client.SendAsync(httpRequestMessage);
if (!httpResponseMessage.IsSuccessStatusCode)
{
LogError(request, httpResponseMessage);
if (!request.IgnoreErrors)
{
LogError(request, httpResponseMessage);
}
}
}
}

@ -7,5 +7,6 @@ namespace Ombi.Api
Task Request(Request request);
Task<T> Request<T>(Request request);
Task<string> RequestContent(Request request);
T DeserializeXml<T>(string receivedString);
}
}

@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Polly" Version="5.8.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Polly" Version="6.1.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup>

@ -10,7 +10,7 @@ namespace Ombi.Api
{
public Request()
{
}
public Request(string endpoint, string baseUrl, HttpMethod http, ContentType contentType = ContentType.Json)
@ -25,9 +25,10 @@ namespace Ombi.Api
public string Endpoint { get; }
public string BaseUrl { get; }
public HttpMethod HttpMethod { get; }
public bool IgnoreErrors { get; set; }
public bool Retry { get; set; }
public List<HttpStatusCode> StatusCodeToRetry { get; set; } = new List<HttpStatusCode>();
public bool IgnoreBaseUrlAppend { get; set; }
public Action<string> OnBeforeDeserialization { get; set; }
@ -38,7 +39,7 @@ namespace Ombi.Api
var sb = new StringBuilder();
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);
return sb.ToString();
@ -105,10 +106,10 @@ namespace Ombi.Api
hasQuery = true;
startingTag = builder.Query.Contains("?") ? "&" : "?";
}
builder.Query = hasQuery
? $"{builder.Query}{startingTag}{key}={value}"
: $"{startingTag}{key}={value}";
_modified = builder.Uri;
}

@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" Version="4.7.99" />
<PackageReference Include="Nunit" Version="3.8.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.7.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.6.1"></packagereference>
<PackageReference Include="Moq" Version="4.9.0" />
<PackageReference Include="Nunit" Version="3.10.1" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.8.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
</ItemGroup>
<ItemGroup>

@ -25,11 +25,14 @@ namespace Ombi.Core.Tests.Rule.Search
[Test]
public async Task Movie_ShouldBe_Available_WhenFoundInEmby()
{
ContextMock.Setup(x => x.Get(It.IsAny<string>())).ReturnsAsync(new EmbyContent
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).ReturnsAsync(new EmbyContent
{
ProviderId = "123"
});
var search = new SearchMovieViewModel();
var search = new SearchMovieViewModel()
{
TheMovieDbId = "123",
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
@ -39,7 +42,7 @@ namespace Ombi.Core.Tests.Rule.Search
[Test]
public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInEmby()
{
ContextMock.Setup(x => x.Get(It.IsAny<string>())).Returns(Task.FromResult(default(EmbyContent)));
ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny<string>())).Returns(Task.FromResult(default(EmbyContent)));
var search = new SearchMovieViewModel();
var result = await Rule.Execute(search);

@ -19,12 +19,14 @@ namespace Ombi.Core.Tests.Rule.Search
MovieMock = new Mock<IMovieRequestRepository>();
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 Mock<IMovieRequestRepository> MovieMock { get; set; }
private Mock<ITvRequestRepository> TvMock { get; set; }
private Mock<IMusicRequestRepository> MusicMock { get; set; }
[Test]

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

@ -0,0 +1,53 @@
using System;
using System.Threading.Tasks;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.OAuth;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
namespace Ombi.Core.Authentication
{
public class PlexOAuthManager : IPlexOAuthManager
{
public PlexOAuthManager(IPlexApi api, ISettingsService<CustomizationSettings> settings)
{
_api = api;
_customizationSettingsService = settings;
}
private readonly IPlexApi _api;
private readonly ISettingsService<CustomizationSettings> _customizationSettingsService;
public async Task<string> GetAccessTokenFromPin(int pinId)
{
var pin = await _api.GetPin(pinId);
if (pin.expiresAt < DateTime.UtcNow)
{
return string.Empty;
}
return pin.authToken;
}
public async Task<PlexAccount> GetAccount(string accessToken)
{
return await _api.GetAccount(accessToken);
}
public async Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null)
{
var settings = await _customizationSettingsService.GetSettingsAsync();
var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl);
return url;
}
public async Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress)
{
var url = await _api.GetOAuthUrl(pinId, code, websiteAddress);
return url;
}
}
}

@ -9,13 +9,12 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using Ombi.Store.Entities;
using Microsoft.AspNetCore.Identity;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine
{
@ -26,18 +25,21 @@ namespace Ombi.Core.Engine
private Dictionary<int, TvRequests> _dbTv;
protected BaseMediaEngine(IPrincipal identity, IRequestServiceMain requestService,
IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService<OmbiSettings> ombiSettings) : base(identity, um, rules)
IRuleEvaluator rules, OmbiUserManager um, ICacheService cache, ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub) : base(identity, um, rules)
{
RequestService = requestService;
Cache = cache;
OmbiSettings = ombiSettings;
_subscriptionRepository = sub;
}
protected IRequestServiceMain RequestService { get; }
protected IMovieRequestRepository MovieRepository => RequestService.MovieRequestService;
protected ITvRequestRepository TvRepository => RequestService.TvRequestService;
protected IMusicRequestRepository MusicRepository => RequestService.MusicRequestRepository;
protected readonly ICacheService Cache;
protected readonly ISettingsService<OmbiSettings> OmbiSettings;
protected readonly IRepository<RequestSubscription> _subscriptionRepository;
protected async Task<Dictionary<int, MovieRequests>> GetMovieRequests()
{
@ -78,7 +80,7 @@ namespace Ombi.Core.Engine
var pendingTv = 0;
var approvedTv = 0;
var availableTv = 0;
var availableTv = 0;
foreach (var tv in tvQuery)
{
foreach (var child in tv.ChildRequests)
@ -108,21 +110,51 @@ namespace Ombi.Core.Engine
protected async Task<HideResult> HideFromOtherUsers()
{
if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser))
var user = await GetUser();
if (await IsInRole(OmbiRoles.Admin) || await IsInRole(OmbiRoles.PowerUser) || user.IsSystemUser)
{
return new HideResult();
return new HideResult
{
UserId = user.Id
};
}
var settings = await Cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await OmbiSettings.GetSettingsAsync());
var result = new HideResult
{
Hide = settings.HideRequestsUsers
Hide = settings.HideRequestsUsers,
UserId = user.Id
};
return result;
}
public async Task SubscribeToRequest(int requestId, RequestType type)
{
var user = await GetUser();
var existingSub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(x =>
x.UserId.Equals(user.Id) && x.RequestId == requestId && x.RequestType == type);
if (existingSub != null)
{
return;
}
var sub = new RequestSubscription
{
UserId = user.Id,
RequestId = requestId,
RequestType = type
};
if (settings.HideRequestsUsers)
await _subscriptionRepository.Add(sub);
}
public async Task UnSubscribeRequest(int requestId, RequestType type)
{
var user = await GetUser();
var existingSub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(x =>
x.UserId.Equals(user.Id) && x.RequestId == requestId && x.RequestType == type);
if (existingSub != null)
{
var user = await GetUser();
result.UserId = user.Id;
await _subscriptionRepository.Delete(existingSub);
}
return result;
}
public class HideResult

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

@ -32,14 +32,7 @@ namespace Ombi.Core.Engine.Interfaces
private OmbiUser _user;
protected async Task<OmbiUser> GetUser()
{
if (IsApiUser)
{
return new OmbiUser
{
UserName = Username,
};
}
return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == Username));
return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(Username, StringComparison.CurrentCultureIgnoreCase)));
}
protected async Task<string> UserAlias()
@ -49,10 +42,6 @@ namespace Ombi.Core.Engine.Interfaces
protected async Task<bool> IsInRole(string roleName)
{
if (IsApiUser && roleName != OmbiRoles.Disabled)
{
return true;
}
return await UserManager.IsInRoleAsync(await GetUser(), roleName);
}
@ -72,7 +61,5 @@ namespace Ombi.Core.Engine.Interfaces
var ruleResults = await Rules.StartSpecificRules(model, rule);
return ruleResults;
}
private bool IsApiUser => Username.Equals("Api", StringComparison.CurrentCultureIgnoreCase);
}
}

@ -17,6 +17,5 @@ namespace Ombi.Core.Engine.Interfaces
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
Task<RequestEngineResult> ApproveMovieById(int requestId);
Task<RequestEngineResult> DenyMovieById(int modelId);
Task<IEnumerable<MovieRequests>> Filter(FilterViewModel vm);
}
}

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Lidarr.Models;
using Ombi.Core.Models.Search;
namespace Ombi.Core.Engine
{
public interface IMusicSearchEngine
{
Task<ArtistResult> GetAlbumArtist(string foreignArtistId);
Task<ArtistResult> GetArtist(int artistId);
Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId);
Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search);
Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search);
}
}

@ -1,6 +1,9 @@
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;
namespace Ombi.Core.Engine.Interfaces
{
@ -11,11 +14,15 @@ namespace Ombi.Core.Engine.Interfaces
//Task<IEnumerable<T>> GetNewRequests();
//Task<IEnumerable<T>> GetAvailableRequests();
RequestCountModel RequestCount();
Task<IEnumerable<T>> GetRequests(int count, int position);
Task<RequestsViewModel<T>> GetRequests(int count, int position, OrderFilterModel model);
Task<IEnumerable<T>> GetRequests();
Task<bool> UserHasRequest(string userId);
Task<RequestEngineResult> MarkUnavailable(int modelId);
Task<RequestEngineResult> MarkAvailable(int modelId);
Task<int> GetTotal();
Task UnSubscribeRequest(int requestId, RequestType type);
Task SubscribeToRequest(int requestId, RequestType type);
Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user = null);
}
}

@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.UI;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Engine.Interfaces
@ -9,15 +10,18 @@ namespace Ombi.Core.Engine.Interfaces
{
Task RemoveTvRequest(int requestId);
Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv);
Task<TvRequests> GetTvRequest(int requestId);
Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
Task<RequestEngineResult> DenyChildRequest(int requestId);
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search);
Task<TvRequests> UpdateTvRequest(TvRequests request);
Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position);
Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId);
Task<ChildRequests> UpdateChildRequest(ChildRequests request);
Task RemoveTvChild(int requestId);
Task<RequestEngineResult> ApproveChildRequest(int id);
Task<IEnumerable<TvRequests>> GetRequestsLite();
Task UpdateQualityProfile(int requestId, int profileId);
Task UpdateRootPath(int requestId, int rootPath);
}
}

@ -7,16 +7,10 @@ namespace Ombi.Core.Engine.Interfaces
public interface ITvSearchEngine
{
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<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree();
Task<IEnumerable<SearchTvShowViewModel>> Popular();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree();
Task<IEnumerable<SearchTvShowViewModel>> Anticipated();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree();
Task<IEnumerable<SearchTvShowViewModel>> MostWatches();
Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree();
Task<IEnumerable<SearchTvShowViewModel>> Trending();
}
}

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

@ -13,11 +13,13 @@ using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Models.UI;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Core.Models;
namespace Ombi.Core.Engine
{
@ -25,7 +27,9 @@ namespace Ombi.Core.Engine
{
public MovieRequestEngine(IMovieDbApi movieApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator r, IMovieSender sender, ILogger<MovieRequestEngine> log,
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache, ISettingsService<OmbiSettings> ombiSettings) : base(user, requestService, r, manager, cache, ombiSettings)
OmbiUserManager manager, IRepository<RequestLog> rl, ICacheService cache,
ISettingsService<OmbiSettings> ombiSettings, IRepository<RequestSubscription> sub)
: base(user, requestService, r, manager, cache, ombiSettings, sub)
{
MovieApi = movieApi;
NotificationHelper = helper;
@ -57,6 +61,7 @@ namespace Ombi.Core.Engine
ErrorMessage = $"Please try again later"
};
}
var fullMovieName =
$"{movieInfo.Title}{(!string.IsNullOrEmpty(movieInfo.ReleaseDate) ? $" ({DateTime.Parse(movieInfo.ReleaseDate).Year})" : string.Empty)}";
@ -81,7 +86,8 @@ namespace Ombi.Core.Engine
};
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
requestModel.DigitalReleaseDate = usDates?.ReleaseDate?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate;
requestModel.DigitalReleaseDate = usDates?.ReleaseDate
?.FirstOrDefault(x => x.Type == ReleaseDateType.Digital)?.ReleaseDate;
var ruleResults = (await RunRequestRules(requestModel)).ToList();
if (ruleResults.Any(x => !x.Success))
@ -124,24 +130,106 @@ namespace Ombi.Core.Engine
/// </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<IEnumerable<MovieRequests>> GetRequests(int count, int position)
public async Task<RequestsViewModel<MovieRequests>> GetRequests(int count, int position,
OrderFilterModel orderFilter)
{
var shouldHide = await HideFromOtherUsers();
List<MovieRequests> allRequests;
IQueryable<MovieRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = await MovieRepository.GetWithUser(shouldHide.UserId).Skip(position).Take(count).ToListAsync();
allRequests =
MovieRepository.GetWithUser(shouldHide
.UserId); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
}
else
{
allRequests = await MovieRepository.GetWithUser().Skip(position).Take(count).ToListAsync();
allRequests =
MovieRepository
.GetWithUser(); //.Skip(position).Take(count).OrderByDescending(x => x.ReleaseDate).ToListAsync();
}
allRequests.ForEach(x =>
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 (OrderMovies(allRequests, orderFilter.OrderType)).Skip(position).Take(count)
.ToListAsync();
requests.ForEach(async x =>
{
x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
await CheckForSubscription(shouldHide, x);
});
return allRequests;
return new RequestsViewModel<MovieRequests>
{
Collection = requests,
Total = total
};
}
private IQueryable<MovieRequests> OrderMovies(IQueryable<MovieRequests> 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);
case OrderType.StatusAsc:
return allRequests.OrderBy(x => x.Status);
case OrderType.StatusDesc:
return allRequests.OrderByDescending(x => x.Status);
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
public async Task<int> GetTotal()
{
var shouldHide = await HideFromOtherUsers();
if (shouldHide.Hide)
{
return await MovieRepository.GetWithUser(shouldHide.UserId).CountAsync();
}
else
{
return await MovieRepository.GetWithUser().CountAsync();
}
}
/// <summary>
@ -160,9 +248,30 @@ namespace Ombi.Core.Engine
{
allRequests = await MovieRepository.GetWithUser().ToListAsync();
}
allRequests.ForEach(async x =>
{
x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
await CheckForSubscription(shouldHide, x);
});
return allRequests;
}
private async Task CheckForSubscription(HideResult shouldHide, MovieRequests 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.Movie);
x.Subscribed = sub != null;
}
}
/// <summary>
/// Searches the movie request.
/// </summary>
@ -180,10 +289,12 @@ namespace Ombi.Core.Engine
{
allRequests = await MovieRepository.GetWithUser().ToListAsync();
}
var results = allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToList();
results.ForEach(x =>
results.ForEach(async x =>
{
x.PosterPath = PosterPathHelper.FixPosterPath(x.PosterPath);
await CheckForSubscription(shouldHide, x);
});
return results;
}
@ -204,6 +315,7 @@ namespace Ombi.Core.Engine
ErrorMessage = "Request does not exist"
};
}
request.Denied = true;
// We are denying a request
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
@ -224,6 +336,8 @@ namespace Ombi.Core.Engine
ErrorMessage = "Request does not exist"
};
}
request.MarkedAsApproved = DateTime.Now;
request.Approved = true;
request.Denied = false;
await MovieRepository.Update(request);
@ -244,6 +358,7 @@ namespace Ombi.Core.Engine
Result = true
};
}
if (!result.Success)
{
Logger.LogWarning("Tried auto sending movie but failed. Message: {0}", result.Message);
@ -254,6 +369,7 @@ namespace Ombi.Core.Engine
Result = false
};
}
// If there are no providers then it's successful but movie has not been sent
}
@ -315,6 +431,7 @@ namespace Ombi.Core.Engine
ErrorMessage = "Request does not exist"
};
}
request.Available = false;
await MovieRepository.Update(request);
@ -335,7 +452,9 @@ namespace Ombi.Core.Engine
ErrorMessage = "Request does not exist"
};
}
request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
NotificationHelper.Notify(request, NotificationType.RequestAvailable);
await MovieRepository.Update(request);
@ -364,45 +483,51 @@ namespace Ombi.Core.Engine
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!"};
}
public async Task<IEnumerable<MovieRequests>> Filter(FilterViewModel vm)
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
{
var shouldHide = await HideFromOtherUsers();
var requests = shouldHide.Hide ? MovieRepository.GetWithUser(shouldHide.UserId) : MovieRepository.GetWithUser();
switch (vm.AvailabilityFilter)
if (user == null)
{
case FilterType.None:
break;
case FilterType.Available:
requests = requests.Where(x => x.Available);
break;
case FilterType.NotAvailable:
requests = requests.Where(x => !x.Available);
break;
default:
throw new ArgumentOutOfRangeException();
user = await GetUser();
// If user is still null after attempting to get the logged in user, return null.
if (user == null)
{
return null;
}
}
switch (vm.StatusFilter)
int limit = user.MovieRequestLimit ?? 0;
if (limit <= 0)
{
case FilterType.None:
break;
case FilterType.Approved:
requests = requests.Where(x => x.Approved);
break;
case FilterType.Processing:
requests = requests.Where(x => x.Approved && !x.Available);
break;
case FilterType.PendingApproval:
requests = requests.Where(x => !x.Approved && !x.Available && !(x.Denied ?? false));
break;
default:
throw new ArgumentOutOfRangeException();
return new RequestQuotaCountModel()
{
HasLimit = false,
Limit = 0,
Remaining = 0,
NextRequest = DateTime.Now,
};
}
return requests;
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),
};
}
}
}

@ -9,20 +9,23 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine
{
public class MovieSearchEngine : BaseMediaEngine, IMovieEngine
{
public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s)
: base(identity, service, r, um, mem, s)
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub)
: base(identity, service, r, um, mem, s, sub)
{
MovieApi = movApi;
Mapper = mapper;
@ -57,7 +60,6 @@ namespace Ombi.Core.Engine
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
}
return null;
@ -88,7 +90,6 @@ namespace Ombi.Core.Engine
var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () => await MovieApi.PopularMovies(), DateTime.Now.AddHours(12));
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
}
return null;
@ -103,7 +104,6 @@ namespace Ombi.Core.Engine
var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () => await MovieApi.TopRated(), DateTime.Now.AddHours(12));
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
}
return null;
@ -133,7 +133,6 @@ namespace Ombi.Core.Engine
var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => await MovieApi.NowPlaying(), DateTime.Now.AddHours(12));
if (result != null)
{
Logger.LogDebug("Search Result: {result}", result);
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
}
return null;
@ -164,10 +163,31 @@ namespace Ombi.Core.Engine
viewMovie.TheMovieDbId = viewMovie.Id.ToString();
await RunSearchRules(viewMovie);
// This requires the rules to be run first to populate the RequestId property
await CheckForSubscription(viewMovie);
return viewMovie;
}
private async Task CheckForSubscription(SearchMovieViewModel viewModel)
{
// Check if this user requested it
var user = await GetUser();
var request = await RequestService.MovieRequestService.GetAll()
.AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id);
if (request)
{
viewModel.ShowSubscribe = false;
}
else
{
viewModel.ShowSubscribe = true;
var sub = await _subscriptionRepository.GetAll().FirstOrDefaultAsync(s => s.UserId == user.Id
&& s.RequestId == viewModel.RequestId && s.RequestType == RequestType.Movie);
viewModel.Subscribed = sub != null;
}
}
private async Task<SearchMovieViewModel> ProcessSingleMovie(MovieSearchResult movie)
{

@ -0,0 +1,503 @@
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
};
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)
{
var request = await MusicRepository.Find(modelId);
if (request == null)
{
return new RequestEngineResult
{
ErrorMessage = "Request does not exist"
};
}
request.Denied = true;
// 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!" };
}
}
}

@ -0,0 +1,219 @@
using System;
using AutoMapper;
using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.External;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Engine
{
public class MusicSearchEngine : BaseMediaEngine, IMusicSearchEngine
{
public MusicSearchEngine(IPrincipal identity, IRequestServiceMain service, ILidarrApi lidarrApi, IMapper mapper,
ILogger<MusicSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub,
ISettingsService<LidarrSettings> lidarrSettings)
: base(identity, service, r, um, mem, s, sub)
{
_lidarrApi = lidarrApi;
_lidarrSettings = lidarrSettings;
Mapper = mapper;
Logger = logger;
}
private readonly ILidarrApi _lidarrApi;
private IMapper Mapper { get; }
private ILogger Logger { get; }
private readonly ISettingsService<LidarrSettings> _lidarrSettings;
/// <summary>
/// Searches the specified album.
/// </summary>
/// <param name="search">The search.</param>
/// <returns></returns>
public async Task<IEnumerable<SearchAlbumViewModel>> SearchAlbum(string search)
{
var settings = await GetSettings();
var result = await _lidarrApi.AlbumLookup(search, settings.ApiKey, settings.FullUri);
var vm = new List<SearchAlbumViewModel>();
foreach (var r in result)
{
vm.Add(await MapIntoAlbumVm(r, settings));
}
return vm;
}
/// <summary>
/// Searches the specified artist
/// </summary>
/// <param name="search">The search.</param>
/// <returns></returns>
public async Task<IEnumerable<SearchArtistViewModel>> SearchArtist(string search)
{
var settings = await GetSettings();
var result = await _lidarrApi.ArtistLookup(search, settings.ApiKey, settings.FullUri);
var vm = new List<SearchArtistViewModel>();
foreach (var r in result)
{
vm.Add(await MapIntoArtistVm(r));
}
return vm;
}
/// <summary>
/// Returns all albums by the specified artist
/// </summary>
/// <param name="foreignArtistId"></param>
/// <returns></returns>
public async Task<IEnumerable<SearchAlbumViewModel>> GetArtistAlbums(string foreignArtistId)
{
var settings = await GetSettings();
var result = await _lidarrApi.GetAlbumsByArtist(foreignArtistId);
// We do not want any Singles (This will include EP's)
var albumsOnly =
result.Albums.Where(x => !x.Type.Equals("Single", StringComparison.InvariantCultureIgnoreCase));
var vm = new List<SearchAlbumViewModel>();
foreach (var album in albumsOnly)
{
vm.Add(await MapIntoAlbumVm(album, result.Id, result.ArtistName, settings));
}
return vm;
}
/// <summary>
/// Returns the artist that produced the album
/// </summary>
/// <param name="foreignArtistId"></param>
/// <returns></returns>
public async Task<ArtistResult> GetAlbumArtist(string foreignArtistId)
{
var settings = await GetSettings();
return await _lidarrApi.GetArtistByForeignId(foreignArtistId, settings.ApiKey, settings.FullUri);
}
public async Task<ArtistResult> GetArtist(int artistId)
{
var settings = await GetSettings();
return await _lidarrApi.GetArtist(artistId, settings.ApiKey, settings.FullUri);
}
private async Task<SearchArtistViewModel> MapIntoArtistVm(ArtistLookup a)
{
var vm = new SearchArtistViewModel
{
ArtistName = a.artistName,
ArtistType = a.artistType,
Banner = a.images?.FirstOrDefault(x => x.coverType.Equals("banner"))?.url,
Logo = a.images?.FirstOrDefault(x => x.coverType.Equals("logo"))?.url,
CleanName = a.cleanName,
Disambiguation = a.disambiguation,
ForignArtistId = a.foreignArtistId,
Links = a.links,
Overview = a.overview,
};
var poster = a.images?.FirstOrDefault(x => x.coverType.Equals("poaster"));
if (poster == null)
{
vm.Poster = a.remotePoster;
}
await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist);
return vm;
}
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(AlbumLookup a, LidarrSettings settings)
{
var vm = new SearchAlbumViewModel
{
ForeignAlbumId = a.foreignAlbumId,
Monitored = a.monitored,
Rating = a.ratings?.value ?? 0m,
ReleaseDate = a.releaseDate,
Title = a.title,
Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url
};
if (a.artistId > 0)
{
//TODO THEY HAVE FIXED THIS IN DEV
// The JSON is different for some stupid reason
// Need to lookup the artist now and all the images -.-"
var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri);
vm.ArtistName = artist.artistName;
vm.ForeignArtistId = artist.foreignArtistId;
}
else
{
vm.ForeignArtistId = a.artist?.foreignArtistId;
vm.ArtistName = a.artist?.artistName;
}
vm.Cover = a.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url;
if (vm.Cover.IsNullOrEmpty())
{
vm.Cover = a.remoteCover;
}
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
await RunSearchRules(vm);
return vm;
}
private async Task<SearchAlbumViewModel> MapIntoAlbumVm(Album a, string artistId, string artistName, LidarrSettings settings)
{
var fullAlbum = await _lidarrApi.GetAlbumByForeignId(a.Id, settings.ApiKey, settings.FullUri);
var vm = new SearchAlbumViewModel
{
ForeignAlbumId = a.Id,
Monitored = fullAlbum.monitored,
Rating = fullAlbum.ratings?.value ?? 0m,
ReleaseDate = fullAlbum.releaseDate,
Title = a.Title,
Disk = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url,
ForeignArtistId = artistId,
ArtistName = artistName,
Cover = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url
};
if (vm.Cover.IsNullOrEmpty())
{
vm.Cover = fullAlbum.remoteCover;
}
await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum);
await RunSearchRules(vm);
return vm;
}
private LidarrSettings _settings;
private async Task<LidarrSettings> GetSettings()
{
return _settings ?? (_settings = await _lidarrSettings.GetSettingsAsync());
}
}
}

@ -63,13 +63,17 @@ namespace Ombi.Core.Engine
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
foreach (var p in plexContent)
{
if (!p.HasTheMovieDb)
{
continue;
}
if (p.Type == PlexMediaTypeEntity.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = p.Id,
ContentId = int.Parse(p.TheMovieDbId),
ContentType = ContentType.Parent
});
}
@ -78,12 +82,18 @@ namespace Ombi.Core.Engine
// Add the episodes
foreach (var ep in p.Episodes)
{
if (!ep.Series.HasTvDb)
{
continue;
}
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentId = ep.Id,
ContentType = ContentType.Episode
ContentId = int.Parse(ep.Series.TvDbId),
ContentType = ContentType.Episode,
EpisodeNumber = ep.EpisodeNumber,
SeasonNumber = ep.SeasonNumber
});
}
}
@ -91,13 +101,17 @@ namespace Ombi.Core.Engine
foreach (var e in embyContent)
{
if (e.TheMovieDbId.IsNullOrEmpty())
{
continue;
}
if (e.Type == EmbyMediaType.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby,
ContentId = e.Id,
ContentId = int.Parse(e.TheMovieDbId),
ContentType = ContentType.Parent
});
}
@ -106,12 +120,18 @@ namespace Ombi.Core.Engine
// Add the episodes
foreach (var ep in e.Episodes)
{
if (ep.Series.TvDbId.IsNullOrEmpty())
{
continue;
}
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby,
ContentId = ep.Id,
ContentType = ContentType.Episode
ContentId = int.Parse(ep.Series.TvDbId),
ContentType = ContentType.Episode,
EpisodeNumber = ep.EpisodeNumber,
SeasonNumber = ep.SeasonNumber
});
}
}
@ -152,7 +172,9 @@ namespace Ombi.Core.Engine
model.Add(new RecentlyAddedMovieModel
{
Id = emby.Id,
ImdbId = emby.ProviderId,
ImdbId = emby.ImdbId,
TheMovieDbId = emby.TheMovieDbId,
TvDbId = emby.TvDbId,
AddedAt = emby.AddedAt,
Title = emby.Title,
});
@ -211,7 +233,9 @@ namespace Ombi.Core.Engine
model.Add(new RecentlyAddedTvModel
{
Id = emby.Id,
ImdbId = emby.ProviderId,
ImdbId = emby.ImdbId,
TvDbId = emby.TvDbId,
TheMovieDbId = emby.TheMovieDbId,
AddedAt = emby.AddedAt,
Title = emby.Title,
EpisodeNumber = episode.EpisodeNumber,

@ -1,23 +0,0 @@
using System.Collections.Generic;
namespace Ombi.Core.Engine
{
public class TreeNode<T>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<T>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
public class TreeNode<T,U>
{
public string Label { get; set; }
public T Data { get; set; }
public List<TreeNode<U>> Children { get; set; }
public bool Leaf { get; set; }
public bool Expanded { get; set; }
}
}

@ -1,6 +1,7 @@
using System;
using AutoMapper;
using Ombi.Api.TvMaze;
using Ombi.Api.TheMovieDb;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Helpers;
@ -14,6 +15,7 @@ using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Helpers;
using Ombi.Core.Models.UI;
using Ombi.Core.Rule;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Senders;
@ -21,16 +23,19 @@ using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Core.Models;
namespace Ombi.Core.Engine
{
public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine
{
public TvRequestEngine(ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user,
public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager,
ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache) : base(user, requestService, rule, manager, cache, settings)
ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
IRepository<RequestSubscription> sub) : base(user, requestService, rule, manager, cache, settings, sub)
{
TvApi = tvApi;
MovieDbApi = movApi;
NotificationHelper = helper;
TvSender = sender;
Audit = audit;
@ -39,17 +44,18 @@ namespace Ombi.Core.Engine
private INotificationHelper NotificationHelper { get; }
private ITvMazeApi TvApi { get; }
private IMovieDbApi MovieDbApi { get; }
private ITvSender TvSender { get; }
private IAuditRepository Audit { get; }
private readonly IRepository<RequestLog> _requestLog;
public async Task<RequestEngineResult> RequestTvShow(SearchTvShowViewModel tv)
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
{
var user = await GetUser();
var tvBuilder = new TvShowRequestBuilder(TvApi);
var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi);
(await tvBuilder
.GetShowInfo(tv.Id))
.GetShowInfo(tv.TvDbId))
.CreateTvList(tv)
.CreateChild(tv, user.Id);
@ -78,9 +84,9 @@ namespace Ombi.Core.Engine
}
}
await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tv.Title}", Username);
await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username);
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.Id);
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
if (existingRequest != null)
{
// Remove requests we already have, we just want new ones
@ -128,7 +134,7 @@ namespace Ombi.Core.Engine
return await AddRequest(newRequest.NewRequest);
}
public async Task<IEnumerable<TvRequests>> GetRequests(int count, int position)
public async Task<RequestsViewModel<TvRequests>> GetRequests(int count, int position, OrderFilterModel type)
{
var shouldHide = await HideFromOtherUsers();
List<TvRequests> allRequests;
@ -138,6 +144,7 @@ namespace Ombi.Core.Engine
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
.Skip(position).Take(count).ToListAsync();
// Filter out children
@ -150,72 +157,143 @@ namespace Ombi.Core.Engine
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault())
.Skip(position).Take(count).ToListAsync();
}
return allRequests;
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return new RequestsViewModel<TvRequests>
{
Collection = allRequests
};
}
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> GetRequestsTreeNode(int count, int position)
public async Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type)
{
var shouldHide = await HideFromOtherUsers();
List<TvRequests> allRequests = null;
if (shouldHide.Hide)
{
var tv = TvRepository.GetLite(shouldHide.UserId);
if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
{
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
}
// Filter out children
FilterChildren(allRequests, shouldHide);
}
else
{
var tv = TvRepository.GetLite();
if (tv.Any() && tv.Select(x => x.ChildRequests).Any())
{
allRequests = await tv.OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()).Skip(position).Take(count).ToListAsync();
}
}
if (allRequests == null)
{
return new RequestsViewModel<TvRequests>();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return new RequestsViewModel<TvRequests>
{
Collection = allRequests
};
}
public async Task<IEnumerable<TvRequests>> GetRequests()
{
var shouldHide = await HideFromOtherUsers();
List<TvRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = await TvRepository.Get(shouldHide.UserId)
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Skip(position).Take(count).ToListAsync();
allRequests = await TvRepository.Get(shouldHide.UserId).ToListAsync();
FilterChildren(allRequests, shouldHide);
}
else
{
allRequests = await TvRepository.Get()
.Include(x => x.ChildRequests)
.ThenInclude(x => x.SeasonRequests)
.ThenInclude(x => x.Episodes)
.Skip(position).Take(count).ToListAsync();
allRequests = await TvRepository.Get().ToListAsync();
}
return ParseIntoTreeNode(allRequests);
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return allRequests;
}
public async Task<IEnumerable<TvRequests>> GetRequests()
public async Task<IEnumerable<TvRequests>> GetRequestsLite()
{
var shouldHide = await HideFromOtherUsers();
IQueryable<TvRequests> allRequests;
List<TvRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = TvRepository.Get(shouldHide.UserId);
allRequests = await TvRepository.GetLite(shouldHide.UserId).ToListAsync();
FilterChildren(allRequests, shouldHide);
}
else
{
allRequests = TvRepository.Get();
allRequests = await TvRepository.GetLite().ToListAsync();
}
return await allRequests.ToListAsync();
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return allRequests;
}
public async Task<TvRequests> GetTvRequest(int requestId)
{
var shouldHide = await HideFromOtherUsers();
TvRequests request;
if (shouldHide.Hide)
{
request = await TvRepository.Get(shouldHide.UserId).Where(x => x.Id == requestId).FirstOrDefaultAsync();
FilterChildren(request, shouldHide);
}
else
{
request = await TvRepository.Get().Where(x => x.Id == requestId).FirstOrDefaultAsync();
}
await CheckForSubscription(shouldHide, request);
return request;
}
private static void FilterChildren(IEnumerable<TvRequests> allRequests, HideResult shouldHide)
{
if (allRequests == null)
{
return;
}
// Filter out children
foreach (var t in allRequests)
{
for (var j = 0; j < t.ChildRequests.Count; j++)
{
var child = t.ChildRequests[j];
if (child.RequestedUserId != shouldHide.UserId)
{
t.ChildRequests.RemoveAt(j);
j--;
}
FilterChildren(t, shouldHide);
}
}
}
private static void FilterChildren(TvRequests t, HideResult shouldHide)
{
// Filter out children
for (var j = 0; j < t.ChildRequests.Count; j++)
{
var child = t.ChildRequests[j];
if (child.RequestedUserId != shouldHide.UserId)
{
t.ChildRequests.RemoveAt(j);
j--;
}
}
}
public async Task<IEnumerable<ChildRequests>> GetAllChldren(int tvId)
{
var shouldHide = await HideFromOtherUsers();
@ -229,6 +307,8 @@ namespace Ombi.Core.Engine
allRequests = await TvRepository.GetChild().Include(x => x.SeasonRequests).Where(x => x.ParentRequestId == tvId).ToListAsync();
}
allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return allRequests;
}
@ -245,23 +325,27 @@ namespace Ombi.Core.Engine
allRequests = TvRepository.Get();
}
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();
results.ForEach(async r => { await CheckForSubscription(shouldHide, r); });
return results;
}
public async Task<IEnumerable<TreeNode<TvRequests, List<ChildRequests>>>> SearchTvRequestTree(string search)
public async Task UpdateRootPath(int requestId, int rootPath)
{
var shouldHide = await HideFromOtherUsers();
IQueryable<TvRequests> allRequests;
if (shouldHide.Hide)
{
allRequests = TvRepository.Get(shouldHide.UserId);
}
else
{
allRequests = TvRepository.Get();
}
var results = await allRequests.Where(x => x.Title.Contains(search, CompareOptions.IgnoreCase)).ToListAsync();
return ParseIntoTreeNode(results);
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
results.RootFolder = rootPath;
await TvRepository.Update(results);
}
public async Task UpdateQualityProfile(int requestId, int profileId)
{
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == requestId);
results.QualityOverride = profileId;
await TvRepository.Update(results);
}
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
@ -274,9 +358,10 @@ namespace Ombi.Core.Engine
results.ImdbId = request.ImdbId;
results.Overview = request.Overview;
results.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath);
results.Background = PosterPathHelper.FixBackgroundPath(request.Background);
results.QualityOverride = request.QualityOverride;
results.RootFolder = request.RootFolder;
await TvRepository.Update(results);
return results;
}
@ -412,6 +497,7 @@ namespace Ombi.Core.Engine
};
}
request.Available = true;
request.MarkedAsAvailable = DateTime.Now;
foreach (var season in request.SeasonRequests)
{
foreach (var e in season.Episodes)
@ -428,6 +514,42 @@ namespace Ombi.Core.Engine
};
}
public async Task<int> GetTotal()
{
var shouldHide = await HideFromOtherUsers();
if (shouldHide.Hide)
{
return await TvRepository.Get(shouldHide.UserId).CountAsync();
}
else
{
return await TvRepository.Get().CountAsync();
}
}
private async Task CheckForSubscription(HideResult shouldHide, TvRequests x)
{
foreach (var tv in x.ChildRequests)
{
await CheckForSubscription(shouldHide, tv);
}
}
private async Task CheckForSubscription(HideResult shouldHide, ChildRequests 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.TvShow);
x.Subscribed = sub != null;
}
}
private async Task<RequestEngineResult> AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest)
{
// Add the child
@ -445,29 +567,7 @@ namespace Ombi.Core.Engine
return await AfterRequest(model.ChildRequests.FirstOrDefault());
}
private static List<TreeNode<TvRequests, List<ChildRequests>>> ParseIntoTreeNode(IEnumerable<TvRequests> result)
{
var node = new List<TreeNode<TvRequests, List<ChildRequests>>>();
foreach (var value in result)
{
node.Add(new TreeNode<TvRequests, List<ChildRequests>>
{
Data = value,
Children = new List<TreeNode<List<ChildRequests>>>
{
new TreeNode<List<ChildRequests>>
{
Data = SortEpisodes(value.ChildRequests),
Leaf = true
}
}
});
}
return node;
}
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
private static List<ChildRequests> SortEpisodes(List<ChildRequests> items)
{
foreach (var value in items)
{
@ -488,6 +588,15 @@ namespace Ombi.Core.Engine
NotificationHelper.NewRequest(model);
}
await _requestLog.Add(new RequestLog
{
UserId = (await GetUser()).Id,
RequestDate = DateTime.UtcNow,
RequestId = model.Id,
RequestType = RequestType.TvShow,
EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(),
});
if (model.Approved)
{
// Autosend
@ -503,15 +612,58 @@ namespace Ombi.Core.Engine
};
}
await _requestLog.Add(new RequestLog
return new RequestEngineResult { Result = true };
}
public async Task<RequestQuotaCountModel> GetRemainingRequests(OmbiUser user)
{
if (user == null)
{
UserId = (await GetUser()).Id,
RequestDate = DateTime.UtcNow,
RequestId = model.Id,
RequestType = RequestType.TvShow,
});
user = await GetUser();
return new RequestEngineResult { Result = true };
// If user is still null after attempting to get the logged in user, return null.
if (user == null)
{
return null;
}
}
int limit = user.EpisodeRequestLimit ?? 0;
if (limit <= 0)
{
return new RequestQuotaCountModel()
{
HasLimit = false,
Limit = 0,
Remaining = 0,
NextRequest = DateTime.Now,
};
}
IQueryable<RequestLog> log = _requestLog.GetAll()
.Where(x => x.UserId == user.Id
&& x.RequestType == RequestType.TvShow
&& x.RequestDate >= DateTime.UtcNow.AddDays(-7));
// Needed, due to a bug which would cause all episode counts to be 0
int zeroEpisodeCount = await log.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync();
int episodeCount = await log.Where(x => x.EpisodeCount != 0).Select(x => x.EpisodeCount).SumAsync();
int count = limit - (zeroEpisodeCount + episodeCount);
DateTime oldestRequestedAt = await log.OrderBy(x => x.RequestDate)
.Select(x => x.RequestDate)
.FirstOrDefaultAsync();
return new RequestQuotaCountModel()
{
HasLimit = true,
Limit = limit,
Remaining = count,
NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc),
};
}
}
}

@ -20,6 +20,7 @@ using Microsoft.Extensions.Caching.Memory;
using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
using Ombi.Store.Entities;
namespace Ombi.Core.Engine
{
@ -27,8 +28,8 @@ namespace Ombi.Core.Engine
{
public TvSearchEngine(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)
: base(identity, service, r, um, memCache, s)
ICacheService memCache, ISettingsService<OmbiSettings> s, IRepository<RequestSubscription> sub)
: base(identity, service, r, um, memCache, s, sub)
{
TvMazeApi = tvMaze;
Mapper = mapper;
@ -53,16 +54,20 @@ namespace Ombi.Core.Engine
if (searchResult != null)
{
return await ProcessResults(searchResult);
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in searchResult)
{
if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false))
{
continue;
}
retVal.Add(await ProcessResult(tvMazeSearch));
}
return retVal;
}
return null;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> SearchTreeNode(string searchTerm)
{
var result = await Search(searchTerm);
return result.Select(ParseIntoTreeNode).ToList();
}
public async Task<SearchTvShowViewModel> GetShowInformation(int tvdbid)
{
var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid);
@ -115,19 +120,6 @@ namespace Ombi.Core.Engine
return await ProcessResult(mapped);
}
public async Task<TreeNode<SearchTvShowViewModel>> GetShowInformationTreeNode(int tvdbid)
{
var result = await GetShowInformation(tvdbid);
return ParseIntoTreeNode(result);
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> PopularTree()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Popular()
{
var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
@ -135,12 +127,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> AnticipatedTree()
{
var result = await Cache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Anticipated()
{
@ -149,12 +135,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatchesTree()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> MostWatches()
{
var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
@ -162,13 +142,6 @@ namespace Ombi.Core.Engine
return processed;
}
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> TrendingTree()
{
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
var processed = await ProcessResults(result);
return processed.Select(ParseIntoTreeNode).ToList();
}
public async Task<IEnumerable<SearchTvShowViewModel>> Trending()
{
var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
@ -176,33 +149,21 @@ namespace Ombi.Core.Engine
return processed;
}
private static TreeNode<SearchTvShowViewModel> ParseIntoTreeNode(SearchTvShowViewModel result)
{
return new TreeNode<SearchTvShowViewModel>
{
Data = result,
Children = new List<TreeNode<SearchTvShowViewModel>>
{
new TreeNode<SearchTvShowViewModel>
{
Data = result, Leaf = true
}
},
Leaf = false
};
}
private async Task<IEnumerable<SearchTvShowViewModel>> ProcessResults<T>(IEnumerable<T> items)
{
var retVal = new List<SearchTvShowViewModel>();
foreach (var tvMazeSearch in items)
{
var viewT = Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
retVal.Add(await ProcessResult(viewT));
retVal.Add(await ProcessResult(tvMazeSearch));
}
return retVal;
}
private async Task<SearchTvShowViewModel> ProcessResult<T>(T tvMazeSearch)
{
return Mapper.Map<SearchTvShowViewModel>(tvMazeSearch);
}
private async Task<SearchTvShowViewModel> ProcessResult(SearchTvShowViewModel item)
{
item.TheTvDbId = item.Id.ToString();

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Store.Entities;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Engine
{
public class UserStatsEngine : IUserStatsEngine
{
public UserStatsEngine(OmbiUserManager um, IMovieRequestRepository movieRequest, ITvRequestRepository tvRequest)
{
_userManager = um;
_movieRequest = movieRequest;
_tvRequest = tvRequest;
}
private readonly OmbiUserManager _userManager;
private readonly IMovieRequestRepository _movieRequest;
private readonly ITvRequestRepository _tvRequest;
public async Task<UserStatsSummary> GetSummary(SummaryRequest request)
{
// get all movie requests
var movies = _movieRequest.GetWithUser();
var filteredMovies = movies.Where(x => x.RequestedDate >= request.From && x.RequestedDate <= request.To);
var tv = _tvRequest.GetLite();
var children = tv.SelectMany(x =>
x.ChildRequests.Where(c => c.RequestedDate >= request.From && c.RequestedDate <= request.To));
var moviesCount = filteredMovies.CountAsync();
var childrenCount = children.CountAsync();
var availableMovies =
movies.Select(x => x.MarkedAsAvailable >= request.From && x.MarkedAsAvailable <= request.To).CountAsync();
var availableChildren = tv.SelectMany(x =>
x.ChildRequests.Where(c => c.MarkedAsAvailable >= request.From && c.MarkedAsAvailable <= request.To)).CountAsync();
var userMovie = filteredMovies.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
var userTv = children.GroupBy(x => x.RequestedUserId).OrderBy(x => x.Key).FirstOrDefaultAsync();
return new UserStatsSummary
{
TotalMovieRequests = await moviesCount,
TotalTvRequests = await childrenCount,
CompletedRequestsTv = await availableChildren,
CompletedRequestsMovies = await availableMovies,
MostRequestedUserMovie = (await userMovie).FirstOrDefault().RequestedUser,
MostRequestedUserTv = (await userTv).FirstOrDefault().RequestedUser,
};
}
}
public class SummaryRequest
{
public DateTime From { get; set; }
public DateTime To { get; set; }
}
public class UserStatsSummary
{
public int TotalRequests => TotalTvRequests + TotalMovieRequests;
public int TotalMovieRequests { get; set; }
public int TotalTvRequests { get; set; }
public int TotalIssues { get; set; }
public int CompletedRequestsMovies { get; set; }
public int CompletedRequestsTv { get; set; }
public int CompletedRequests => CompletedRequestsMovies + CompletedRequestsTv;
public OmbiUser MostRequestedUserMovie { get; set; }
public OmbiUser MostRequestedUserTv { get; set; }
}
}

@ -40,6 +40,18 @@ namespace Ombi.Core
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
}
public void NewRequest(AlbumRequest model)
{
var notificationModel = new NotificationOptions
{
RequestId = model.Id,
DateTime = DateTime.Now,
NotificationType = NotificationType.NewRequest,
RequestType = model.RequestType
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
}
public void Notify(MovieRequests model, NotificationType type)
{
@ -66,5 +78,19 @@ namespace Ombi.Core
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
}
public void Notify(AlbumRequest model, NotificationType type)
{
var notificationModel = new NotificationOptions
{
RequestId = model.Id,
DateTime = DateTime.Now,
NotificationType = type,
RequestType = model.RequestType,
Recipient = model.RequestedUser?.Email ?? string.Empty
};
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
}
}
}

@ -3,7 +3,10 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Api.TvMaze;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TvMaze.Models;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Helpers;
using Ombi.Store.Entities;
@ -15,25 +18,39 @@ namespace Ombi.Core.Helpers
public class TvShowRequestBuilder
{
public TvShowRequestBuilder(ITvMazeApi tvApi)
public TvShowRequestBuilder(ITvMazeApi tvApi, IMovieDbApi movApi)
{
TvApi = tvApi;
MovieDbApi = movApi;
}
private ITvMazeApi TvApi { get; }
private IMovieDbApi MovieDbApi { get; }
public ChildRequests ChildRequest { get; set; }
public List<SeasonRequests> TvRequests { get; protected set; }
public List<SeasonsViewModel> TvRequests { get; protected set; }
public string PosterPath { get; protected set; }
public string BackdropPath { get; protected set; }
public DateTime FirstAir { get; protected set; }
public TvRequests NewRequest { get; protected set; }
protected TvMazeShow ShowInfo { get; set; }
protected List<TvSearchResult> Results { get; set; }
public async Task<TvShowRequestBuilder> GetShowInfo(int id)
{
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
Results = await MovieDbApi.SearchTv(ShowInfo.name);
foreach (TvSearchResult result in Results) {
if (result.Name == ShowInfo.name)
{
var showIds = await MovieDbApi.GetTvExternals(result.Id);
ShowInfo.externals.imdb = showIds.imdb_id;
BackdropPath = result.BackdropPath;
break;
}
}
DateTime.TryParse(ShowInfo.premiered, out DateTime dt);
DateTime.TryParse(ShowInfo.premiered, out var dt);
FirstAir = dt;
@ -43,37 +60,29 @@ namespace Ombi.Core.Helpers
return this;
}
public TvShowRequestBuilder CreateChild(SearchTvShowViewModel model, string userId)
public TvShowRequestBuilder CreateChild(TvRequestViewModel model, string userId)
{
ChildRequest = new ChildRequests
{
Id = model.Id,
Id = model.TvDbId,
RequestType = RequestType.TvShow,
RequestedDate = DateTime.UtcNow,
Approved = false,
RequestedUserId = userId,
SeasonRequests = new List<SeasonRequests>(),
Title = model.Title,
SeriesType = ShowInfo.type.Equals("Animation", StringComparison.CurrentCultureIgnoreCase) ? SeriesType.Anime : SeriesType.Standard
Title = ShowInfo.name,
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.OrdinalIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
};
return this;
}
public TvShowRequestBuilder CreateTvList(SearchTvShowViewModel tv)
public TvShowRequestBuilder CreateTvList(TvRequestViewModel tv)
{
TvRequests = new List<SeasonRequests>();
TvRequests = new List<SeasonsViewModel>();
// Only have the TV requests we actually requested and not everything
foreach (var season in tv.SeasonRequests)
foreach (var season in tv.Seasons)
{
for (int i = season.Episodes.Count - 1; i >= 0; i--)
{
if (!season.Episodes[i].Requested)
{
season.Episodes.RemoveAt(i); // Remove the episode since it's not requested
}
}
if (season.Episodes.Any())
{
TvRequests.Add(season);
@ -81,11 +90,10 @@ namespace Ombi.Core.Helpers
}
return this;
}
public async Task<TvShowRequestBuilder> BuildEpisodes(SearchTvShowViewModel tv)
public async Task<TvShowRequestBuilder> BuildEpisodes(TvRequestViewModel tv)
{
if (tv.RequestAll)
{
@ -173,26 +181,69 @@ namespace Ombi.Core.Helpers
else
{
// It's a custom request
ChildRequest.SeasonRequests = TvRequests;
var seasonRequests = new List<SeasonRequests>();
var episodes = await TvApi.EpisodeLookup(ShowInfo.id);
foreach (var ep in episodes)
{
var existingSeasonRequest = seasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season);
if (existingSeasonRequest != null)
{
var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season);
var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.number) ?? false;
if (requestedSeason != null && requestedEpisode)
{
// We already have this, let's just add the episodes to it
existingSeasonRequest.Episodes.Add(new EpisodeRequests
{
EpisodeNumber = ep.number,
AirDate = FormatDate(ep.airdate),
Title = ep.name,
Url = ep.url,
});
}
}
else
{
var newRequest = new SeasonRequests {SeasonNumber = ep.season};
var requestedSeason = tv.Seasons.FirstOrDefault(x => x.SeasonNumber == ep.season);
var requestedEpisode = requestedSeason?.Episodes?.Any(x => x.EpisodeNumber == ep.number) ?? false;
if (requestedSeason != null && requestedEpisode)
{
newRequest.Episodes.Add(new EpisodeRequests
{
EpisodeNumber = ep.number,
AirDate = FormatDate(ep.airdate),
Title = ep.name,
Url = ep.url,
});
seasonRequests.Add(newRequest);
}
}
}
foreach (var s in seasonRequests)
{
ChildRequest.SeasonRequests.Add(s);
}
}
return this;
}
public TvShowRequestBuilder CreateNewRequest(SearchTvShowViewModel tv)
public TvShowRequestBuilder CreateNewRequest(TvRequestViewModel tv)
{
NewRequest = new TvRequests
{
Id = tv.Id,
Overview = ShowInfo.summary.RemoveHtml(),
PosterPath = PosterPath,
Title = ShowInfo.name,
ReleaseDate = FirstAir,
Status = ShowInfo.status,
ImdbId = ShowInfo.externals?.imdb ?? string.Empty,
TvDbId = tv.Id,
TvDbId = tv.TvDbId,
ChildRequests = new List<ChildRequests>(),
TotalSeasons = tv.SeasonRequests.Count()
TotalSeasons = tv.Seasons.Count(),
Background = BackdropPath
};
NewRequest.ChildRequests.Add(ChildRequest);

@ -0,0 +1,14 @@
using System;
using System.Threading.Tasks;
using Ombi.Api.Plex.Models;
namespace Ombi.Core.Authentication
{
public interface IPlexOAuthManager
{
Task<string> GetAccessTokenFromPin(int pinId);
Task<Uri> GetOAuthUrl(int pinId, string code, string websiteAddress = null);
Task<Uri> GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
Task<PlexAccount> GetAccount(string accessToken);
}
}

@ -0,0 +1,15 @@
using System;
namespace Ombi.Core.Models
{
public class RequestQuotaCountModel
{
public bool HasLimit { get; set; }
public int Limit { get; set; }
public int Remaining { get; set; }
public DateTime NextRequest { get; set; }
}
}

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Ombi.Core.Models.Requests
{
public class FilterResult<T>
{
public int Total { get; set; }
public IEnumerable<T> Collection { get; set; }
}
}

@ -4,6 +4,8 @@
{
public FilterType AvailabilityFilter { get; set; }
public FilterType StatusFilter { get; set; }
public int Position { get; set; }
public int Count { get; set; }
}
public enum FilterType

@ -7,5 +7,6 @@ namespace Ombi.Core.Models.Requests
{
IMovieRequestRepository MovieRequestService { get; }
ITvRequestRepository TvRequestService { get; }
IMusicRequestRepository MusicRequestRepository { get; }
}
}

@ -0,0 +1,7 @@
namespace Ombi.Core.Models.Requests
{
public class MusicAlbumRequestViewModel
{
public string ForeignAlbumId { get; set; }
}
}

@ -5,13 +5,15 @@ namespace Ombi.Core.Models.Requests
{
public class RequestService : IRequestServiceMain
{
public RequestService(ITvRequestRepository tv, IMovieRequestRepository movie)
public RequestService(ITvRequestRepository tv, IMovieRequestRepository movie, IMusicRequestRepository music)
{
TvRequestService = tv;
MovieRequestService = movie;
MusicRequestRepository = music;
}
public ITvRequestRepository TvRequestService { get; }
public IMusicRequestRepository MusicRequestRepository { get; }
public IMovieRequestRepository MovieRequestService { get; }
}
}

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace Ombi.Core.Models.Requests
{
public class TvRequestViewModel
{
public bool RequestAll { get; set; }
public bool LatestSeason { get; set; }
public bool FirstSeason { get; set; }
public int TvDbId { get; set; }
public List<SeasonsViewModel> Seasons { get; set; } = new List<SeasonsViewModel>();
}
public class SeasonsViewModel
{
public int SeasonNumber { get; set; }
public List<EpisodesViewModel> Episodes { get; set; } = new List<EpisodesViewModel>();
}
public class EpisodesViewModel
{
public int EpisodeNumber { get; set; }
}
}

@ -0,0 +1,23 @@
using System;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.Search
{
public class SearchAlbumViewModel : SearchViewModel
{
public string Title { get; set; }
public string ForeignAlbumId { get; set; }
public bool Monitored { get; set; }
public string AlbumType { get; set; }
public decimal Rating { get; set; }
public DateTime ReleaseDate { get; set; }
public string ArtistName { get; set; }
public string ForeignArtistId { get; set; }
public string Cover { get; set; }
public string Disk { get; set; }
public decimal PercentOfTracks { get; set; }
public override RequestType Type => RequestType.Album;
public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0;
public bool FullyAvailable => PercentOfTracks == 100;
}
}

@ -0,0 +1,19 @@
using Ombi.Api.Lidarr.Models;
namespace Ombi.Core.Models.Search
{
public class SearchArtistViewModel
{
public string ArtistName { get; set; }
public string ForignArtistId { get; set; }
public string Overview { get; set; }
public string Disambiguation { get; set; }
public string Banner { get; set; }
public string Poster { get; set; }
public string Logo { get; set; }
public bool Monitored { get; set; }
public string ArtistType { get; set; }
public string CleanName { get; set; }
public Link[] Links { get; set; } // Couldn't be bothered to map it
}
}

@ -56,7 +56,6 @@ namespace Ombi.Core.Models.Search
public bool FullyAvailable { get; set; }
// We only have some episodes
public bool PartlyAvailable { get; set; }
public override RequestType Type => RequestType.TvShow;
}
}

@ -8,12 +8,13 @@ namespace Ombi.Core.Models.Search
public int Id { get; set; }
public bool Approved { get; set; }
public bool Requested { get; set; }
public int RequestId { get; set; }
public bool Available { get; set; }
public string PlexUrl { get; set; }
public string EmbyUrl { get; set; }
public string Quality { get; set; }
public abstract RequestType Type { get; }
/// <summary>
/// This is used for the PlexAvailabilityCheck/EmbyAvailabilityRule rule
/// </summary>
@ -26,5 +27,11 @@ namespace Ombi.Core.Models.Search
public string TheTvDbId { get; set; }
[NotMapped]
public string TheMovieDbId { get; set; }
[NotMapped]
public bool Subscribed { get; set; }
[NotMapped]
public bool ShowSubscribe { get; set; }
}
}

@ -0,0 +1,11 @@
using Ombi.Core.Models.Requests;
namespace Ombi.Core.Models.UI
{
public class OrderFilterModel
{
public FilterType AvailabilityFilter { get; set; }
public FilterType StatusFilter { get; set; }
public OrderType OrderType { get; set; }
}
}

@ -0,0 +1,12 @@
namespace Ombi.Core.Models.UI
{
public enum OrderType
{
RequestedDateAsc =1,
RequestedDateDesc =2,
TitleAsc =3,
TitleDesc=4,
StatusAsc=5,
StatusDesc=6
}
}

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Ombi.Core.Models.UI
{
public class RequestsViewModel<T>
{
public IEnumerable<T> Collection { get; set; }
public int Total { get; set; }
}
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.UI
{
@ -16,6 +17,11 @@ namespace Ombi.Core.Models.UI
public UserType UserType { get; set; }
public int MovieRequestLimit { get; set; }
public int EpisodeRequestLimit { get; set; }
public RequestQuotaCountModel EpisodeRequestQuota { get; set; }
public RequestQuotaCountModel MovieRequestQuota { get; set; }
public RequestQuotaCountModel MusicRequestQuota { get; set; }
public int MusicRequestLimit { get; set; }
public UserQualityProfiles UserQualityProfiles { get; set; }
}
public class ClaimCheckboxes

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

Loading…
Cancel
Save