diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eb51423c..8bd996857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,215 @@ # Changelog +## v3.0.3587 (2018-08-19) + +### **New Features** + +- Added the ability to invite Plex Friends from the user management screen. [Jamie] + +- Added rich notifications for mobile. [Jamie] + +- Updater fixes. [Jamie] + +- Added updater test mode. [Jamie Rees] + +- Added a new API method to delete issue comments. [TidusJar] + +- Updated @ngu/carousel to beta version to remove rxjs-compat dependency. [Matt Jeanes] + +- Update to Angular 6/Webpack 4. [Matt Jeanes] + +- Update CHANGELOG.md. [Jamie] + +- Updated the way we create the wizard user, errors show now be fed back to the user. [Jamie] + +- Added Brazillian Portuguese as a language and also Polish. [Jamie] + +- Updated swagger. [Jamie] + +- Updated to 2.1.1. [Jamie] + +### **Fixes** + +- #2408 Added the feature to delete comments on issues. [Jamie] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (French) [Jamie] + +- Fixed #2440. [TidusJar] + +- Delete cake.config. [Chris Pritchard] + +- Initial attempt at getting anime seriestype working. [Chris Pritchard] + +- Add cake.config. [Chris Pritchard] + +- Fixed the issue where we wouldn't correctly mark some shows as available when there was no provider id #2429. [Jamie] + +- Fixed the 'loop' in the cacher #2429. [Jamie] + +- Fixed #2427. [Jamie] + +- Fixed #2424. [Jamie] + +- Fixed #2409. [Jamie] + +- More updater. [Jamie] + +- Humanize the request type enum in notifications e.g. TvShow will now appear as "Tv Show" #2416. [TidusJar] + +- Made the quality override and root folder override load when we load the show (It will now appear) [Jamie] + +- Fixed #2415 where power users could not set the Sonarr Quality Override or Root Folder Override. [Jamie] + +- #2371 Fixed the issue where certain actions would not setup the series correctly in Sonarr. [Jamie] + +- Tightened up the security from an API perspecitve. [TidusJar] + +- Stop the root folder and profile calls from erroring. [TidusJar] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- Fixed all linting. [TidusJar] + +- Comment out envparam stuff. [Matt Jeanes] + +- Fixed prod build issue. [Matt Jeanes] + +- Missed a tiny bit. [Matt Jeanes] + +- Fix test. [Matt Jeanes] + +- Fix test build. [Matt Jeanes] + +- Linting + remove debug. [Matt Jeanes] + +- Switch to Yarn and disable auto publish in release mode. [Matt Jeanes] + +- Fix for #2409. [TidusJar] + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (Spanish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Norwegian) [Jamie] + +- New translations en.json (Italian) [Jamie] + +- New translations en.json (German) [Jamie] + +- New translations en.json (French) [Jamie] + +- New translations en.json (Dutch) [Jamie] + +- New translations en.json (Danish) [Jamie] + +- Possible fix for #2298. [D34DC3N73R] + +- Fixed the text for #2370. [Jamie] + +- Fixed where you couldn't bulk edit the limits to 0 #2318. [Jamie] + +- Upgraded to .net 2.1.2 (Includes security fixes) [Jamie] + + +## v3.0.3477 (2018-07-18) + +### **New Features** + +- Updated the Emby availability checker to bring it more in line with what we do with Plex. [TidusJar] + +- Added the ability to impersonate a user when using the API Key. This allows people to use the API and request as a certain user. #2363. [Jamie Rees] + +- Added more background images and it will loop through the available ones. [Jamie Rees] + +- Added chunk hashing to resolve #2330. [Jamie Rees] + +- Added API at /api/v1/status/info to get branch and version information #2331. [Jamie Rees] + +- Update to .net 2.1.1. [Jamie] + +### **Fixes** + +- Fix #2322 caused by continue statement inside try catch block. [Anojh] + +- Fixed #2367. [TidusJar] + +- Fixed the issue where you could not delete a user #2365. [TidusJar] + +- Another attempt to fix #2366. [Jamie Rees] + +- Fixed the Plex OAuth warning. [Jamie] + +- Revert "Fixed Plex OAuth, should no longer show Insecure warning" [Jamie Rees] + +- Fixed Plex OAuth, should no longer show Insecure warning. [Jamie Rees] + +- Fixed the View On Emby URL since the Link changed #2368. [Jamie Rees] + +- Fixed the issue where episodes were not being marked as available in the search #2367. [Jamie Rees] + +- Fixed #2371. [Jamie Rees] + +- Fixed collection issues in Emby #2366. [Jamie Rees] + +- Do not delete the Emby Information every time we run, let's keep the content now. [Jamie Rees] + +- Emby Improvements: Batch up the amount we get from the server. [Jamie Rees] + +- Log errors when they are uncaught. [Jamie Rees] + +- Fix unclosed table tags causing overflow #2322. [Anojh] + +- This should now fix #2350. [Jamie] + +- Improve the validation around the Application URL. [Jamie Rees] + +- Fixed #2341. [Jamie Rees] + +- Stop spamming errors when FanArt doesn't have the image. [Jamie Rees] + +- Fixed #2338. [Jamie Rees] + +- Removed some logging statements. [Jamie Rees] + +- Fixed the api key being case sensative #2350. [Jamie Rees] + +- Improved the Emby API #2230 Thanks Luke! [Jamie Rees] + +- Revert. [Jamie Rees] + +- Fixed a small error in the Mobile Notification Provider. [Jamie Rees] + +- Minor style tweaks. [Randall Bruder] + +- Downgrade to .net core 2.0. [Jamie Rees] + +- Downgrade Microsoft.AspNetCore.All package back to 2.0.8. [Jamie Rees] + +- Removed old code. [Jamie Rees] + +- Swap out the old way of validating the API key with a real middlewear this time. [Jamie Rees] + + ## v3.0.3421 (2018-06-23) ### **New Features** diff --git a/appveyor.yml b/appveyor.yml index 39d67b91d..862993a21 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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" diff --git a/build.cake b/build.cake index a497c5f77..d706e7b6b 100644 --- a/build.cake +++ b/build.cake @@ -1,10 +1,10 @@ #tool "nuget:?package=GitVersion.CommandLine" #addin "Cake.Gulp" -#addin "nuget:?package=Cake.Npm&version=0.13.0" #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); }); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..10c7546e4 --- /dev/null +++ b/package-lock.json @@ -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=" + } + } +} diff --git a/src/Ombi.Api.Emby/EmbyApi.cs b/src/Ombi.Api.Emby/EmbyApi.cs index 3ac70c844..fcb989094 100644 --- a/src/Ombi.Api.Emby/EmbyApi.cs +++ b/src/Ombi.Api.Emby/EmbyApi.cs @@ -91,27 +91,31 @@ namespace Ombi.Api.Emby request.AddContentHeader("Content-Type", "application/json"); } - public async Task> GetCollection(string mediaId, string apiKey, string userId, string baseUrl) + public async Task> 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>(request); + request.AddQueryString("Fields", "ProviderIds,Overview"); + + request.AddQueryString("VirtualItem", "False"); + + return await Api.Request>(request); } - public async Task> GetAllMovies(string apiKey, string userId, string baseUri) + public async Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, string baseUri) { - return await GetAll("Movie", apiKey, userId, baseUri, true); + return await GetAll("Movie", apiKey, userId, baseUri, true, startIndex, count); } - public async Task> GetAllEpisodes(string apiKey, string userId, string baseUri) + public async Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, string baseUri) { - return await GetAll("Episode", apiKey, userId, baseUri); + return await GetAll("Episode", apiKey, userId, baseUri, false, startIndex, count); } - public async Task> GetAllShows(string apiKey, string userId, string baseUri) + public async Task> GetAllShows(string apiKey, int startIndex, int count, string userId, string baseUri) { - return await GetAll("Series", apiKey, userId, baseUri); + return await GetAll("Series", apiKey, userId, baseUri, false, startIndex, count); } public async Task GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl) @@ -145,7 +149,25 @@ namespace Ombi.Api.Emby request.AddQueryString("IncludeItemTypes", type); request.AddQueryString("Fields", includeOverview ? "ProviderIds,Overview" : "ProviderIds"); - request.AddQueryString("VirtualItem","False"); + request.AddQueryString("VirtualItem", "False"); + + AddHeaders(request, apiKey); + + + var obj = await Api.Request>(request); + return obj; + } + private async Task> GetAll(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); diff --git a/src/Ombi.Api.Emby/IEmbyApi.cs b/src/Ombi.Api.Emby/IEmbyApi.cs index 625ae3c13..b4641ea5f 100644 --- a/src/Ombi.Api.Emby/IEmbyApi.cs +++ b/src/Ombi.Api.Emby/IEmbyApi.cs @@ -14,12 +14,17 @@ namespace Ombi.Api.Emby Task LogIn(string username, string password, string apiKey, string baseUri); Task LoginConnectUser(string username, string password); - Task> GetAllMovies(string apiKey, string userId, string baseUri); - Task> GetAllEpisodes(string apiKey, string userId, string baseUri); - Task> GetAllShows(string apiKey, string userId, string baseUri); + Task> GetAllMovies(string apiKey, int startIndex, int count, string userId, + string baseUri); - Task> GetCollection(string mediaId, string apiKey, string userId, - string baseUrl); + Task> GetAllEpisodes(string apiKey, int startIndex, int count, string userId, + string baseUri); + + Task> GetAllShows(string apiKey, int startIndex, int count, string userId, + string baseUri); + + Task> GetCollection(string mediaId, + string apiKey, string userId, string baseUrl); Task GetSeriesInformation(string mediaId, string apiKey, string userId, string baseUrl); Task GetMovieInformation(string mediaId, string apiKey, string userId, string baseUrl); diff --git a/src/Ombi.Api.Notifications/IOneSignalApi.cs b/src/Ombi.Api.Notifications/IOneSignalApi.cs index 2e3ef106e..6de64d11e 100644 --- a/src/Ombi.Api.Notifications/IOneSignalApi.cs +++ b/src/Ombi.Api.Notifications/IOneSignalApi.cs @@ -6,6 +6,6 @@ namespace Ombi.Api.Notifications { public interface IOneSignalApi { - Task PushNotification(List playerIds, string message); + Task PushNotification(List playerIds, string message, bool isAdminNotification, int requestId, int requestType); } } \ No newline at end of file diff --git a/src/Ombi.Api.Notifications/Models/OneSignalNotificationBody.cs b/src/Ombi.Api.Notifications/Models/OneSignalNotificationBody.cs index 6c024fa67..e65222bd5 100644 --- a/src/Ombi.Api.Notifications/Models/OneSignalNotificationBody.cs +++ b/src/Ombi.Api.Notifications/Models/OneSignalNotificationBody.cs @@ -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; } + } + } \ No newline at end of file diff --git a/src/Ombi.Api.Notifications/Ombi.Api.Notifications.csproj b/src/Ombi.Api.Notifications/Ombi.Api.Notifications.csproj index a3651df3c..7b890e2dd 100644 --- a/src/Ombi.Api.Notifications/Ombi.Api.Notifications.csproj +++ b/src/Ombi.Api.Notifications/Ombi.Api.Notifications.csproj @@ -4,6 +4,10 @@ netstandard2.0 + + + + diff --git a/src/Ombi.Api.Notifications/OneSignalApi.cs b/src/Ombi.Api.Notifications/OneSignalApi.cs index d4760bb09..8d5fc04a9 100644 --- a/src/Ombi.Api.Notifications/OneSignalApi.cs +++ b/src/Ombi.Api.Notifications/OneSignalApi.cs @@ -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 PushNotification(List playerIds, string message) + public async Task PushNotification(List 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(request); diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index cc61dfa5d..343eaa2d7 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -11,6 +11,7 @@ namespace Ombi.Api.Plex public interface IPlexApi { Task GetStatus(string authToken, string uri); + Task GetLibrariesForMachineId(string authToken, string machineId); Task SignIn(UserRequest user); Task GetServer(string authToken); Task GetLibrarySections(string authToken, string plexFullHost); @@ -22,8 +23,8 @@ namespace Ombi.Api.Plex Task GetUsers(string authToken); Task GetAccount(string authToken); Task GetRecentlyAdded(string authToken, string uri, string sectionId); - Task CreatePin(); Task GetPin(int pinId); - Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); + Task GetOAuthUrl(int pinId, string code, string applicationUrl); + Task AddUser(string emailAddress, string serverId, string authToken, int[] libs); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/PlexAdd.cs b/src/Ombi.Api.Plex/Models/PlexAdd.cs new file mode 100644 index 000000000..fb0a550d0 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/PlexAdd.cs @@ -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 { 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; + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs b/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs new file mode 100644 index 000000000..17ac59b81 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs @@ -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 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; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index a16dee9ec..f0808622f 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -16,14 +16,16 @@ namespace Ombi.Api.Plex { public class PlexApi : IPlexApi { - public PlexApi(IApi api, ISettingsService settings) + public PlexApi(IApi api, ISettingsService settings, ISettingsService p) { Api = api; _custom = settings; + _plexSettings = p; } private IApi Api { get; } private readonly ISettingsService _custom; + private readonly ISettingsService _plexSettings; private string _app; private string ApplicationName @@ -39,7 +41,18 @@ namespace Ombi.Api.Plex } else { - _app = settings.ApplicationName; + // Check for non-ascii characters (New .Net Core HTTPLib does not allow this) + var chars = settings.ApplicationName.ToCharArray(); + var hasNonAscii = false; + foreach (var c in chars) + { + if (c > 128) + { + hasNonAscii = true; + } + } + + _app = hasNonAscii ? "Ombi" : settings.ApplicationName; } return _app; @@ -69,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(request); @@ -80,14 +93,14 @@ namespace Ombi.Api.Plex public async Task 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(request); } public async Task GetAccount(string authToken) { var request = new Request(GetAccountUri, string.Empty, HttpMethod.Get); - AddHeaders(request, authToken); + await AddHeaders(request, authToken); return await Api.Request(request); } @@ -95,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(request); } @@ -103,17 +116,24 @@ namespace Ombi.Api.Plex public async Task 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(request); } public async Task 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(request); } + public async Task 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(request); + } + /// // 192.168.1.69:32400/library/metadata/3662/allLeaves // The metadata ratingkey should be in the Cache @@ -128,21 +148,21 @@ namespace Ombi.Api.Plex public async Task 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(request); } public async Task 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(request); } public async Task 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(request); } @@ -161,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(request); + return await Api.Request(request); } /// @@ -174,8 +194,8 @@ namespace Ombi.Api.Plex /// public async Task 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(request); } @@ -183,43 +203,36 @@ namespace Ombi.Api.Plex public async Task GetRecentlyAdded(string authToken, string uri, string sectionId) { var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get); - AddHeaders(request, authToken); + await AddHeaders(request, authToken); AddLimitHeaders(request, 0, 50); return await Api.Request(request); } - public async Task CreatePin() - { - var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post); - request.AddQueryString("strong", "true"); - AddHeaders(request); - - return await Api.Request(request); - } - public async Task GetPin(int pinId) { var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get); - AddHeaders(request); + await AddHeaders(request); return await Api.Request(request); } - public Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard) + public async Task GetOAuthUrl(int pinId, string code, string applicationUrl) { var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get); - AddHeaders(request); - var forwardUrl = wizard - ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) - : new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get); - - request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); + await AddHeaders(request); + request.AddQueryString("pinID", pinId.ToString()); request.AddQueryString("code", code); - request.AddQueryString("context[device][product]", "Ombi"); + request.AddQueryString("context[device][product]", ApplicationName); request.AddQueryString("context[device][environment]", "bundled"); - request.AddQueryString("clientID", $"OmbiV3"); + request.AddQueryString("context[device][layout]", "desktop"); + request.AddQueryString("context[device][platform]", "Web"); + request.AddQueryString("context[device][device]", "Ombi (Web)"); + + var s = await GetSettings(); + await CheckInstallId(s); + request.AddQueryString("clientID", s.InstallId.ToString("N")); if (request.FullUri.Fragment.Equals("#")) { @@ -233,26 +246,58 @@ namespace Ombi.Api.Plex return request.FullUri; } + public async Task 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(result); + return new PlexAddWrapper{Add = add}; + } + catch (InvalidOperationException) + { + var error = Api.DeserializeXml(result); + return new PlexAddWrapper{Error = error}; + } + } + + /// /// Adds the required headers and also the authorization header /// /// /// - 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); } /// /// Adds the main required headers to the Plex Request /// /// - private void AddHeaders(Request request) + private async Task AddHeaders(Request request) { - request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3"); + var s = await GetSettings(); + await CheckInstallId(s); + request.AddHeader("X-Plex-Client-Identifier", s.InstallId.ToString("N")); request.AddHeader("X-Plex-Product", ApplicationName); request.AddHeader("X-Plex-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"); } @@ -262,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 GetSettings() + { + return _settings ?? (_settings = await _plexSettings.GetSettingsAsync()); + } } } diff --git a/src/Ombi.Api.Radarr/Ombi.Api.Radarr.csproj b/src/Ombi.Api.Radarr/Ombi.Api.Radarr.csproj index d2b605337..0c615f301 100644 --- a/src/Ombi.Api.Radarr/Ombi.Api.Radarr.csproj +++ b/src/Ombi.Api.Radarr/Ombi.Api.Radarr.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Ombi.Api.Radarr/RadarrApi.cs b/src/Ombi.Api.Radarr/RadarrApi.cs index 1f897b60b..fd4deb140 100644 --- a/src/Ombi.Api.Radarr/RadarrApi.cs +++ b/src/Ombi.Api.Radarr/RadarrApi.cs @@ -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 diff --git a/src/Ombi.Api.Service/Ombi.Api.Service.csproj b/src/Ombi.Api.Service/Ombi.Api.Service.csproj index 67f37c80d..8cbddd874 100644 --- a/src/Ombi.Api.Service/Ombi.Api.Service.csproj +++ b/src/Ombi.Api.Service/Ombi.Api.Service.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Ombi.Api.Sonarr/Models/Episode.cs b/src/Ombi.Api.Sonarr/Models/Episode.cs index c17f5486c..b01e6fd8c 100644 --- a/src/Ombi.Api.Sonarr/Models/Episode.cs +++ b/src/Ombi.Api.Sonarr/Models/Episode.cs @@ -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; } } diff --git a/src/Ombi.Api.Sonarr/Models/NewSeries.cs b/src/Ombi.Api.Sonarr/Models/NewSeries.cs index 4d2c17308..ef18baddb 100644 --- a/src/Ombi.Api.Sonarr/Models/NewSeries.cs +++ b/src/Ombi.Api.Sonarr/Models/NewSeries.cs @@ -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 images { get; set; } diff --git a/src/Ombi.Api.Sonarr/Models/Quality.cs b/src/Ombi.Api.Sonarr/Models/Quality.cs index 76a1c92d8..9989a9c3e 100644 --- a/src/Ombi.Api.Sonarr/Models/Quality.cs +++ b/src/Ombi.Api.Sonarr/Models/Quality.cs @@ -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; } } diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index b0e7066a8..19dab7530 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -80,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(receivedString); } } } + public T DeserializeXml(string receivedString) + { + XmlSerializer serializer = new XmlSerializer(typeof(T)); + StringReader reader = new StringReader(receivedString); + var value = (T) serializer.Deserialize(reader); + return value; + } + public async Task RequestContent(Request request) { using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) diff --git a/src/Ombi.Api/IApi.cs b/src/Ombi.Api/IApi.cs index 2b7f71bb8..e573d2d07 100644 --- a/src/Ombi.Api/IApi.cs +++ b/src/Ombi.Api/IApi.cs @@ -7,5 +7,6 @@ namespace Ombi.Api Task Request(Request request); Task Request(Request request); Task RequestContent(Request request); + T DeserializeXml(string receivedString); } } \ No newline at end of file diff --git a/src/Ombi.Api/Ombi.Api.csproj b/src/Ombi.Api/Ombi.Api.csproj index 32fc60bb6..a37c128fb 100644 --- a/src/Ombi.Api/Ombi.Api.csproj +++ b/src/Ombi.Api/Ombi.Api.csproj @@ -9,9 +9,9 @@ - + - + diff --git a/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj b/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj index db98876df..8f0abee8f 100644 --- a/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj +++ b/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netcoreapp2.1 @@ -9,7 +9,7 @@ - + diff --git a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs index 0633d641e..99ff5b6bd 100644 --- a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs @@ -29,7 +29,10 @@ namespace Ombi.Core.Tests.Rule.Search { ProviderId = "123" }); - var search = new SearchMovieViewModel(); + var search = new SearchMovieViewModel() + { + TheMovieDbId = "123", + }; var result = await Rule.Execute(search); Assert.True(result.Success); diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index 37ed7d2f7..426037bb7 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -20,12 +20,6 @@ namespace Ombi.Core.Authentication private readonly IPlexApi _api; private readonly ISettingsService _customizationSettingsService; - public async Task RequestPin() - { - var pin = await _api.CreatePin(); - return pin; - } - public async Task GetAccessTokenFromPin(int pinId) { var pin = await _api.GetPin(pinId); @@ -34,19 +28,6 @@ namespace Ombi.Core.Authentication return string.Empty; } - if (pin.authToken.IsNullOrEmpty()) - { - // Looks like we do not have a pin yet, we should retry a few times. - var retryCount = 0; - var retryMax = 5; - var retryWaitMs = 1000; - while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax) - { - retryCount++; - await Task.Delay(retryWaitMs); - pin = await _api.GetPin(pinId); - } - } return pin.authToken; } @@ -58,14 +39,14 @@ namespace Ombi.Core.Authentication public async Task GetOAuthUrl(int pinId, string code, string websiteAddress = null) { var settings = await _customizationSettingsService.GetSettingsAsync(); - var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false); + var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl); return url; } - public Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress) + public async Task GetWizardOAuthUrl(int pinId, string code, string websiteAddress) { - var url = _api.GetOAuthUrl(pinId, code, websiteAddress, true); + var url = await _api.GetOAuthUrl(pinId, code, websiteAddress); return url; } } diff --git a/src/Ombi.Core/Engine/IUserStatsEngine.cs b/src/Ombi.Core/Engine/IUserStatsEngine.cs new file mode 100644 index 000000000..3b8474749 --- /dev/null +++ b/src/Ombi.Core/Engine/IUserStatsEngine.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Core.Engine +{ + public interface IUserStatsEngine + { + Task GetSummary(SummaryRequest request); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs index 348dc91e7..36ae7da61 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvRequestEngine.cs @@ -15,13 +15,13 @@ namespace Ombi.Core.Engine.Interfaces Task DenyChildRequest(int requestId); Task> GetRequestsLite(int count, int position, OrderFilterModel type); Task> SearchTvRequest(string search); - Task>>> SearchTvRequestTree(string search); Task UpdateTvRequest(TvRequests request); - Task>>> GetRequestsTreeNode(int count, int position); Task> GetAllChldren(int tvId); Task UpdateChildRequest(ChildRequests request); Task RemoveTvChild(int requestId); Task ApproveChildRequest(int id); Task> GetRequestsLite(); + Task UpdateQualityProfile(int requestId, int profileId); + Task UpdateRootPath(int requestId, int rootPath); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs index 53721f792..0926a7f9a 100644 --- a/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/ITvSearchEngine.cs @@ -7,16 +7,10 @@ namespace Ombi.Core.Engine.Interfaces public interface ITvSearchEngine { Task> Search(string searchTerm); - Task>> SearchTreeNode(string searchTerm); - Task> GetShowInformationTreeNode(int tvdbid); Task GetShowInformation(int tvdbid); - Task>> PopularTree(); Task> Popular(); - Task>> AnticipatedTree(); Task> Anticipated(); - Task>> MostWatchesTree(); Task> MostWatches(); - Task>> TrendingTree(); Task> Trending(); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 15383ed12..f73c6fda1 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -452,6 +452,7 @@ namespace Ombi.Core.Engine } request.Available = true; + request.MarkedAsAvailable = DateTime.Now; NotificationHelper.Notify(request, NotificationType.RequestAvailable); await MovieRepository.Update(request); diff --git a/src/Ombi.Core/Engine/TreeNode.cs b/src/Ombi.Core/Engine/TreeNode.cs deleted file mode 100644 index 14f2f4cb0..000000000 --- a/src/Ombi.Core/Engine/TreeNode.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; - -namespace Ombi.Core.Engine -{ - - public class TreeNode - { - public string Label { get; set; } - public T Data { get; set; } - public List> Children { get; set; } - public bool Leaf { get; set; } - public bool Expanded { get; set; } - } - - public class TreeNode - { - public string Label { get; set; } - public T Data { get; set; } - public List> Children { get; set; } - public bool Leaf { get; set; } - public bool Expanded { get; set; } - } -} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index e8fc65a26..90760f759 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -143,7 +143,7 @@ namespace Ombi.Core.Engine .Include(x => x.ChildRequests) .ThenInclude(x => x.SeasonRequests) .ThenInclude(x => x.Episodes) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) + .OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()) .Skip(position).Take(count).ToListAsync(); // Filter out children @@ -156,8 +156,9 @@ namespace Ombi.Core.Engine .Include(x => x.ChildRequests) .ThenInclude(x => x.SeasonRequests) .ThenInclude(x => x.Episodes) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) + .OrderByDescending(x => x.ChildRequests.Select(y => y.RequestedDate).FirstOrDefault()) .Skip(position).Take(count).ToListAsync(); + } allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); @@ -171,24 +172,30 @@ namespace Ombi.Core.Engine public async Task> GetRequestsLite(int count, int position, OrderFilterModel type) { var shouldHide = await HideFromOtherUsers(); - List allRequests; + List allRequests = null; if (shouldHide.Hide) { - allRequests = await TvRepository.GetLite(shouldHide.UserId) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) - .Skip(position).Take(count).ToListAsync(); + 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 { - allRequests = await TvRepository.GetLite() - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) - .Skip(position).Take(count).ToListAsync(); + 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(); } - allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); return new RequestsViewModel @@ -196,38 +203,6 @@ namespace Ombi.Core.Engine Collection = allRequests }; } - - public async Task>>> GetRequestsTreeNode(int count, int position) - { - var shouldHide = await HideFromOtherUsers(); - List allRequests; - if (shouldHide.Hide) - { - allRequests = await TvRepository.Get(shouldHide.UserId) - .Include(x => x.ChildRequests) - .ThenInclude(x => x.SeasonRequests) - .ThenInclude(x => x.Episodes) - .Where(x => x.ChildRequests.Any()) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) - .Skip(position).Take(count).ToListAsync(); - - FilterChildren(allRequests, shouldHide); - } - else - { - allRequests = await TvRepository.Get() - .Include(x => x.ChildRequests) - .ThenInclude(x => x.SeasonRequests) - .ThenInclude(x => x.Episodes) - .Where(x => x.ChildRequests.Any()) - .OrderByDescending(x => x.ChildRequests.Max(y => y.RequestedDate)) - .Skip(position).Take(count).ToListAsync(); - } - - allRequests.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); - return ParseIntoTreeNode(allRequests); - } - public async Task> GetRequests() { var shouldHide = await HideFromOtherUsers(); @@ -288,6 +263,10 @@ namespace Ombi.Core.Engine private static void FilterChildren(IEnumerable allRequests, HideResult shouldHide) { + if (allRequests == null) + { + return; + } // Filter out children foreach (var t in allRequests) { @@ -350,21 +329,22 @@ namespace Ombi.Core.Engine return results; } - public async Task>>> SearchTvRequestTree(string search) + public async Task UpdateRootPath(int requestId, int rootPath) { - var shouldHide = await HideFromOtherUsers(); - IQueryable 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(); - results.ForEach(async r => { await CheckForSubscription(shouldHide, r); }); - 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 UpdateTvRequest(TvRequests request) @@ -516,6 +496,7 @@ namespace Ombi.Core.Engine }; } request.Available = true; + request.MarkedAsAvailable = DateTime.Now; foreach (var season in request.SeasonRequests) { foreach (var e in season.Episodes) @@ -585,29 +566,7 @@ namespace Ombi.Core.Engine return await AfterRequest(model.ChildRequests.FirstOrDefault()); } - private static List>> ParseIntoTreeNode(IEnumerable result) - { - var node = new List>>(); - - foreach (var value in result) - { - node.Add(new TreeNode> - { - Data = value, - Children = new List>> - { - new TreeNode> - { - Data = SortEpisodes(value.ChildRequests), - Leaf = true - } - } - }); - } - return node; - } - - private static List SortEpisodes(List items) + private static List SortEpisodes(List items) { foreach (var value in items) { diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index bc5a2e984..253363ec1 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -59,11 +59,6 @@ namespace Ombi.Core.Engine return null; } - public async Task>> SearchTreeNode(string searchTerm) - { - var result = await Search(searchTerm); - return result.Select(ParseIntoTreeNode).ToList(); - } public async Task GetShowInformation(int tvdbid) { var show = await TvMazeApi.ShowLookupByTheTvDbId(tvdbid); @@ -116,19 +111,6 @@ namespace Ombi.Core.Engine return await ProcessResult(mapped); } - public async Task> GetShowInformationTreeNode(int tvdbid) - { - var result = await GetShowInformation(tvdbid); - return ParseIntoTreeNode(result); - } - - public async Task>> 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> Popular() { var result = await Cache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12)); @@ -136,12 +118,6 @@ namespace Ombi.Core.Engine return processed; } - public async Task>> 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> Anticipated() { @@ -150,12 +126,6 @@ namespace Ombi.Core.Engine return processed; } - public async Task>> 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> MostWatches() { var result = await Cache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12)); @@ -163,13 +133,6 @@ namespace Ombi.Core.Engine return processed; } - public async Task>> 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> Trending() { var result = await Cache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12)); @@ -177,22 +140,6 @@ namespace Ombi.Core.Engine return processed; } - private static TreeNode ParseIntoTreeNode(SearchTvShowViewModel result) - { - return new TreeNode - { - Data = result, - Children = new List> - { - new TreeNode - { - Data = result, Leaf = true - } - }, - Leaf = false - }; - } - private async Task> ProcessResults(IEnumerable items) { var retVal = new List(); diff --git a/src/Ombi.Core/Engine/UserStatsEngine.cs b/src/Ombi.Core/Engine/UserStatsEngine.cs new file mode 100644 index 000000000..06ab65f92 --- /dev/null +++ b/src/Ombi.Core/Engine/UserStatsEngine.cs @@ -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 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; } + + } +} diff --git a/src/Ombi.Core/IPlexOAuthManager.cs b/src/Ombi.Core/IPlexOAuthManager.cs index 9c4f0582e..a5c0c44ff 100644 --- a/src/Ombi.Core/IPlexOAuthManager.cs +++ b/src/Ombi.Core/IPlexOAuthManager.cs @@ -1,16 +1,14 @@ using System; using System.Threading.Tasks; using Ombi.Api.Plex.Models; -using Ombi.Api.Plex.Models.OAuth; namespace Ombi.Core.Authentication { public interface IPlexOAuthManager { Task GetAccessTokenFromPin(int pinId); - Task RequestPin(); Task GetOAuthUrl(int pinId, string code, string websiteAddress = null); - Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress); + Task GetWizardOAuthUrl(int pinId, string code, string websiteAddress); Task GetAccount(string accessToken); } } \ No newline at end of file diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index 2037113bd..104db24fc 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -12,9 +12,9 @@ - - - + + + diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index 4f2f56b28..486de9ea8 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Models.Search; @@ -59,10 +61,19 @@ namespace Ombi.Core.Rule.Rules.Search { EmbyEpisode epExists = null; - epExists = await allEpisodes.FirstOrDefaultAsync(x => - x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber && - x.Series.ProviderId == item.ProviderId.ToString()); - + if (item.HasImdb) + { + epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber + && e.ImdbId == item.ImdbId); + } if (item.HasTvDb && epExists == null) + { + epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber + && e.Series.TvDbId == item.TvDbId); + } if (item.HasTheMovieDb && epExists == null) + { + epExists = await allEpisodes.FirstOrDefaultAsync(e => e.EpisodeNumber == episode.EpisodeNumber && e.SeasonNumber == season.SeasonNumber + && e.TheMovieDbId == item.TheMovieDbId); + } if (epExists != null) { diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 12124d270..47d965acc 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -120,6 +120,7 @@ namespace Ombi.Core.Senders int qualityToUse; string rootFolderPath; + string seriesType; if (model.SeriesType == SeriesType.Anime) { @@ -128,6 +129,8 @@ namespace Ombi.Core.Senders // TODO make this overrideable via the UI rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPathAnime), s); int.TryParse(s.QualityProfileAnime, out qualityToUse); + seriesType = "anime"; + } else { @@ -136,6 +139,7 @@ namespace Ombi.Core.Senders // For some reason, if we haven't got one use the first root folder in Sonarr // TODO make this overrideable via the UI rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPath), s); + seriesType = "standard"; } if (model.ParentRequest.QualityOverride.HasValue) @@ -163,27 +167,18 @@ namespace Ombi.Core.Senders rootFolderPath = rootFolderPath, qualityProfileId = qualityToUse, titleSlug = model.ParentRequest.Title, + seriesType = seriesType, addOptions = new AddOptions { - ignoreEpisodesWithFiles = true, // There shouldn't be any episodes with files, this is a new season - ignoreEpisodesWithoutFiles = true, // We want all missing + ignoreEpisodesWithFiles = false, // There shouldn't be any episodes with files, this is a new season + ignoreEpisodesWithoutFiles = false, // We want all missing searchForMissingEpisodes = false // we want dont want to search yet. We want to make sure everything is unmonitored/monitored correctly. } }; // Montitor the correct seasons, // If we have that season in the model then it's monitored! - var seasonsToAdd = new List(); - for (var i = 0; i < model.ParentRequest.TotalSeasons + 1; i++) - { - var index = i; - var season = new Season - { - seasonNumber = i, - monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0) - }; - seasonsToAdd.Add(season); - } + var seasonsToAdd = GetSeasonsToCreate(model); newSeries.seasons = seasonsToAdd; var result = await SonarrApi.AddSeries(newSeries, s.ApiKey, s.FullUri); existingSeries = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri); @@ -237,7 +232,7 @@ namespace Ombi.Core.Senders { var sonarrEp = sonarrEpList.FirstOrDefault(x => x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber); - if (sonarrEp != null) + if (sonarrEp != null && !sonarrEp.monitored) { sonarrEp.monitored = true; episodesToUpdate.Add(sonarrEp); @@ -245,22 +240,64 @@ namespace Ombi.Core.Senders } } var seriesChanges = false; + foreach (var season in model.SeasonRequests) { var sonarrSeason = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber); var sonarrEpCount = sonarrSeason.Count(); var ourRequestCount = season.Episodes.Count; + var existingSeason = + result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber); + if (existingSeason == null) + { + Logger.LogError("There was no season numer {0} in Sonarr for title {1}", season.SeasonNumber, model.ParentRequest.Title); + continue; + } + + if (sonarrEpCount == ourRequestCount) { // We have the same amount of requests as all of the episodes in the season. - var existingSeason = - result.seasons.First(x => x.seasonNumber == season.SeasonNumber); - existingSeason.monitored = true; - seriesChanges = true; + + if (!existingSeason.monitored) + { + existingSeason.monitored = true; + seriesChanges = true; + } } else { + // Make sure this season is set to monitored + if (!existingSeason.monitored) + { + // We need to monitor it, problem being is all episodes will now be monitored + // So we need to monior the series but unmonitor every episode + // Except the episodes that are already monitored before we update the series (we do not want to unmonitor episodes that are monitored beforehand) + existingSeason.monitored = true; + var sea = result.seasons.FirstOrDefault(x => x.seasonNumber == existingSeason.seasonNumber); + sea.monitored = true; + //var previouslyMonitoredEpisodes = sonarrEpList.Where(x => + // x.seasonNumber == existingSeason.seasonNumber && x.monitored).Select(x => x.episodeNumber).ToList(); // We probably don't actually care about this + result = await SonarrApi.UpdateSeries(result, s.ApiKey, s.FullUri); + var epToUnmonitor = new List(); + var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the orignal member + foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList()) + { + //if (previouslyMonitoredEpisodes.Contains(ep.episodeNumber)) + //{ + // // This was previously monitored. + // continue; + //} + ep.monitored = false; + epToUnmonitor.Add(ep); + } + + foreach (var epToUpdate in epToUnmonitor) + { + await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri); + } + } // Now update the episodes that need updating foreach (var epToUpdate in episodesToUpdate.Where(x => x.seasonNumber == season.SeasonNumber)) { @@ -280,6 +317,24 @@ namespace Ombi.Core.Senders } } + private static List GetSeasonsToCreate(ChildRequests model) + { + // Let's get a list of seasons just incase we need to change it + var seasonsToUpdate = new List(); + for (var i = 0; i < model.ParentRequest.TotalSeasons + 1; i++) + { + var index = i; + var sea = new Season + { + seasonNumber = i, + monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0) + }; + seasonsToUpdate.Add(sea); + } + + return seasonsToUpdate; + } + private async Task SendToSickRage(ChildRequests model, SickRageSettings settings, string qualityId = null) { var tvdbid = model.ParentRequest.TvDbId; diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index dcae39cb0..2644fa9c7 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -79,6 +79,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 675f6461b..2e7f984a7 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -9,9 +9,9 @@ - - - + + + diff --git a/src/Ombi.Helpers/EmbyHelper.cs b/src/Ombi.Helpers/EmbyHelper.cs index cf6904a0c..567bcfe7e 100644 --- a/src/Ombi.Helpers/EmbyHelper.cs +++ b/src/Ombi.Helpers/EmbyHelper.cs @@ -10,7 +10,7 @@ namespace Ombi.Helpers public static string GetEmbyMediaUrl(string mediaId) { var url = - $"http://app.emby.media/itemdetails.html?id={mediaId}"; + $"http://app.emby.media/#!/itemdetails.html?id={mediaId}"; return url; } } diff --git a/src/Ombi.Helpers/Ombi.Helpers.csproj b/src/Ombi.Helpers/Ombi.Helpers.csproj index 12c6fecc4..e94afc816 100644 --- a/src/Ombi.Helpers/Ombi.Helpers.csproj +++ b/src/Ombi.Helpers/Ombi.Helpers.csproj @@ -10,8 +10,8 @@ - - + + diff --git a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html index 81190334a..450e7df2a 100644 --- a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html +++ b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html @@ -182,14 +182,16 @@ - {@RECENTLYADDED} + {@RECENTLYADDED} + +