mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-20 21:33:15 -07:00
commit
5ee25be669
164 changed files with 5028 additions and 2280 deletions
136
CHANGELOG.md
136
CHANGELOG.md
|
@ -1,15 +1,148 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
## (unreleased)
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Added a global language flag that now applies to the search by default. [tidusjar]
|
||||
|
||||
- Updated the frontend packages (Using Angular 7 now) [TidusJar]
|
||||
|
||||
- Added capture of anonymous analytical data. [tidusjar]
|
||||
|
||||
- Added {AvailableDate} as a Notification Variable, this is the date the request was marked as available. See here: https://github.com/tidusjar/Ombi/wiki/Notification-Template-Variables. [tidusjar]
|
||||
|
||||
- Added the ability to search movies via the movie db with a different language! [tidusjar]
|
||||
|
||||
- Added the ability to specify a year when searching for movies. [tidusjar]
|
||||
|
||||
- Update NewsletterTemplate.html. [d1slact0r]
|
||||
|
||||
- Update NewsletterTemplate.html. [d1slact0r]
|
||||
|
||||
- Update NewsletterTemplate.html. [d1slact0r]
|
||||
|
||||
- Update HtmlTemplateGenerator.cs. [d1slact0r]
|
||||
|
||||
- Update NewsletterTemplate.html. [d1slact0r]
|
||||
|
||||
- Update HtmlTemplateGenerator.cs. [d1slact0r]
|
||||
|
||||
- Update NewsletterTemplate.html. [d1slact0r]
|
||||
|
||||
- Update NewsletterTemplate.html. [d1slact0r]
|
||||
|
||||
- Update NewsletterTemplate.html. [d1slact0r]
|
||||
|
||||
- Update HtmlTemplateGenerator.cs. [d1slact0r]
|
||||
|
||||
- Updated boostrap #2694. [Jamie]
|
||||
|
||||
- Added the ability to deny a request with a reason. [TidusJar]
|
||||
|
||||
## v3.0.4036 (2018-12-11)
|
||||
|
||||
|
||||
- Update EmbyEpisodeSync.cs. [Jamie]
|
||||
|
||||
- Updated to .net core 2.2 and included a linux-arm64 build. [TidusJar]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- Made the newsletter use the default lanuage code set in the Ombi settings for movie information. [TidusJar]
|
||||
|
||||
- Save the language code against the request so we can use it later e.g. Sending to the DVR apps. [tidusjar]
|
||||
|
||||
- Fixed #2716. [tidusjar]
|
||||
|
||||
- Make the newsletter BCC the users rather than creating a million newsletters (Hopefully will stop SMTP providers from marking as spam). This does mean that the custom user customization in the newsletter will no longer work. [TidusJar]
|
||||
|
||||
- If we don't know the Plex agent, then see if it's a ImdbId, if it's not check the string for any episode and season hints #2695. [tidusjar]
|
||||
|
||||
- New translations en.json (Swedish) [Jamie]
|
||||
|
||||
- New translations en.json (Spanish) [Jamie]
|
||||
|
||||
- New translations en.json (Portuguese, Brazilian) [Jamie]
|
||||
|
||||
- New translations en.json (Polish) [Jamie]
|
||||
|
||||
- New translations en.json (Norwegian) [Jamie]
|
||||
|
||||
- New translations en.json (Italian) [Jamie]
|
||||
|
||||
- New translations en.json (German) [Jamie]
|
||||
|
||||
- New translations en.json (French) [Jamie]
|
||||
|
||||
- New translations en.json (Dutch) [Jamie]
|
||||
|
||||
- New translations en.json (Danish) [Jamie]
|
||||
|
||||
- New translations en.json (Dutch) [Jamie]
|
||||
|
||||
- New translations en.json (Dutch) [Jamie]
|
||||
|
||||
- New translations en.json (Dutch) [Jamie]
|
||||
|
||||
- Made the search results the language specified in the search refinement. [tidusjar]
|
||||
|
||||
- Fixed #2704. [tidusjar]
|
||||
|
||||
- Now it is fixed :) [d1slact0r]
|
||||
|
||||
- Android please be nice now. [d1slact0r]
|
||||
|
||||
- Fixed title bit better. [d1slact0r]
|
||||
|
||||
- Fixed titles. [d1slact0r]
|
||||
|
||||
- This should fix the build for sure (stupid quotes) [d1slact0r]
|
||||
|
||||
- Fixes build. [d1slact0r]
|
||||
|
||||
- Rewritten the whole newsletter template. [d1slact0r]
|
||||
|
||||
- Fixed #2697. [tidusjar]
|
||||
|
||||
- Add linux-arm runtime identifier. [aptalca]
|
||||
|
||||
- Add back arm packages. [aptalca]
|
||||
|
||||
- Add arm32 package. [aptalca]
|
||||
|
||||
- Fixed #2691. [tidusjar]
|
||||
|
||||
- Fixed linting. [TidusJar]
|
||||
|
||||
- Fixed the Plex OAuth when going through the wizard. [TidusJar]
|
||||
|
||||
- Fixed #2678. [TidusJar]
|
||||
|
||||
- Deny reason for movie requests. [TidusJar]
|
||||
|
||||
- Set the landing and login pages background refresh to 15 seconds rather than 10 and 7. [TidusJar]
|
||||
|
||||
- Fixed a bug with us thinking future dated emby episodes are not available, Consoldated the emby and plex search rules (since they have the same logic) [TidusJar]
|
||||
|
||||
- Fixed build. [TidusJar]
|
||||
|
||||
|
||||
## v3.0.4036 (2018-12-11)
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Changelog. [Jamie]
|
||||
|
||||
- Added Sonarr v3 #2359. [TidusJar]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- !changelog. [Jamie]
|
||||
|
||||
- Fixed a missing translation. [Jamie]
|
||||
|
||||
- Fixed a potential security vulnerability. [Jamie]
|
||||
|
||||
- Sorted out some of the settings pages, trying to make it consistent. [Jamie]
|
||||
|
@ -37,8 +170,7 @@
|
|||
|
||||
### **Fixes**
|
||||
|
||||
|
||||
- Fixed #2601 [TidusJar]
|
||||
- !changelog. [Jamie]
|
||||
|
||||
- Made the subscribe/unsubscribe button more obvious on the UI #2309. [Jamie]
|
||||
|
||||
|
|
20
appveyor.yml
20
appveyor.yml
|
@ -27,22 +27,22 @@ test: off
|
|||
after_build:
|
||||
- cmd: >-
|
||||
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\windows.zip"
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows.zip"
|
||||
|
||||
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\osx.tar.gz"
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\osx.tar.gz"
|
||||
|
||||
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.1\linux.tar.gz"
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux.tar.gz"
|
||||
|
||||
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm.tar.gz"
|
||||
|
||||
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\windows-32bit.zip"
|
||||
|
||||
|
||||
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.1\windows-32bit.zip"
|
||||
|
||||
|
||||
# appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.0\linux-arm64.tar.gz"
|
||||
appveyor PushArtifact "%APPVEYOR_BUILD_FOLDER%\src\Ombi\bin\Release\netcoreapp2.2\linux-arm64.tar.gz"
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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.1";
|
||||
var frameworkVer = "netcoreapp2.2";
|
||||
|
||||
var buildSettings = new DotNetCoreBuildSettings
|
||||
{
|
||||
|
@ -151,7 +151,7 @@ Task("Package")
|
|||
GZipCompress(osxArtifactsFolder, artifactsFolder + "osx.tar.gz");
|
||||
GZipCompress(linuxArtifactsFolder, artifactsFolder + "linux.tar.gz");
|
||||
GZipCompress(linuxArmArtifactsFolder, artifactsFolder + "linux-arm.tar.gz");
|
||||
//GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz");
|
||||
GZipCompress(linuxArm64BitArtifactsFolder, artifactsFolder + "linux-arm64.tar.gz");
|
||||
});
|
||||
|
||||
Task("Publish")
|
||||
|
@ -227,7 +227,7 @@ 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);
|
||||
});
|
||||
|
|
291
package-lock.json
generated
291
package-lock.json
generated
|
@ -1,291 +0,0 @@
|
|||
{
|
||||
"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="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -44,6 +44,7 @@ namespace Ombi.Api.Sonarr.Models
|
|||
public DateTime added { get; set; }
|
||||
public Ratings ratings { get; set; }
|
||||
public int qualityProfileId { get; set; }
|
||||
public int languageProfileId { get; set; }
|
||||
public int id { get; set; }
|
||||
public DateTime nextAiring { get; set; }
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="Polly" Version="6.1.0" />
|
||||
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -10,7 +10,7 @@
|
|||
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -157,6 +157,24 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
}
|
||||
|
||||
private string defaultLangCode;
|
||||
protected async Task<string> DefaultLanguageCode(string currentCode)
|
||||
{
|
||||
if (currentCode.HasValue())
|
||||
{
|
||||
return currentCode;
|
||||
}
|
||||
|
||||
var s = await GetOmbiSettings();
|
||||
return s.DefaultLanguageCode;
|
||||
}
|
||||
|
||||
private OmbiSettings ombiSettings;
|
||||
protected async Task<OmbiSettings> GetOmbiSettings()
|
||||
{
|
||||
return ombiSettings ?? (ombiSettings = await OmbiSettings.GetSettingsAsync());
|
||||
}
|
||||
|
||||
public class HideResult
|
||||
{
|
||||
public bool Hide { get; set; }
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
Task<RequestEngineResult>ApproveAlbum(AlbumRequest request);
|
||||
Task<RequestEngineResult> ApproveAlbumById(int requestId);
|
||||
Task<RequestEngineResult> DenyAlbumById(int modelId);
|
||||
Task<RequestEngineResult> DenyAlbumById(int modelId, string reason);
|
||||
Task<IEnumerable<AlbumRequest>> GetRequests();
|
||||
Task<RequestsViewModel<AlbumRequest>> GetRequests(int count, int position, OrderFilterModel orderFilter);
|
||||
Task<int> GetTotal();
|
||||
|
|
|
@ -10,14 +10,14 @@ namespace Ombi.Core
|
|||
|
||||
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> Search(string search);
|
||||
Task<IEnumerable<SearchMovieViewModel>> Search(string search, int? year, string languageCode);
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
|
||||
|
||||
Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId);
|
||||
Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId, string langCode = null);
|
||||
|
||||
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId);
|
||||
Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode);
|
||||
}
|
||||
}
|
|
@ -17,6 +17,6 @@ namespace Ombi.Core.Engine.Interfaces
|
|||
Task<MovieRequests> UpdateMovieRequest(MovieRequests request);
|
||||
Task<RequestEngineResult> ApproveMovie(MovieRequests request);
|
||||
Task<RequestEngineResult> ApproveMovieById(int requestId);
|
||||
Task<RequestEngineResult> DenyMovieById(int modelId);
|
||||
Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ namespace Ombi.Core.Engine.Interfaces
|
|||
Task RemoveTvRequest(int requestId);
|
||||
Task<TvRequests> GetTvRequest(int requestId);
|
||||
Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv);
|
||||
Task<RequestEngineResult> DenyChildRequest(int requestId);
|
||||
Task<RequestEngineResult> DenyChildRequest(int requestId, string reason);
|
||||
Task<RequestsViewModel<TvRequests>> GetRequestsLite(int count, int position, OrderFilterModel type);
|
||||
Task<IEnumerable<TvRequests>> SearchTvRequest(string search);
|
||||
Task<TvRequests> UpdateTvRequest(TvRequests request);
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<RequestEngineResult> RequestMovie(MovieRequestViewModel model)
|
||||
{
|
||||
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId);
|
||||
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(model.TheMovieDbId, model.LanguageCode);
|
||||
if (movieInfo == null || movieInfo.Id == 0)
|
||||
{
|
||||
return new RequestEngineResult
|
||||
|
@ -82,7 +82,8 @@ namespace Ombi.Core.Engine
|
|||
RequestedDate = DateTime.UtcNow,
|
||||
Approved = false,
|
||||
RequestedUserId = userDetails.Id,
|
||||
Background = movieInfo.BackdropPath
|
||||
Background = movieInfo.BackdropPath,
|
||||
LangCode = model.LanguageCode
|
||||
};
|
||||
|
||||
var usDates = movieInfo.ReleaseDates?.Results?.FirstOrDefault(x => x.IsoCode == "US");
|
||||
|
@ -305,7 +306,7 @@ namespace Ombi.Core.Engine
|
|||
return await ApproveMovie(request);
|
||||
}
|
||||
|
||||
public async Task<RequestEngineResult> DenyMovieById(int modelId)
|
||||
public async Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason)
|
||||
{
|
||||
var request = await MovieRepository.Find(modelId);
|
||||
if (request == null)
|
||||
|
@ -317,6 +318,7 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
|
||||
request.Denied = true;
|
||||
request.DeniedReason = denyReason;
|
||||
// We are denying a request
|
||||
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
|
||||
await MovieRepository.Update(request);
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
using System;
|
||||
using AutoMapper;
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Core.Authentication;
|
||||
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.Core.Authentication;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
|
@ -36,14 +35,17 @@ namespace Ombi.Core.Engine
|
|||
private IMapper Mapper { get; }
|
||||
private ILogger<MovieSearchEngine> Logger { get; }
|
||||
|
||||
private const int MovieLimit = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Lookups the imdb information.
|
||||
/// </summary>
|
||||
/// <param name="theMovieDbId">The movie database identifier.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId)
|
||||
public async Task<SearchMovieViewModel> LookupImdbInformation(int theMovieDbId, string langCode = null)
|
||||
{
|
||||
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId);
|
||||
langCode = await DefaultLanguageCode(langCode);
|
||||
var movieInfo = await MovieApi.GetMovieInformationWithExtraInfo(theMovieDbId, langCode);
|
||||
var viewMovie = Mapper.Map<SearchMovieViewModel>(movieInfo);
|
||||
|
||||
return await ProcessSingleMovie(viewMovie, true);
|
||||
|
@ -54,13 +56,14 @@ namespace Ombi.Core.Engine
|
|||
/// </summary>
|
||||
/// <param name="search">The search.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search)
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> Search(string search, int? year, string langaugeCode)
|
||||
{
|
||||
var result = await MovieApi.SearchMovie(search);
|
||||
langaugeCode = await DefaultLanguageCode(langaugeCode);
|
||||
var result = await MovieApi.SearchMovie(search, year, langaugeCode);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
||||
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -70,13 +73,14 @@ namespace Ombi.Core.Engine
|
|||
/// </summary>
|
||||
/// <param name="theMovieDbId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId)
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> SimilarMovies(int theMovieDbId, string langCode)
|
||||
{
|
||||
var result = await MovieApi.SimilarMovies(theMovieDbId);
|
||||
langCode = await DefaultLanguageCode(langCode);
|
||||
var result = await MovieApi.SimilarMovies(theMovieDbId, langCode);
|
||||
if (result != null)
|
||||
{
|
||||
Logger.LogDebug("Search Result: {result}", result);
|
||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
||||
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -87,10 +91,15 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
|
||||
{
|
||||
var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () => await MovieApi.PopularMovies(), DateTime.Now.AddHours(12));
|
||||
|
||||
var result = await Cache.GetOrAdd(CacheKeys.PopularMovies, async () =>
|
||||
{
|
||||
var langCode = await DefaultLanguageCode(null);
|
||||
return await MovieApi.PopularMovies(langCode);
|
||||
}, DateTime.Now.AddHours(12));
|
||||
if (result != null)
|
||||
{
|
||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
||||
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -101,10 +110,14 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
|
||||
{
|
||||
var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () => await MovieApi.TopRated(), DateTime.Now.AddHours(12));
|
||||
var result = await Cache.GetOrAdd(CacheKeys.TopRatedMovies, async () =>
|
||||
{
|
||||
var langCode = await DefaultLanguageCode(null);
|
||||
return await MovieApi.TopRated(langCode);
|
||||
}, DateTime.Now.AddHours(12));
|
||||
if (result != null)
|
||||
{
|
||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
||||
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -115,11 +128,15 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
|
||||
{
|
||||
var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () => await MovieApi.Upcoming(), DateTime.Now.AddHours(12));
|
||||
var result = await Cache.GetOrAdd(CacheKeys.UpcomingMovies, async () =>
|
||||
{
|
||||
var langCode = await DefaultLanguageCode(null);
|
||||
return await MovieApi.Upcoming(langCode);
|
||||
}, DateTime.Now.AddHours(12));
|
||||
if (result != null)
|
||||
{
|
||||
Logger.LogDebug("Search Result: {result}", result);
|
||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
||||
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -130,10 +147,14 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
|
||||
{
|
||||
var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => await MovieApi.NowPlaying(), DateTime.Now.AddHours(12));
|
||||
var result = await Cache.GetOrAdd(CacheKeys.NowPlayingMovies, async () =>
|
||||
{
|
||||
var langCode = await DefaultLanguageCode(null);
|
||||
return await MovieApi.NowPlaying(langCode);
|
||||
}, DateTime.Now.AddHours(12));
|
||||
if (result != null)
|
||||
{
|
||||
return await TransformMovieResultsToResponse(result.Take(10)); // Take 10 to stop us overloading the API
|
||||
return await TransformMovieResultsToResponse(result.Take(MovieLimit)); // Take x to stop us overloading the API
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -174,6 +195,10 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
// Check if this user requested it
|
||||
var user = await GetUser();
|
||||
if (user == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var request = await RequestService.MovieRequestService.GetAll()
|
||||
.AnyAsync(x => x.RequestedUserId.Equals(user.Id) && x.TheMovieDbId == viewModel.Id);
|
||||
if (request)
|
||||
|
|
|
@ -299,7 +299,7 @@ namespace Ombi.Core.Engine
|
|||
return await ApproveAlbum(request);
|
||||
}
|
||||
|
||||
public async Task<RequestEngineResult> DenyAlbumById(int modelId)
|
||||
public async Task<RequestEngineResult> DenyAlbumById(int modelId, string reason)
|
||||
{
|
||||
var request = await MusicRepository.Find(modelId);
|
||||
if (request == null)
|
||||
|
@ -311,6 +311,7 @@ namespace Ombi.Core.Engine
|
|||
}
|
||||
|
||||
request.Denied = true;
|
||||
request.DeniedReason = reason;
|
||||
// We are denying a request
|
||||
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
|
||||
await MusicRepository.Update(request);
|
||||
|
|
|
@ -403,7 +403,7 @@ namespace Ombi.Core.Engine
|
|||
};
|
||||
}
|
||||
|
||||
public async Task<RequestEngineResult> DenyChildRequest(int requestId)
|
||||
public async Task<RequestEngineResult> DenyChildRequest(int requestId, string reason)
|
||||
{
|
||||
var request = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId);
|
||||
if (request == null)
|
||||
|
@ -414,6 +414,7 @@ namespace Ombi.Core.Engine
|
|||
};
|
||||
}
|
||||
request.Denied = true;
|
||||
request.DeniedReason = reason;
|
||||
await TvRepository.UpdateChild(request);
|
||||
NotificationHelper.Notify(request, NotificationType.RequestDeclined);
|
||||
return new RequestEngineResult
|
||||
|
|
|
@ -29,5 +29,6 @@ namespace Ombi.Core.Models.Requests
|
|||
public class MovieRequestViewModel
|
||||
{
|
||||
public int TheMovieDbId { get; set; }
|
||||
public string LanguageCode { get; set; } = "en";
|
||||
}
|
||||
}
|
|
@ -11,11 +11,11 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="6.1.1" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.2.0" />
|
||||
<PackageReference Include="Hangfire" Version="1.6.19" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.3" />
|
||||
<PackageReference Include="Hangfire" Version="1.6.21" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
|
||||
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
97
src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs
Normal file
97
src/Ombi.Core/Rule/Rules/Search/AvailabilityRuleHelper.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository.Requests;
|
||||
|
||||
namespace Ombi.Core.Rule.Rules.Search
|
||||
{
|
||||
public static class AvailabilityRuleHelper
|
||||
{
|
||||
public static void CheckForUnairedEpisodes(SearchTvShowViewModel search)
|
||||
{
|
||||
if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available)))
|
||||
{
|
||||
search.FullyAvailable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var airedButNotAvailable = search.SeasonRequests.Any(x =>
|
||||
x.Episodes.Any(c => !c.Available && c.AirDate <= DateTime.Now.Date && c.AirDate != DateTime.MinValue));
|
||||
if (!airedButNotAvailable)
|
||||
{
|
||||
var unairedEpisodes = search.SeasonRequests.Any(x =>
|
||||
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date));
|
||||
if (unairedEpisodes)
|
||||
{
|
||||
search.FullyAvailable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<PlexEpisode> allEpisodes, EpisodeRequests episode,
|
||||
SeasonRequests season, PlexServerContent item, bool useTheMovieDb, bool useTvDb)
|
||||
{
|
||||
PlexEpisode epExists = null;
|
||||
if (useImdb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.ImdbId == item.ImdbId.ToString());
|
||||
}
|
||||
|
||||
if (useTheMovieDb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
|
||||
}
|
||||
|
||||
if (useTvDb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.TvDbId == item.TvDbId.ToString());
|
||||
}
|
||||
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
public static async Task SingleEpisodeCheck(bool useImdb, IQueryable<EmbyEpisode> allEpisodes, EpisodeRequests episode,
|
||||
SeasonRequests season, EmbyContent item, bool useTheMovieDb, bool useTvDb)
|
||||
{
|
||||
EmbyEpisode epExists = null;
|
||||
if (useImdb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.ImdbId == item.ImdbId.ToString());
|
||||
}
|
||||
|
||||
if (useTheMovieDb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
|
||||
}
|
||||
|
||||
if (useTvDb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.TvDbId == item.TvDbId.ToString());
|
||||
}
|
||||
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
|
@ -23,15 +21,27 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
public async Task<RuleResult> Execute(SearchViewModel obj)
|
||||
{
|
||||
EmbyContent item = null;
|
||||
var useImdb = false;
|
||||
var useTheMovieDb = false;
|
||||
var useTvDb = false;
|
||||
|
||||
if (obj.ImdbId.HasValue())
|
||||
{
|
||||
item = await EmbyContentRepository.GetByImdbId(obj.ImdbId);
|
||||
if (item != null)
|
||||
{
|
||||
useImdb = true;
|
||||
}
|
||||
}
|
||||
if (item == null)
|
||||
{
|
||||
if (obj.TheMovieDbId.HasValue())
|
||||
{
|
||||
item = await EmbyContentRepository.GetByTheMovieDbId(obj.TheMovieDbId);
|
||||
if (item != null)
|
||||
{
|
||||
useTheMovieDb = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
|
@ -39,10 +49,14 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
if (obj.TheTvDbId.HasValue())
|
||||
{
|
||||
item = await EmbyContentRepository.GetByTvDbId(obj.TheTvDbId);
|
||||
if (item != null)
|
||||
{
|
||||
useTvDb = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
obj.Available = true;
|
||||
|
@ -59,29 +73,12 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
{
|
||||
foreach (var episode in season.Episodes)
|
||||
{
|
||||
EmbyEpisode epExists = null;
|
||||
|
||||
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)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AvailabilityRuleHelper.CheckForUnairedEpisodes(search);
|
||||
}
|
||||
}
|
||||
return Success();
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models.Search;
|
||||
using Ombi.Core.Rule.Interfaces;
|
||||
using Ombi.Helpers;
|
||||
|
@ -74,56 +72,17 @@ namespace Ombi.Core.Rule.Rules.Search
|
|||
{
|
||||
foreach (var episode in season.Episodes)
|
||||
{
|
||||
PlexEpisode epExists = null;
|
||||
if (useImdb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.ImdbId == item.ImdbId.ToString());
|
||||
}
|
||||
if (useTheMovieDb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.TheMovieDbId == item.TheMovieDbId.ToString());
|
||||
}
|
||||
if (useTvDb)
|
||||
{
|
||||
epExists = await allEpisodes.FirstOrDefaultAsync(x =>
|
||||
x.EpisodeNumber == episode.EpisodeNumber && x.SeasonNumber == season.SeasonNumber &&
|
||||
x.Series.TvDbId == item.TvDbId.ToString());
|
||||
}
|
||||
|
||||
if (epExists != null)
|
||||
{
|
||||
episode.Available = true;
|
||||
}
|
||||
await AvailabilityRuleHelper.SingleEpisodeCheck(useImdb, allEpisodes, episode, season, item, useTheMovieDb, useTvDb);
|
||||
}
|
||||
}
|
||||
|
||||
if (search.SeasonRequests.All(x => x.Episodes.All(e => e.Available)))
|
||||
{
|
||||
search.FullyAvailable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var airedButNotAvailable = search.SeasonRequests.Any(x =>
|
||||
x.Episodes.Any(c => !c.Available && c.AirDate <= DateTime.Now.Date));
|
||||
if (!airedButNotAvailable)
|
||||
{
|
||||
var unairedEpisodes = search.SeasonRequests.Any(x =>
|
||||
x.Episodes.Any(c => !c.Available && c.AirDate > DateTime.Now.Date));
|
||||
if (unairedEpisodes)
|
||||
{
|
||||
search.FullyAvailable = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
AvailabilityRuleHelper.CheckForUnairedEpisodes(search);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Success();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -348,16 +348,16 @@ namespace Ombi.Core.Senders
|
|||
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)
|
||||
// So we need to monitor the series but unmonitor every episode
|
||||
// Except the episodes that are already monitored before we update the series (we do not want to unmonitored 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<Episode>();
|
||||
var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the orignal member
|
||||
var epToUnmonitored = new List<Episode>();
|
||||
var newEpList = sonarrEpList.ConvertAll(ep => new Episode(ep)); // Clone it so we don't modify the original member
|
||||
foreach (var ep in newEpList.Where(x => x.seasonNumber == existingSeason.seasonNumber).ToList())
|
||||
{
|
||||
//if (previouslyMonitoredEpisodes.Contains(ep.episodeNumber))
|
||||
|
@ -366,10 +366,10 @@ namespace Ombi.Core.Senders
|
|||
// continue;
|
||||
//}
|
||||
ep.monitored = false;
|
||||
epToUnmonitor.Add(ep);
|
||||
epToUnmonitored.Add(ep);
|
||||
}
|
||||
|
||||
foreach (var epToUpdate in epToUnmonitor)
|
||||
foreach (var epToUpdate in epToUnmonitored)
|
||||
{
|
||||
await SonarrApi.UpdateEpisode(epToUpdate, s.ApiKey, s.FullUri);
|
||||
}
|
||||
|
|
|
@ -200,6 +200,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<ILidarrAvailabilityChecker, LidarrAvailabilityChecker>();
|
||||
services.AddTransient<IIssuesPurge, IssuesPurge>();
|
||||
services.AddTransient<IResendFailedRequests, ResendFailedRequests>();
|
||||
services.AddTransient<IMediaDatabaseRefresh, MediaDatabaseRefresh>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
19
src/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj
Normal file
19
src/Ombi.Helpers.Tests/Ombi.Helpers.Tests.csproj
Normal file
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="nunit" Version="3.11.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
52
src/Ombi.Helpers.Tests/PlexHelperTests.cs
Normal file
52
src/Ombi.Helpers.Tests/PlexHelperTests.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using NUnit.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Helpers.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class PlexHelperTests
|
||||
{
|
||||
|
||||
[TestCaseSource(nameof(ProviderIdGuidData))]
|
||||
public string GetProviderIdFromPlexGuidTests(string guidInput, ProviderIdType type)
|
||||
{
|
||||
var result = PlexHelper.GetProviderIdFromPlexGuid(guidInput);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ProviderIdType.Imdb:
|
||||
Assert.That(result.ImdbId, Is.Not.Null);
|
||||
return result.ImdbId;
|
||||
case ProviderIdType.TvDb:
|
||||
Assert.That(result.TheTvDb, Is.Not.Null);
|
||||
return result.TheTvDb;
|
||||
case ProviderIdType.MovieDb:
|
||||
Assert.That(result.TheMovieDb, Is.Not.Null);
|
||||
return result.TheMovieDb;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(type), type, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TestCaseData> ProviderIdGuidData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData("com.plexapp.agents.thetvdb://269586/2/8?lang=en", ProviderIdType.TvDb).Returns("269586").SetName("Regular TvDb Id");
|
||||
yield return new TestCaseData("com.plexapp.agents.themoviedb://390043?lang=en", ProviderIdType.MovieDb).Returns("390043").SetName("Regular MovieDb Id");
|
||||
yield return new TestCaseData("com.plexapp.agents.imdb://tt2543164?lang=en", ProviderIdType.Imdb).Returns("tt2543164").SetName("Regular Imdb Id");
|
||||
yield return new TestCaseData("com.plexapp.agents.agent47://tt2543456?lang=en", ProviderIdType.Imdb).Returns("tt2543456").SetName("Unknown IMDB agent");
|
||||
yield return new TestCaseData("com.plexapp.agents.agent47://456822/1/1?lang=en", ProviderIdType.TvDb).Returns("456822").SetName("Unknown TvDb agent");
|
||||
yield return new TestCaseData("com.plexapp.agents.agent47://456822/999/999?lang=en", ProviderIdType.TvDb).Returns("456822").SetName("Unknown TvDb agent, large episode and season");
|
||||
}
|
||||
}
|
||||
|
||||
public enum ProviderIdType
|
||||
{
|
||||
Imdb,
|
||||
TvDb,
|
||||
MovieDb
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ namespace Ombi.Helpers
|
|||
public static EventId PlexContentCacher => new EventId(2008);
|
||||
public static EventId SickRageCacher => new EventId(2009);
|
||||
public static EventId LidarrArtistCache => new EventId(2010);
|
||||
public static EventId MediaReferesh => new EventId(2011);
|
||||
|
||||
public static EventId MovieSender => new EventId(3000);
|
||||
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EasyCrypto" Version="3.3.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
|
||||
<PackageReference Include="System.Security.Claims" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -27,12 +27,15 @@
|
|||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ombi.Helpers
|
||||
{
|
||||
public class PlexHelper
|
||||
{
|
||||
|
||||
private const string ImdbMatchExpression = "tt([0-9]{1,10})";
|
||||
private const string TvDbIdMatchExpression = "//[0-9]+/([0-9]{1,3})/([0-9]{1,3})";
|
||||
|
||||
public static ProviderId GetProviderIdFromPlexGuid(string guid)
|
||||
{
|
||||
//com.plexapp.agents.thetvdb://269586/2/8?lang=en
|
||||
|
@ -52,7 +55,7 @@ namespace Ombi.Helpers
|
|||
{
|
||||
TheTvDb = guidSplit[1]
|
||||
};
|
||||
}
|
||||
} else
|
||||
if (guid.Contains("themoviedb", CompareOptions.IgnoreCase))
|
||||
{
|
||||
return new ProviderId
|
||||
|
@ -60,6 +63,7 @@ namespace Ombi.Helpers
|
|||
TheMovieDb = guidSplit[1]
|
||||
};
|
||||
}
|
||||
else
|
||||
if (guid.Contains("imdb", CompareOptions.IgnoreCase))
|
||||
{
|
||||
return new ProviderId
|
||||
|
@ -67,6 +71,31 @@ namespace Ombi.Helpers
|
|||
ImdbId = guidSplit[1]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
var imdbRegex = new Regex(ImdbMatchExpression, RegexOptions.Compiled);
|
||||
var tvdbRegex = new Regex(TvDbIdMatchExpression, RegexOptions.Compiled);
|
||||
var imdbMatch = imdbRegex.IsMatch(guid);
|
||||
if (imdbMatch)
|
||||
{
|
||||
return new ProviderId
|
||||
{
|
||||
ImdbId = guidSplit[1]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if it matches the TvDb pattern
|
||||
var tvdbMatch = tvdbRegex.IsMatch(guid);
|
||||
if (tvdbMatch)
|
||||
{
|
||||
return new ProviderId
|
||||
{
|
||||
TheTvDb = guidSplit[1]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return new ProviderId();
|
||||
}
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
<!doctype html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Ombi</title>
|
||||
<style type="text/css">
|
||||
<style>
|
||||
/* -------------------------------------
|
||||
GLOBAL RESETS
|
||||
------------------------------------- */
|
||||
|
||||
/*All the styling goes here*/
|
||||
|
||||
img {
|
||||
border: none;
|
||||
-ms-interpolation-mode: bicubic;
|
||||
|
@ -12,6 +19,7 @@
|
|||
}
|
||||
|
||||
body {
|
||||
background-color: #1f1f1f;
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-size: 14px;
|
||||
|
@ -20,6 +28,8 @@
|
|||
padding: 0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table {
|
||||
|
@ -31,39 +41,54 @@
|
|||
|
||||
table td {
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
BODY & CONTAINER
|
||||
------------------------------------- */
|
||||
|
||||
.body {
|
||||
color: #FFF;
|
||||
background-color: #1f1f1f;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */
|
||||
|
||||
.container {
|
||||
display: block;
|
||||
margin: 0 auto !important;
|
||||
max-width: 1042px;
|
||||
Margin: 0 auto !important;
|
||||
/* makes it centered */
|
||||
max-width: 1036px;
|
||||
padding: 10px;
|
||||
width: 1042px;
|
||||
width: 1036px;
|
||||
}
|
||||
|
||||
/* This should also be a block element, so that it will fill 100% of the .container */
|
||||
|
||||
.content {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
Margin: 0 auto;
|
||||
max-width: 1037px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
HEADER, FOOTER, MAIN
|
||||
------------------------------------- */
|
||||
|
||||
.main {
|
||||
color: #FFF;
|
||||
background: #1f1f1f;
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
box-sizing: border-box;
|
||||
padding: 5px;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.content-block {
|
||||
|
@ -71,58 +96,241 @@
|
|||
padding-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
.media-card {
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
vertical-align: top;
|
||||
padding: 3px;
|
||||
width: 500px;
|
||||
min-width: 500px;
|
||||
max-width: 500px;
|
||||
height: 252px;
|
||||
max-height: 252px;
|
||||
min-height: 252px;
|
||||
}
|
||||
|
||||
.card-bg {
|
||||
background-image: url({0});
|
||||
border-collapse: separate;
|
||||
mso-table-lspace: 0pt;
|
||||
mso-table-rspace: 0pt;
|
||||
width: 500px;
|
||||
background-color: #1f1f1f;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-clip: padding-box;
|
||||
border: 2px solid rgba(255, 118, 27, .4);
|
||||
height: 252px;
|
||||
max-height: 252px;
|
||||
}
|
||||
|
||||
.bg-tint {
|
||||
background-color: rgba(0, 0, 0, .6);
|
||||
}
|
||||
|
||||
.poster-container {
|
||||
vertical-align: top;
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
height: 225px;
|
||||
max-height: 225px;
|
||||
min-height: 225px;
|
||||
}
|
||||
|
||||
.poster-img {}
|
||||
|
||||
.poster-overlay {}
|
||||
|
||||
.movie-info {
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
vertical-align: top;
|
||||
padding-left: 4px;
|
||||
text-align: left;
|
||||
height: 227px;
|
||||
}
|
||||
|
||||
.title h1 {
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
font-size: 22px;
|
||||
line-height: 24px;
|
||||
vertical-align: top;
|
||||
max-width: 320px;
|
||||
white-space: normal;
|
||||
display: block;
|
||||
height: 50px;
|
||||
min-height: 50px;
|
||||
max-height: 50px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
height: 130px;
|
||||
max-height: 130px;
|
||||
max-width: 320px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
font-size: 14px !important;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.meta {
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
max-width: 300px;
|
||||
min-width: 300px;
|
||||
padding: 3px 7px;
|
||||
margin-top: 10px;
|
||||
font-size: 14px !important;
|
||||
line-height: 1;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
background-color: rgba(255, 118, 27, 0.5);
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.footer {
|
||||
clear: both;
|
||||
margin-top: 10px;
|
||||
Margin-top: 10px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.footer td,
|
||||
.footer p,
|
||||
.footer span,
|
||||
.footer a {
|
||||
color: #fff;
|
||||
color: #999999;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #ffffff;
|
||||
/* -------------------------------------
|
||||
TYPOGRAPHY
|
||||
------------------------------------- */
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
color: #ff761b;
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
color: #ff761b;
|
||||
font-size: 22px;
|
||||
line-height: 24px;
|
||||
margin: 0 auto;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p {
|
||||
p,
|
||||
ul,
|
||||
ol {
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p li,
|
||||
ul li {
|
||||
ul li,
|
||||
ol li {
|
||||
list-style-position: inside;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ff761b !important;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
OTHER STYLES THAT MIGHT BE USEFUL
|
||||
------------------------------------- */
|
||||
|
||||
.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.mt0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb0 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.preheader {
|
||||
color: transparent;
|
||||
display: none;
|
||||
height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
mso-hide: all;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.powered-by a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1040px) {
|
||||
.media-card {
|
||||
display: block !important;
|
||||
margin-top: 0 !important;
|
||||
margin-right: auto !important;
|
||||
margin-bottom: 10px !important;
|
||||
margin-left: auto !important;
|
||||
hr {
|
||||
border: 0;
|
||||
border-bottom: 1px solid #f6f6f6;
|
||||
Margin: 20px 0;
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
RESPONSIVE AND MOBILE FRIENDLY STYLES
|
||||
------------------------------------- */
|
||||
|
||||
@media only screen and (max-width: 1059px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .container {
|
||||
|
@ -135,9 +343,52 @@
|
|||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.media-card {
|
||||
display: flex !important;
|
||||
margin-top: 0 !important;
|
||||
margin-right: auto !important;
|
||||
margin-bottom: 10px !important;
|
||||
margin-left: auto !important;
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
width: 500px !important;
|
||||
max-width: 500px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------
|
||||
PRESERVE THESE STYLES IN THE HEAD
|
||||
------------------------------------- */
|
||||
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
|
@ -146,66 +397,78 @@
|
|||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;font-size: 14px;line-height: 1.4;margin: 0;padding: 0;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%; background-color: #1f1f1f; color: #fff;">
|
||||
<tr>
|
||||
<td class="container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;display: block;max-width: 1042px;padding: 10px;width: 1042px;margin: 0 auto !important;">
|
||||
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 1037px; padding: 10px;">
|
||||
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ombi Recently Added</span>
|
||||
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; border-radius: 3px;">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; max-width: 1042px; width: 100%;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="{@LOGO}" width="400px" text-align="center"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
|
||||
<br />
|
||||
<br />
|
||||
<p style="color: #fff; font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px; text-align: center;">{@INTRO}</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{@RECENTLYADDED}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer" style="clear: both; padding-top: 10px; text-align: center; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center">
|
||||
Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a>
|
||||
</td>
|
||||
</tr>
|
||||
<body class="" style="background-color: #1f1f1f; font-family: 'Open Sans', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; margin: 0; padding: 0; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; color: #FFF; text-align: center;">
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body" width="100%" bgcolor="#1f1f1f" align="center" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #FFF; background-color: #1f1f1f; width: 100%; text-align: center;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="container" width="1036" valign="top" style="font-family: sans-serif; font-size: 14px; vertical-align: top; max-width: 1036px; padding: 10px; width: 1036px; Margin: 0 auto;">
|
||||
<div class="content" style="box-sizing: border-box; Margin: 0 auto; max-width: 1037px; padding: 10px;">
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ombi recently added</span>
|
||||
<table role="presentation" class="main" width="100%" align="center" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; color: #FFF; background: #1f1f1f; border-radius: 3px; width: 100%; text-align: center;">
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="wrapper" valign="top" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;">
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td valign="top" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; vertical-align: top;">
|
||||
<img src="{@LOGO}" style="border: none; -ms-interpolation-mode: bicubic; max-width: 100%;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; vertical-align: top;">
|
||||
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: normal; margin: 0; margin-bottom: 15px;">{@INTRO}</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif; vertical-align: top;">
|
||||
{@RECENTLYADDED}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer" style="clear: both; Margin-top: 10px; text-align: center; width: 100%;">
|
||||
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="content-block powered-by" valign="top" align="center" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; color: #999999; font-size: 12px; text-align: center;">
|
||||
Powered by <a href="https://github.com/tidusjar/Ombi" style="font-weight: 400; font-size: 12px; text-align: center; text-decoration: none; color: #ff761b;">Ombi</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END FOOTER -->
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
|
||||
<!-- END FOOTER -->
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> </td>
|
||||
</tr>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -83,22 +83,20 @@ namespace Ombi.Notifications
|
|||
{
|
||||
client.Authenticate(settings.Username, settings.Password);
|
||||
}
|
||||
//Log.Info("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated);
|
||||
_log.LogDebug("sending message to {0} \r\n from: {1}\r\n Are we authenticated: {2}", message.To, message.From, client.IsAuthenticated);
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//Log.Error(e);
|
||||
throw new InvalidOperationException(e.Message);
|
||||
_log.LogError(e, "Exception when attempting to send an email");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Send(NotificationMessage model, EmailNotificationSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
EnsureArg.IsNotNullOrEmpty(settings.SenderAddress);
|
||||
EnsureArg.IsNotNullOrEmpty(model.To);
|
||||
EnsureArg.IsNotNullOrEmpty(model.Message);
|
||||
|
@ -120,9 +118,18 @@ namespace Ombi.Notifications
|
|||
Subject = model.Subject
|
||||
};
|
||||
|
||||
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
|
||||
message.To.Add(new MailboxAddress(model.To, model.To));
|
||||
|
||||
await Send(message, settings);
|
||||
|
||||
}
|
||||
|
||||
public async Task Send(MimeMessage message, EmailNotificationSettings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
|
||||
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
if (settings.DisableCertificateChecking)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Threading.Tasks;
|
||||
using MimeKit;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
|
||||
|
@ -8,5 +9,6 @@ namespace Ombi.Notifications
|
|||
{
|
||||
Task Send(NotificationMessage model, EmailNotificationSettings settings);
|
||||
Task SendAdHoc(NotificationMessage model, EmailNotificationSettings settings);
|
||||
Task Send(MimeMessage message, EmailNotificationSettings settings);
|
||||
}
|
||||
}
|
|
@ -17,10 +17,6 @@ namespace Ombi.Notifications
|
|||
public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s, UserNotificationPreferences pref)
|
||||
{
|
||||
LoadIssues(opts);
|
||||
if (pref != null)
|
||||
{
|
||||
UserPreference = pref.Value;
|
||||
}
|
||||
|
||||
string title;
|
||||
if (req == null)
|
||||
|
@ -41,6 +37,10 @@ namespace Ombi.Notifications
|
|||
}
|
||||
|
||||
Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
|
||||
if (pref != null)
|
||||
{
|
||||
UserPreference = pref.Value.HasValue() ? pref.Value : Alias;
|
||||
}
|
||||
Title = title;
|
||||
RequestedDate = req?.RequestedDate.ToString("D");
|
||||
if (Type.IsNullOrEmpty())
|
||||
|
@ -49,7 +49,8 @@ namespace Ombi.Notifications
|
|||
}
|
||||
Overview = req?.Overview;
|
||||
Year = req?.ReleaseDate.Year.ToString();
|
||||
|
||||
DenyReason = req?.DeniedReason;
|
||||
AvailableDate = req?.MarkedAsAvailable?.ToString("D") ?? string.Empty;
|
||||
if (req?.RequestType == RequestType.Movie)
|
||||
{
|
||||
PosterImage = string.Format((req?.PosterPath ?? string.Empty).StartsWith("/", StringComparison.InvariantCultureIgnoreCase)
|
||||
|
@ -66,10 +67,7 @@ namespace Ombi.Notifications
|
|||
public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s, UserNotificationPreferences pref)
|
||||
{
|
||||
LoadIssues(opts);
|
||||
if (pref != null)
|
||||
{
|
||||
UserPreference = pref.Enabled ? pref.Value : string.Empty;
|
||||
}
|
||||
|
||||
string title;
|
||||
if (req == null)
|
||||
{
|
||||
|
@ -88,7 +86,13 @@ namespace Ombi.Notifications
|
|||
UserName = req?.RequestedUser?.UserName;
|
||||
}
|
||||
|
||||
AvailableDate = req?.MarkedAsAvailable?.ToString("D") ?? string.Empty;
|
||||
DenyReason = req?.DeniedReason;
|
||||
Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
|
||||
if (pref != null)
|
||||
{
|
||||
UserPreference = pref.Value.HasValue() ? pref.Value : Alias;
|
||||
}
|
||||
Title = title;
|
||||
RequestedDate = req?.RequestedDate.ToString("D");
|
||||
if (Type.IsNullOrEmpty())
|
||||
|
@ -101,22 +105,15 @@ namespace Ombi.Notifications
|
|||
AdditionalInformation = opts?.AdditionalInformation ?? string.Empty;
|
||||
}
|
||||
|
||||
public void SetupNewsletter(CustomizationSettings s, OmbiUser username)
|
||||
public void SetupNewsletter(CustomizationSettings s)
|
||||
{
|
||||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = username.UserName;
|
||||
UserName = username.UserName;
|
||||
Alias = username.Alias.HasValue() ? username.Alias : username.UserName;
|
||||
}
|
||||
|
||||
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s, UserNotificationPreferences pref)
|
||||
{
|
||||
LoadIssues(opts);
|
||||
if (pref != null)
|
||||
{
|
||||
UserPreference = pref.Enabled ? pref.Value : string.Empty;
|
||||
}
|
||||
string title;
|
||||
if (req == null)
|
||||
{
|
||||
|
@ -126,6 +123,7 @@ namespace Ombi.Notifications
|
|||
{
|
||||
title = req?.ParentRequest.Title;
|
||||
}
|
||||
DenyReason = req?.DeniedReason;
|
||||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = req?.RequestedUser?.UserName;
|
||||
|
@ -134,7 +132,12 @@ namespace Ombi.Notifications
|
|||
// Can be set if it's an issue
|
||||
UserName = req?.RequestedUser?.UserName;
|
||||
}
|
||||
AvailableDate = req?.MarkedAsAvailable?.ToString("D") ?? string.Empty;
|
||||
Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName;
|
||||
if (pref != null)
|
||||
{
|
||||
UserPreference = pref.Value.HasValue() ? pref.Value : Alias;
|
||||
}
|
||||
Title = title;
|
||||
RequestedDate = req?.RequestedDate.ToString("D");
|
||||
if (Type.IsNullOrEmpty())
|
||||
|
@ -217,7 +220,6 @@ namespace Ombi.Notifications
|
|||
public string UserName { get; set; }
|
||||
public string IssueUser => UserName;
|
||||
public string Alias { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
public string RequestedDate { get; set; }
|
||||
public string Type { get; set; }
|
||||
|
@ -235,6 +237,8 @@ namespace Ombi.Notifications
|
|||
public string IssueSubject { get; set; }
|
||||
public string NewIssueComment { get; set; }
|
||||
public string UserPreference { get; set; }
|
||||
public string DenyReason { get; set; }
|
||||
public string AvailableDate { get; set; }
|
||||
|
||||
// System Defined
|
||||
private string LongDate => DateTime.Now.ToString("D");
|
||||
|
@ -269,6 +273,8 @@ namespace Ombi.Notifications
|
|||
{nameof(UserName),UserName},
|
||||
{nameof(Alias),Alias},
|
||||
{nameof(UserPreference),UserPreference},
|
||||
{nameof(DenyReason),DenyReason},
|
||||
{nameof(AvailableDate),AvailableDate},
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,16 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -7,6 +7,7 @@ using Ombi.Schedule.Jobs.Emby;
|
|||
using Ombi.Schedule.Jobs.Lidarr;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Schedule.Jobs.Plex;
|
||||
using Ombi.Schedule.Jobs.Plex.Interfaces;
|
||||
using Ombi.Schedule.Jobs.Radarr;
|
||||
using Ombi.Schedule.Jobs.SickRage;
|
||||
using Ombi.Schedule.Jobs.Sonarr;
|
||||
|
@ -21,7 +22,7 @@ namespace Ombi.Schedule
|
|||
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
|
||||
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
|
||||
INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedPlex, ILidarrArtistSync artist,
|
||||
IIssuesPurge purge, IResendFailedRequests resender)
|
||||
IIssuesPurge purge, IResendFailedRequests resender, IMediaDatabaseRefresh dbRefresh)
|
||||
{
|
||||
_plexContentSync = plexContentSync;
|
||||
_radarrSync = radarrSync;
|
||||
|
@ -39,6 +40,7 @@ namespace Ombi.Schedule
|
|||
_lidarrArtistSync = artist;
|
||||
_issuesPurge = purge;
|
||||
_resender = resender;
|
||||
_mediaDatabaseRefresh = dbRefresh;
|
||||
}
|
||||
|
||||
private readonly IPlexContentSync _plexContentSync;
|
||||
|
@ -57,6 +59,7 @@ namespace Ombi.Schedule
|
|||
private readonly ILidarrArtistSync _lidarrArtistSync;
|
||||
private readonly IIssuesPurge _issuesPurge;
|
||||
private readonly IResendFailedRequests _resender;
|
||||
private readonly IMediaDatabaseRefresh _mediaDatabaseRefresh;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
|
@ -80,9 +83,9 @@ namespace Ombi.Schedule
|
|||
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
|
||||
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
|
||||
RecurringJob.AddOrUpdate(() => _resender.Start(), JobSettingsHelper.ResendFailedRequests(s));
|
||||
RecurringJob.AddOrUpdate(() => _mediaDatabaseRefresh.Start(), JobSettingsHelper.MediaDatabaseRefresh(s));
|
||||
}
|
||||
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
{
|
||||
processed++;
|
||||
|
||||
if (ep.LocationType.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (ep.LocationType?.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase) ?? false)
|
||||
{
|
||||
// For some reason Emby is not respecting the `IsVirtualItem` field.
|
||||
continue;
|
||||
|
@ -154,4 +154,4 @@ namespace Ombi.Schedule.Jobs.Emby
|
|||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,18 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
protected virtual void AddBackgroundInsideTable(StringBuilder sb, string url)
|
||||
{
|
||||
sb.Append("<td align=\"center\" valign=\"top\" class=\"media-card\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 12px; vertical-align: top; padding: 3px; width: 502px; min-width: 500px; max-width: 500px; height: 235px; \">");
|
||||
sb.AppendFormat("<table class=\"card-bg\" style=\"background-image: url({0}); border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 500px; background-color: #1f1f1f; background-position: center; background-size: cover; background-repeat: no-repeat; background-clip: padding-box; border: 2px solid rgba(255,118,27,.4); height: 248px; max-height: 500px; \">", url);
|
||||
sb.Append("<td align=\"center\" valign=\"top\" width=\"500\" height=\"252\" class=\"media-card\" style=\"font-size: 14px; font-family: 'Open Sans', Helvetica, Arial, sans-serif; vertical-align: top; padding: 3px; width: 500px; min-width: 500px; max-width: 500px; height: 252px; max-height: 252px; \">");
|
||||
sb.AppendFormat("<table class=\"card-bg\" width=\"500\" height=\"252\" background=\"url(0)\" bgcolor=\"#1f1f1f\" style=\"background-image: url(0); border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 500px; background-color: #1f1f1f; background-position: center; background-size: cover; background-repeat: no-repeat; background-clip: padding-box; border: 2px solid rgba(255, 118, 27, .4); height: 252px; max-height: 252px; \">", url);
|
||||
sb.Append("<tr>");
|
||||
sb.Append("<td>");
|
||||
sb.Append("<table class=\"bg-tint\" style=\"background-color: rgba(0, 0, 0, .6); position: absolute; width: 490px; height: 239px; \">");
|
||||
sb.Append("<td style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
||||
sb.Append("<table class=\"bg-tint\" width=\"100%\" bgcolor=\"rgba(0, 0, 0, .6)\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: rgba(0, 0, 0, .6); \">");
|
||||
}
|
||||
|
||||
protected virtual void AddPosterInsideTable(StringBuilder sb, string url)
|
||||
{
|
||||
sb.Append("<tr>");
|
||||
sb.Append("<td class=\"poster-container\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; width: 150px; min-width: 15px; height: 225px; \">");
|
||||
sb.AppendFormat("<table class=\"poster-img\" style=\"background-image: url({0}); border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: transparent; background-position: center; background-size: cover; background-repeat: no-repeat; background-clip: padding-box; border: 1px solid rgba(255,255,255,.1); \">", url);
|
||||
sb.Append("<td class=\"poster-container\" width=\"150\" height=\"225\" valign=\"top\" style=\"ont-family: sans-serif; font-size: 14px; vertical-align: top; width: 150px; min-width: 150px; height: 225px; max-height: 225px; min-height: 225px; \">");
|
||||
sb.AppendFormat("<table class=\"poster-img\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">", url);
|
||||
}
|
||||
|
||||
protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url)
|
||||
|
@ -27,10 +27,10 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
sb.Append("<tr>");
|
||||
sb.Append(
|
||||
"<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||
"<td style=\"font-family: sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||
sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", mediaurl);
|
||||
sb.AppendFormat(
|
||||
"<img class=\"poster-overlay\" src=\"{0}\" width=\"150\" height=\"225\" style=\"border: none;-ms-interpolation-mode: bicubic; max-width: 100%;display: block; visibility: hidden; \">",
|
||||
"<img class=\"poster-overlay\" src=\"{0}\" width=\"150\" height=\"225\" style=\"border: none; -ms-interpolation-mode: bicubic; max-width: 100%; \">",
|
||||
url);
|
||||
sb.Append("</a>");
|
||||
sb.Append("</td>");
|
||||
|
@ -44,16 +44,16 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
protected virtual void AddInfoTable(StringBuilder sb)
|
||||
{
|
||||
sb.Append(
|
||||
"<td class=\"movie-info\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; padding-left: 4px; text-align: left; height: 227px; \">");
|
||||
"<td class=\"movie-info\" height=\"227\" valign=\"top\" align=\"left\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; padding-left: 4px; text-align: left; height: 227px; \">");
|
||||
sb.Append("<table style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; height: 100%; \">");
|
||||
}
|
||||
|
||||
protected virtual void AddTitle(StringBuilder sb, string url, string title)
|
||||
{
|
||||
sb.Append("<tr>");
|
||||
sb.Append("<td class=\"title\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.9rem; vertical-align: top; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; line-height: 1.2rem; padding: 5px; \">");
|
||||
if(url.HasValue()) sb.AppendFormat("<a href=\"{0}\" target=\"_blank\">", url);
|
||||
sb.AppendFormat("<h1 style=\"white-space: normal; line-height: 1;\" >{0}</h1>", title);
|
||||
sb.Append("<tr class=\"title\" valign=\"top\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 22px;line-height: 24px;vertical-align: top;max-width: 320px;display: block;height: 50px;min-height: 50px;max-height: 50px; \">");
|
||||
sb.Append("<td>");
|
||||
if(url.HasValue()) sb.AppendFormat("<a href=\"{0}\" target=\"_blank\" style=\"text-decoration: none; font-weight: 400; color: #ff761b;\">", url);
|
||||
sb.AppendFormat("<h1 style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 22px;line-height: 24px;vertical-align: top;max-width: 320px;display: block;height: 50px;min-height: 50px;max-height: 50px;\" >{0}</h1>", title);
|
||||
if (url.HasValue()) sb.Append("</a>");
|
||||
sb.Append("</td>");
|
||||
sb.Append("</tr>");
|
||||
|
@ -61,30 +61,30 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
|
||||
protected virtual void AddParagraph(StringBuilder sb, string text)
|
||||
{
|
||||
sb.Append("<tr class=\"description\">");
|
||||
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.75rem; vertical-align: top; padding: 5px; height: 100%; \">");
|
||||
sb.AppendFormat("<p style=\"color: #fff; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: 400; margin: 0; max-width: 325px; text-align: justify; \">{0}</p>", text);
|
||||
sb.Append("<tr class=\"description\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif;height: 130px;max-height: 130px;max-width: 320px;overflow: hidden;text-overflow: ellipsis;display: block;font-size: 14px !important;text-align: justify;\" valign=\"top\">");
|
||||
sb.Append("<td style=\"font-family: sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||
sb.AppendFormat("<p style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: normal; margin: 0; margin-bottom: 15px; \">{0}</p>", text);
|
||||
sb.Append("</td>");
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
|
||||
protected virtual void AddTvParagraph(StringBuilder sb, string episodes, string summary)
|
||||
{
|
||||
sb.Append("<tr class=\"description\">");
|
||||
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 0.75rem; vertical-align: top; padding: 5px; height: 100%; \">");
|
||||
sb.AppendFormat("<p style=\"color: #fff; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: 400; margin: 0; max-width: 325px; margin-bottom: 10px; \">{0}</p>", episodes);
|
||||
sb.AppendFormat("<div style=\"color: #fff; font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: 400; margin: 0; max-width: 325px; overflow: hidden; text-align: justify; \">{0}</div>", summary);
|
||||
sb.Append("<tr class=\"description\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif;height: 130px;max-height: 130px;max-width: 320px;overflow: hidden;text-overflow: ellipsis;display: block;font-size: 14px !important;text-align: justify;\" valign=\"top\">");
|
||||
sb.Append("<td style=\"font-family: sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||
sb.AppendFormat("<p style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-weight: normal; margin: 0; margin-bottom: 15px; \">{0}</p>", episodes);
|
||||
sb.AppendFormat("<div>{0}</div>", summary);
|
||||
sb.Append("</td>");
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
|
||||
protected virtual void AddGenres(StringBuilder sb, string text)
|
||||
{
|
||||
sb.Append("<tr class=\"meta\">");
|
||||
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; max-width: 265px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; \">");
|
||||
sb.AppendFormat("<span style=\"display: inline-block; min-width: 10px; padding: 3px 7px; font-size: 11px; line-height: 1; text-align: center; white-space: nowrap; vertical-align: middle; background-color: rgba(255, 118, 27, 0.5); color: #fff; border-radius: 2px; text-overflow: ellipsis; overflow: hidden; \">{0}</span>", text);
|
||||
sb.Append("<tr class=\"meta\" style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; max-width: 300px; min-width: 300px; padding: 3px 7px; margin-top: 10px; line-height: 1; text-align: left; white-space: nowrap; vertical-align: middle; background-color: rgba(255, 118, 27, 0.5); color: #fff; border-radius: 2px; overflow: hidden; display: block; font-size: 0.9rem;\" align=\"left\" valign=\"middle\" bgcolor=\"rgba(255, 118, 27, 0.5)\">");
|
||||
sb.Append("<td style=\"font-family: sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||
sb.AppendFormat("<span>{0}</span>", text);
|
||||
sb.Append("</td>");
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Plex.Interfaces
|
||||
{
|
||||
public interface IMediaDatabaseRefresh : IBaseJob
|
||||
{
|
||||
Task Start();
|
||||
}
|
||||
}
|
120
src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs
Normal file
120
src/Ombi.Schedule/Jobs/Ombi/MediaDatabaseRefresh.cs
Normal file
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Hangfire;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Ombi.Api.Plex;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Schedule.Jobs.Emby;
|
||||
using Ombi.Schedule.Jobs.Plex.Interfaces;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Plex
|
||||
{
|
||||
public class MediaDatabaseRefresh : IMediaDatabaseRefresh
|
||||
{
|
||||
public MediaDatabaseRefresh(ISettingsService<PlexSettings> s, ILogger<MediaDatabaseRefresh> log, IPlexApi plexApi,
|
||||
IPlexContentRepository plexRepo, IPlexContentSync c, IEmbyContentRepository embyRepo, IEmbyContentSync embySync)
|
||||
{
|
||||
_settings = s;
|
||||
_log = log;
|
||||
_api = plexApi;
|
||||
_plexRepo = plexRepo;
|
||||
_plexContentSync = c;
|
||||
_embyRepo = embyRepo;
|
||||
_embyContentSync = embySync;
|
||||
_settings.ClearCache();
|
||||
}
|
||||
|
||||
private readonly ISettingsService<PlexSettings> _settings;
|
||||
private readonly ILogger _log;
|
||||
private readonly IPlexApi _api;
|
||||
private readonly IPlexContentRepository _plexRepo;
|
||||
private readonly IPlexContentSync _plexContentSync;
|
||||
private readonly IEmbyContentRepository _embyRepo;
|
||||
private readonly IEmbyContentSync _embyContentSync;
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
await RemovePlexData();
|
||||
await RemoveEmbyData();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Media Data Failed");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task RemoveEmbyData()
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = await _settings.GetSettingsAsync();
|
||||
if (!s.Enable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const string episodeSQL = "DELETE FROM EmbyEpisode";
|
||||
const string mainSql = "DELETE FROM EmbyContent";
|
||||
await _embyRepo.ExecuteSql(episodeSQL);
|
||||
await _embyRepo.ExecuteSql(mainSql);
|
||||
|
||||
BackgroundJob.Enqueue(() => _embyContentSync.Start());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Emby Data Failed");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemovePlexData()
|
||||
{
|
||||
try
|
||||
{
|
||||
var s = await _settings.GetSettingsAsync();
|
||||
if (!s.Enable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const string episodeSQL = "DELETE FROM PlexEpisode";
|
||||
const string seasonsSql = "DELETE FROM PlexSeasonsContent";
|
||||
const string mainSql = "DELETE FROM PlexServerContent";
|
||||
await _plexRepo.ExecuteSql(episodeSQL);
|
||||
await _plexRepo.ExecuteSql(seasonsSql);
|
||||
await _plexRepo.ExecuteSql(mainSql);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_log.LogError(LoggingEvents.MediaReferesh, e, "Refreshing Plex Data Failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_plexRepo?.Dispose();
|
||||
_settings?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ using MailKit;
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeKit;
|
||||
using Ombi.Api.Lidarr;
|
||||
using Ombi.Api.Lidarr.Models;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
|
@ -24,6 +25,7 @@ using Ombi.Settings.Settings.Models.External;
|
|||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using ContentType = Ombi.Store.Entities.ContentType;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
|
@ -33,7 +35,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
|
||||
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
|
||||
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log,
|
||||
ILidarrApi lidarrApi, IRepository<LidarrAlbumCache> albumCache, ISettingsService<LidarrSettings> lidarrSettings)
|
||||
ILidarrApi lidarrApi, IRepository<LidarrAlbumCache> albumCache, ISettingsService<LidarrSettings> lidarrSettings,
|
||||
ISettingsService<OmbiSettings> ombiSettings)
|
||||
{
|
||||
_plex = plex;
|
||||
_emby = emby;
|
||||
|
@ -53,6 +56,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
_lidarrApi = lidarrApi;
|
||||
_lidarrAlbumRepository = albumCache;
|
||||
_lidarrSettings = lidarrSettings;
|
||||
_ombiSettings = ombiSettings;
|
||||
_ombiSettings.ClearCache();
|
||||
_lidarrSettings.ClearCache();
|
||||
}
|
||||
|
||||
|
@ -66,6 +71,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
private readonly INotificationTemplatesRepository _templateRepo;
|
||||
private readonly ISettingsService<EmailNotificationSettings> _emailSettings;
|
||||
private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
|
||||
private readonly ISettingsService<OmbiSettings> _ombiSettings;
|
||||
private readonly UserManager<OmbiUser> _userManager;
|
||||
private readonly ILogger _log;
|
||||
private readonly ILidarrApi _lidarrApi;
|
||||
|
@ -162,7 +168,23 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
Email = emails
|
||||
});
|
||||
}
|
||||
var emailTasks = new List<Task>();
|
||||
|
||||
var messageContent = ParseTemplate(template, customization);
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
|
||||
|
||||
var bodyBuilder = new BodyBuilder
|
||||
{
|
||||
HtmlBody = html,
|
||||
};
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = bodyBuilder.ToMessageBody(),
|
||||
Subject = messageContent.Subject
|
||||
};
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
// Get the users to send it to
|
||||
|
@ -170,17 +192,13 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var messageContent = ParseTemplate(template, customization, user);
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
|
||||
|
||||
emailTasks.Add(_email.Send(
|
||||
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email },
|
||||
emailSettings));
|
||||
// BCC the messages
|
||||
message.Bcc.Add(new MailboxAddress(user.Email, user.Email));
|
||||
}
|
||||
|
||||
// Send the email
|
||||
await _email.Send(message, emailSettings);
|
||||
|
||||
// Now add all of this to the Recently Added log
|
||||
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
|
||||
foreach (var p in plexContentMoviesToSend)
|
||||
|
@ -234,7 +252,6 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
});
|
||||
}
|
||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||
await Task.WhenAll(emailTasks.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -245,7 +262,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
continue;
|
||||
}
|
||||
var messageContent = ParseTemplate(template, customization, a);
|
||||
var messageContent = ParseTemplate(template, customization);
|
||||
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
|
@ -305,12 +322,12 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
return itemsToReturn;
|
||||
}
|
||||
|
||||
private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings, OmbiUser username)
|
||||
private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings)
|
||||
{
|
||||
var resolver = new NotificationMessageResolver();
|
||||
var curlys = new NotificationMessageCurlys();
|
||||
|
||||
curlys.SetupNewsletter(settings, username);
|
||||
curlys.SetupNewsletter(settings);
|
||||
|
||||
return resolver.ParseMessage(template, curlys);
|
||||
}
|
||||
|
@ -318,6 +335,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend,
|
||||
HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, HashSet<LidarrAlbumCache> albums, NewsletterSettings settings)
|
||||
{
|
||||
var ombiSettings = await _ombiSettings.GetSettingsAsync();
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var plexMovies = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie);
|
||||
|
@ -331,8 +349,8 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
sb.Append("<td style=\"font-family: 'Open Sans', Helvetica, Arial, sans-serif; font-size: 14px; vertical-align: top; \">");
|
||||
sb.Append("<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; \">");
|
||||
sb.Append("<tr>");
|
||||
await ProcessPlexMovies(plexMovies, sb);
|
||||
await ProcessEmbyMovies(embyMovies, sb);
|
||||
await ProcessPlexMovies(plexMovies, sb, ombiSettings.DefaultLanguageCode);
|
||||
await ProcessEmbyMovies(embyMovies, sb, ombiSettings.DefaultLanguageCode);
|
||||
sb.Append("</tr>");
|
||||
sb.Append("</table>");
|
||||
sb.Append("</td>");
|
||||
|
@ -379,7 +397,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
private async Task ProcessPlexMovies(IQueryable<PlexServerContent> plexContentToSend, StringBuilder sb)
|
||||
private async Task ProcessPlexMovies(IQueryable<PlexServerContent> plexContentToSend, StringBuilder sb, string defaultLanguageCode)
|
||||
{
|
||||
int count = 0;
|
||||
var ordered = plexContentToSend.OrderByDescending(x => x.AddedAt);
|
||||
|
@ -390,7 +408,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
continue;
|
||||
}
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId);
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId, defaultLanguageCode);
|
||||
var mediaurl = content.Url;
|
||||
if (info == null)
|
||||
{
|
||||
|
@ -453,7 +471,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
}
|
||||
|
||||
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
|
||||
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb, string defaultLangaugeCode)
|
||||
{
|
||||
int count = 0;
|
||||
var ordered = embyContent.OrderByDescending(x => x.AddedAt);
|
||||
|
@ -474,7 +492,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
|
||||
var mediaurl = content.Url;
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId));
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId), defaultLangaugeCode);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="1.50.2" />
|
||||
<PackageReference Include="Hangfire" Version="1.6.19" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.19" />
|
||||
<PackageReference Include="Hangfire" Version="1.6.21" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.21" />
|
||||
<PackageReference Include="Hangfire.Console" Version="1.3.10" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
|
||||
<PackageReference Include="Hangfire.SQLite" Version="1.4.2" />
|
||||
<PackageReference Include="Serilog" Version="2.6.0-dev-00892" />
|
||||
<PackageReference Include="Serilog" Version="2.7.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.18.2" />
|
||||
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.6.13" />
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
9
src/Ombi.Settings/Settings/Models/CustomPageSettings.cs
Normal file
9
src/Ombi.Settings/Settings/Models/CustomPageSettings.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ombi.Settings.Settings.Models
|
||||
{
|
||||
public class CustomPageSettings : Settings
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Html { get; set; }
|
||||
public string FontAwesomeIcon { get; set; }
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
public string CustomDonationMessage { get; set; }
|
||||
public string Logo { get; set; }
|
||||
public bool RecentlyAddedPage { get; set; }
|
||||
public bool UseCustomPage { get; set; }
|
||||
|
||||
public void AddToUrl(string part)
|
||||
{
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
public string LidarrArtistSync { get; set; }
|
||||
public string IssuesPurge { get; set; }
|
||||
public string RetryRequests { get; set; }
|
||||
public string MediaDatabaseRefresh { get; set; }
|
||||
}
|
||||
}
|
|
@ -65,7 +65,10 @@ namespace Ombi.Settings.Settings.Models
|
|||
{
|
||||
return Get(s.RetryRequests, Cron.Daily(6));
|
||||
}
|
||||
|
||||
public static string MediaDatabaseRefresh(JobSettings s)
|
||||
{
|
||||
return Get(s.MediaDatabaseRefresh, Cron.DayInterval(5));
|
||||
}
|
||||
private static string Get(string settings, string defaultCron)
|
||||
{
|
||||
return settings.HasValue() ? settings : defaultCron;
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
{
|
||||
public string BaseUrl { get; set; }
|
||||
public bool CollectAnalyticData { get; set; }
|
||||
public bool Set { get; set; }
|
||||
public bool Wizard { get; set; }
|
||||
public string ApiKey { get; set; }
|
||||
public bool IgnoreCertificateErrors { get; set; }
|
||||
public bool DoNotSendNotificationsForAutoApprove { get; set; }
|
||||
public bool HideRequestsUsers { get; set; }
|
||||
|
||||
public string DefaultLanguageCode { get; set; } = "en";
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Ombi.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Ombi.Store.Entities.Requests
|
||||
{
|
||||
|
@ -19,5 +20,14 @@ namespace Ombi.Store.Entities.Requests
|
|||
|
||||
public int RootPathOverride { get; set; }
|
||||
public int QualityOverride { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Only Use for setting the Language Code, Use the LanguageCode property for reading
|
||||
/// </summary>
|
||||
public string LangCode { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
[JsonIgnore]
|
||||
public string LanguageCode => LangCode.IsNullOrEmpty() ? "en" : LangCode;
|
||||
}
|
||||
}
|
||||
|
|
1206
src/Ombi.Store/Migrations/20190104203305_LanguageCode.Designer.cs
generated
Normal file
1206
src/Ombi.Store/Migrations/20190104203305_LanguageCode.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
22
src/Ombi.Store/Migrations/20190104203305_LanguageCode.cs
Normal file
22
src/Ombi.Store/Migrations/20190104203305_LanguageCode.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
public partial class LanguageCode : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "LangCode",
|
||||
table: "MovieRequests",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LangCode",
|
||||
table: "MovieRequests");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ namespace Ombi.Store.Migrations
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.4-rtm-31024");
|
||||
.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
|
@ -530,6 +530,24 @@ namespace Ombi.Store.Migrations
|
|||
b.ToTable("RequestQueue");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("RequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RequestSubscription");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -713,6 +731,8 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("LangCode");
|
||||
|
||||
b.Property<DateTime>("MarkedAsApproved");
|
||||
|
||||
b.Property<DateTime?>("MarkedAsAvailable");
|
||||
|
@ -800,24 +820,6 @@ namespace Ombi.Store.Migrations
|
|||
b.ToTable("TvRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("RequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("RequestSubscription");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -1082,6 +1084,13 @@ namespace Ombi.Store.Migrations
|
|||
.HasForeignKey("PlexServerContentId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
|
@ -1146,13 +1155,6 @@ namespace Ombi.Store.Migrations
|
|||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Ombi.Store.Migrations.Settings
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.1.3-rtm-32065");
|
||||
.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
|
||||
{
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.2.0" />
|
||||
<PackageReference Include="Moq" Version="4.10.0" />
|
||||
<PackageReference Include="Nunit" Version="3.10.1" />
|
||||
<PackageReference Include="NUnit.ConsoleRunner" Version="3.9.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.8.0"></packagereference>
|
||||
<packagereference Include="Microsoft.NET.Test.Sdk" Version="15.9.0"></packagereference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -8,14 +8,14 @@ namespace Ombi.Api.TheMovieDb
|
|||
public interface IMovieDbApi
|
||||
{
|
||||
Task<MovieResponseDto> GetMovieInformation(int movieId);
|
||||
Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId);
|
||||
Task<List<MovieSearchResult>> NowPlaying();
|
||||
Task<List<MovieSearchResult>> PopularMovies();
|
||||
Task<List<MovieSearchResult>> SearchMovie(string searchTerm);
|
||||
Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId, string langCode = "en");
|
||||
Task<List<MovieSearchResult>> NowPlaying(string languageCode);
|
||||
Task<List<MovieSearchResult>> PopularMovies(string languageCode);
|
||||
Task<List<MovieSearchResult>> SearchMovie(string searchTerm, int? year, string languageCode);
|
||||
Task<List<TvSearchResult>> SearchTv(string searchTerm);
|
||||
Task<List<MovieSearchResult>> TopRated();
|
||||
Task<List<MovieSearchResult>> Upcoming();
|
||||
Task<List<MovieSearchResult>> SimilarMovies(int movieId);
|
||||
Task<List<MovieSearchResult>> TopRated(string languageCode);
|
||||
Task<List<MovieSearchResult>> Upcoming(string languageCode);
|
||||
Task<List<MovieSearchResult>> SimilarMovies(int movieId, string langCode);
|
||||
Task<FindResult> Find(string externalId, ExternalSource source);
|
||||
Task<TvExternals> GetTvExternals(int theMovieDbId);
|
||||
Task<TvInfo> GetTVInfo(string themoviedbid);
|
||||
|
|
|
@ -63,68 +63,80 @@ namespace Ombi.Api.TheMovieDb
|
|||
return await Api.Request<TvExternals>(request);
|
||||
}
|
||||
|
||||
public async Task<List<MovieSearchResult>> SimilarMovies(int movieId)
|
||||
public async Task<List<MovieSearchResult>> SimilarMovies(int movieId, string langCode)
|
||||
{
|
||||
var request = new Request($"movie/{movieId}/similar", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langCode);
|
||||
AddRetry(request);
|
||||
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
||||
public async Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId)
|
||||
public async Task<MovieResponseDto> GetMovieInformationWithExtraInfo(int movieId, string langCode = "en")
|
||||
{
|
||||
var request = new Request($"movie/{movieId}", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("append_to_response", "videos,release_dates");
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langCode);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<MovieResponse>(request);
|
||||
return Mapper.Map<MovieResponseDto>(result);
|
||||
}
|
||||
|
||||
public async Task<List<MovieSearchResult>> SearchMovie(string searchTerm)
|
||||
public async Task<List<MovieSearchResult>> SearchMovie(string searchTerm, int? year, string langageCode)
|
||||
{
|
||||
var request = new Request($"search/movie", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
||||
if (year.HasValue && year.Value > 0)
|
||||
{
|
||||
request.FullUri = request.FullUri.AddQueryParameter("year", year.Value.ToString());
|
||||
}
|
||||
AddRetry(request);
|
||||
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
||||
public async Task<List<MovieSearchResult>> PopularMovies()
|
||||
public async Task<List<MovieSearchResult>> PopularMovies(string langageCode)
|
||||
{
|
||||
var request = new Request($"movie/popular", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
||||
public async Task<List<MovieSearchResult>> TopRated()
|
||||
public async Task<List<MovieSearchResult>> TopRated(string langageCode)
|
||||
{
|
||||
var request = new Request($"movie/top_rated", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
||||
public async Task<List<MovieSearchResult>> Upcoming()
|
||||
public async Task<List<MovieSearchResult>> Upcoming(string langageCode)
|
||||
{
|
||||
var request = new Request($"movie/upcoming", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
}
|
||||
|
||||
public async Task<List<MovieSearchResult>> NowPlaying()
|
||||
public async Task<List<MovieSearchResult>> NowPlaying(string langageCode)
|
||||
{
|
||||
var request = new Request($"movie/now_playing", BaseUri, HttpMethod.Get);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken);
|
||||
request.FullUri = request.FullUri.AddQueryParameter("language", langageCode);
|
||||
AddRetry(request);
|
||||
var result = await Api.Request<TheMovieDbContainer<SearchResult>>(request);
|
||||
return Mapper.Map<List<MovieSearchResult>>(result.results);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RuntimeIdentifiers>win10-x64;win10-x86;osx-x64;ubuntu-x64;debian.8-x64;centos.7-x64;linux-x64;linux-arm;linux-arm64;</RuntimeIdentifiers>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<AssemblyVersion>3.0.0.0</AssemblyVersion>
|
||||
<FileVersion>3.0.0.0</FileVersion>
|
||||
<Version></Version>
|
||||
|
@ -12,17 +12,17 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.1.1" />
|
||||
<PackageReference Include="Serilog" Version="2.6.0-dev-00892" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" />
|
||||
<PackageReference Include="Serilog" Version="2.7.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="2.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="3.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.1-dev-00771" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -94,7 +94,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.SickRage", "Ombi.A
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Notifications", "Ombi.Api.Notifications\Ombi.Api.Notifications.csproj", "{10D1FE9D-9124-42B7-B1E1-CEB99B832618}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Lidarr", "Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj", "{4FA21A20-92F4-462C-B929-2C517A88CC56}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Lidarr", "Ombi.Api.Lidarr\Ombi.Api.Lidarr.csproj", "{4FA21A20-92F4-462C-B929-2C517A88CC56}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -250,6 +252,10 @@ Global
|
|||
{4FA21A20-92F4-462C-B929-2C517A88CC56}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4FA21A20-92F4-462C-B929-2C517A88CC56}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4FA21A20-92F4-462C-B929-2C517A88CC56}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -286,6 +292,7 @@ Global
|
|||
{94C9A366-2595-45EA-AABB-8E4A2E90EC5B} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
{10D1FE9D-9124-42B7-B1E1-CEB99B832618} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
{4FA21A20-92F4-462C-B929-2C517A88CC56} = {9293CA11-360A-4C20-A674-B9E794431BF5}
|
||||
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {192E9BF8-00B4-45E4-BCCC-4C215725C869}
|
||||
|
|
|
@ -34,6 +34,14 @@
|
|||
<i class="fa fa-th-list"></i> {{ 'NavigationBar.Requests' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div *ngIf="customizationSettings && customizationSettings.useCustomPage && customPageSettings">
|
||||
<ul class="nav navbar-nav">
|
||||
<li id="RecentlyAdded" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/Custom']">
|
||||
<i class="fa {{customPageSettings.fontAwesomeIcon}}"></i> {{customPageSettings.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="customizationSettings">
|
||||
<ul *ngIf="customizationSettings.recentlyAddedPage" class="nav navbar-nav">
|
||||
<li id="RecentlyAdded" [routerLinkActive]="['active']">
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ILocalUser } from "./auth/IUserLogin";
|
|||
import { IdentityService, NotificationService } from "./services";
|
||||
import { JobService, SettingsService } from "./services";
|
||||
|
||||
import { ICustomizationSettings } from "./interfaces";
|
||||
import { ICustomizationSettings, ICustomPage } from "./interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "ombi",
|
||||
|
@ -17,6 +17,7 @@ import { ICustomizationSettings } from "./interfaces";
|
|||
export class AppComponent implements OnInit {
|
||||
|
||||
public customizationSettings: ICustomizationSettings;
|
||||
public customPageSettings: ICustomPage;
|
||||
public issuesEnabled = false;
|
||||
public user: ILocalUser;
|
||||
public showNav: boolean;
|
||||
|
@ -53,7 +54,18 @@ export class AppComponent implements OnInit {
|
|||
public ngOnInit() {
|
||||
this.user = this.authService.claims();
|
||||
|
||||
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
|
||||
this.settingsService.getCustomization().subscribe(x => {
|
||||
this.customizationSettings = x;
|
||||
if(this.customizationSettings.useCustomPage) {
|
||||
this.settingsService.getCustomPage().subscribe(c => {
|
||||
this.customPageSettings = c;
|
||||
if(!this.customPageSettings.title) {
|
||||
this.customPageSettings.title = "Custom Page";
|
||||
this.customPageSettings.fontAwesomeIcon = "fa-check";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.settingsService.issueEnabled().subscribe(x => this.issuesEnabled = x);
|
||||
this.settingsService.voteEnabled().subscribe(x => this.voteEnabled =x);
|
||||
|
||||
|
|
|
@ -14,13 +14,16 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
|||
import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
|
||||
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
|
||||
import { CookieService } from "ng2-cookies";
|
||||
import { NgxEditorModule } from "ngx-editor";
|
||||
import { GrowlModule } from "primeng/components/growl/growl";
|
||||
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule, DialogModule, OverlayPanelModule, SharedModule, SidebarModule, TooltipModule } from "primeng/primeng";
|
||||
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule, DialogModule, OverlayPanelModule, SharedModule, SidebarModule,
|
||||
TooltipModule } from "primeng/primeng";
|
||||
|
||||
// Components
|
||||
import { AppComponent } from "./app.component";
|
||||
|
||||
import { CookieComponent } from "./auth/cookie.component";
|
||||
import { CustomPageComponent } from "./custompage/custompage.component";
|
||||
import { PageNotFoundComponent } from "./errors/not-found.component";
|
||||
import { LandingPageComponent } from "./landingpage/landingpage.component";
|
||||
import { LoginComponent } from "./login/login.component";
|
||||
|
@ -43,6 +46,7 @@ const routes: Routes = [
|
|||
{ path: "", redirectTo: "/search", pathMatch: "full" },
|
||||
{ path: "login", component: LoginComponent },
|
||||
{ path: "Login/OAuth/:pin", component: LoginOAuthComponent },
|
||||
{ path: "Custom", component: CustomPageComponent },
|
||||
{ path: "login/:landing", component: LoginComponent },
|
||||
{ path: "reset", component: ResetPasswordComponent },
|
||||
{ path: "token", component: TokenResetPasswordComponent },
|
||||
|
@ -88,6 +92,7 @@ export function JwtTokenGetter() {
|
|||
FormsModule,
|
||||
DataTableModule,
|
||||
SharedModule,
|
||||
NgxEditorModule,
|
||||
DialogModule,
|
||||
MatButtonModule,
|
||||
NgbModule.forRoot(),
|
||||
|
@ -121,6 +126,7 @@ export function JwtTokenGetter() {
|
|||
LandingPageComponent,
|
||||
ResetPasswordComponent,
|
||||
TokenResetPasswordComponent,
|
||||
CustomPageComponent,
|
||||
CookieComponent,
|
||||
LoginOAuthComponent,
|
||||
],
|
||||
|
|
31
src/Ombi/ClientApp/app/custompage/custompage.component.html
Normal file
31
src/Ombi/ClientApp/app/custompage/custompage.component.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<div *ngIf="form">
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
||||
<button type="button" *ngIf="isAdmin" class="btn btn-info-outline" (click)="isEditing = !isEditing">Edit</button>
|
||||
<div *ngIf="isEditing">
|
||||
<div class="form-group">
|
||||
<label for="Ip" class="control-label">Page Title
|
||||
|
||||
<i *ngIf="form.get('title').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Title is required"></i>
|
||||
</label>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" formControlName="title" [ngClass]="{'form-error': form.get('title').hasError('required')}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="Ip" class="control-label">Font Awesome Icon
|
||||
|
||||
<i *ngIf="form.get('fontAwesomeIcon').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Font Awesome Icon is required"></i>
|
||||
</label>
|
||||
|
||||
<input type="text" class="form-control form-control-custom " id="fontAwesomeIcon" name="fontAwesomeIcon" formControlName="fontAwesomeIcon" [ngClass]="{'form-error': form.get('fontAwesomeIcon').hasError('required')}">
|
||||
</div>
|
||||
|
||||
<app-ngx-editor [placeholder]="'Enter text here...'" [minHeight]="100" formControlName="html"></app-ngx-editor>
|
||||
<button type="submit" class="btn btn-primary-outline">Save</button>
|
||||
<hr />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div [innerHTML]="form.controls['html'].value"></div>
|
||||
</div>
|
44
src/Ombi/ClientApp/app/custompage/custompage.component.scss
Normal file
44
src/Ombi/ClientApp/app/custompage/custompage.component.scss
Normal file
|
@ -0,0 +1,44 @@
|
|||
$primary-colour: #df691a;
|
||||
$primary-colour-outline: #ff761b;
|
||||
$bg-colour: #333333;
|
||||
$bg-colour-disabled: #252424;
|
||||
|
||||
:host app-ngx-editor /deep/ .ngx-toolbar {
|
||||
background-color: $bg-colour;
|
||||
border: 1px solid $primary-colour-outline;
|
||||
}
|
||||
|
||||
:host app-ngx-editor /deep/ .ngx-toolbar-set {
|
||||
background-color: transparent !important;
|
||||
border: 1px solid $primary-colour-outline !important;
|
||||
}
|
||||
|
||||
:host app-ngx-editor /deep/ .ngx-editor-button {
|
||||
-o-transition: all 0.218s;
|
||||
-moz-transition: all 0.218s;
|
||||
-webkit-transition: all 0.218s;
|
||||
transition: all 0.218s;
|
||||
color: $primary-colour-outline;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
border-color: $primary-colour-outline !important;
|
||||
}
|
||||
|
||||
:host app-ngx-editor /deep/ .ngx-editor-button:hover {
|
||||
color: black;
|
||||
background-color: $primary-colour !important;
|
||||
border-color: $primary-colour-outline !important;
|
||||
}
|
||||
|
||||
:host app-ngx-editor /deep/ .ngx-editor-grippie {
|
||||
background-color: $bg-colour;
|
||||
border: 1px solid $primary-colour-outline;
|
||||
}
|
||||
|
||||
:host app-ngx-editor /deep/ .ngx-editor-textarea {
|
||||
border: 1px solid $primary-colour-outline !important;
|
||||
}
|
||||
|
||||
:host app-ngx-editor /deep/ .ngx-editor-message {
|
||||
display:none !important;
|
||||
}
|
48
src/Ombi/ClientApp/app/custompage/custompage.component.ts
Normal file
48
src/Ombi/ClientApp/app/custompage/custompage.component.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { Component, OnInit, SecurityContext } from "@angular/core";
|
||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { NotificationService, SettingsService } from "../services";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./custompage.component.html",
|
||||
styleUrls: ["./custompage.component.scss"],
|
||||
})
|
||||
export class CustomPageComponent implements OnInit {
|
||||
|
||||
public form: FormGroup;
|
||||
public isEditing: boolean;
|
||||
public isAdmin: boolean;
|
||||
|
||||
constructor(private auth: AuthService, private settings: SettingsService, private fb: FormBuilder,
|
||||
private notificationService: NotificationService,
|
||||
private sanitizer: DomSanitizer) {
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.settings.getCustomPage().subscribe(x => {
|
||||
x.html = this.sanitizer.sanitize(SecurityContext.HTML, this.sanitizer.bypassSecurityTrustHtml(x.html));
|
||||
this.form = this.fb.group({
|
||||
enabled: [x.enabled],
|
||||
title: [x.title, [Validators.required]],
|
||||
html: [x.html, [Validators.required]],
|
||||
fontAwesomeIcon: [x.fontAwesomeIcon, [Validators.required]],
|
||||
});
|
||||
});
|
||||
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
|
||||
}
|
||||
|
||||
public onSubmit() {
|
||||
if (this.form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
this.settings.saveCustomPage(this.form.value).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully saved Custom Page settings");
|
||||
} else {
|
||||
this.notificationService.success("There was an error when saving the Custom Page settings");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -47,10 +47,18 @@ export interface IMovieUpdateModel {
|
|||
id: number;
|
||||
}
|
||||
|
||||
export interface IDenyMovieModel extends IMovieUpdateModel {
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface IAlbumUpdateModel {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface IDenyAlbumModel extends IAlbumUpdateModel {
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface IFullBaseRequest extends IBaseRequest {
|
||||
imdbId: string;
|
||||
overview: string;
|
||||
|
@ -113,6 +121,10 @@ export interface ITvUpdateModel {
|
|||
id: number;
|
||||
}
|
||||
|
||||
export interface ITvDenyModel extends ITvUpdateModel {
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export enum OrderType {
|
||||
RequestedDateAsc = 1,
|
||||
RequestedDateDesc = 2,
|
||||
|
@ -142,6 +154,7 @@ export interface IEpisodesRequests {
|
|||
|
||||
export interface IMovieRequestModel {
|
||||
theMovieDbId: number;
|
||||
languageCode: string | undefined;
|
||||
}
|
||||
|
||||
export interface IFilter {
|
||||
|
|
|
@ -34,6 +34,12 @@
|
|||
background: any;
|
||||
}
|
||||
|
||||
export interface ILanguageRefine {
|
||||
code: string;
|
||||
name: string;
|
||||
nativeName: string;
|
||||
}
|
||||
|
||||
export interface ISearchMovieResultContainer {
|
||||
movies: ISearchMovieResult[];
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ export interface IOmbiSettings extends ISettings {
|
|||
ignoreCertificateErrors: boolean;
|
||||
doNotSendNotificationsForAutoApprove: boolean;
|
||||
hideRequestsUsers: boolean;
|
||||
defaultLanguageCode: string;
|
||||
}
|
||||
|
||||
export interface IUpdateSettings extends ISettings {
|
||||
|
@ -119,6 +120,7 @@ export interface ICustomizationSettings extends ISettings {
|
|||
customDonationUrl: string;
|
||||
customDonationMessage: string;
|
||||
recentlyAddedPage: boolean;
|
||||
useCustomPage: boolean;
|
||||
}
|
||||
|
||||
export interface IJobSettings {
|
||||
|
@ -136,6 +138,7 @@ export interface IJobSettings {
|
|||
lidarrArtistSync: string;
|
||||
issuesPurge: string;
|
||||
retryRequests: string;
|
||||
mediaDatabaseRefresh: string;
|
||||
}
|
||||
|
||||
export interface IIssueSettings extends ISettings {
|
||||
|
@ -157,6 +160,13 @@ export interface IAuthenticationSettings extends ISettings {
|
|||
enableOAuth: boolean;
|
||||
}
|
||||
|
||||
export interface ICustomPage extends ISettings {
|
||||
enabled: boolean;
|
||||
fontAwesomeIcon: string;
|
||||
title: string;
|
||||
html: any;
|
||||
}
|
||||
|
||||
export interface IUserManagementSettings extends ISettings {
|
||||
importPlexUsers: boolean;
|
||||
importPlexAdmin: boolean;
|
||||
|
|
|
@ -37,7 +37,7 @@ export class LandingPageComponent implements OnDestroy, OnInit {
|
|||
});
|
||||
this.timer = setInterval(() => {
|
||||
this.cycleBackground();
|
||||
}, 10000);
|
||||
}, 15000);
|
||||
|
||||
const base = this.location.getBaseHrefFromDOM();
|
||||
if (base.length > 1) {
|
||||
|
|
|
@ -87,7 +87,7 @@ export class LoginComponent implements OnDestroy, OnInit {
|
|||
});
|
||||
this.timer = setInterval(() => {
|
||||
this.cycleBackground();
|
||||
}, 7000);
|
||||
}, 15000);
|
||||
|
||||
const base = this.location.getBaseHrefFromDOM();
|
||||
if (base.length > 1) {
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
</div>
|
||||
<div *ngIf="request.denied" id="requestDenied">
|
||||
{{ 'Requests.Denied' | translate }}
|
||||
<i style="color:red;" class="fa fa-check"></i>
|
||||
<i style="color:red;" class="fa fa-check" pTooltip="{{request.deniedReason}}"></i>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -215,6 +215,14 @@
|
|||
<p-paginator [rows]="10" [totalRecords]="totalMovies" (onPageChange)="paginate($event)"></p-paginator>
|
||||
</div>
|
||||
|
||||
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
|
||||
<span>Please enter a rejection reason, the user will be notified of this:</span>
|
||||
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
|
||||
<p-footer>
|
||||
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
|
||||
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
</p-footer>
|
||||
</p-dialog>
|
||||
|
||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]="issueProviderId"></issue-report>
|
||||
|
|
|
@ -37,9 +37,12 @@ export class MovieRequestsComponent implements OnInit {
|
|||
|
||||
public orderType: OrderType = OrderType.RequestedDateDesc;
|
||||
public OrderType = OrderType;
|
||||
public denyDisplay: boolean;
|
||||
public requestToDeny: IMovieRequests;
|
||||
public rejectionReason: string;
|
||||
|
||||
public totalMovies: number = 100;
|
||||
private currentlyLoaded: number;
|
||||
public currentlyLoaded: number;
|
||||
private amountToLoad: number;
|
||||
|
||||
constructor(
|
||||
|
@ -130,8 +133,22 @@ export class MovieRequestsComponent implements OnInit {
|
|||
}
|
||||
|
||||
public deny(request: IMovieRequests) {
|
||||
request.denied = true;
|
||||
this.denyRequest(request);
|
||||
this.requestToDeny = request;
|
||||
this.denyDisplay = true;
|
||||
}
|
||||
|
||||
public denyRequest() {
|
||||
this.requestService.denyMovie({ id: this.requestToDeny.id, reason: this.rejectionReason })
|
||||
.subscribe(x => {
|
||||
this.denyDisplay = false;
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`Request for ${this.requestToDeny.title} has been denied successfully`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage);
|
||||
this.requestToDeny.denied = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public selectRootFolder(searchResult: IMovieRequests, rootFolderSelected: IRadarrRootFolder, event: any) {
|
||||
|
@ -278,19 +295,6 @@ export class MovieRequestsComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
private denyRequest(request: IMovieRequests) {
|
||||
this.requestService.denyMovie({ id: request.id })
|
||||
.subscribe(x => {
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`Request for ${request.title} has been denied successfully`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage);
|
||||
request.denied = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadInit() {
|
||||
this.requestService.getMovieRequests(this.amountToLoad, 0, this.orderType, this.filter)
|
||||
.subscribe(x => {
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
</div>
|
||||
<div *ngIf="request.denied" id="requestDenied">
|
||||
{{ 'Requests.Denied' | translate }}
|
||||
<i style="color:red;" class="fa fa-check"></i>
|
||||
<i style="color:red;" class="fa fa-check" pTooltip="{{request.deniedReason}}"></i>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -265,4 +265,15 @@
|
|||
|
||||
<button class="btn btn-sm btn-primary-outline" (click)="clearFilter($event)">
|
||||
<i class="fa fa-filter"></i> {{ 'Filter.ClearFilter' | translate }}</button>
|
||||
</p-sidebar>
|
||||
</p-sidebar>
|
||||
|
||||
|
||||
|
||||
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
|
||||
<span>Please enter a rejection reason, the user will be notified of this:</span>
|
||||
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
|
||||
<p-footer>
|
||||
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
|
||||
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
</p-footer>
|
||||
</p-dialog>
|
|
@ -34,9 +34,12 @@ export class MusicRequestsComponent implements OnInit {
|
|||
|
||||
public orderType: OrderType = OrderType.RequestedDateDesc;
|
||||
public OrderType = OrderType;
|
||||
public denyDisplay: boolean;
|
||||
public requestToDeny: IAlbumRequest;
|
||||
public rejectionReason: string;
|
||||
|
||||
public totalAlbums: number = 100;
|
||||
private currentlyLoaded: number;
|
||||
public currentlyLoaded: number;
|
||||
private amountToLoad: number;
|
||||
|
||||
constructor(
|
||||
|
@ -126,23 +129,22 @@ export class MusicRequestsComponent implements OnInit {
|
|||
}
|
||||
|
||||
public deny(request: IAlbumRequest) {
|
||||
request.denied = true;
|
||||
this.denyRequest(request);
|
||||
this.requestToDeny = request;
|
||||
this.denyDisplay = true;
|
||||
}
|
||||
|
||||
// public selectRootFolder(searchResult: IAlbumRequest, rootFolderSelected: IRadarrRootFolder, event: any) {
|
||||
// event.preventDefault();
|
||||
// // searchResult.rootPathOverride = rootFolderSelected.id;
|
||||
// this.setOverride(searchResult);
|
||||
// this.updateRequest(searchResult);
|
||||
// }
|
||||
|
||||
// public selectQualityProfile(searchResult: IMovieRequests, profileSelected: IRadarrProfile, event: any) {
|
||||
// event.preventDefault();
|
||||
// searchResult.qualityOverride = profileSelected.id;
|
||||
// this.setOverride(searchResult);
|
||||
// this.updateRequest(searchResult);
|
||||
// }
|
||||
public denyRequest() {
|
||||
this.requestService.denyAlbum({ id: this.requestToDeny.id, reason: this.rejectionReason })
|
||||
.subscribe(x => {
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`Request for ${this.requestToDeny.title} has been denied successfully`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage);
|
||||
this.requestToDeny.denied = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public reportIssue(catId: IIssueCategory, req: IAlbumRequest) {
|
||||
this.issueRequest = req;
|
||||
|
@ -266,19 +268,6 @@ export class MusicRequestsComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
private denyRequest(request: IAlbumRequest) {
|
||||
this.requestService.denyAlbum({ id: request.id })
|
||||
.subscribe(x => {
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`Request for ${request.title} has been denied successfully`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage);
|
||||
request.denied = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private loadInit() {
|
||||
this.requestService.getAlbumRequests(this.amountToLoad, 0, this.orderType, this.filter)
|
||||
.subscribe(x => {
|
||||
|
|
|
@ -21,7 +21,8 @@
|
|||
<button id="unavailableBtn" *ngIf="child.available" (click)="changeAvailability(child, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}</button>
|
||||
<button id="availableBtn" *ngIf="!child.available" (click)="changeAvailability(child, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}</button>
|
||||
|
||||
<button id="denyBtn" *ngIf="!child.denied" type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}</button>
|
||||
<button id="denyBtn" *ngIf="!child.denied" type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny">
|
||||
<i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}</button>
|
||||
|
||||
</div>
|
||||
<div *ngIf="isAdmin || isRequestUser(child)">
|
||||
|
@ -76,7 +77,9 @@
|
|||
{{ep.airDate | amLocal | amDateFormat: 'L' }}
|
||||
</td>
|
||||
<td>
|
||||
<span *ngIf="child.denied" class="label label-danger" id="deniedLabel" [translate]="'Common.Denied'"></span>
|
||||
<span *ngIf="child.denied" class="label label-danger" id="deniedLabel" [translate]="'Common.Denied'">
|
||||
<i style="color:red;" class="fa fa-check" pTooltip="{{child.deniedReason}}"></i>
|
||||
</span>
|
||||
<span *ngIf="!child.denied && ep.available" class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span>
|
||||
<span *ngIf="!child.denied &&ep.approved && !ep.available" class="label label-info" id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span>
|
||||
<div *ngIf="!child.denied && !ep.approved">
|
||||
|
@ -98,3 +101,12 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p-dialog *ngIf="requestToDeny" header="Deny Request '{{requestToDeny.title}}''" [(visible)]="denyDisplay" [draggable]="false">
|
||||
<span>Please enter a rejection reason, the user will be notified of this:</span>
|
||||
<textarea [(ngModel)]="rejectionReason" class="form-control-custom form-control"></textarea>
|
||||
<p-footer>
|
||||
<button type="button" (click)="denyRequest();" label="Reject" class="btn btn-success">Deny</button>
|
||||
<button type="button"(click)="denyDisplay=false" label="Close" class="btn btn-danger">Close</button>
|
||||
</p-footer>
|
||||
</p-dialog>
|
||||
|
|
|
@ -11,6 +11,10 @@ export class TvRequestChildrenComponent {
|
|||
@Input() public childRequests: IChildRequests[];
|
||||
@Input() public isAdmin: boolean;
|
||||
@Input() public currentUser: string;
|
||||
|
||||
public denyDisplay: boolean;
|
||||
public requestToDeny: IChildRequests;
|
||||
public rejectionReason: string;
|
||||
|
||||
@Output() public requestDeleted = new EventEmitter<number>();
|
||||
|
||||
|
@ -57,20 +61,26 @@ export class TvRequestChildrenComponent {
|
|||
|
||||
public deny(request: IChildRequests) {
|
||||
request.denied = true;
|
||||
this.requestToDeny = request;
|
||||
this.denyDisplay = true;
|
||||
|
||||
request.seasonRequests.forEach((season) => {
|
||||
season.episodes.forEach((ep) => {
|
||||
ep.approved = false;
|
||||
});
|
||||
});
|
||||
this.requestService.denyChild({ id: request.id })
|
||||
}
|
||||
|
||||
public denyRequest() {
|
||||
this.requestService.denyChild({ id: this.requestToDeny.id, reason: this.rejectionReason })
|
||||
.subscribe(x => {
|
||||
this.denyDisplay = false;
|
||||
if (x.result) {
|
||||
this.notificationService.success(
|
||||
`Request has been denied successfully`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage);
|
||||
request.approved = false;
|
||||
this.requestToDeny.approved = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<!-- Movie tab -->
|
||||
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
|
||||
|
||||
<div class="input-group">
|
||||
<div class="input-group search-bar-background">
|
||||
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons"
|
||||
(keyup)="search($event)">
|
||||
<div class="input-group-addon right-radius">
|
||||
<div class="btn-group">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
|
||||
{{ 'Search.Suggestions' | translate }}
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
|
@ -16,10 +16,43 @@
|
|||
<li><a (click)="topRatedMovies()" [translate]="'Search.Movies.TopRatedMovies'"></a></li>
|
||||
<li><a (click)="nowPlayingMovies()" [translate]="'Search.Movies.NowPlayingMovies'"></a></li>
|
||||
</ul>
|
||||
<button class="btn btn-sm btn-primary-outline" (click)="refineOpen()">
|
||||
{{ 'Search.Refine' | translate }}
|
||||
<i class="fa" [ngClass]="{'fa-chevron-down': !refineSearchEnabled, 'fa-chevron-up': refineSearchEnabled}"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<i class="fa fa-search"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Refine search options -->
|
||||
<div class="row top-spacing form-group vcenter" *ngIf="refineSearchEnabled">
|
||||
<div class="col-md-1">
|
||||
<div class="form-group">
|
||||
<label class="control-label">Year</label>
|
||||
|
||||
<input [(ngModel)]="searchYear" class="form-control form-control-custom refine-option">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <label for="name" class="col-xs-2 col-md-1">Language:</label> -->
|
||||
<div class="col-md-2">
|
||||
<div class="form-group">
|
||||
<label for="select" class="control-label">Language</label>
|
||||
<div id="profiles">
|
||||
<select [(ngModel)]="selectedLanguage" class="form-control form-control-custom refine-option"
|
||||
id="select">
|
||||
<option *ngFor="let lang of langauges" value="{{lang.code}}">{{lang.nativeName}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<button class="btn pull-right btn-success-outline" (click)="applyRefinedSearch()">Apply</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<remaining-requests [movie]="true" [quotaRefreshEvents]="movieRequested.asObservable()" #remainingFilms></remaining-requests>
|
||||
|
||||
|
@ -111,11 +144,15 @@
|
|||
|
||||
<br />
|
||||
<div *ngIf="result.available">
|
||||
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> {{'Search.ViewOnPlex' | translate}}</a>
|
||||
<a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> {{'Search.ViewOnEmby' | translate}}</a>
|
||||
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}"
|
||||
target="_blank"><i class="fa fa-eye"></i> {{'Search.ViewOnPlex' | translate}}</a>
|
||||
<a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline"
|
||||
href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> {{'Search.ViewOnEmby' |
|
||||
translate}}</a>
|
||||
</div>
|
||||
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> {{'Requests.ReportIssue' | translate}}
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
|
|
|
@ -6,12 +6,15 @@ import { Subject } from "rxjs";
|
|||
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
|
||||
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces";
|
||||
import { NotificationService, RequestService, SearchService } from "../services";
|
||||
import { IIssueCategory, ILanguageRefine, IRequestEngineResult, ISearchMovieResult } from "../interfaces";
|
||||
import { NotificationService, RequestService, SearchService, SettingsService } from "../services";
|
||||
|
||||
import * as languageData from "../../other/iso-lang.json";
|
||||
|
||||
@Component({
|
||||
selector: "movie-search",
|
||||
templateUrl: "./moviesearch.component.html",
|
||||
styleUrls: ["./search.component.scss"],
|
||||
})
|
||||
export class MovieSearchComponent implements OnInit {
|
||||
|
||||
|
@ -22,6 +25,10 @@ export class MovieSearchComponent implements OnInit {
|
|||
public result: IRequestEngineResult;
|
||||
|
||||
public searchApplied = false;
|
||||
public refineSearchEnabled = false;
|
||||
public searchYear?: number;
|
||||
public selectedLanguage: string;
|
||||
public langauges: ILanguageRefine[];
|
||||
|
||||
@Input() public issueCategories: IIssueCategory[];
|
||||
@Input() public issuesEnabled: boolean;
|
||||
|
@ -36,24 +43,14 @@ export class MovieSearchComponent implements OnInit {
|
|||
private searchService: SearchService, private requestService: RequestService,
|
||||
private notificationService: NotificationService, private authService: AuthService,
|
||||
private readonly translate: TranslateService, private sanitizer: DomSanitizer,
|
||||
private readonly platformLocation: PlatformLocation) {
|
||||
private readonly platformLocation: PlatformLocation, private settingsService: SettingsService) {
|
||||
this.langauges = <ILanguageRefine[]><any>languageData;
|
||||
this.searchChanged.pipe(
|
||||
debounceTime(600), // Wait Xms after the last event before emitting last event
|
||||
distinctUntilChanged(), // only emit if value is different from previous value
|
||||
).subscribe(x => {
|
||||
this.searchText = x as string;
|
||||
if (this.searchText === "") {
|
||||
this.clearResults();
|
||||
return;
|
||||
}
|
||||
this.searchService.searchMovie(this.searchText)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.searchApplied = true;
|
||||
// Now let's load some extra info including IMDB Id
|
||||
// This way the search is fast at displaying results.
|
||||
this.getExtraInfo();
|
||||
});
|
||||
this.runSearch();
|
||||
});
|
||||
this.defaultPoster = "../../../images/default_movie_poster.png";
|
||||
const base = this.platformLocation.getBaseHrefFromDOM();
|
||||
|
@ -70,7 +67,7 @@ export class MovieSearchComponent implements OnInit {
|
|||
result: false,
|
||||
errorMessage: "",
|
||||
};
|
||||
|
||||
this.settingsService.getDefaultLanguage().subscribe(x => this.selectedLanguage = x);
|
||||
this.popularMovies();
|
||||
}
|
||||
|
||||
|
@ -87,7 +84,8 @@ export class MovieSearchComponent implements OnInit {
|
|||
}
|
||||
|
||||
try {
|
||||
this.requestService.requestMovie({ theMovieDbId: searchResult.id })
|
||||
const language = this.selectedLanguage && this.selectedLanguage.length > 0 ? this.selectedLanguage : "en";
|
||||
this.requestService.requestMovie({ theMovieDbId: searchResult.id, languageCode: language })
|
||||
.subscribe(x => {
|
||||
this.result = x;
|
||||
if (this.result.result) {
|
||||
|
@ -161,7 +159,8 @@ export class MovieSearchComponent implements OnInit {
|
|||
|
||||
public similarMovies(theMovieDbId: number) {
|
||||
this.clearResults();
|
||||
this.searchService.similarMovies(theMovieDbId)
|
||||
const lang = this.selectedLanguage && this.selectedLanguage.length > 0 ? this.selectedLanguage : "";
|
||||
this.searchService.similarMovies(theMovieDbId, lang)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.getExtraInfo();
|
||||
|
@ -184,6 +183,17 @@ export class MovieSearchComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
public refineOpen() {
|
||||
this.refineSearchEnabled = !this.refineSearchEnabled;
|
||||
if (!this.refineSearchEnabled) {
|
||||
this.searchYear = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public applyRefinedSearch() {
|
||||
this.runSearch();
|
||||
}
|
||||
|
||||
private getExtraInfo() {
|
||||
|
||||
this.movieResults.forEach((val, index) => {
|
||||
|
@ -194,10 +204,18 @@ export class MovieSearchComponent implements OnInit {
|
|||
}
|
||||
val.background = this.sanitizer.bypassSecurityTrustStyle
|
||||
("url(" + "https://image.tmdb.org/t/p/w1280" + val.backdropPath + ")");
|
||||
this.searchService.getMovieInformation(val.id)
|
||||
|
||||
if (this.applyRefinedSearch) {
|
||||
this.searchService.getMovieInformationWithRefined(val.id, this.selectedLanguage)
|
||||
.subscribe(m => {
|
||||
this.updateItem(val, m);
|
||||
});
|
||||
} else {
|
||||
this.searchService.getMovieInformation(val.id)
|
||||
.subscribe(m => {
|
||||
this.updateItem(val, m);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -214,4 +232,30 @@ export class MovieSearchComponent implements OnInit {
|
|||
this.movieResults = [];
|
||||
this.searchApplied = false;
|
||||
}
|
||||
|
||||
private runSearch() {
|
||||
if (this.searchText === "") {
|
||||
this.clearResults();
|
||||
return;
|
||||
}
|
||||
if (this.refineOpen) {
|
||||
this.searchService.searchMovieWithRefined(this.searchText, this.searchYear, this.selectedLanguage)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.searchApplied = true;
|
||||
// Now let's load some extra info including IMDB Id
|
||||
// This way the search is fast at displaying results.
|
||||
this.getExtraInfo();
|
||||
});
|
||||
} else {
|
||||
this.searchService.searchMovie(this.searchText)
|
||||
.subscribe(x => {
|
||||
this.movieResults = x;
|
||||
this.searchApplied = true;
|
||||
// Now let's load some extra info including IMDB Id
|
||||
// This way the search is fast at displaying results.
|
||||
this.getExtraInfo();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ export class MovieSearchGridComponent implements OnInit {
|
|||
}
|
||||
|
||||
try {
|
||||
this.requestService.requestMovie({ theMovieDbId: searchResult.id })
|
||||
this.requestService.requestMovie({ theMovieDbId: searchResult.id, languageCode: "en" })
|
||||
.subscribe(x => {
|
||||
this.result = x;
|
||||
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { Subject } from "rxjs";
|
||||
import { debounceTime, distinctUntilChanged } from "rxjs/operators";
|
||||
|
||||
import { AuthService } from "../../auth/auth.service";
|
||||
import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../../interfaces";
|
||||
import { IIssueCategory, IRequestEngineResult } from "../../interfaces";
|
||||
import { ISearchAlbumResult, ISearchArtistResult } from "../../interfaces/ISearchMusicResult";
|
||||
import { NotificationService, RequestService, SearchService } from "../../services";
|
||||
import { SearchService } from "../../services";
|
||||
|
||||
@Component({
|
||||
selector: "music-search",
|
||||
|
@ -35,10 +32,8 @@ export class MusicSearchComponent implements OnInit {
|
|||
public defaultPoster: string;
|
||||
|
||||
constructor(
|
||||
private searchService: SearchService, private requestService: RequestService,
|
||||
private notificationService: NotificationService, private authService: AuthService,
|
||||
private readonly translate: TranslateService, private sanitizer: DomSanitizer,
|
||||
private readonly platformLocation: PlatformLocation) {
|
||||
private searchService: SearchService, private sanitizer: DomSanitizer,
|
||||
private platformLocation: PlatformLocation) {
|
||||
|
||||
this.searchChanged.pipe(
|
||||
debounceTime(600), // Wait Xms after the last event before emitting last event
|
||||
|
@ -110,45 +105,6 @@ export class MusicSearchComponent implements OnInit {
|
|||
this.searchChanged.next(`lidarr:${artistId}`);
|
||||
}
|
||||
|
||||
public request(searchResult: ISearchMovieResult) {
|
||||
searchResult.requested = true;
|
||||
searchResult.requestProcessing = true;
|
||||
searchResult.showSubscribe = false;
|
||||
if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) {
|
||||
searchResult.approved = true;
|
||||
}
|
||||
|
||||
try {
|
||||
this.requestService.requestMovie({ theMovieDbId: searchResult.id })
|
||||
.subscribe(x => {
|
||||
this.result = x;
|
||||
|
||||
if (this.result.result) {
|
||||
this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => {
|
||||
this.notificationService.success(x);
|
||||
searchResult.processed = true;
|
||||
});
|
||||
} else {
|
||||
if (this.result.errorMessage && this.result.message) {
|
||||
this.notificationService.warning("Request Added", `${this.result.message} - ${this.result.errorMessage}`);
|
||||
} else {
|
||||
this.notificationService.warning("Request Added", this.result.message ? this.result.message : this.result.errorMessage);
|
||||
}
|
||||
searchResult.requested = false;
|
||||
searchResult.approved = false;
|
||||
searchResult.processed = false;
|
||||
searchResult.requestProcessing = false;
|
||||
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
searchResult.processed = false;
|
||||
searchResult.requestProcessing = false;
|
||||
this.notificationService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public viewAlbumsForArtist(albums: ISearchAlbumResult[]) {
|
||||
this.clearArtistResults();
|
||||
this.searchAlbum = true;
|
||||
|
|
30
src/Ombi/ClientApp/app/search/search.component.scss
Normal file
30
src/Ombi/ClientApp/app/search/search.component.scss
Normal file
|
@ -0,0 +1,30 @@
|
|||
@media (max-width: 978px) {
|
||||
.top-spacing {
|
||||
padding-top: 5%
|
||||
}
|
||||
.form-control-search {
|
||||
width: 77%;
|
||||
}
|
||||
|
||||
}
|
||||
@media (min-width: 979px) {
|
||||
.top-spacing {
|
||||
padding-top: 2%
|
||||
}
|
||||
.form-control-search {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar-background {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.vcenter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.refine-option {
|
||||
box-shadow: inset 0 1px 5px rgba(0,0,0,1.0);
|
||||
}
|
|
@ -5,8 +5,8 @@ import { HttpClient } from "@angular/common/http";
|
|||
import { Observable } from "rxjs";
|
||||
|
||||
import { TreeNode } from "primeng/primeng";
|
||||
import { FilterType, IAlbumRequest, IAlbumRequestModel, IAlbumUpdateModel, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests,
|
||||
IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces";
|
||||
import { FilterType, IAlbumRequest, IAlbumRequestModel, IAlbumUpdateModel, IChildRequests, IDenyAlbumModel, IDenyMovieModel, IFilter,
|
||||
IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvDenyModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces";
|
||||
import { ITvRequestViewModel } from "../interfaces";
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
|
||||
|
@ -50,7 +50,7 @@ export class RequestService extends ServiceHelpers {
|
|||
return this.http.post<IRequestEngineResult>(`${this.url}Movie/Approve`, JSON.stringify(movie), {headers: this.headers});
|
||||
}
|
||||
|
||||
public denyMovie(movie: IMovieUpdateModel): Observable<IRequestEngineResult> {
|
||||
public denyMovie(movie: IDenyMovieModel): Observable<IRequestEngineResult> {
|
||||
return this.http.put<IRequestEngineResult>(`${this.url}Movie/Deny`, JSON.stringify(movie), {headers: this.headers});
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ export class RequestService extends ServiceHelpers {
|
|||
return this.http.put<IChildRequests>(`${this.url}tv/child`, JSON.stringify(child), {headers: this.headers});
|
||||
}
|
||||
|
||||
public denyChild(child: ITvUpdateModel): Observable<IRequestEngineResult> {
|
||||
public denyChild(child: ITvDenyModel): Observable<IRequestEngineResult> {
|
||||
return this.http.put<IRequestEngineResult>(`${this.url}tv/deny`, JSON.stringify(child), {headers: this.headers});
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ export class RequestService extends ServiceHelpers {
|
|||
return this.http.post<IRequestEngineResult>(`${this.url}music/Approve`, JSON.stringify(Album), {headers: this.headers});
|
||||
}
|
||||
|
||||
public denyAlbum(Album: IAlbumUpdateModel): Observable<IRequestEngineResult> {
|
||||
public denyAlbum(Album: IDenyAlbumModel): Observable<IRequestEngineResult> {
|
||||
return this.http.put<IRequestEngineResult>(`${this.url}music/Deny`, JSON.stringify(Album), {headers: this.headers});
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,15 @@ export class SearchService extends ServiceHelpers {
|
|||
|
||||
// Movies
|
||||
public searchMovie(searchTerm: string): Observable<ISearchMovieResult[]> {
|
||||
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/` + searchTerm);
|
||||
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/${searchTerm}`);
|
||||
}
|
||||
public similarMovies(theMovieDbId: number): Observable<ISearchMovieResult[]> {
|
||||
return this.http.get<ISearchMovieResult[]>(`${this.url}/Movie/${theMovieDbId}/similar`);
|
||||
|
||||
public searchMovieWithRefined(searchTerm: string, year: number | undefined, langCode: string): Observable<ISearchMovieResult[]> {
|
||||
return this.http.post<ISearchMovieResult[]>(`${this.url}/Movie/`, { searchTerm, year, languageCode: langCode });
|
||||
}
|
||||
|
||||
public similarMovies(theMovieDbId: number, langCode: string): Observable<ISearchMovieResult[]> {
|
||||
return this.http.post<ISearchMovieResult[]>(`${this.url}/Movie/similar`, {theMovieDbId, languageCode: langCode});
|
||||
}
|
||||
|
||||
public popularMovies(): Observable<ISearchMovieResult[]> {
|
||||
|
@ -40,34 +45,38 @@ export class SearchService extends ServiceHelpers {
|
|||
return this.http.get<ISearchMovieResult>(`${this.url}/Movie/info/${theMovieDbId}`);
|
||||
}
|
||||
|
||||
public getMovieInformationWithRefined(theMovieDbId: number, langCode: string): Observable<ISearchMovieResult> {
|
||||
return this.http.post<ISearchMovieResult>(`${this.url}/Movie/info`, { theMovieDbId, languageCode: langCode });
|
||||
}
|
||||
|
||||
// TV
|
||||
public searchTv(searchTerm: string): Observable<ISearchTvResult[]> {
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/${searchTerm}`, {headers: this.headers});
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/${searchTerm}`, { headers: this.headers });
|
||||
}
|
||||
|
||||
public searchTvTreeNode(searchTerm: string): Observable<TreeNode[]> {
|
||||
return this.http.get<TreeNode[]>(`${this.url}/Tv/${searchTerm}/tree`, {headers: this.headers});
|
||||
return this.http.get<TreeNode[]>(`${this.url}/Tv/${searchTerm}/tree`, { headers: this.headers });
|
||||
}
|
||||
|
||||
public getShowInformationTreeNode(theTvDbId: number): Observable<TreeNode> {
|
||||
return this.http.get<TreeNode>(`${this.url}/Tv/info/${theTvDbId}/Tree`, {headers: this.headers});
|
||||
return this.http.get<TreeNode>(`${this.url}/Tv/info/${theTvDbId}/Tree`, { headers: this.headers });
|
||||
}
|
||||
|
||||
public getShowInformation(theTvDbId: number): Observable<ISearchTvResult> {
|
||||
return this.http.get<ISearchTvResult>(`${this.url}/Tv/info/${theTvDbId}`, {headers: this.headers});
|
||||
return this.http.get<ISearchTvResult>(`${this.url}/Tv/info/${theTvDbId}`, { headers: this.headers });
|
||||
}
|
||||
|
||||
public popularTv(): Observable<ISearchTvResult[]> {
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/popular`, {headers: this.headers});
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/popular`, { headers: this.headers });
|
||||
}
|
||||
public mostWatchedTv(): Observable<ISearchTvResult[]> {
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/mostwatched`, {headers: this.headers});
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/mostwatched`, { headers: this.headers });
|
||||
}
|
||||
public anticipatedTv(): Observable<ISearchTvResult[]> {
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/anticipated`, {headers: this.headers});
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/anticipated`, { headers: this.headers });
|
||||
}
|
||||
public trendingTv(): Observable<ISearchTvResult[]> {
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/trending`, {headers: this.headers});
|
||||
return this.http.get<ISearchTvResult[]>(`${this.url}/Tv/trending`, { headers: this.headers });
|
||||
}
|
||||
// Music
|
||||
public searchArtist(searchTerm: string): Observable<ISearchArtistResult[]> {
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
ICronTestModel,
|
||||
ICronViewModelBody,
|
||||
ICustomizationSettings,
|
||||
ICustomPage,
|
||||
IDiscordNotifcationSettings,
|
||||
IDogNzbSettings,
|
||||
IEmailNotificationSettings,
|
||||
|
@ -52,6 +53,10 @@ export class SettingsService extends ServiceHelpers {
|
|||
return this.http.get<IOmbiSettings>(`${this.url}/Ombi/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getDefaultLanguage(): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}/defaultlanguage/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public saveOmbi(settings: IOmbiSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}/Ombi/`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
@ -108,6 +113,14 @@ export class SettingsService extends ServiceHelpers {
|
|||
return this.http.get<IAuthenticationSettings>(`${this.url}/Authentication`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getCustomPage(): Observable<ICustomPage> {
|
||||
return this.http.get<ICustomPage>(`${this.url}/CustomPage`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public saveCustomPage(model: ICustomPage): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}/CustomPage`, model, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getClientId(): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}/clientid`, {headers: this.headers});
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
<div class="form-group">
|
||||
<label for="applicationurl" class="control-label">Application URL</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="settings.applicationUrl" class="form-control form-control-custom " id="applicationurl" name="applicationurl"
|
||||
placeholder="http://ombi.io/" value="{{settings.applicationUrl}}">
|
||||
<input type="text" [(ngModel)]="settings.applicationUrl" class="form-control form-control-custom " id="applicationurl"
|
||||
name="applicationurl" placeholder="http://ombi.io/" value="{{settings.applicationUrl}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -36,8 +36,8 @@
|
|||
<div class="form-group">
|
||||
<label for="logo" class="control-label">Custom Logo</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="settings.logo" class="form-control form-control-custom " id="logo" name="logo" value="{{settings.logo}}"
|
||||
tooltipPosition="top" pTooltip="Use a URL e.g. www.google.com/logo.png">
|
||||
<input type="text" [(ngModel)]="settings.logo" class="form-control form-control-custom " id="logo" name="logo"
|
||||
value="{{settings.logo}}" tooltipPosition="top" pTooltip="Use a URL e.g. www.google.com/logo.png">
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="settings.logo" class="form-group">
|
||||
|
@ -50,14 +50,16 @@
|
|||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enableCustomDonations" name="enableCustomDonations" [(ngModel)]="settings.enableCustomDonations">
|
||||
<label for="enableCustomDonations" tooltipPosition="top" pTooltip="Enable to show a custom donation link in the navigation bar">Enable custom donation link</label>
|
||||
<label for="enableCustomDonations" tooltipPosition="top" pTooltip="Enable to show a custom donation link in the navigation bar">Enable
|
||||
custom donation link</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="settings.enableCustomDonations">
|
||||
<label for="customDonation" class="control-label">Custom Donation URL</label>
|
||||
<div>
|
||||
<input [disabled]="!settings.enableCustomDonations" type="text" [(ngModel)]="settings.customDonationUrl" class="form-control form-control-custom " name="customDonation" value="{{settings.customDonationUrl}}"
|
||||
<input [disabled]="!settings.enableCustomDonations" type="text" [(ngModel)]="settings.customDonationUrl"
|
||||
class="form-control form-control-custom " name="customDonation" value="{{settings.customDonationUrl}}"
|
||||
tooltipPosition="top" pTooltip="A link to a Paypal address, or your custom donation url.">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -65,10 +67,19 @@
|
|||
<div class="form-group" *ngIf="settings.enableCustomDonations">
|
||||
<label for="customDonationMessage" class="control-label">Donation Button Message</label>
|
||||
<div>
|
||||
<input [disabled]="!settings.enableCustomDonations" type="text" [(ngModel)]="settings.customDonationMessage" class="form-control form-control-custom " name="customDonationMessage" value="{{settings.customDonationMessage}}"
|
||||
<input [disabled]="!settings.enableCustomDonations" type="text" [(ngModel)]="settings.customDonationMessage"
|
||||
class="form-control form-control-custom " name="customDonationMessage" value="{{settings.customDonationMessage}}"
|
||||
tooltipPosition="top" pTooltip="Set a custom message to be displayed in the navigation bar.">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="useCustomPage" name="useCustomPage" [(ngModel)]="settings.useCustomPage">
|
||||
<label for="useCustomPage" tooltipPosition="top" pTooltip="Enabled a custom page where you can fully edit">Use
|
||||
Custom Page</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
@ -85,9 +96,8 @@
|
|||
<label for="customCss" class="control-label">Custom CSS</label>
|
||||
</div>
|
||||
<div class="form-group language-css" pCode>
|
||||
<textarea rows="25" type="text"
|
||||
pTooltip="Enter your custom styles here" tooltipPosition="top"
|
||||
class="form-control-custom form-control " id="themeContent" name="themeContent" [(ngModel)]="settings.customCss"> {{settings.customCss}} </textarea>
|
||||
<textarea rows="25" type="text" pTooltip="Enter your custom styles here" tooltipPosition="top" class="form-control-custom form-control "
|
||||
id="themeContent" name="themeContent" [(ngModel)]="settings.customCss"> {{settings.customCss}} </textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -105,6 +105,13 @@
|
|||
<small *ngIf="form.get('issuesPurge').hasError('required')" class="error-text">The Issues Purge is required</small>
|
||||
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('issuesPurge')?.value)">Test</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="userImporter" class="control-label">Media Data Refresh</label>
|
||||
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('mediaDatabaseRefresh').hasError('required')}" id="mediaDatabaseRefresh" name="mediaDatabaseRefresh" formControlName="mediaDatabaseRefresh">
|
||||
<small *ngIf="form.get('mediaDatabaseRefresh').hasError('required')" class="error-text">The Media Database Refresh is required</small>
|
||||
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('mediaDatabaseRefresh')?.value)">Test</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
|
|
|
@ -37,6 +37,7 @@ export class JobsComponent implements OnInit {
|
|||
lidarrArtistSync: [x.lidarrArtistSync, Validators.required],
|
||||
issuesPurge: [x.issuesPurge, Validators.required],
|
||||
retryRequests: [x.retryRequests, Validators.required],
|
||||
mediaDatabaseRefresh: [x.mediaDatabaseRefresh, Validators.required],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -22,64 +22,80 @@
|
|||
</div>
|
||||
</div>-->
|
||||
<div class="col-md-6">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="baseUrl" class="control-label">Base Url</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom" id="baseUrl" name="baseUrl" formControlName="baseUrl">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ApiKey" class="control-label">Api Key</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control form-control-custom" id="ApiKey" name="ApiKey" formControlName="apiKey" readonly="readonly" #apiKey>
|
||||
|
||||
<div class="input-group-addon">
|
||||
<div (click)="refreshApiKey()" id="refreshKey" class="fa fa-refresh" title="Reset API Key" pTooltip="This will invalidate the old API key" ></div>
|
||||
</div>
|
||||
|
||||
<div class="input-group-addon">
|
||||
<div ngxClipboard [ngxClipboard]="apiKey" class="fa fa-clipboard" (cbOnSuccess)="successfullyCopied()"></div>
|
||||
<div class="form-group">
|
||||
<label for="baseUrl" class="control-label">Base Url</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom" id="baseUrl" name="baseUrl"
|
||||
formControlName="baseUrl">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="doNotSendNotificationsForAutoApprove" name="doNotSendNotificationsForAutoApprove" formControlName="doNotSendNotificationsForAutoApprove">
|
||||
<label for="doNotSendNotificationsForAutoApprove">Do not send Notifications if a User has the Auto Approve permission</label>
|
||||
<div class="form-group">
|
||||
<label for="ApiKey" class="control-label">Api Key</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control form-control-custom" id="ApiKey" name="ApiKey"
|
||||
formControlName="apiKey" readonly="readonly" #apiKey>
|
||||
|
||||
<div class="input-group-addon">
|
||||
<div (click)="refreshApiKey()" id="refreshKey" class="fa fa-refresh" title="Reset API Key"
|
||||
pTooltip="This will invalidate the old API key"></div>
|
||||
</div>
|
||||
|
||||
<div class="input-group-addon">
|
||||
<div ngxClipboard [ngxClipboard]="apiKey" class="fa fa-clipboard" (cbOnSuccess)="successfullyCopied()"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="hideRequestsUsers" name="hideRequestsUsers" formControlName="hideRequestsUsers">
|
||||
<label for="hideRequestsUsers">Hide requests from other users</label>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="doNotSendNotificationsForAutoApprove" name="doNotSendNotificationsForAutoApprove"
|
||||
formControlName="doNotSendNotificationsForAutoApprove">
|
||||
<label for="doNotSendNotificationsForAutoApprove">Do not send Notifications if a User has the Auto
|
||||
Approve permission</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="ignoreCertificateErrors" name="ignoreCertificateErrors" formControlName="ignoreCertificateErrors">
|
||||
<label for="ignoreCertificateErrors" tooltipPosition="top" pTooltip="Enable if you are having connectivity problems over SSL">Ignore any certificate errors</label>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="hideRequestsUsers" name="hideRequestsUsers" formControlName="hideRequestsUsers">
|
||||
<label for="hideRequestsUsers">Hide requests from other users</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="CollectAnalyticData" name="CollectAnalyticData" formControlName="collectAnalyticData">
|
||||
<label for="CollectAnalyticData">Allow us to collect anonymous analytical data e.g. browser used</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="ignoreCertificateErrors" name="ignoreCertificateErrors" formControlName="ignoreCertificateErrors">
|
||||
<label for="ignoreCertificateErrors" tooltipPosition="top" pTooltip="Enable if you are having connectivity problems over SSL">Ignore
|
||||
any certificate errors</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="CollectAnalyticData" name="CollectAnalyticData" formControlName="collectAnalyticData">
|
||||
<label for="CollectAnalyticData" tooltipPosition="top" pTooltip="This will allow us to have a better understanding of the userbase so we know what we should be supporting">Allow
|
||||
us to collect anonymous analytical data e.g. browser used</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="langauges">
|
||||
<label for="select" class="control-label">Language</label>
|
||||
<div id="profiles">
|
||||
<select formControlName="defaultLanguageCode" class="form-control form-control-custom" id="select">
|
||||
<option *ngFor="let lang of langauges" value="{{lang.code}}">{{lang.nativeName}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
</fieldset>
|
|
@ -1,16 +1,19 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup } from "@angular/forms";
|
||||
|
||||
import { IOmbiSettings } from "../../interfaces";
|
||||
import { ILanguageRefine, IOmbiSettings } from "../../interfaces";
|
||||
import { NotificationService } from "../../services";
|
||||
import { SettingsService } from "../../services";
|
||||
|
||||
import * as languageData from "../../../other/iso-lang.json";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./ombi.component.html",
|
||||
})
|
||||
export class OmbiComponent implements OnInit {
|
||||
|
||||
public form: FormGroup;
|
||||
public langauges: ILanguageRefine[];
|
||||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
|
@ -25,8 +28,10 @@ export class OmbiComponent implements OnInit {
|
|||
baseUrl: [x.baseUrl],
|
||||
doNotSendNotificationsForAutoApprove: [x.doNotSendNotificationsForAutoApprove],
|
||||
hideRequestsUsers: [x.hideRequestsUsers],
|
||||
defaultLanguageCode: [x.defaultLanguageCode],
|
||||
});
|
||||
});
|
||||
this.langauges = <ILanguageRefine[]><any>languageData;
|
||||
}
|
||||
|
||||
public refreshApiKey() {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue