1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-18 21:35:51 -04:00

Compare commits

...

139 Commits

Author SHA1 Message Date
Devin Buhl 9522bf3095 Merge pull request #532 from Radarr/patch/add-import-from-list-back
add importfromlist abck
2017-01-30 17:30:02 -05:00
Devin Buhl c3c7387390 add importfromlist abck 2017-01-30 17:21:56 -05:00
Devin Buhl 09fb58c3e9 Merge pull request #531 from Radarr/patch/fix-filter-movie-list
Fix the filter modes on the movie list xD
2017-01-30 17:07:24 -05:00
Devin Buhl 3d33e630ec Fix the filter modes on the movie list xD 2017-01-30 17:00:52 -05:00
Devin Buhl b51916fb2b Merge pull request #530 from Radarr/patch/ptp-updates
updates to ptp, and using caching for cookie
2017-01-30 16:46:14 -05:00
Devin Buhl c9ee92cc30 updates to ptp, and using caching for cookie 2017-01-30 16:35:12 -05:00
Leonardo Galli 0e81416c2f Fix issues with different languages than english when adding alternative titles.
Fixes #509
2017-01-30 17:29:42 +01:00
Devin Buhl 6cd7d46208 Merge pull request #528 from Radarr/patch/passthepopcorn-username-etc
Use username, password and passkey for passthepopcorn
2017-01-30 01:54:09 -05:00
Devin Buhl 6af6da16b4 Use username, password and passkey for passthepopcorn 2017-01-30 01:48:28 -05:00
Devin Buhl 4c5900373d Merge pull request #517 from Radarr/patch/update-qualities
Patch/update qualities
2017-01-29 16:14:54 -05:00
Tim Turner 2fd167c446 Merged branch patch/update-qualities into patch/update-qualities 2017-01-29 16:08:18 -05:00
Tim Turner 31149aeed9 Ensure qualities don't overflow profile card 2017-01-29 16:08:14 -05:00
Devin Buhl 977b87cbf3 migration migraine-tion 2017-01-29 16:07:30 -05:00
Tim Turner 42ed4ff48a Update Fetch List button style
Let's make it not look sad and disabled
2017-01-29 15:53:30 -05:00
Devin Buhl 060c71f439 migration 2017-01-29 15:49:32 -05:00
Devin Buhl 63527858e7 Merge branch 'develop' into patch/update-qualities 2017-01-29 15:47:26 -05:00
Devin Buhl 2f4ea9cac7 migration 2017-01-29 15:47:08 -05:00
Devin Buhl a1f2369306 Update the regex in Parser, Add workprint and telesync, change R5 to regional allow for R[0-9]{1}, changed the weights 2017-01-29 14:22:04 -05:00
Tim Turner a35c5f79c1 Set Drone Factory Interval default to 0 (#515)
Fixes #447
2017-01-29 14:09:54 -05:00
Devin Buhl d382e15749 Merge pull request #504 from Radarr/patch/trakt-integrations
Added options for watched, and watchlist, and customlist to trakt
2017-01-28 21:27:51 -05:00
Devin Buhl 5b7d513986 Added options for watched, and watchlist, and customlist to trakt 2017-01-28 21:17:55 -05:00
Devin Buhl be083cfd53 Merge pull request #500 from Radarr/patch/net-import-changes
Patch/net import changes
2017-01-28 17:35:09 -05:00
Devin Buhl f9844c284a Merge pull request #487 from mitchellcash/port_validation
Fixed: Proper port validation for download clients and connections
2017-01-28 17:32:49 -05:00
Devin Buhl e8065d07b3 Make year nullable for trakt 2017-01-28 17:27:53 -05:00
Devin Buhl 8a0820ad1e make year nullable, and rmember the profileid 2017-01-28 17:14:51 -05:00
Devin Buhl 8274cc016f Merge pull request #499 from Radarr/patch/hdbits
Update HDBits to work with Radarr
2017-01-28 16:52:36 -05:00
Devin Buhl b88281b458 Update HDBits to work with Radarr 2017-01-28 16:46:54 -05:00
Devin Buhl a1cb5eb420 Merge pull request #497 from Radarr/feature/net-import
Feature/net import
2017-01-28 15:39:08 -05:00
Devin Buhl d458c4ecc8 update taskscheduler when config is saved with netimportsynccommand 2017-01-28 15:30:46 -05:00
Devin Buhl b6e4f53597 Make NetImport sync interval work (needs some testing) 2017-01-28 14:59:21 -05:00
Leonardo Galli 4abbf55ee4 Merge Develop into Net-Import 2017-01-28 20:32:57 +01:00
Tim Turner 1fd909cff6 Net Import UI Updates
- Change name to "StevenLu" to fix
- Hide header on New List modal
- Show "Import Selected" only after a list has been fetched
2017-01-28 13:27:54 -05:00
Tim Turner 7ca53d1073 Only show "Display Existing Movies" toggle after selecting a folder 2017-01-28 12:16:50 -05:00
vertigo235 2bda0f700c Update Synology Indexer For Movies (#486) 2017-01-28 18:13:10 +01:00
vertigo235 16b2cc49d4 Allow Duplicate Preferred Words (#484)
Allows you to weigh certain words more heavily. 

ex. This, This, That = This > That
2017-01-28 18:13:01 +01:00
Tim Turner e3e67d1098 Clean up settings UI 2017-01-28 12:03:45 -05:00
Leonardo Galli 3276e3ec52 Fix for movies without an imdbid. Fixes 176 2017-01-28 17:54:19 +01:00
Leonardo Galli 5e9a79afe8 Quality of an existing movie file can now be edited. 2017-01-28 17:35:51 +01:00
Leonardo Galli c6912a193c Fix recognition of 4k Movies upon import. 2017-01-28 16:24:21 +01:00
Leonardo Galli 47083e6be1 This should hopefully fix the error that decisions were not ordered correctly and therefore just the first release was grabbed. 2017-01-28 16:18:44 +01:00
Leonardo Galli ca78cc5c3c Remove confusing warning about file not being loaded. 2017-01-28 16:01:30 +01:00
Tim Turner 76db305ffa Add movie year to NotificationService (#496) 2017-01-28 09:42:37 -05:00
Tim Turner 5d2ef4786b Revert "Merge branch 'rename-existing-folder' into develop"
This reverts commit a340bc4da3, reversing
changes made to 4be7772d53.
2017-01-28 06:49:02 -05:00
Tim Turner 9953a5ed06 Revert "Add movie year to NotificationService (#489)"
This reverts commit e2d6e39168.
2017-01-28 06:33:15 -05:00
Tim Turner dd194d41be Revert "Ensure the movie isn't delete when the folder isn't renamed (#491)"
This reverts commit 8168cf82c5.
2017-01-28 06:29:58 -05:00
Tim Turner 8168cf82c5 Ensure the movie isn't delete when the folder isn't renamed (#491) 2017-01-27 21:24:01 -05:00
Tim Turner e2d6e39168 Add movie year to NotificationService (#489) 2017-01-27 20:32:16 -05:00
Tim Turner a340bc4da3 Merge branch 'rename-existing-folder' into develop 2017-01-27 19:31:55 -05:00
Tim Turner 4bcb0e17f8 Merge branch 'develop' into rename-existing-folder 2017-01-27 19:31:39 -05:00
Tim Turner 25a51df894 Merge FileNameBuilder 2017-01-27 19:31:37 -05:00
Tim Turner f9c6ffcdaa merge from Develop 2017-01-27 19:31:03 -05:00
Mark McDowall b76c54ceac Fixed: Proper port validation for download clients and connections 2017-01-28 09:26:47 +10:00
vertigo235 4be7772d53 Kodi Update Fix: OldFiles -> OldMovieFiles (#483) 2017-01-27 20:34:52 +01:00
Leonardo Galli cbc70a8ff3 Added option to specify preferred words in quality profile. (#462)
* UI Done

* Should theoretically work.

* Preferred tags works now correctly :)
2017-01-27 20:01:28 +01:00
Leonardo Galli dd8af0ad8c Manual Import works now!
Also fixed a few bugs.
2017-01-27 19:36:25 +01:00
vertigo235 00541e6cc1 More Notification Updates (#482)
* Custom Script: Add Movie_Path

* Add Emby Update Support

* Notifications: Maybe add Kodi / XBMC Update Support
2017-01-27 19:01:18 +01:00
Tim Turner a8eec60c9d Remove old folder and all contents 2017-01-26 19:57:31 -05:00
Tim Turner 7bb319b6d6 Update Files tab when movie renamed 2017-01-26 19:16:19 -05:00
Tim Turner 50a5a2de9b Movie reference properly updates UI now
Still need to fix the 'Files' tab to be updated
2017-01-26 17:58:19 -05:00
Devin Buhl dbe5946d10 Only wanted is default for CP 2017-01-26 17:31:27 -05:00
Devin Buhl d5caac5d3b make DVDR not unlimited 2017-01-26 17:10:08 -05:00
vertigo235 487c5e22ce Moviefile, what movie file? (#466)
Send the moviefile object.
2017-01-26 21:46:55 +01:00
vertigo235 6efd63a292 Remove mofilefile id for now (#464) 2017-01-26 21:13:27 +01:00
Devin Buhl f31dc3c054 Update weights 2017-01-26 14:53:43 -05:00
Devin Buhl c60be3a467 Added new qualities, added new qualities to profile class.
Left to do: write migration, and tests
2017-01-26 14:43:24 -05:00
Leonardo Galli 44b4e71c05 Manual importing almost done. Needs fixing for mapping movies. 2017-01-26 14:21:35 +01:00
Devin Buhl 5ebd035b1c Merge pull request #460 from vertigo235/notifications
Download Movie Quality & Formatting
2017-01-25 21:36:25 -05:00
vertigo235 d50514f8bc Download Movie Quality & Formatting
Fix downloaded movie quality and add space between movie name and quality.
2017-01-25 21:19:59 -05:00
Tim Turner 7da2183080 Be more proper about Ensuring the folder exists
Still need a way to tell the UI model to update itself
2017-01-25 20:11:02 -05:00
Devin Buhl a417ac2716 Merge pull request #459 from jrdnlc/develop
Update GeneralViewTemplate.hbs
2017-01-25 19:59:50 -05:00
Jordan a89e662721 Update GeneralViewTemplate.hbs
Updated Wiki link to Radarr
2017-01-25 16:34:35 -08:00
Tim Turner 72aac6e551 Update Rename Preview to support folder renaming 2017-01-25 19:03:40 -05:00
Devin Buhl 4b1e4eecfb Merge pull request #456 from Radarr/patch/lang
Change lang in UI to what profile / lang they choose when they add a …
2017-01-25 18:07:26 -05:00
Devin Buhl 8e452f8b6d Change lang in UI to what profile / lang they choose when they add a movie 2017-01-25 18:00:46 -05:00
Devin Buhl 7e6db89eff Merge pull request #454 from vertigo235/notifications
Custom Script Fix: Parse movie not episode
2017-01-25 17:02:33 -05:00
vertigo235 16214e666b Custom Script Fix: Parse movie not episode 2017-01-25 16:55:36 -05:00
Devin Buhl 2b25ce237f Merge pull request #448 from hotio/patch-1
Fixes issue #447 (Notification Icon for Join)
2017-01-25 11:55:31 -05:00
Devin Buhl f0a9955447 Merge pull request #450 from Radarr/patch/ahd
only use internal for RSS Sync
2017-01-25 11:55:19 -05:00
Devin Buhl d0439296fe only use internal for RSS Sync 2017-01-25 11:40:04 -05:00
Devin Buhl e2c2bdb65b nullable all the fields.. 2017-01-25 11:35:10 -05:00
Devin Buhl bc3fdb0f80 Merge pull request #449 from Radarr/patch/ahd
include only internal for AHD
2017-01-25 11:31:04 -05:00
Devin Buhl 186b2ada36 include only internal for AHD 2017-01-25 11:24:20 -05:00
Devin Buhl 577125f345 Merge pull request #435 from vertigo235/notifications
Notification Fixes/Updates
2017-01-25 10:56:11 -05:00
hotio 687dd8a05f Update JoinProxy.cs 2017-01-25 16:54:57 +01:00
hotio 81861c6121 Fixes issue #447 (Notification Icon for Join) 2017-01-25 16:50:38 +01:00
Devin Buhl 302462f48c Merge pull request #441 from schumi2004/rss-sync
fix new rss-sync threshold
2017-01-25 06:22:48 -05:00
schumi2004 d132f55830 fix new rss-sync threshold 2017-01-25 10:52:29 +01:00
vertigo235 6bbd64e59a Update Plex Movie Sections 2017-01-24 21:32:20 -05:00
vertigo235 290c4e1f2e Update slack for movies. 2017-01-24 17:56:10 -05:00
Devin Buhl 95d97c59d7 rephrase wording 2017-01-24 13:47:44 -05:00
Devin Buhl a75f3e1f8e monitored to false for movies already downloaded on CP 2017-01-24 13:42:27 -05:00
Devin Buhl d09d30544f allow null value for seed time. 2017-01-24 13:22:02 -05:00
Devin Buhl 43d904d20b added trakt user list importing 2017-01-23 18:06:42 -05:00
Devin Buhl 244e82722d Merged branch feature/net-import into feature/net-import 2017-01-23 15:18:13 -05:00
Devin Buhl e182d8b964 fix importing for StevenLu 2017-01-23 15:18:04 -05:00
Leonardo Galli 02e610a5f2 Merged branch feature/net-import into feature/net-import 2017-01-23 20:52:05 +01:00
Leonardo Galli 032bc2d5c4 Add basic ui of manual import. 2017-01-23 20:51:33 +01:00
Devin Buhl e59db74cad Add StevenLu to csproj 2017-01-23 14:39:27 -05:00
Leonardo Galli fbe9ad6582 First pass at ui for manually importing from lists. 2017-01-23 20:00:31 +01:00
Devin Buhl 87da542758 Add import from http://movies.stevenlu.com/ 2017-01-23 12:54:31 -05:00
Leonardo Galli 8b3b46b724 Added easy to use List Selection for manual import use later. The place where this resides will change. 2017-01-23 16:04:52 +01:00
Leonardo Galli 80e53f209d Movies can now be added monitored or unmonitored. 2017-01-23 15:31:09 +01:00
Leonardo Galli 82f29cdc70 Add Ability to set RootFolderPath for Net Import List
Fixed other things. Finished command to automatically add a movie.
2017-01-23 15:21:49 +01:00
Leonardo Galli 93d6505f85 Fix netimport search and add NetImportSyncCommand 2017-01-23 14:04:01 +01:00
Devin Buhl 2b7afd3272 remove duplicate code 2017-01-23 00:29:31 -05:00
Devin Buhl 6d4e1f6c2e fix movies being clobbered when a new list is sent thru 2017-01-22 23:23:51 -05:00
Devin Buhl c0f323b05a Implement NetImportSearchService
check it out
2017-01-22 23:20:12 -05:00
Leonardo Galli f25d4463f5 Merged branch feature/net-import into feature/net-import 2017-01-22 23:11:38 +01:00
Leonardo Galli 00099a5e40 Added Base URL 2017-01-22 23:09:07 +01:00
Leonardo Galli 7c251157ed Fix media info parsing of multiple audio channels. Fixes #315 Fixes #294 2017-01-22 23:03:47 +01:00
Devin Buhl 3ff5b36d29 Add urlBase option to CP settings 2017-01-22 14:53:21 -05:00
Leonardo Galli 1fa736c6d8 Fixed styling. Fixed definitions not being returned. 2017-01-22 19:52:25 +01:00
Leonardo Galli 94eccc6c14 Rethought about where certain things are stored.
Profiles are now a component of the NetImportDefinition.
2017-01-22 17:02:20 +01:00
Leonardo Galli 9fffcfaea3 Fix stuff regarding the ordering of Fields. 2017-01-22 15:08:25 +01:00
Leonardo Galli f481676c81 Fix migration to include ConfigContract and EnableAuto. Also fixed redirects on lists.
Please delete NetImport Table and the corresponding VersionInfo Row (123)
2017-01-22 14:55:25 +01:00
Leonardo Galli 76a42b28f3 Second UI Pass, Testing now works and other little things. 2017-01-22 14:30:33 +01:00
Leonardo Galli ad26e48408 Fix up presets. 2017-01-22 14:03:09 +01:00
Leonardo Galli 0bab97e02f Make presets work for RSS Import :) 2017-01-22 13:44:03 +01:00
Leonardo Galli 2413457323 Add CP list class. 2017-01-22 13:34:03 +01:00
Devin Buhl bfcd05206f migration migrainetion 2017-01-21 19:25:28 -05:00
Devin Buhl ddf10ed137 Added couchpotato, and added a test 2017-01-21 19:15:06 -05:00
Devin Buhl b4d1c0e053 couchpotato API classes 2017-01-21 17:24:21 -05:00
Tim Turner 68f58fb37d Undo unecessary changes 2017-01-21 17:22:21 -05:00
Tim Turner f9923f4592 Move folder on rename; event doesn't fire yet 2017-01-21 17:20:30 -05:00
Leonardo Galli dd553b9439 WIP UI Update for adding lists. 2017-01-21 22:13:55 +01:00
Leonardo Galli 451f2d30e4 Merged branch develop into feature/net-import 2017-01-21 21:28:44 +01:00
Leonardo Galli 463d85e479 Basis of UI Update. 2017-01-21 21:28:14 +01:00
Leonardo Galli 2c52795822 Add base for netimport api. Still nothing on the UI side. 2017-01-21 21:09:02 +01:00
Leonardo Galli 4f37a36619 Updated HttpNetImporterBase. Still needs work to correctly handle failures. 2017-01-21 20:57:29 +01:00
Leonardo Galli 5aaba98c57 Imdbid parsing works now from url 2017-01-21 20:37:08 +01:00
Leonardo Galli a98b69859c Big Abstraction for IMDBWatchlist -> RSSImport (With a test) 2017-01-21 20:29:31 +01:00
Leonardo Galli 734a36de06 Added some abstraction for settings. 2017-01-21 18:42:58 +01:00
Devin Buhl b02944a3b2 Added Qualties to Settings 2017-01-17 20:30:21 -05:00
Devin Buhl 6878abe2a2 whoops, only parse title once 2017-01-15 15:35:38 -05:00
Devin Buhl ec1c81e3ed updates and compile-able 2017-01-15 15:28:35 -05:00
Devin Buhl 47824426c6 Merge branch 'develop' into feature/net-import 2017-01-15 11:14:42 -05:00
Devin Buhl 4ded288c5d few changes 2017-01-12 18:33:30 -05:00
Devin Buhl d123ca6063 updates 2017-01-11 16:12:17 -05:00
Devin Buhl 0ee8b75b54 initial autoimporter commit 2017-01-11 15:42:37 -05:00
195 changed files with 8088 additions and 1120 deletions
+19 -4
View File
@@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Reflection; using NzbDrone.Common.Reflection;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Profiles;
namespace NzbDrone.Api.ClientSchema namespace NzbDrone.Api.ClientSchema
{ {
@@ -73,14 +76,14 @@ namespace NzbDrone.Api.ClientSchema
if (propertyInfo.PropertyType == typeof(int)) if (propertyInfo.PropertyType == typeof(int))
{ {
var value = Convert.ToInt32(field.Value); var value = field.Value.ToString().ParseInt32();
propertyInfo.SetValue(target, value, null); propertyInfo.SetValue(target, value ?? 0, null);
} }
else if (propertyInfo.PropertyType == typeof(long)) else if (propertyInfo.PropertyType == typeof(long))
{ {
var value = Convert.ToInt64(field.Value); var value = field.Value.ToString().ParseInt64();
propertyInfo.SetValue(target, value, null); propertyInfo.SetValue(target, value ?? 0, null);
} }
else if (propertyInfo.PropertyType == typeof(int?)) else if (propertyInfo.PropertyType == typeof(int?))
@@ -147,6 +150,18 @@ namespace NzbDrone.Api.ClientSchema
private static List<SelectOption> GetSelectOptions(Type selectOptions) private static List<SelectOption> GetSelectOptions(Type selectOptions)
{ {
if (selectOptions == typeof(Profile))
{
return new List<SelectOption>();
}
if (selectOptions == typeof(Quality))
{
var qOptions = from Quality q in selectOptions.GetProperties(BindingFlags.Static | BindingFlags.Public)
select new SelectOption {Name = q.Name, Value = q.Id};
return qOptions.OrderBy(o => o.Value).ToList();
}
var options = from Enum e in Enum.GetValues(selectOptions) var options = from Enum e in Enum.GetValues(selectOptions)
select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() }; select new SelectOption { Value = Convert.ToInt32(e), Name = e.ToString() };
@@ -0,0 +1,22 @@
using FluentValidation;
using NzbDrone.Api.Validation;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Config
{
public class NetImportConfigModule : NzbDroneConfigModule<NetImportConfigResource>
{
public NetImportConfigModule(IConfigService configService)
: base(configService)
{
SharedValidator.RuleFor(c => c.NetImportSyncInterval)
.IsValidNetImportSyncInterval();
}
protected override NetImportConfigResource ToResource(IConfigService model)
{
return NetImportConfigResourceMapper.ToResource(model);
}
}
}
@@ -0,0 +1,21 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Config
{
public class NetImportConfigResource : RestResource
{
public int NetImportSyncInterval { get; set; }
}
public static class NetImportConfigResourceMapper
{
public static NetImportConfigResource ToResource(IConfigService model)
{
return new NetImportConfigResource
{
NetImportSyncInterval = model.NetImportSyncInterval
};
}
}
}
+11 -11
View File
@@ -35,21 +35,21 @@ namespace NzbDrone.Api.EpisodeFiles
_seriesService = seriesService; _seriesService = seriesService;
_qualityUpgradableSpecification = qualityUpgradableSpecification; _qualityUpgradableSpecification = qualityUpgradableSpecification;
_logger = logger; _logger = logger;
/*GetResourceById = GetEpisodeFile; GetResourceById = GetMovieFile;
GetResourceAll = GetEpisodeFiles; /*GetResourceAll = GetEpisodeFiles;
UpdateResource = SetQuality;*/ UpdateResource = SetQuality;*/
UpdateResource = SetQuality;
DeleteResource = DeleteEpisodeFile; DeleteResource = DeleteEpisodeFile;
} }
/*private EpisodeFileResource GetEpisodeFile(int id) private MovieFileResource GetMovieFile(int id)
{ {
var episodeFile = _mediaFileService.Get(id); var episodeFile = _mediaFileService.GetMovie(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
return episodeFile.ToResource(series, _qualityUpgradableSpecification); return episodeFile.ToResource();
} }
private List<EpisodeFileResource> GetEpisodeFiles() /*private List<EpisodeFileResource> GetEpisodeFiles()
{ {
if (!Request.Query.SeriesId.HasValue) if (!Request.Query.SeriesId.HasValue)
{ {
@@ -62,13 +62,13 @@ namespace NzbDrone.Api.EpisodeFiles
return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification)); return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification));
} }
*/
private void SetQuality(EpisodeFileResource episodeFileResource) private void SetQuality(MovieFileResource episodeFileResource)
{ {
var episodeFile = _mediaFileService.Get(episodeFileResource.Id); var episodeFile = _mediaFileService.GetMovie(episodeFileResource.Id);
episodeFile.Quality = episodeFileResource.Quality; episodeFile.Quality = episodeFileResource.Quality;
_mediaFileService.Update(episodeFile); _mediaFileService.Update(episodeFile);
}*/ }
private void DeleteEpisodeFile(int id) private void DeleteEpisodeFile(int id)
{ {
@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using Nancy;
using Nancy.Extensions;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Movie;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.NetImport
{
public class ListImportModule : NzbDroneApiModule
{
private readonly IMovieService _movieService;
private readonly ISearchForNewMovie _movieSearch;
public ListImportModule(IMovieService movieService, ISearchForNewMovie movieSearch)
: base("/movie/import")
{
_movieService = movieService;
_movieSearch = movieSearch;
Put["/"] = Movie => SaveAll();
}
private Response SaveAll()
{
var resources = Request.Body.FromJson<List<MovieResource>>();
var Movies = resources.Select(MovieResource => _movieSearch.MapMovieToTmdbMovie(MovieResource.ToModel())).Where(m => m != null).DistinctBy(m => m.TmdbId).ToList();
return _movieService.AddMovies(Movies).ToResource().AsResponse(HttpStatusCode.Accepted);
}
}
}
@@ -0,0 +1,44 @@
using NzbDrone.Api.ClientSchema;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.Profiles;
namespace NzbDrone.Api.NetImport
{
public class NetImportModule : ProviderModuleBase<NetImportResource, INetImport, NetImportDefinition>
{
private readonly IProfileService _profileService;
public NetImportModule(NetImportFactory indexerFactory, IProfileService profileService)
: base(indexerFactory, "netimport")
{
_profileService = profileService;
}
protected override void MapToResource(NetImportResource resource, NetImportDefinition definition)
{
base.MapToResource(resource, definition);
resource.Enabled = definition.Enabled;
resource.EnableAuto = definition.EnableAuto;
resource.ProfileId = definition.ProfileId;
resource.RootFolderPath = definition.RootFolderPath;
resource.ShouldMonitor = definition.ShouldMonitor;
}
protected override void MapToModel(NetImportDefinition definition, NetImportResource resource)
{
base.MapToModel(definition, resource);
definition.Enabled = resource.Enabled;
definition.EnableAuto = resource.EnableAuto;
definition.ProfileId = resource.ProfileId;
definition.RootFolderPath = resource.RootFolderPath;
definition.ShouldMonitor = resource.ShouldMonitor;
}
protected override void Validate(NetImportDefinition definition, bool includeWarnings)
{
if (!definition.Enable) return;
base.Validate(definition, includeWarnings);
}
}
}
@@ -0,0 +1,13 @@
using NzbDrone.Core.NetImport;
namespace NzbDrone.Api.NetImport
{
public class NetImportResource : ProviderResource
{
public bool Enabled { get; set; }
public bool EnableAuto { get; set; }
public bool ShouldMonitor { get; set; }
public string RootFolderPath { get; set; }
public int ProfileId { get; set; }
}
}
+8 -1
View File
@@ -109,6 +109,8 @@
<Compile Include="ClientSchema\SelectOption.cs" /> <Compile Include="ClientSchema\SelectOption.cs" />
<Compile Include="Commands\CommandModule.cs" /> <Compile Include="Commands\CommandModule.cs" />
<Compile Include="Commands\CommandResource.cs" /> <Compile Include="Commands\CommandResource.cs" />
<Compile Include="Config\NetImportConfigModule.cs" />
<Compile Include="Config\NetImportConfigResource.cs" />
<Compile Include="Extensions\AccessControlHeaders.cs" /> <Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" /> <Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" /> <Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />
@@ -121,6 +123,9 @@
<Compile Include="Movies\RenameMovieModule.cs" /> <Compile Include="Movies\RenameMovieModule.cs" />
<Compile Include="Movies\RenameMovieResource.cs" /> <Compile Include="Movies\RenameMovieResource.cs" />
<Compile Include="Movies\MovieEditorModule.cs" /> <Compile Include="Movies\MovieEditorModule.cs" />
<Compile Include="NetImport\ListImportModule.cs" />
<Compile Include="NetImport\NetImportModule.cs" />
<Compile Include="NetImport\NetImportResource.cs" />
<Compile Include="Parse\ParseModule.cs" /> <Compile Include="Parse\ParseModule.cs" />
<Compile Include="Parse\ParseResource.cs" /> <Compile Include="Parse\ParseResource.cs" />
<Compile Include="ManualImport\ManualImportModule.cs" /> <Compile Include="ManualImport\ManualImportModule.cs" />
@@ -234,6 +239,7 @@
<Compile Include="SeasonPass\SeasonPassResource.cs" /> <Compile Include="SeasonPass\SeasonPassResource.cs" />
<Compile Include="Series\AlternateTitleResource.cs" /> <Compile Include="Series\AlternateTitleResource.cs" />
<Compile Include="Series\MovieFileResource.cs" /> <Compile Include="Series\MovieFileResource.cs" />
<Compile Include="Series\FetchMovieListModule.cs" />
<Compile Include="Series\SeasonResource.cs" /> <Compile Include="Series\SeasonResource.cs" />
<Compile Include="SeasonPass\SeasonPassModule.cs" /> <Compile Include="SeasonPass\SeasonPassModule.cs" />
<Compile Include="Series\SeriesEditorModule.cs" /> <Compile Include="Series\SeriesEditorModule.cs" />
@@ -254,6 +260,7 @@
<Compile Include="TinyIoCNancyBootstrapper.cs" /> <Compile Include="TinyIoCNancyBootstrapper.cs" />
<Compile Include="Update\UpdateModule.cs" /> <Compile Include="Update\UpdateModule.cs" />
<Compile Include="Update\UpdateResource.cs" /> <Compile Include="Update\UpdateResource.cs" />
<Compile Include="Validation\NetImportSyncIntervalValidator.cs" />
<Compile Include="Validation\RssSyncIntervalValidator.cs" /> <Compile Include="Validation\RssSyncIntervalValidator.cs" />
<Compile Include="Validation\EmptyCollectionValidator.cs" /> <Compile Include="Validation\EmptyCollectionValidator.cs" />
<Compile Include="Validation\RuleBuilderExtensions.cs" /> <Compile Include="Validation\RuleBuilderExtensions.cs" />
@@ -295,4 +302,4 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>
@@ -11,6 +11,7 @@ namespace NzbDrone.Api.Profiles
{ {
public string Name { get; set; } public string Name { get; set; }
public Quality Cutoff { get; set; } public Quality Cutoff { get; set; }
public string PreferredTags { get; set; }
public List<ProfileQualityItemResource> Items { get; set; } public List<ProfileQualityItemResource> Items { get; set; }
public Language Language { get; set; } public Language Language { get; set; }
} }
@@ -33,6 +34,7 @@ namespace NzbDrone.Api.Profiles
Name = model.Name, Name = model.Name,
Cutoff = model.Cutoff, Cutoff = model.Cutoff,
PreferredTags = model.PreferredTags != null ? string.Join(",", model.PreferredTags) : "",
Items = model.Items.ConvertAll(ToResource), Items = model.Items.ConvertAll(ToResource),
Language = model.Language Language = model.Language
}; };
@@ -59,6 +61,7 @@ namespace NzbDrone.Api.Profiles
Name = resource.Name, Name = resource.Name,
Cutoff = (Quality)resource.Cutoff.Id, Cutoff = (Quality)resource.Cutoff.Id,
PreferredTags = resource.PreferredTags.Split(',').ToList(),
Items = resource.Items.ConvertAll(ToModel), Items = resource.Items.ConvertAll(ToModel),
Language = resource.Language Language = resource.Language
}; };
@@ -0,0 +1,60 @@
using System.Collections.Generic;
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using System.Linq;
using NzbDrone.Core.NetImport;
namespace NzbDrone.Api.Movie
{
public class FetchMovieListModule : NzbDroneRestModule<MovieResource>
{
private readonly IFetchNetImport _fetchNetImport;
private readonly ISearchForNewMovie _movieSearch;
public FetchMovieListModule(IFetchNetImport netImport, ISearchForNewMovie movieSearch)
: base("/netimport/movies")
{
_fetchNetImport = netImport;
_movieSearch = movieSearch;
Get["/"] = x => Search();
}
private Response Search()
{
var results = _fetchNetImport.FetchAndFilter((int) Request.Query.listId, false);
List<Core.Tv.Movie> realResults = new List<Core.Tv.Movie>();
/*foreach (var movie in results)
{
var mapped = _movieSearch.MapMovieToTmdbMovie(movie);
if (mapped != null)
{
realResults.Add(mapped);
}
}*/
return MapToResource(results).AsResponse();
}
private static IEnumerable<MovieResource> MapToResource(IEnumerable<Core.Tv.Movie> movies)
{
foreach (var currentSeries in movies)
{
var resource = currentSeries.ToResource();
var poster = currentSeries.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Poster);
if (poster != null)
{
resource.RemotePoster = poster.Url;
}
yield return resource;
}
}
}
}
@@ -0,0 +1,34 @@
using FluentValidation.Validators;
namespace NzbDrone.Api.Validation
{
public class NetImportSyncIntervalValidator : PropertyValidator
{
public NetImportSyncIntervalValidator()
: base("Must be between 10 and 1440 or 0 to disable")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
if (context.PropertyValue == null)
{
return true;
}
var value = (int)context.PropertyValue;
if (value == 0)
{
return true;
}
if (value >= 10 && value <= 1440)
{
return true;
}
return false;
}
}
}
@@ -5,7 +5,7 @@ namespace NzbDrone.Api.Validation
public class RssSyncIntervalValidator : PropertyValidator public class RssSyncIntervalValidator : PropertyValidator
{ {
public RssSyncIntervalValidator() public RssSyncIntervalValidator()
: base("Must be between 10 and 120 or 0 to disable") : base("Must be between 10 and 720 or 0 to disable")
{ {
} }
@@ -23,7 +23,7 @@ namespace NzbDrone.Api.Validation
return true; return true;
} }
if (value >= 10 && value <= 120) if (value >= 10 && value <= 720)
{ {
return true; return true;
} }
@@ -36,5 +36,10 @@ namespace NzbDrone.Api.Validation
{ {
return ruleBuilder.SetValidator(new RssSyncIntervalValidator()); return ruleBuilder.SetValidator(new RssSyncIntervalValidator());
} }
public static IRuleBuilderOptions<T, int> IsValidNetImportSyncInterval<T>(this IRuleBuilder<T, int> ruleBuilder)
{
return ruleBuilder.SetValidator(new NetImportSyncIntervalValidator());
}
} }
} }
@@ -196,18 +196,18 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue(); Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue();
} }
[Test] //[Test]
public void should_return_true_if_RAWHD() //public void should_return_true_if_RAWHD()
{ //{
parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.RAWHD); // parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.RAWHD);
series.Runtime = 45; // series.Runtime = 45;
parseResultSingle.Series = series; // parseResultSingle.Series = series;
parseResultSingle.Series.SeriesType = SeriesTypes.Daily; // parseResultSingle.Series.SeriesType = SeriesTypes.Daily;
parseResultSingle.Release.Size = 8000.Megabytes(); // parseResultSingle.Release.Size = 8000.Megabytes();
Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue(); // Subject.IsSatisfiedBy(parseResultSingle, null).Accepted.Should().BeTrue();
} //}
[Test] [Test]
public void should_return_true_for_special() public void should_return_true_for_special()
@@ -0,0 +1,449 @@
{
"movies": [
{
"status": "active",
"info": {
"rating": { "imdb": [ 8.1, 228515 ] },
"genres": [ "Action", "Adventure", "Fantasy", "Science Fiction", "Thriller", "War", "Sci-Fi" ],
"tmdb_id": 330459,
"plot": "A rogue band of resistance fighters unite for a mission to steal the Death Star plans and bring a new hope to the galaxy.",
"tagline": "A Rebellion Built on Hope",
"release_date": {
"dvd": 1461016800,
"expires": 1486410729,
"theater": 1453417200,
"bluray": true
},
"year": 2016,
"original_title": "Rogue One: A Star Wars Story",
"actor_roles": {
"Warwick Davis": "Bistan",
"Michael Giacchino": "Stormtrooper",
"Lex Lang": "Stormtrooper",
"Samuel Witwer": "Stormtrooper",
"Steen Young": "Vault Officer",
"Russell Balogh": "X-Wing Pilot",
"Alan Tudyk": "K-2SO",
"Angus Cook": "Mechanic",
"David Boat": "Stormtrooper",
"Kevin Hickman": "Stormtrooper",
"Aidan Cook": "Edrio Two Tubes",
"Valene Kane": "Lyra Erso",
"Simon Farnaby": "Blue Squadron",
"Donnie Yen": "Chirrut Imwe",
"Forest Whitaker": "Saw Gerrera",
"Jordan Stephens": "Corporal Tonc",
"Verona Blue": "Stormtrooper",
"David Sobolov": "Stormtrooper",
"Attila G. Kerekes": "Rebel Marine on Yavin",
"Ian McElhinney": "General Dodonna",
"John Gilroy": "Stormtrooper",
"Matthew Wood": "Stormtrooper",
"Jiang Wen": "Baze Malbus",
"Sharon Duncan-Brewster": "Senator Pamlo",
"Christopher Scarabosio": "Stormtrooper",
"Stephen Stanton": "Admiral Raddus (voice)",
"Andrew Zographos": "X-Wing Pilot",
"Ben Daniels": "General Merrick",
"James Arnold Taylor": "Stormtrooper",
"Robin Atkin Downes": "Stormtrooper",
"Guy Henry": "Grand Moff Tarkin",
"Mac Pietowski": "Commi Tech / Marine Soldier",
"James Earl Jones": "Darth Vader (voice)",
"Daniel Naprous": "Darth Vader",
"Geraldine James": "Blue Squadron",
"Eugene Byrd": "Stormtrooper",
"Michael Donovan": "Stormtrooper",
"Paul Kasey": "Admiral Raddus",
"Fred Tatasciore": "Stormtrooper",
"Vanessa Lengies": "Stormtrooper",
"Duncan Pow": "Sergeant Melshi",
"Dolly Gadsdon": "Younger Jyn (as Dolly Gadson)",
"David Acord": "Stormtrooper",
"Nick Kellington": "Bistan",
"Julian Stone": "Stormtrooper",
"Christian Simpson": "Stormtrooper",
"Alistair Petrie": "General Draven",
"Ariyon Bakare": "Blue Squadron",
"Drewe Henley": "Red Leader Garven Dreis",
"Ram Bergman": "Death Star technician",
"Anthony Daniels": "C-3PO",
"Derek Arnold": "Pao",
"Karen Huie": "Stormtrooper",
"Steve Bardrack": "Stormtrooper",
"Jonathan Aris": "Senator Jebel",
"Alexi Melvin": "Stormtroooper",
"Emeson Nwolie": "Personnel",
"Tyrone Love": "Rebel Marine Commander",
"John S. Schwartz": "Stormtrooper",
"Orly Schuchmacher": "Stormtrooper",
"Dave Filoni": "Stormtrooper",
"Yuri Lowenthal": "Stormtrooper",
"Mads Mikkelsen": "Galen Erso",
"Fares Fares": "Senator Vaspar",
"Ian Whyte": "Moroff",
"Genevieve O'Reilly": "Mon Mothma",
"Jorge Leon Martinez": "X-Wing Pilot",
"Beau Gadsdon": "Young Jyn",
"Katie Sheridan": "Stormtrooper",
"Michael Smiley": "Dr. Evazan",
"Babou Ceesay": "Lieutenant Sefla",
"Tom Harrison-Read": "Stormtrooper",
"Spencer Wilding": "Darth Vader",
"Tom Kane": "Stormtrooper",
"Riz Ahmed": "Bodhi Rook",
"Ingvild Deila": "Princess Leia",
"Tony Gilroy": "Stormtrooper",
"Felicity Jones": "Jyn Erso",
"Jonathan Dixon": "Stormtrooper",
"Angus MacInnes": "Gold Leader Dutch Vander",
"William M. Patrick": "Stormtroooper",
"Diego Luna": "Captain Cassian Andor",
"Sam Hanover": "Imperial Officer",
"Jimmy Smits": "Bail Organa",
"Ned Dennehy": "Prisoner",
"Rian Johnson": "Death Star Technician",
"Jimmy Vee": "R2-D2",
"David Cowgill": "Stormtrooper",
"Vanessa Marshall": "Stormtrooper",
"Terri Douglas": "Stormtrooper",
"David Ankrum": "Wedge Antilles",
"Flora Miller": "Stormtroooper",
"Steve Blum": "Stormtrooper",
"Ben Mendelsohn": "Director Orson Krennic"
},
"via_imdb": true,
"images": {
"disc_art": [],
"poster": [ "https://images-na.ssl-images-amazon.com/images/M/MV5BMjEwMzMxODIzOV5BMl5BanBnXkFtZTgwNzg3OTAzMDI@._V1_SX300.jpg" ],
"backdrop": [ "https://image.tmdb.org/t/p/w1280/tZjVVIYXACV4IIIhXeIM59ytqwS.jpg" ],
"extra_thumbs": [],
"poster_original": [ "https://image.tmdb.org/t/p/original/qjiskwlV1qQzRCjpV0cL9pEMF9a.jpg" ],
"actors": {
"Warwick Davis": "https://image.tmdb.org/t/p/w185/5xBunTQJexQOuCmtlh8MNJerbaM.jpg",
"Michael Giacchino": "https://image.tmdb.org/t/p/w185/2YW8sSVvRhCwiQmsFCgtFsGkbv8.jpg",
"Michael Smiley": "https://image.tmdb.org/t/p/w185/muzJQpsKJ4srfVpyRa7qkrRYWSq.jpg",
"Babou Ceesay": "https://image.tmdb.org/t/p/w185/7HtIvbNxACa03ofJpN4EFQTNtRU.jpg",
"Julian Stone": "https://image.tmdb.org/t/p/w185/sNKqRYXFYHCz8lXExXl0DAl3iGD.jpg",
"Jordan Stephens": "https://image.tmdb.org/t/p/w185/oCQl5rkRExrDhGXNPeSxsmC5wvk.jpg",
"Alistair Petrie": "https://image.tmdb.org/t/p/w185/tC5CHVPnxAMqF0W0csTqcDAawwj.jpg",
"Samuel Witwer": "https://image.tmdb.org/t/p/w185/e4FRojd6SmiyRLo2nQQGUXwi16v.jpg",
"Ben Daniels": "https://image.tmdb.org/t/p/w185/x6MI4Fdz1XbERbNbXYoxTK6NAgv.jpg",
"Ariyon Bakare": "https://image.tmdb.org/t/p/w185/xjJlH9hU58Ocy6GxKfBlEvTif1p.jpg",
"James Arnold Taylor": "https://image.tmdb.org/t/p/w185/rAtyfY0diWt078qQIg0IX9xxG9F.jpg",
"Robin Atkin Downes": "https://image.tmdb.org/t/p/w185/pCnIQMMgrFc4hBOE4LJDdebqRZ4.jpg",
"Drewe Henley": "https://image.tmdb.org/t/p/w185/C28FmnpDyhI9BwD6YjagAe1U53.jpg",
"Spencer Wilding": "https://image.tmdb.org/t/p/w185/g3FJIpQZri7gG515rLehuo81T6W.jpg",
"Alan Tudyk": "https://image.tmdb.org/t/p/w185/6QuMtbD8kmhpwWhFKfNzEvHRLOu.jpg",
"Guy Henry": "https://image.tmdb.org/t/p/w185/zNjPC6BTZj7DZK4KFL0nMC1El2S.jpg",
"Angus Cook": "https://image.tmdb.org/t/p/w185/jPc794vF0h8bmslQ3sO8O3vUVIa.jpg",
"David Boat": "https://image.tmdb.org/t/p/w185/4ewxttZW0bhlta27oc5Tjrxel3p.jpg",
"Tom Kane": "https://image.tmdb.org/t/p/w185/hAyEHNuhD6PqbPdCNR7iUyM271I.jpg",
"Anthony Daniels": "https://image.tmdb.org/t/p/w185/cljvryjb3VwTsNR7fjQKjNPMaBB.jpg",
"Duncan Pow": "https://image.tmdb.org/t/p/w185/vJOzoMzxszyZGnySfql3KY9zR78.jpg",
"Fares Fares": "https://image.tmdb.org/t/p/w185/1BE5IG3hcFXfMjBuJJyKs2JpPjI.jpg",
"Tony Gilroy": "https://image.tmdb.org/t/p/w185/9HOtDgcO6F4Fa4BaIjt0t3Vbxrj.jpg",
"Felicity Jones": "https://image.tmdb.org/t/p/w185/9YekpRl6ndS7zpY0wwZAWcAXkl8.jpg",
"Eugene Byrd": "https://image.tmdb.org/t/p/w185/ab4zEcqdBSjpaz4CPQ2Z6q4rLmO.jpg",
"Jonathan Aris": "https://image.tmdb.org/t/p/w185/6RMuwGYfLLGq01LNGBydj9jpTWn.jpg",
"Valene Kane": "https://image.tmdb.org/t/p/w185/7TcV6HqGXjf28yjuSU42Z5XZRYb.jpg",
"Angus MacInnes": "https://image.tmdb.org/t/p/w185/qftkol8hj7yBBP3KCxRWYkhRyLC.jpg",
"James Earl Jones": "https://image.tmdb.org/t/p/w185/2ZuBf3ip2RXhkiQqGUjbUzAf4Nx.jpg",
"Emeson Nwolie": "https://image.tmdb.org/t/p/w185/dWCOK3qCOm1Vve567FXKhBp5x8B.jpg",
"Terri Douglas": "https://image.tmdb.org/t/p/w185/lECiABogAKm5Zl8Je6niNAoqz5N.jpg",
"Simon Farnaby": "https://image.tmdb.org/t/p/w185/3u1ObLUvaTyEMmpWQnkRg5Trlng.jpg",
"Donnie Yen": "https://image.tmdb.org/t/p/w185/vlKBbOc0htUsDGvcxeULcFXDMRo.jpg",
"Forest Whitaker": "https://image.tmdb.org/t/p/w185/4pMQkelS5lK661m9Kz3oIxLYiyS.jpg",
"Diego Luna": "https://image.tmdb.org/t/p/w185/9f1y0pLqohP8U3eEVCa4di1tESb.jpg",
"Dave Filoni": "https://image.tmdb.org/t/p/w185/1m7ijGgs29Emn3Sj08c1GwGTUm0.jpg",
"Jimmy Smits": "https://image.tmdb.org/t/p/w185/tZfr6EaIxzlT9MhY5T4C6cL3UjF.jpg",
"Yuri Lowenthal": "https://image.tmdb.org/t/p/w185/d5vbYEkrPYAiVdTee8e4xCm7Fg1.jpg",
"Verona Blue": "https://image.tmdb.org/t/p/w185/9UJiyVd65nGCVLsTuFjtF3ejCqa.jpg",
"David Sobolov": "https://image.tmdb.org/t/p/w185/lUXbnlyQPsfAGg0oinCtj6KlOkt.jpg",
"Ned Dennehy": "https://image.tmdb.org/t/p/w185/k4kgPvUND2eTrgmotrVWVJM0JUG.jpg",
"Ian McElhinney": "https://image.tmdb.org/t/p/w185/33RGircMDTbdvD6LUp8sLmQKWvA.jpg",
"Fred Tatasciore": "https://image.tmdb.org/t/p/w185/lNe4zn9fJ302GehQVaFk5BNcGGM.jpg",
"Mads Mikkelsen": "https://image.tmdb.org/t/p/w185/nJjN0bS6ssbOrXcnPJrNEIsbX9s.jpg",
"Paul Kasey": "https://image.tmdb.org/t/p/w185/56f0ouOg2ASKKKZlaywor8E5V3J.jpg",
"David Cowgill": "https://image.tmdb.org/t/p/w185/kcGjj4EuHfMp0VILRVoacoPqNFL.jpg",
"Ian Whyte": "https://image.tmdb.org/t/p/w185/6mRY7hTtHfDTGuTLmZmODOu9buF.jpg",
"Genevieve O'Reilly": "https://image.tmdb.org/t/p/w185/8NrrFxrGng88GU7lxwOyK3PZv05.jpg",
"Jorge Leon Martinez": "https://image.tmdb.org/t/p/w185/nWYveATaySCXosWAjcSS8VNPRe7.jpg",
"Katie Sheridan": "https://image.tmdb.org/t/p/w185/awNPsff9HU7NgAhG1qQ4Kh7pMmj.jpg",
"Vanessa Marshall": "https://image.tmdb.org/t/p/w185/wOXilt4TVOd0LuTw6RbWhe5DUy4.jpg",
"Vanessa Lengies": "https://image.tmdb.org/t/p/w185/vU4syqfb0PYE9efbBq9YZQu24cY.jpg",
"David Ankrum": "https://image.tmdb.org/t/p/w185/vo6JMA38exMSSbyQ3K0YCBwBrWT.jpg",
"Riz Ahmed": "https://image.tmdb.org/t/p/w185/yWjuIP634unLBCB4XjSgmJs5QGC.jpg",
"Steve Blum": "https://image.tmdb.org/t/p/w185/asCL6bWSZ7Xl2kSoRqrPB0CUUUU.jpg",
"Rian Johnson": "https://image.tmdb.org/t/p/w185/qWWRFkeMjTjQKoyEXhsV0QQp4qd.jpg",
"Matthew Wood": "https://image.tmdb.org/t/p/w185/oB9wVbEIg8fjY3ulDKjKsGn2A55.jpg",
"Jiang Wen": "https://image.tmdb.org/t/p/w185/sLLXxXg11VFdVYFthF9RB8wIQKv.jpg",
"Ben Mendelsohn": "https://image.tmdb.org/t/p/w185/nAeZkSUXh9CUAUq1cFAg77rZLIS.jpg",
"Geraldine James": "https://image.tmdb.org/t/p/w185/iHKFccX2qpSzMbhIBdfvr835MVg.jpg",
"Russell Balogh": "https://image.tmdb.org/t/p/w185/yCfE3Pf1npGB15Rw8GHt4nvgK6p.jpg"
},
"backdrop_original": [ "https://image.tmdb.org/t/p/original/tZjVVIYXACV4IIIhXeIM59ytqwS.jpg" ],
"clear_art": [],
"logo": [],
"banner": [],
"landscape": [],
"extra_fanart": []
},
"directors": [ "Gareth Edwards" ],
"titles": [ "Rogue One: A Star Wars Story", "Rogue One", "Star Wars: Rogue One", "Star Wars Anthology: Rogue One", "Rogue One: Uma História Star Wars", "星際大戰外傳:俠盜一號", "Rogue One - A Star Wars Story", "星球大战外传:侠盗一号", "Rogue One: История от Междузвездни войни", "Star Wars - Rouge One" ],
"imdb": "tt3748528",
"mpaa": "PG-13",
"via_tmdb": true,
"actors": [ "Felicity Jones", "Diego Luna", "Alan Tudyk", "Donnie Yen" ],
"writers": [ "Chris Weitz (screenplay)", "Tony Gilroy (screenplay)", "John Knoll (story by)", "Gary Whitta (story by)", "George Lucas (based on characters created by)" ],
"runtime": 133,
"type": "movie",
"released": "16 Dec 2016"
},
"_t": "media",
"releases": [],
"title": "Rogue One: A Star Wars Story",
"_rev": "00030f77",
"profile_id": "38699ec285c447bab0bc6267ffb2f3ad",
"_id": "d9d4e0ff9b0842518b9d5f5184a60f31",
"category_id": null,
"type": "movie",
"files": { "image_poster": [ "C:\\Users\\devin\\AppData\\Roaming\\CouchPotato\\cache\\2100049b45a923e858dd161ae28b1f4d.jpg" ] },
"identifiers": { "imdb": "tt3748528" }
},
{
"status": "active",
"info": {
"rating": { "imdb": [ 7.3, 16900 ] },
"genres": [ "Animation", "Comedy", "Family", "Music", "Drama" ],
"tmdb_id": 335797,
"plot": "In a city of humanoid animals, a hustling theater impresario's attempt to save his theater with a singing competition becomes grander than he anticipates even as its finalists' find that their lives will never be the same.",
"tagline": "Auditions begin 2016.",
"release_date": {
"dvd": 1490997600,
"expires": 1485114888,
"theater": 1482274800,
"bluray": true
},
"year": 2016,
"original_title": "Sing",
"actor_roles": {
"Taron Egerton": "Johnny (voice)",
"Catherine Cavadini": "Additional Voices (voice)",
"Beck Bennett": "Lance (voice)",
"Rhea Perlman": "Judith (voice)",
"Jon Robert Hall": "Frog (voice)",
"Abby Craden": "Additional Voices (voice)",
"Jim Cummings": "Additional Voices (voice)",
"Peter Serafinowicz": "Big Daddy (voice)",
"Bill Farmer": "News Reporter Dog (voice)",
"Jessica Rau": "Additional Voices (voice)",
"Townsend Coleman": "Additional Voices (voice)",
"Jen Faith Brown": "Singer (voice)",
"Brad Morris": "Baboon (voice)",
"Doug Burch": "Additional Voices (voice)",
"Jennifer Hudson": "Young Nana (voice)",
"Laura Dickinson": "Spider (voice)",
"Jeremy Maxwell": "Additional Voices (voice)",
"Asher Blinkoff": "Piglet (voice)",
"Reese Witherspoon": "Rosita (voice)",
"Scarlett Johansson": "Ash (voice)",
"Carlos Alazraqui": "Additional Voices (voice)",
"Edgar Wright": "Additional Voices (voice)",
"Asa Jennings": "Piglet (voice)",
"Nick Offerman": "Norman (voice)",
"Mickael Carreira": "Voice 3",
"Sara Mann": "Additional Voices (voice)",
"Jay Pharoah": "Meena's Grandfather (voice)",
"Adam Buxton": "Stan (voice)",
"Garth Jennings": "Miss Crawly / Additional Voices (voice)",
"Deolinda Kinzimba": "Voice 4",
"Jess Harnell": "Additional Voices (voice)",
"Bob Bergen": "Additional Voices (voice)",
"Leslie Jones": "Meena's Mother (voice)",
"Chris Renaud": "Additional Voices (voice)",
"Nick Kroll": "Gunter (voice)",
"Seth MacFarlane": "Mike (voice)",
"Marisa Liz": "Voice 2",
"Áurea": "Voice 1",
"Leo Jennings": "Piglet (voice)",
"Oscar Jennings": "Piglet (voice)",
"Tara Strong": "Additional Voices (voice)",
"John C. Reilly": "Eddie (voice)",
"Matthew McConaughey": "Buster Moon (voice)",
"Caspar Jennings": "Piglet (voice)",
"Daamen J. Krall": "Additional Voices (voice)",
"Tori Kelly": "Meena (voice)",
"Laraine Newman": "Meena's Grandmother / Additional Voices (voice)",
"Willow Geer": "Additional Voices (voice)",
"Wes Anderson": "Additional Voices (voice)",
"Jason Pace": "Additional Voices (voice)",
"Jennifer Saunders": "Nana (voice)",
"John DeMita": "Additional Voices (voice)"
},
"via_imdb": true,
"images": {
"disc_art": [],
"poster": [ "https://images-na.ssl-images-amazon.com/images/M/MV5BMTYzODYzODU2Ml5BMl5BanBnXkFtZTgwNTc1MTA2NzE@._V1_SX300.jpg" ],
"backdrop": [ "https://image.tmdb.org/t/p/w1280/fxDXp8un4qNY9b1dLd7SH6CKzC.jpg" ],
"extra_thumbs": [],
"poster_original": [ "https://image.tmdb.org/t/p/original/5XFchtGifv8mz4qlyT8PZ7ZsjfG.jpg" ],
"actors": {
"Taron Egerton": "https://image.tmdb.org/t/p/w185/bVsLVoO3BGoHRLjWoM4Gjav2hNb.jpg",
"Catherine Cavadini": "https://image.tmdb.org/t/p/w185/o2wULQltvbzCTCJitNeT72AjklR.jpg",
"Beck Bennett": "https://image.tmdb.org/t/p/w185/oblaqelpyBvtB5GaSgQpDrfka9M.jpg",
"Daamen J. Krall": "https://image.tmdb.org/t/p/w185/u0CORJ8e2vvw1dFARU4estHYS2I.jpg",
"Rhea Perlman": "https://image.tmdb.org/t/p/w185/cq7Cf4z3BHD9o58ki7MgCioty8q.jpg",
"Abby Craden": "https://image.tmdb.org/t/p/w185/biX1xErOEwsuRvidr8Pw6edEyK4.jpg",
"Jim Cummings": "https://image.tmdb.org/t/p/w185/i9frXvIJsGtoFikBEFVqE7uN8Bq.jpg",
"Peter Serafinowicz": "https://image.tmdb.org/t/p/w185/nfXHDKeetwO16agC0S7tDmLt1il.jpg",
"Bill Farmer": "https://image.tmdb.org/t/p/w185/4aDBlkt8nEkr1RkEhiKIbDWhpZB.jpg",
"Jessica Rau": "https://image.tmdb.org/t/p/w185/jBbIYc3UQf7JU8ggQVkfezpmgVZ.jpg",
"Townsend Coleman": "https://image.tmdb.org/t/p/w185/j7PvxQ7XuOQc1ggSRHWRP6CB8CU.jpg",
"Brad Morris": "https://image.tmdb.org/t/p/w185/qX6oVdAt7Vzzcnw28bdXFp05BBH.jpg",
"Doug Burch": "https://image.tmdb.org/t/p/w185/zwfqhPuIFrUL70bWPESdJZWXc7F.jpg",
"Jennifer Hudson": "https://image.tmdb.org/t/p/w185/zqTu7AANIUsVMAYz5rK1YPnvbWR.jpg",
"Asher Blinkoff": "https://image.tmdb.org/t/p/w185/780sIDWQoAIVVaUbAQex50Vam0V.jpg",
"Reese Witherspoon": "https://image.tmdb.org/t/p/w185/a3o8T1P6yy4KWL7wZG6HuDeuh5n.jpg",
"Scarlett Johansson": "https://image.tmdb.org/t/p/w185/f3c1rwcOoeU0v6Ak5loUvMyifR0.jpg",
"Carlos Alazraqui": "https://image.tmdb.org/t/p/w185/o62NevO1Vt9n1MdYsWOsDyhUt3A.jpg",
"Nick Offerman": "https://image.tmdb.org/t/p/w185/8rJOtmxL5GIfNdOfksVPzepQOy2.jpg",
"Sara Mann": "https://image.tmdb.org/t/p/w185/1TiV16ODOJtTZQrWmHRwOyQnMb0.jpg",
"Jay Pharoah": "https://image.tmdb.org/t/p/w185/yRD2vypRF0niEdoCCI0pNZENzvm.jpg",
"Tara Strong": "https://image.tmdb.org/t/p/w185/rFUZnJ4BaSaQVKW734xnUHSN9pm.jpg",
"Garth Jennings": "https://image.tmdb.org/t/p/w185/ahQh5uW5CXLe1LotxN4Y20aj5Gx.jpg",
"Jess Harnell": "https://image.tmdb.org/t/p/w185/k0BOzEyMkZ1CcoCaohjqTyQJjP1.jpg",
"Leslie Jones": "https://image.tmdb.org/t/p/w185/2cXrwJoX0QHGBtNMsMLqeF6bR3s.jpg",
"Chris Renaud": "https://image.tmdb.org/t/p/w185/yK3RxNsIEBljUe9jPG0iz53Iz6t.jpg",
"Nick Kroll": "https://image.tmdb.org/t/p/w185/puZov7sMmuVkvdqJvmlxtWcS1fU.jpg",
"Seth MacFarlane": "https://image.tmdb.org/t/p/w185/v4c6JhGYpjMRBwf95gtPxBnElNu.jpg",
"Bob Bergen": "https://image.tmdb.org/t/p/w185/kuWDjNTw6OVnc3q1ugMGBYpMMMa.jpg",
"Edgar Wright": "https://image.tmdb.org/t/p/w185/ypyH2s4egy5BkviuGDfeltpb19N.jpg",
"Matthew McConaughey": "https://image.tmdb.org/t/p/w185/jdRmHrG0TWXGhs4tO6TJNSoL25T.jpg",
"John C. Reilly": "https://image.tmdb.org/t/p/w185/kUo2TPQp4kOWWvijvkjLl0v9PQB.jpg",
"Adam Buxton": "https://image.tmdb.org/t/p/w185/zL31NlBBKL1NTjR48h610by5Rld.jpg",
"Tori Kelly": "https://image.tmdb.org/t/p/w185/dMyLOIOYqTMQtMEiK9DSxxHTz6F.jpg",
"Laraine Newman": "https://image.tmdb.org/t/p/w185/ApYftBOqDMBnVColOQwXIodOt5s.jpg",
"Willow Geer": "https://image.tmdb.org/t/p/w185/q2TjAxrQSpPPUiTUwFBXcLJ7qxc.jpg",
"Wes Anderson": "https://image.tmdb.org/t/p/w185/r6mr3gvbuocMznHXSlXVKDj7mEI.jpg",
"Jason Pace": "https://image.tmdb.org/t/p/w185/2q6KfNytYUiHuf8Rx9HyBGoD1T7.jpg",
"Jennifer Saunders": "https://image.tmdb.org/t/p/w185/nlxiFy0LUYGlICaFY3rF2DRovcc.jpg",
"John DeMita": "https://image.tmdb.org/t/p/w185/lzwHtcKVd5oenYtoFtJYeNddpwT.jpg"
},
"backdrop_original": [ "https://image.tmdb.org/t/p/original/fxDXp8un4qNY9b1dLd7SH6CKzC.jpg" ],
"clear_art": [],
"logo": [],
"banner": [],
"landscape": [],
"extra_fanart": []
},
"directors": [ "Christophe Lourdelet", "Garth Jennings" ],
"titles": [ "Sing", "Welcome to the Auditions" ],
"imdb": "tt3470600",
"mpaa": "PG",
"via_tmdb": true,
"actors": [ "Matthew McConaughey", "Reese Witherspoon", "Seth MacFarlane", "Scarlett Johansson" ],
"writers": [ "Garth Jennings" ],
"runtime": 110,
"type": "movie",
"released": "21 Dec 2016"
},
"_t": "media",
"releases": [],
"title": "Sing",
"_rev": "00031b86",
"profile_id": "38699ec285c447bab0bc6267ffb2f3ad",
"_id": "f12dc6bbff294daa85db0d839646442a",
"category_id": null,
"type": "movie",
"files": { "image_poster": [ "C:\\Users\\devin\\AppData\\Roaming\\CouchPotato\\cache\\2ad327d73e8ef4deab7a4b564d3b9cb4.jpg" ] },
"identifiers": { "imdb": "tt3470600" }
},
{
"status": "active",
"info": {
"rating": { "imdb": [ 6.4, 10027 ] },
"genres": [ "Action", "Horror" ],
"tmdb_id": 346672,
"plot": "Vampire death dealer Selene fends off brutal attacks from both the Lycan clan and the Vampire faction that betrayed her. With her only allies, David and his father Thomas, she must stop the eternal war between Lycans and Vampires, even if it means she has to make the ultimate sacrifice.",
"tagline": "Protect the Bloodline",
"release_date": {
"dvd": 1493589600,
"expires": 1485114954,
"theater": 1483657200,
"bluray": true
},
"year": 2016,
"original_title": "Underworld: Blood Wars",
"actor_roles": {
"India Eisley": "Eve",
"Kate Beckinsale": "Selene",
"Oliver Stark": "Gregor",
"Brian Caspe": "Hajna",
"Charles Dance": "Thomas",
"Alicia Vela-Bailey": "Safehouse Lycan",
"Bradley James": "Varga",
"David Bowles": "Grey Lycan",
"Theo James": "David",
"Lara Pulver": "Semira",
"Eva Larvoire": "Tech Lycan",
"Tobias Menzies": "Marius",
"Daisy Head": "Alexia",
"Trent Garrett": "Hybrid Michael"
},
"via_imdb": true,
"images": {
"disc_art": [],
"poster": [ "https://images-na.ssl-images-amazon.com/images/M/MV5BMjI5Njk0NTIyNV5BMl5BanBnXkFtZTgwNjU4MjY5MDI@._V1_SX300.jpg" ],
"backdrop": [ "https://image.tmdb.org/t/p/w1280/PIXSMakrO3s2dqA7mCvAAoVR0E.jpg" ],
"extra_thumbs": [],
"poster_original": [ "https://image.tmdb.org/t/p/original/nHXiMnWUAUba2LZ0dFkNDVdvJ1o.jpg" ],
"actors": {
"India Eisley": "https://image.tmdb.org/t/p/w185/njL744BT8mz9jf2TxcZDnSOEZFb.jpg",
"Kate Beckinsale": "https://image.tmdb.org/t/p/w185/pTRtcZn9gWQZRiet36qWKh94urn.jpg",
"Oliver Stark": "https://image.tmdb.org/t/p/w185/5yULYfaUMymZdSLhk2W96hZIQBP.jpg",
"Brian Caspe": "https://image.tmdb.org/t/p/w185/1fDVsCwZOwp97Pdl7q743seHCMP.jpg",
"Charles Dance": "https://image.tmdb.org/t/p/w185/bLT03rnI29YmbYWjA1JJCl4xVXw.jpg",
"Alicia Vela-Bailey": "https://image.tmdb.org/t/p/w185/kVuyn6sS7ZSBlXVjjxq0LSE3k4I.jpg",
"Bradley James": "https://image.tmdb.org/t/p/w185/4XAtJsz67pmpIsCQ9SBKfqayk2d.jpg",
"Trent Garrett": "https://image.tmdb.org/t/p/w185/w9J2snV7QI71B5F7rCxfPqeS7GU.jpg",
"Theo James": "https://image.tmdb.org/t/p/w185/hLNSoQ3gc52X5VVb172yO3CuUEq.jpg",
"Eva Larvoire": "https://image.tmdb.org/t/p/w185/Aq96CWP3Pub2CdWSNbL5eaTwRt0.jpg",
"Tobias Menzies": "https://image.tmdb.org/t/p/w185/bXUpxFsIowySRyyqchaE1XprptI.jpg",
"Daisy Head": "https://image.tmdb.org/t/p/w185/33JAZTxDWj646mxdW1HksqHOsiY.jpg",
"Lara Pulver": "https://image.tmdb.org/t/p/w185/ve68vtNYVXmKjzn81zKhI7TWEvy.jpg"
},
"backdrop_original": [ "https://image.tmdb.org/t/p/original/PIXSMakrO3s2dqA7mCvAAoVR0E.jpg" ],
"clear_art": [],
"logo": [],
"banner": [],
"landscape": [],
"extra_fanart": []
},
"directors": [ "Anna Foerster" ],
"titles": [ "Underworld: Blood Wars", "Inframundo: Guerras de Sangre", "Anjos da Noite: Guerras de Sangue", "Underworld Reboot", "Underworld: Next Generation", "決戰異世界:弒血之戰", "Інший світ 5: Кровна помста", "Інший світ 5", "Underworld 5 - Blood Wars" ],
"imdb": "tt3717252",
"mpaa": "R",
"via_tmdb": true,
"actors": [ "Kate Beckinsale", "Theo James", "Tobias Menzies", "Lara Pulver" ],
"writers": [ "Cory Goodman (screenplay)", "Kyle Ward (story by)", "Cory Goodman (story by)", "Kevin Grevioux (based on characters created by)", "Len Wiseman (based on characters created by)", "Danny McBride (based on characters created by)" ],
"runtime": 91,
"type": "movie",
"released": "06 Jan 2017"
},
"_t": "media",
"releases": [],
"title": "Underworld: Blood Wars",
"_rev": "00037887",
"profile_id": "38699ec285c447bab0bc6267ffb2f3ad",
"_id": "4040237fdbd349629a51e29e8ff634f2",
"category_id": null,
"type": "movie",
"files": { "image_poster": [ "C:\\Users\\devin\\AppData\\Roaming\\CouchPotato\\cache\\e41f29a177dd6756dce94f24148c81fe.jpg" ] },
"identifiers": { "imdb": "tt3717252" }
}
],
"total": 3,
"empty": false,
"success": true
}
File diff suppressed because it is too large Load Diff
@@ -45,6 +45,7 @@ namespace NzbDrone.Core.Test.IndexerTests
return new IndexerResponse(new IndexerRequest(httpRequest), httpResponse); return new IndexerResponse(new IndexerRequest(httpRequest), httpResponse);
} }
[Test] [Test]
public void should_handle_relative_url() public void should_handle_relative_url()
{ {
@@ -0,0 +1,37 @@
using System.Linq;
using System.Text;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.CouchPotato;
using NzbDrone.Core.NetImport.RSSImport;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.NetImport.CouchPotato
{
public class CouchPotatoTest : CoreTest<CouchPotatoParser>
{
private NetImportResponse CreateResponse(string url, string content)
{
var httpRequest = new HttpRequest(url);
var httpResponse = new HttpResponse(httpRequest, new HttpHeader(), Encoding.UTF8.GetBytes(content));
return new NetImportResponse(new NetImportRequest(httpRequest), httpResponse);
}
[Test]
public void should_parse_json_of_couchpotato()
{
var json = ReadAllText("Files/couchpotato_movie_list.json");
var result = Subject.ParseResponse(CreateResponse("http://my.indexer.com/api?q=My+Favourite+Show", json));
result.First().Title.Should().Be("Rogue One: A Star Wars Story");
result.First().ImdbId.Should().Be("tt3748528");
}
}
}
@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.RSSImport;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.NetImport
{
[TestFixture]
public class RSSImportFixture : CoreTest<RSSImport>
{
[SetUp]
public void Setup()
{
Subject.Definition = Subject.DefaultDefinitions.First();
}
private void GivenRecentFeedResponse(string rssXmlFile)
{
var recentFeed = ReadAllText(@"Files/" + rssXmlFile);
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.IsAny<HttpRequest>()))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
}
[Test]
public void should_fetch_imdb_list()
{
GivenRecentFeedResponse("imdb_watchlist.xml");
var result = Subject.Fetch();
result.First().Title.Should().Be("Think Like a Man Too");
result.First().ImdbId.Should().Be("tt2239832");
}
}
}
@@ -0,0 +1,36 @@
using System.Linq;
using System.Text;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.NetImport.RSSImport;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.NetImport
{
public class RSSImportTest : CoreTest<RSSImportParser>
{
private NetImportResponse CreateResponse(string url, string content)
{
var httpRequest = new HttpRequest(url);
var httpResponse = new HttpResponse(httpRequest, new HttpHeader(), Encoding.UTF8.GetBytes(content));
return new NetImportResponse(new NetImportRequest(httpRequest), httpResponse);
}
[Test]
public void should_parse_xml_of_imdb()
{
var xml = ReadAllText("Files/imdb_watchlist.xml");
var result = Subject.ParseResponse(CreateResponse("http://my.indexer.com/api?q=My+Favourite+Show", xml));
result.First().Title.Should().Be("Think Like a Man Too");
result.First().ImdbId.Should().Be("tt2239832");
}
}
}
@@ -190,6 +190,9 @@
<Compile Include="Download\Pending\PendingReleaseServiceTests\RemoveRejectedFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\RemoveRejectedFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\RemoveGrabbedFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\RemoveGrabbedFixture.cs" />
<Compile Include="Download\Pending\PendingReleaseServiceTests\AddFixture.cs" /> <Compile Include="Download\Pending\PendingReleaseServiceTests\AddFixture.cs" />
<None Include="Files\couchpotato_movie_list.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="Files\Indexers\Rarbg\RecentFeed_v1.json"> <None Include="Files\Indexers\Rarbg\RecentFeed_v1.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
@@ -284,6 +287,9 @@
<Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" /> <Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" />
<Compile Include="MetadataSource\SearchSeriesComparerFixture.cs" /> <Compile Include="MetadataSource\SearchSeriesComparerFixture.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" /> <Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
<Compile Include="NetImport\CouchPotato\CouchPotatoParserFixture.cs" />
<Compile Include="NetImport\RSSImportFixture.cs" />
<Compile Include="NetImport\RSSImportParserFixture.cs" />
<Compile Include="NotificationTests\SynologyIndexerFixture.cs" /> <Compile Include="NotificationTests\SynologyIndexerFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" /> <Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
@@ -409,6 +415,9 @@
<Link>sqlite3.dll</Link> <Link>sqlite3.dll</Link>
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Files\imdb_watchlist.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="License.txt" /> <Content Include="License.txt" />
<None Include="Files\Indexers\BroadcastheNet\RecentFeed.json"> <None Include="Files\Indexers\BroadcastheNet\RecentFeed.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
@@ -215,19 +215,19 @@ namespace NzbDrone.Core.Test.ParserTests
ParseAndVerifyQuality(title, Quality.Bluray1080p, proper); ParseAndVerifyQuality(title, Quality.Bluray1080p, proper);
} }
[TestCase("POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false)] //[TestCase("POI S02E11 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
[TestCase("How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false)] //[TestCase("How I Met Your Mother S01E18 Nothing Good Happens After 2 A.M. 720p HDTV DD5.1 MPEG2-TrollHD", false)]
[TestCase("The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false)] //[TestCase("The Voice S01E11 The Finals 1080i HDTV DD5.1 MPEG2-TrollHD", false)]
[TestCase("Californication.S07E11.1080i.HDTV.DD5.1.MPEG2-NTb.ts", false)] //[TestCase("Californication.S07E11.1080i.HDTV.DD5.1.MPEG2-NTb.ts", false)]
[TestCase("Game of Thrones S04E10 1080i HDTV MPEG2 DD5.1-CtrlHD.ts", false)] //[TestCase("Game of Thrones S04E10 1080i HDTV MPEG2 DD5.1-CtrlHD.ts", false)]
[TestCase("VICE.S02E05.1080i.HDTV.DD2.0.MPEG2-NTb.ts", false)] //[TestCase("VICE.S02E05.1080i.HDTV.DD2.0.MPEG2-NTb.ts", false)]
[TestCase("Show - S03E01 - Episode Title Raw-HD.ts", false)] //[TestCase("Show - S03E01 - Episode Title Raw-HD.ts", false)]
[TestCase("Saturday.Night.Live.Vintage.S10E09.Eddie.Murphy.The.Honeydrippers.1080i.UPSCALE.HDTV.DD5.1.MPEG2-zebra", false)] //[TestCase("Saturday.Night.Live.Vintage.S10E09.Eddie.Murphy.The.Honeydrippers.1080i.UPSCALE.HDTV.DD5.1.MPEG2-zebra", false)]
[TestCase("The.Colbert.Report.2011-08-04.1080i.HDTV.MPEG-2-CtrlHD", false)] //[TestCase("The.Colbert.Report.2011-08-04.1080i.HDTV.MPEG-2-CtrlHD", false)]
public void should_parse_raw_quality(string title, bool proper) //public void should_parse_raw_quality(string title, bool proper)
{ //{
ParseAndVerifyQuality(title, Quality.RAWHD, proper); // ParseAndVerifyQuality(title, Quality.RAWHD, proper);
} //}
[TestCase("Sonny.With.a.Chance.S02E15", false)] [TestCase("Sonny.With.a.Chance.S02E15", false)]
[TestCase("Law & Order: Special Victims Unit - 11x11 - Quickie", false)] [TestCase("Law & Order: Special Victims Unit - 11x11 - Quickie", false)]
@@ -23,7 +23,7 @@ namespace NzbDrone.Core.Test.Qualities
new object[] {7, Quality.Bluray1080p}, new object[] {7, Quality.Bluray1080p},
new object[] {8, Quality.WEBDL480p}, new object[] {8, Quality.WEBDL480p},
new object[] {9, Quality.HDTV1080p}, new object[] {9, Quality.HDTV1080p},
new object[] {10, Quality.RAWHD}, //new object[] {10, Quality.RAWHD},
new object[] {16, Quality.HDTV2160p}, new object[] {16, Quality.HDTV2160p},
new object[] {18, Quality.WEBDL2160p}, new object[] {18, Quality.WEBDL2160p},
new object[] {19, Quality.Bluray2160p}, new object[] {19, Quality.Bluray2160p},
@@ -41,7 +41,7 @@ namespace NzbDrone.Core.Test.Qualities
new object[] {Quality.Bluray1080p, 7}, new object[] {Quality.Bluray1080p, 7},
new object[] {Quality.WEBDL480p, 8}, new object[] {Quality.WEBDL480p, 8},
new object[] {Quality.HDTV1080p, 9}, new object[] {Quality.HDTV1080p, 9},
new object[] {Quality.RAWHD, 10}, //new object[] {Quality.RAWHD, 10},
new object[] {Quality.HDTV2160p, 16}, new object[] {Quality.HDTV2160p, 16},
new object[] {Quality.WEBDL2160p, 18}, new object[] {Quality.WEBDL2160p, 18},
new object[] {Quality.Bluray2160p, 19}, new object[] {Quality.Bluray2160p, 19},
@@ -65,20 +65,27 @@ namespace NzbDrone.Core.Test.Qualities
{ {
var qualities = new List<Quality> var qualities = new List<Quality>
{ {
Quality.Unknown, Quality.CAM,
Quality.TELECINE,
Quality.DVDSCR,
Quality.REGIONAL,
Quality.SDTV, Quality.SDTV,
Quality.WEBDL480p,
Quality.DVD, Quality.DVD,
Quality.DVDR,
Quality.HDTV720p, Quality.HDTV720p,
Quality.HDTV1080p, Quality.HDTV1080p,
Quality.HDTV2160p, Quality.HDTV2160p,
Quality.RAWHD, Quality.WEBDL480p,
Quality.WEBDL720p, Quality.WEBDL720p,
Quality.WEBDL1080p, Quality.WEBDL1080p,
Quality.WEBDL2160p, Quality.WEBDL2160p,
Quality.Bluray480p,
Quality.Bluray576p,
Quality.Bluray720p, Quality.Bluray720p,
Quality.Bluray1080p, Quality.Bluray1080p,
Quality.Bluray2160p, Quality.Bluray2160p,
Quality.BRDISK,
Quality.RAWHD
}; };
if (allowed.Length == 0) if (allowed.Length == 0)
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
{ {
new ProfileQualityItem { Allowed = true, Quality = Quality.SDTV }, new ProfileQualityItem { Allowed = true, Quality = Quality.SDTV },
new ProfileQualityItem { Allowed = true, Quality = Quality.WEBDL480p }, new ProfileQualityItem { Allowed = true, Quality = Quality.WEBDL480p },
new ProfileQualityItem { Allowed = true, Quality = Quality.RAWHD } //new ProfileQualityItem { Allowed = true, Quality = Quality.RAWHD }
} }
}; };
@@ -71,13 +71,13 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
var qualityMet = new EpisodeFile { RelativePath = "a", Quality = new QualityModel { Quality = Quality.WEBDL480p } }; var qualityMet = new EpisodeFile { RelativePath = "a", Quality = new QualityModel { Quality = Quality.WEBDL480p } };
var qualityUnmet = new EpisodeFile { RelativePath = "b", Quality = new QualityModel { Quality = Quality.SDTV } }; var qualityUnmet = new EpisodeFile { RelativePath = "b", Quality = new QualityModel { Quality = Quality.SDTV } };
var qualityRawHD = new EpisodeFile { RelativePath = "c", Quality = new QualityModel { Quality = Quality.RAWHD } }; //var qualityRawHD = new EpisodeFile { RelativePath = "c", Quality = new QualityModel { Quality = Quality.RAWHD } };
MediaFileRepository fileRepository = Mocker.Resolve<MediaFileRepository>(); MediaFileRepository fileRepository = Mocker.Resolve<MediaFileRepository>();
qualityMet = fileRepository.Insert(qualityMet); qualityMet = fileRepository.Insert(qualityMet);
qualityUnmet = fileRepository.Insert(qualityUnmet); qualityUnmet = fileRepository.Insert(qualityUnmet);
qualityRawHD = fileRepository.Insert(qualityRawHD); //qualityRawHD = fileRepository.Insert(qualityRawHD);
var monitoredSeriesEpisodes = Builder<Episode>.CreateListOfSize(4) var monitoredSeriesEpisodes = Builder<Episode>.CreateListOfSize(4)
.All() .All()
@@ -90,8 +90,8 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
.With(e => e.Monitored = false) .With(e => e.Monitored = false)
.With(e => e.EpisodeFileId = qualityMet.Id) .With(e => e.EpisodeFileId = qualityMet.Id)
.TheNext(1) .TheNext(1)
.With(e => e.EpisodeFileId = qualityRawHD.Id) //.With(e => e.EpisodeFileId = qualityRawHD.Id)
.TheLast(1) //.TheLast(1)
.With(e => e.SeasonNumber = 0) .With(e => e.SeasonNumber = 0)
.Build(); .Build();
@@ -105,6 +105,13 @@ namespace NzbDrone.Core.Configuration
set { SetValue("RssSyncInterval", value); } set { SetValue("RssSyncInterval", value); }
} }
public int NetImportSyncInterval
{
get { return GetValueInt("NetImportSyncInterval", 60); }
set { SetValue("NetImportSyncInterval", value); }
}
public int MinimumAge public int MinimumAge
{ {
get { return GetValueInt("MinimumAge", 0); } get { return GetValueInt("MinimumAge", 0); }
@@ -169,7 +176,7 @@ namespace NzbDrone.Core.Configuration
public int DownloadedEpisodesScanInterval public int DownloadedEpisodesScanInterval
{ {
get { return GetValueInt("DownloadedEpisodesScanInterval", 1); } get { return GetValueInt("DownloadedEpisodesScanInterval", 0); }
set { SetValue("DownloadedEpisodesScanInterval", value); } set { SetValue("DownloadedEpisodesScanInterval", value); }
} }
@@ -46,6 +46,8 @@ namespace NzbDrone.Core.Configuration
int RssSyncInterval { get; set; } int RssSyncInterval { get; set; }
int MinimumAge { get; set; } int MinimumAge { get; set; }
int NetImportSyncInterval { get; set; }
//UI //UI
int FirstDayOfWeek { get; set; } int FirstDayOfWeek { get; set; }
string CalendarWeekColumnHeader { get; set; } string CalendarWeekColumnHeader { get; set; }
@@ -0,0 +1,27 @@
using FluentMigrator;
using FluentMigrator.Expressions;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(123)]
public class create_netimport_table : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
if (!this.Schema.Schema("dbo").Table("NetImport").Exists())
{
Create.TableForModel("NetImport")
.WithColumn("Enabled").AsBoolean()
.WithColumn("Name").AsString().Unique()
.WithColumn("Implementation").AsString()
.WithColumn("ConfigContract").AsString().Nullable()
.WithColumn("Settings").AsString().Nullable()
.WithColumn("EnableAuto").AsInt32()
.WithColumn("RootFolderPath").AsString()
.WithColumn("ShouldMonitor").AsInt32()
.WithColumn("ProfileId").AsInt32();
}
}
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Data;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(124)]
public class add_preferred_tags_to_profile : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Profiles").AddColumn("PreferredTags").AsString().Nullable();
}
}
}
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Data;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(125)]
public class fix_imdb_unique : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(DeleteUniqueIndex);
}
private void DeleteUniqueIndex(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand getSeriesCmd = conn.CreateCommand())
{
getSeriesCmd.Transaction = tran;
getSeriesCmd.CommandText = @"DROP INDEX 'IX_Movies_ImdbId'";
getSeriesCmd.ExecuteNonQuery();
}
}
}
}
@@ -0,0 +1,35 @@
using System.Data;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(126)]
public class update_qualities_and_profiles : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(ConvertProfile);
}
private void ConvertProfile(IDbConnection conn, IDbTransaction tran)
{
var updater = new ProfileUpdater70(conn, tran);
updater.SplitQualityAppend(0, 27); // TELECINE AFTER Unknown
updater.SplitQualityAppend(0, 26); // TELESYNC AFTER Unknown
updater.SplitQualityAppend(0, 25); // CAM AFTER Unknown
updater.SplitQualityAppend(0, 24); // WORKPRINT AFTER Unknown
updater.SplitQualityPrepend(2, 23); // DVDR BEFORE DVD
updater.SplitQualityPrepend(2, 28); // DVDSCR BEFORE DVD
updater.SplitQualityPrepend(2, 29); // REGIONAL BEFORE DVD
updater.SplitQualityAppend(2, 21); // Bluray576p AFTER SDTV
updater.SplitQualityAppend(2, 20); // Bluray480p AFTER SDTV
updater.AppendQuality(22);
updater.Commit();
}
}
}
@@ -34,6 +34,7 @@ using NzbDrone.Core.Extras.Metadata.Files;
using NzbDrone.Core.Extras.Others; using NzbDrone.Core.Extras.Others;
using NzbDrone.Core.Extras.Subtitles; using NzbDrone.Core.Extras.Subtitles;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.NetImport;
namespace NzbDrone.Core.Datastore namespace NzbDrone.Core.Datastore
{ {
@@ -55,6 +56,11 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.SupportsRss) .Ignore(i => i.SupportsRss)
.Ignore(i => i.SupportsSearch); .Ignore(i => i.SupportsSearch);
Mapper.Entity<NetImportDefinition>().RegisterDefinition("NetImport")
.Ignore(i => i.Enable)
.Relationship()
.HasOne(n => n.Profile, n => n.ProfileId);
Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications") Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications")
.Ignore(i => i.SupportsOnGrab) .Ignore(i => i.SupportsOnGrab)
.Ignore(i => i.SupportsOnDownload) .Ignore(i => i.SupportsOnDownload)
@@ -23,6 +23,7 @@ namespace NzbDrone.Core.DecisionEngine
var comparers = new List<CompareDelegate> var comparers = new List<CompareDelegate>
{ {
CompareQuality, CompareQuality,
ComparePreferredWords,
CompareProtocol, CompareProtocol,
ComparePeersIfTorrent, ComparePeersIfTorrent,
CompareAgeIfUsenet, CompareAgeIfUsenet,
@@ -65,6 +66,26 @@ namespace NzbDrone.Core.DecisionEngine
CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version)); CompareBy(x.RemoteEpisode, y.RemoteEpisode, remoteEpisode => remoteEpisode.ParsedEpisodeInfo.Quality.Revision.Version));
} }
private int ComparePreferredWords(DownloadDecision x, DownloadDecision y)
{
return CompareBy(x.RemoteMovie, y.RemoteMovie, remoteMovie =>
{
var title = remoteMovie.Release.Title;
remoteMovie.Movie.Profile.LazyLoad();
var preferredWords = remoteMovie.Movie.Profile.Value.PreferredTags;
if (preferredWords == null)
{
return 0;
}
var num = preferredWords.AsEnumerable().Count(w => title.ToLower().Contains(w.ToLower()));
return num;
});
; }
private int CompareProtocol(DownloadDecision x, DownloadDecision y) private int CompareProtocol(DownloadDecision x, DownloadDecision y)
{ {
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
public DelugeSettingsValidator() public DelugeSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.MovieCategory).Matches("^[-a-z]*$").WithMessage("Allowed characters a-z and -"); RuleFor(c => c.MovieCategory).Matches("^[-a-z]*$").WithMessage("Allowed characters a-z and -");
} }
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download.Clients.Hadouken
public HadoukenSettingsValidator() public HadoukenSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.Username).NotEmpty() RuleFor(c => c.Username).NotEmpty()
.WithMessage("Username must not be empty."); .WithMessage("Username must not be empty.");
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download.Clients.NzbVortex
public NzbVortexSettingsValidator() public NzbVortexSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.ApiKey).NotEmpty() RuleFor(c => c.ApiKey).NotEmpty()
.WithMessage("API Key is required"); .WithMessage("API Key is required");
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
public NzbgetSettingsValidator() public NzbgetSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.Username).NotEmpty().When(c => !string.IsNullOrWhiteSpace(c.Password)); RuleFor(c => c.Username).NotEmpty().When(c => !string.IsNullOrWhiteSpace(c.Password));
RuleFor(c => c.Password).NotEmpty().When(c => !string.IsNullOrWhiteSpace(c.Username)); RuleFor(c => c.Password).NotEmpty().When(c => !string.IsNullOrWhiteSpace(c.Username));
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
public QBittorrentSettingsValidator() public QBittorrentSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).InclusiveBetween(0, 65535); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
} }
} }
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
public SabnzbdSettingsValidator() public SabnzbdSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.ApiKey).NotEmpty() RuleFor(c => c.ApiKey).NotEmpty()
.WithMessage("API Key is required when username/password are not configured") .WithMessage("API Key is required when username/password are not configured")
@@ -12,7 +12,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public TransmissionSettingsValidator() public TransmissionSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.UrlBase).ValidUrlBase(); RuleFor(c => c.UrlBase).ValidUrlBase();
@@ -10,10 +10,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
public RTorrentSettingsValidator() public RTorrentSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).InclusiveBetween(0, 65535); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.MovieCategory).NotEmpty() RuleFor(c => c.MovieCategory).NotEmpty()
.WithMessage("A category is recommended") .WithMessage("A category is recommended")
.AsWarning(); .AsWarning();
} }
} }
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
public UTorrentSettingsValidator() public UTorrentSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).InclusiveBetween(0, 65535); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.TvCategory).NotEmpty(); RuleFor(c => c.TvCategory).NotEmpty();
} }
} }
@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Download
public ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions) public ProcessedDecisions ProcessDecisions(List<DownloadDecision> decisions)
{ {
//var qualifiedReports = GetQualifiedReports(decisions); //var qualifiedReports = GetQualifiedReports(decisions);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions); var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisionsForMovies(decisions);
var grabbed = new List<DownloadDecision>(); var grabbed = new List<DownloadDecision>();
var pending = new List<DownloadDecision>(); var pending = new List<DownloadDecision>();
@@ -54,13 +54,19 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
private IEnumerable<IndexerRequest> GetRequest(string searchParameters) private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
{ {
var onlyInternal = "";
if (Settings.Internal)
{
onlyInternal = "&internal=true";
}
if (searchParameters != null) if (searchParameters != null)
{ {
yield return new IndexerRequest(string.Format("{0}/searchapi.php?action=imdbsearch&passkey={1}&imdb={2}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.Passkey.Trim(), searchParameters), HttpAccept.Rss); yield return new IndexerRequest($"{Settings.BaseUrl.Trim().TrimEnd('/')}/searchapi.php?action=imdbsearch&passkey={Settings.Passkey.Trim()}&imdb={searchParameters}", HttpAccept.Rss);
} }
else else
{ {
yield return new IndexerRequest(string.Format("{0}/searchapi.php?action=latestmovies&passkey={1}", Settings.BaseUrl.Trim().TrimEnd('/'), Settings.Passkey.Trim()), HttpAccept.Rss); yield return new IndexerRequest($"{Settings.BaseUrl.Trim().TrimEnd('/')}/searchapi.php?action=latestmovies&passkey={Settings.Passkey.Trim()}{onlyInternal}", HttpAccept.Rss);
} }
} }
@@ -29,6 +29,9 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
[FieldDefinition(1, Label = "Passkey")] [FieldDefinition(1, Label = "Passkey")]
public string Passkey { get; set; } public string Passkey { get; set; }
[FieldDefinition(2, Type = FieldType.Checkbox, Label = "Require Internal", HelpText = "Will only include internal releases for RSS Sync.")]
public bool Internal { get; set; }
public NzbDroneValidationResult Validate() public NzbDroneValidationResult Validate()
{ {
return new NzbDroneValidationResult(Validator.Validate(this)); return new NzbDroneValidationResult(Validator.Validate(this));
+1 -1
View File
@@ -27,4 +27,4 @@ namespace NzbDrone.Core.Indexers.HDBits
return new HDBitsParser(Settings); return new HDBitsParser(Settings);
} }
} }
} }
@@ -129,4 +129,4 @@ namespace NzbDrone.Core.Indexers.HDBits
ImdbImportFail = 8, ImdbImportFail = 8,
ImdbTvNotAllowed = 9 ImdbTvNotAllowed = 9
} }
} }
@@ -87,4 +87,4 @@ namespace NzbDrone.Core.Indexers.HDBits
} }
} }
} }
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
@@ -45,17 +44,34 @@ namespace NzbDrone.Core.Indexers.HDBits
return new IndexerPageableRequestChain(); return new IndexerPageableRequestChain();
} }
public IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria) public virtual IndexerPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
var queryBase = new TorrentQuery(); var queryBase = new TorrentQuery();
var query = queryBase.Clone();
query.ImdbInfo.Id = int.Parse(searchCriteria.Movie.ImdbId.Substring(2)); if (TryAddSearchParameters(queryBase, searchCriteria))
pageableRequests.Add(GetRequest(query)); {
var query = queryBase.Clone();
query.ImdbInfo.Id = int.Parse(searchCriteria.Movie.ImdbId.Substring(2));
pageableRequests.Add(GetRequest(query));
}
return pageableRequests; return pageableRequests;
} }
private bool TryAddSearchParameters(TorrentQuery query, SearchCriteriaBase searchCriteria)
{
var imdbId = int.Parse(searchCriteria.Movie.ImdbId.Substring(2));
if (imdbId != 0)
{
query.ImdbInfo = query.ImdbInfo ?? new ImdbInfo();
query.ImdbInfo.Id = imdbId;
return true;
}
return false;
}
private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query) private IEnumerable<IndexerRequest> GetRequest(TorrentQuery query)
{ {
var request = new HttpRequestBuilder(Settings.BaseUrl) var request = new HttpRequestBuilder(Settings.BaseUrl)
@@ -75,4 +91,4 @@ namespace NzbDrone.Core.Indexers.HDBits
yield return new IndexerRequest(request); yield return new IndexerRequest(request);
} }
} }
} }
@@ -306,6 +306,8 @@ namespace NzbDrone.Core.Indexers
request.HttpRequest.RateLimit = RateLimit; request.HttpRequest.RateLimit = RateLimit;
} }
request.HttpRequest.AllowAutoRedirect = true;
return new IndexerResponse(request, _httpClient.Execute(request.HttpRequest)); return new IndexerResponse(request, _httpClient.Execute(request.HttpRequest));
} }
@@ -1,7 +1,9 @@
using NLog; using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser; using NzbDrone.Core.Parser;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.PassThePopcorn namespace NzbDrone.Core.Indexers.PassThePopcorn
{ {
@@ -13,13 +15,28 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public override bool SupportsSearch => true; public override bool SupportsSearch => true;
public override int PageSize => 50; public override int PageSize => 50;
public PassThePopcorn(IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, IParsingService parsingService, Logger logger) private readonly ICached<Dictionary<string, string>> _authCookieCache;
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public PassThePopcorn(IHttpClient httpClient, ICacheManager cacheManager, IIndexerStatusService indexerStatusService,
IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, indexerStatusService, configService, parsingService, logger) : base(httpClient, indexerStatusService, configService, parsingService, logger)
{ } {
_httpClient = httpClient;
_logger = logger;
_authCookieCache = cacheManager.GetCache<Dictionary<string, string>>(GetType(), "authCookies");
}
public override IIndexerRequestGenerator GetRequestGenerator() public override IIndexerRequestGenerator GetRequestGenerator()
{ {
return new PassThePopcornRequestGenerator() { Settings = Settings }; return new PassThePopcornRequestGenerator()
{
Settings = Settings,
HttpClient = _httpClient,
Logger = _logger,
AuthCookieCache = _authCookieCache
};
} }
public override IParseIndexerResponse GetParser() public override IParseIndexerResponse GetParser()
@@ -56,4 +56,12 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public string PassKey { get; set; } public string PassKey { get; set; }
} }
public class PassThePopcornAuthResponse
{
public string Result { get; set; }
public string Popcron { get; set; }
public string AntiCsrfToken { get; set; }
}
} }
@@ -1,16 +1,30 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.IndexerSearch.Definitions; using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Common.Cache;
using NLog;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Indexers.PassThePopcorn namespace NzbDrone.Core.Indexers.PassThePopcorn
{ {
public class PassThePopcornRequestGenerator : IIndexerRequestGenerator public class PassThePopcornRequestGenerator : IIndexerRequestGenerator
{ {
public PassThePopcornSettings Settings { get; set; } public PassThePopcornSettings Settings { get; set; }
public ICached<Dictionary<string, string>> AuthCookieCache { get; set; }
public IHttpClient HttpClient { get; set; }
public Logger Logger { get; set; }
//public PassThePopcornRequestGenerator(ICacheManager cacheManager, IHttpClient httpClient, Logger logger)
//{
// _httpClient = httpClient;
// _logger = logger;
// _authCookieCache = cacheManager.GetCache<Dictionary<string, string>>(GetType(), "authCookies");
//}
public virtual IndexerPageableRequestChain GetRecentRequests() public virtual IndexerPageableRequestChain GetRecentRequests()
{ {
var pageableRequests = new IndexerPageableRequestChain(); var pageableRequests = new IndexerPageableRequestChain();
@@ -54,14 +68,70 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
private IEnumerable<IndexerRequest> GetRequest(string searchParameters) private IEnumerable<IndexerRequest> GetRequest(string searchParameters)
{ {
var request = new IndexerRequest(string.Format("{0}/torrents.php?json=noredirect&searchstr={1}", Settings.BaseUrl.Trim().TrimEnd('/'), searchParameters), HttpAccept.Json); Authenticate();
foreach (var cookie in HttpHeader.ParseCookies(Settings.Cookie)) var request =
new IndexerRequest(
$"{Settings.BaseUrl.Trim().TrimEnd('/')}/torrents.php?json=noredirect&searchstr={searchParameters}",
HttpAccept.Json);
var cookies = AuthCookieCache.Find(Settings.BaseUrl.Trim().TrimEnd('/'));
foreach (var cookie in cookies)
{ {
request.HttpRequest.Cookies[cookie.Key] = cookie.Value; request.HttpRequest.Cookies[cookie.Key] = cookie.Value;
} }
yield return request; yield return request;
} }
private void Authenticate()
{
var requestBuilder = new HttpRequestBuilder($"{Settings.BaseUrl.Trim().TrimEnd('/')}")
{
LogResponseContent = true
};
requestBuilder.Method = HttpMethod.POST;
requestBuilder.Resource("ajax.php?action=login");
requestBuilder.PostProcess += r => r.RequestTimeout = TimeSpan.FromSeconds(15);
var authKey = Settings.BaseUrl.Trim().TrimEnd('/');
var cookies = AuthCookieCache.Find(authKey);
if (cookies == null)
{
AuthCookieCache.Remove(authKey);
var authLoginRequest = requestBuilder
.AddFormParameter("username", Settings.Username)
.AddFormParameter("password", Settings.Password)
.AddFormParameter("passkey", Settings.Passkey)
.AddFormParameter("keeplogged", "1")
.AddFormParameter("login", "Log In!")
.SetHeader("Content-Type", "multipart/form-data")
.Accept(HttpAccept.Json)
.Build();
// authLoginRequest.Method = HttpMethod.POST;
var response = HttpClient.Execute(authLoginRequest);
var result = Json.Deserialize<PassThePopcornAuthResponse>(response.Content);
if (result.Result != "Ok" || string.IsNullOrWhiteSpace(result.Result))
{
Logger.Debug("PassThePopcorn authentication failed.");
throw new Exception("Failed to authenticate with PassThePopcorn.");
}
Logger.Debug("PassThePopcorn authentication succeeded.");
cookies = response.GetCookies();
AuthCookieCache.Set(authKey, cookies);
requestBuilder.SetCookies(cookies);
}
else
{
requestBuilder.SetCookies(cookies);
}
}
} }
} }
@@ -11,12 +11,9 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
public PassThePopcornSettingsValidator() public PassThePopcornSettingsValidator()
{ {
RuleFor(c => c.BaseUrl).ValidRootUrl(); RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.Cookie).NotEmpty(); RuleFor(c => c.Username).NotEmpty();
RuleFor(c => c.Password).NotEmpty();
RuleFor(c => c.Cookie) RuleFor(c => c.Passkey).NotEmpty();
.Matches(@"__cfduid=[0-9a-f]{43}", RegexOptions.IgnoreCase)
.WithMessage("Wrong pattern")
.AsWarning();
} }
} }
@@ -29,21 +26,30 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
BaseUrl = "https://passthepopcorn.me"; BaseUrl = "https://passthepopcorn.me";
} }
[FieldDefinition(0, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your cookie will be sent to that host.")] [FieldDefinition(0, Label = "URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your cookie will be sent to that host.")]
public string BaseUrl { get; set; } public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "Cookie", HelpText = "PassThePopcorn uses a login cookie needed to access the API, you'll have to retrieve it via a browser.")] [FieldDefinition(1, Label = "Username", HelpText = "PTP Username")]
public string Cookie { get; set; } public string Username { get; set; }
[FieldDefinition(2, Type = FieldType.Checkbox, Label = "Prefer Golden", HelpText = "Favors Golden Popcorn-releases over all other releases.")] [FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "PTP Password")]
public string Password { get; set; }
[FieldDefinition(3, Label = "Passkey", HelpText = "PTP Passkey")]
public string Passkey { get; set; }
[FieldDefinition(4, Label = "Prefer Golden", Type = FieldType.Checkbox , HelpText = "Favors Golden Popcorn-releases over all other releases.")]
public bool Golden { get; set; } public bool Golden { get; set; }
[FieldDefinition(3, Type = FieldType.Checkbox, Label = "Prefer Scene", HelpText = "Favors scene-releases over non-scene releases.")] [FieldDefinition(5, Label = "Prefer Scene", Type = FieldType.Checkbox, HelpText = "Favors scene-releases over non-scene releases.")]
public bool Scene { get; set; } public bool Scene { get; set; }
[FieldDefinition(4, Type = FieldType.Checkbox, Label = "Require Approved", HelpText = "Require staff-approval for releases to be accepted.")] [FieldDefinition(6, Label = "Require Approved", Type = FieldType.Checkbox, HelpText = "Require staff-approval for releases to be accepted.")]
public bool Approved { get; set; } public bool Approved { get; set; }
[FieldDefinition(7, Label = "Require Golden", Type = FieldType.Checkbox, HelpText = "Require Golden Popcorn-releases for releases to be accepted.")]
public bool RequireGolden { get; set; }
public NzbDroneValidationResult Validate() public NzbDroneValidationResult Validate()
{ {
return new NzbDroneValidationResult(Validator.Validate(this)); return new NzbDroneValidationResult(Validator.Validate(this));
+28 -1
View File
@@ -14,6 +14,7 @@ using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.NetImport;
using NzbDrone.Core.Tv.Commands; using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Update.Commands; using NzbDrone.Core.Update.Commands;
@@ -85,6 +86,12 @@ namespace NzbDrone.Core.Jobs
TypeName = typeof(RssSyncCommand).FullName TypeName = typeof(RssSyncCommand).FullName
}, },
new ScheduledTask
{
Interval = GetNetImportSyncInterval(),
TypeName = typeof(NetImportSyncCommand).FullName
},
new ScheduledTask new ScheduledTask
{ {
Interval = _configService.DownloadedEpisodesScanInterval, Interval = _configService.DownloadedEpisodesScanInterval,
@@ -138,6 +145,23 @@ namespace NzbDrone.Core.Jobs
return interval; return interval;
} }
private int GetNetImportSyncInterval()
{
var interval = _configService.NetImportSyncInterval;
if (interval > 0 && interval < 10)
{
return 10;
}
if (interval < 0)
{
return 0;
}
return interval;
}
public void Handle(CommandExecutedEvent message) public void Handle(CommandExecutedEvent message)
{ {
var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.Command.Body.GetType().FullName); var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.Command.Body.GetType().FullName);
@@ -157,7 +181,10 @@ namespace NzbDrone.Core.Jobs
var downloadedEpisodes = _scheduledTaskRepository.GetDefinition(typeof(DownloadedEpisodesScanCommand)); var downloadedEpisodes = _scheduledTaskRepository.GetDefinition(typeof(DownloadedEpisodesScanCommand));
downloadedEpisodes.Interval = _configService.DownloadedEpisodesScanInterval; downloadedEpisodes.Interval = _configService.DownloadedEpisodesScanInterval;
_scheduledTaskRepository.UpdateMany(new List<ScheduledTask> { rss, downloadedEpisodes }); var netImport = _scheduledTaskRepository.GetDefinition(typeof(NetImportSyncCommand));
netImport.Interval = _configService.NetImportSyncInterval;
_scheduledTaskRepository.UpdateMany(new List<ScheduledTask> { rss, downloadedEpisodes, netImport });
} }
} }
} }
@@ -128,7 +128,48 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
var current = localMovie.Quality; var current = localMovie.Quality;
var qualityName = current.Quality.Name.ToLower(); var qualityName = current.Quality.Name.ToLower();
QualityModel updated = null; QualityModel updated = null;
if (width > 1400) if (width > 2000)
{
if (qualityName.Contains("bluray"))
{
updated = new QualityModel(Quality.Bluray2160p);
}
else if (qualityName.Contains("webdl"))
{
updated = new QualityModel(Quality.WEBDL2160p);
}
else if (qualityName.Contains("hdtv"))
{
updated = new QualityModel(Quality.HDTV2160p);
}
else
{
var def = _qualitiesService.Get(Quality.HDTV2160p);
if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size)
{
updated = new QualityModel(Quality.HDTV2160p);
}
def = _qualitiesService.Get(Quality.WEBDL2160p);
if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size)
{
updated = new QualityModel(Quality.WEBDL2160p);
}
def = _qualitiesService.Get(Quality.Bluray2160p);
if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size)
{
updated = new QualityModel(Quality.Bluray2160p);
}
if (updated == null)
{
updated = new QualityModel(Quality.Bluray2160p);
}
}
}
else if (width > 1400)
{ {
if (qualityName.Contains("bluray")) if (qualityName.Contains("bluray"))
{ {
@@ -147,7 +188,6 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
else else
{ {
var def = _qualitiesService.Get(Quality.HDTV1080p); var def = _qualitiesService.Get(Quality.HDTV1080p);
if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size) if (localMovie.Size > def.MinSize && def.MaxSize > localMovie.Size)
{ {
@@ -1,6 +1,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using Newtonsoft.Json; using Newtonsoft.Json;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.MediaFiles
} }
else else
{ {
_logger.Warn("The existing movie file was not lazy loaded."); //_logger.Warn("The existing movie file was not lazy loaded.");
} }
@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MetadataSource namespace NzbDrone.Core.MetadataSource
@@ -7,6 +8,6 @@ namespace NzbDrone.Core.MetadataSource
public interface IProvideMovieInfo public interface IProvideMovieInfo
{ {
Movie GetMovieInfo(string ImdbId); Movie GetMovieInfo(string ImdbId);
Movie GetMovieInfo(int TmdbId); Movie GetMovieInfo(int TmdbId, Profile profile);
} }
} }
@@ -6,5 +6,7 @@ namespace NzbDrone.Core.MetadataSource
public interface ISearchForNewMovie public interface ISearchForNewMovie
{ {
List<Movie> SearchForNewMovie(string title); List<Movie> SearchForNewMovie(string title);
Movie MapMovieToTmdbMovie(Movie movie);
} }
} }
@@ -42,6 +42,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public bool adult { get; set; } public bool adult { get; set; }
public string backdrop_path { get; set; } public string backdrop_path { get; set; }
public Belongs_To_Collection belongs_to_collection { get; set; } public Belongs_To_Collection belongs_to_collection { get; set; }
public int? status_code { get; set; }
public string status_message { get; set; }
public int budget { get; set; } public int budget { get; set; }
public Genre[] genres { get; set; } public Genre[] genres { get; set; }
public string homepage { get; set; } public string homepage { get; set; }
@@ -14,6 +14,8 @@ using NzbDrone.Core.Tv;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Text; using System.Text;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Profiles;
namespace NzbDrone.Core.MetadataSource.SkyHook namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
@@ -67,24 +69,55 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
return new Tuple<Series, List<Episode>>(series, episodes.ToList()); return new Tuple<Series, List<Episode>>(series, episodes.ToList());
} }
public Movie GetMovieInfo(int TmdbId) public Movie GetMovieInfo(int TmdbId, Profile profile = null)
{ {
var langCode = profile != null ? IsoLanguages.Get(profile.Language).TwoLetterCode : "us";
var request = _movieBuilder.Create() var request = _movieBuilder.Create()
.SetSegment("route", "movie") .SetSegment("route", "movie")
.SetSegment("id", TmdbId.ToString()) .SetSegment("id", TmdbId.ToString())
.SetSegment("secondaryRoute", "") .SetSegment("secondaryRoute", "")
.AddQueryParam("append_to_response", "alternative_titles,release_dates,videos") .AddQueryParam("append_to_response", "alternative_titles,release_dates,videos")
.AddQueryParam("country", "US") .AddQueryParam("language", langCode.ToUpper())
// .AddQueryParam("country", "US")
.Build(); .Build();
request.AllowAutoRedirect = true; request.AllowAutoRedirect = true;
request.SuppressHttpError = true; request.SuppressHttpError = true;
var response = _httpClient.Get<MovieResourceRoot>(request); var response = _httpClient.Get<MovieResourceRoot>(request);
var resource = response.Resource; var resource = response.Resource;
var movie = new Movie(); if (resource.status_message != null)
{
if (resource.status_code == 34)
{
_logger.Warn("Movie with TmdbId {0} could not be found. This is probably the case when the movie was deleted from TMDB.", TmdbId);
return null;
}
_logger.Warn(resource.status_message);
return null;
}
var movie = new Movie();
if (langCode != "us")
{
movie.AlternativeTitles.Add(resource.original_title);
}
foreach (var alternativeTitle in resource.alternative_titles.titles)
{
if (alternativeTitle.iso_3166_1.ToLower() == langCode)
{
movie.AlternativeTitles.Add(alternativeTitle.title);
}
else if (alternativeTitle.iso_3166_1.ToLower() == "us")
{
movie.AlternativeTitles.Add(alternativeTitle.title);
}
}
movie.TmdbId = TmdbId; movie.TmdbId = TmdbId;
movie.ImdbId = resource.imdb_id; movie.ImdbId = resource.imdb_id;
@@ -106,10 +139,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
movie.Images.Add(_configService.GetCoverForURL(resource.backdrop_path, MediaCoverTypes.Banner)); movie.Images.Add(_configService.GetCoverForURL(resource.backdrop_path, MediaCoverTypes.Banner));
movie.Runtime = resource.runtime; movie.Runtime = resource.runtime;
foreach(Title title in resource.alternative_titles.titles) //foreach(Title title in resource.alternative_titles.titles)
{ //{
movie.AlternativeTitles.Add(title.title); // movie.AlternativeTitles.Add(title.title);
} //}
foreach(ReleaseDates releaseDates in resource.release_dates.results) foreach(ReleaseDates releaseDates in resource.release_dates.results)
{ {
@@ -149,7 +182,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
movie.Status = MovieStatusType.Announced; movie.Status = MovieStatusType.Announced;
} }
if (resource.videos != null) if (resource.videos != null)
{ {
foreach (Video video in resource.videos.results) foreach (Video video in resource.videos.results)
@@ -561,5 +594,40 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
return value; return value;
} }
public Movie MapMovieToTmdbMovie(Movie movie)
{
Movie newMovie = movie;
if (movie.TmdbId > 0)
{
newMovie = GetMovieInfo(movie.TmdbId);
}
else if (movie.ImdbId.IsNotNullOrWhiteSpace())
{
newMovie = GetMovieInfo(movie.ImdbId);
}
else
{
var yearStr = "";
if (movie.Year > 1900)
{
yearStr = $" {movie.Year}";
}
newMovie = SearchForNewMovie(movie.Title + yearStr).FirstOrDefault();
}
if (newMovie == null)
{
_logger.Warn("Couldn't map movie {0} to a movie on The Movie DB. It will not be added :(", movie.Title);
return null;
}
newMovie.Path = movie.Path;
newMovie.RootFolderPath = movie.RootFolderPath;
newMovie.ProfileId = movie.ProfileId;
newMovie.Monitored = movie.Monitored;
return newMovie;
}
} }
} }
@@ -0,0 +1,91 @@
using System.Collections.Generic;
using System.Windows.Forms;
using System.Xml.Serialization;
namespace NzbDrone.Core.NetImport.CouchPotato
{
public class CouchPotatoResponse
{
public Movie[] movies { get; set; }
public int total { get; set; }
public bool empty { get; set; }
public bool success { get; set; }
}
public class Movie
{
public string status { get; set; }
public Info info { get; set; }
public string _t { get; set; }
public List<Release> releases { get; set; }
public string title { get; set; }
public string _rev { get; set; }
public string profile_id { get; set; }
public string _id { get; set; }
public object category_id { get; set; }
public string type { get; set; }
}
public class Info
{
public string[] genres { get; set; }
public int? tmdb_id { get; set; }
public string plot { get; set; }
public string tagline { get; set; }
public int? year { get; set; }
public string original_title { get; set; }
public bool? via_imdb { get; set; }
public string[] directors { get; set; }
public string[] titles { get; set; }
public string imdb { get; set; }
public string mpaa { get; set; }
public bool? via_tmdb { get; set; }
public string[] actors { get; set; }
public string[] writers { get; set; }
public int? runtime { get; set; }
public string type { get; set; }
public string released { get; set; }
}
public class ReleaseInfo
{
public double? size { get; set; }
public int? seeders { get; set; }
public string protocol { get; set; }
public string description { get; set; }
public string url { get; set; }
public int? age { get; set; }
public string id { get; set; }
public int? leechers { get; set; }
public int? score { get; set; }
public string provider { get; set; }
public int? seed_time { get; set; }
public string provider_extra { get; set; }
public string detail_url { get; set; }
public string type { get; set; }
public double? seed_ratio { get; set; }
public string name { get; set; }
}
public class DownloadInfo
{
public bool? status_support { get; set; }
public string id { get; set; }
public string downloader { get; set; }
}
public class Release
{
public string status { get; set; }
public ReleaseInfo info { get; set; }
public DownloadInfo download_info { get; set; }
public string _id { get; set; }
public string media_id { get; set; }
public string _rev { get; set; }
public string _t { get; set; }
public bool? is_3d { get; set; }
public int? last_edit { get; set; }
public string identifier { get; set; }
public string quality { get; set; }
}
}
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.PassThePopcorn;
using NzbDrone.Core.Parser;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport.CouchPotato
{
public class CouchPotatoImport : HttpNetImportBase<CouchPotatoSettings>
{
public override string Name => "CouchPotato";
public override bool Enabled => true;
public override bool EnableAuto => false;
public CouchPotatoImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger)
{ }
/*public new virtual IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
var config = (CouchPotatoSettings)new CouchPotatoSettings();
config.Link = "http://localhost";
config.Port = "5050";
yield return new NetImportDefinition
{
Name = "Localhost",
Enabled = config.Validate().IsValid && Enabled,
Implementation = GetType().Name,
Settings = config
};
}
}*/
public override INetImportRequestGenerator GetRequestGenerator()
{
return new CouchPotatoRequestGenerator() { Settings = Settings };
}
public override IParseNetImportResponse GetParser()
{
return new CouchPotatoParser(Settings);
}
}
}
@@ -0,0 +1,109 @@
using Newtonsoft.Json;
using NzbDrone.Core.NetImport.Exceptions;
using NzbDrone.Core.Tv;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.NetImport.CouchPotato
{
public class CouchPotatoParser : IParseNetImportResponse
{
private readonly CouchPotatoSettings _settings;
private NetImportResponse _importResponse;
private readonly Logger _logger;
private static readonly Regex ReplaceEntities = new Regex("&[a-z]+;", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public CouchPotatoParser(CouchPotatoSettings settings)
{
_settings = settings;
}
public IList<Tv.Movie> ParseResponse(NetImportResponse importResponse)
{
_importResponse = importResponse;
var movies = new List<Tv.Movie>();
if (!PreProcess(_importResponse))
{
return movies;
}
var jsonResponse = JsonConvert.DeserializeObject<CouchPotatoResponse>(_importResponse.Content);
// no movies were return
if (jsonResponse.total == 0)
{
return movies;
}
var responseData = jsonResponse.movies;
foreach (var item in responseData)
{
int tmdbid = item.info.tmdb_id ?? 0;
// if there are no releases at all the movie wasn't found on CP, so return movies
if (!item.releases.Any() && item.type == "movie")
{
movies.AddIfNotNull(new Tv.Movie()
{
Title = item.title,
ImdbId = item.info.imdb,
TmdbId = tmdbid
});
}
else
{
// snatched,missing,available,downloaded
// done,seeding
bool isCompleted = item.releases.Any(rel => (rel.status == "done" || rel.status == "seeding"));
if (!isCompleted)
{
movies.AddIfNotNull(new Tv.Movie()
{
Title = item.title,
ImdbId = item.info.imdb,
TmdbId = tmdbid,
Monitored = false
});
}
}
}
return movies;
}
protected virtual bool PreProcess(NetImportResponse indexerResponse)
{
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode);
}
if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/json") &&
indexerResponse.HttpRequest.Headers.Accept != null && !indexerResponse.HttpRequest.Headers.Accept.Contains("text/json"))
{
throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
}
return true;
}
}
}
@@ -0,0 +1,41 @@
using NzbDrone.Common.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.NetImport.CouchPotato
{
public class CouchPotatoRequestGenerator : INetImportRequestGenerator
{
public CouchPotatoSettings Settings { get; set; }
public virtual NetImportPageableRequestChain GetMovies()
{
var pageableRequests = new NetImportPageableRequestChain();
pageableRequests.Add(GetMovies(null));
return pageableRequests;
}
private IEnumerable<NetImportRequest> GetMovies(string searchParameters)
{
var urlBase = "";
if (!string.IsNullOrWhiteSpace(Settings.UrlBase))
{
urlBase = Settings.UrlBase.StartsWith("/") ? Settings.UrlBase : $"/{Settings.UrlBase}";
}
var status = "";
if (Settings.OnlyActive)
{
status = "?status=active";
}
var request = new NetImportRequest($"{Settings.Link.Trim()}:{Settings.Port}{urlBase}/api/{Settings.ApiKey}/movie.list/{status}", HttpAccept.Json);
yield return request;
}
}
}
@@ -0,0 +1,48 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.NetImport.CouchPotato
{
public class CouchPotatoSettingsValidator : AbstractValidator<CouchPotatoSettings>
{
public CouchPotatoSettingsValidator()
{
RuleFor(c => c.Link).ValidRootUrl();
RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.ApiKey).NotEmpty();
}
}
public class CouchPotatoSettings : NetImportBaseSettings
{
public CouchPotatoSettings()
{
Link = "http://localhost";
Port = 5050;
UrlBase = "";
OnlyActive = true;
}
[FieldDefinition(0, Label = "CouchPotato URL", HelpText = "Link to your CoouchPootato.")]
public new string Link { get; set; }
[FieldDefinition(1, Label = "CouchPotato Port", HelpText = "Port your CoouchPootato uses.")]
public int Port { get; set; }
[FieldDefinition(2, Label = "CouchPotato Url Base",
HelpText = "UrlBase your CoouchPootato uses, leave blank for none")]
public string UrlBase { get; set; }
[FieldDefinition(3, Label = "CouchPotato API Key", HelpText = "CoouchPootato API Key.")]
public string ApiKey { get; set; }
[FieldDefinition(4, Label = "Only Wanted", HelpText = "Only add wanted movies.", Type = FieldType.Checkbox)]
public bool OnlyActive { get; set; }
}
}
@@ -0,0 +1,23 @@
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.NetImport.Exceptions
{
public class NetImportException : NzbDroneException
{
private readonly NetImportResponse _netImportResponse;
public NetImportException(NetImportResponse response, string message, params object[] args)
: base(message, args)
{
_netImportResponse = response;
}
public NetImportException(NetImportResponse response, string message)
: base(message)
{
_netImportResponse = response;
}
public NetImportResponse Response => _netImportResponse;
}
}
@@ -0,0 +1,303 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Http.CloudFlare;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.NetImport.Exceptions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport
{
public abstract class HttpNetImportBase<TSettings> : NetImportBase<TSettings>
where TSettings : IProviderConfig, new()
{
protected const int MaxNumResultsPerQuery = 1000;
protected readonly IHttpClient _httpClient;
public override bool Enabled => true;
public bool SupportsPaging => PageSize > 0;
public virtual int PageSize => 0;
public virtual TimeSpan RateLimit => TimeSpan.FromSeconds(2);
public abstract INetImportRequestGenerator GetRequestGenerator();
public abstract IParseNetImportResponse GetParser();
public HttpNetImportBase(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger)
: base(configService, parsingService, logger)
{
_httpClient = httpClient;
}
public override IList<Movie> Fetch()
{
var generator = GetRequestGenerator();
return FetchMovies(generator.GetMovies());
}
protected virtual IList<Movie> FetchMovies(NetImportPageableRequestChain pageableRequestChain, bool isRecent = false)
{
var movies = new List<Movie>();
var url = string.Empty;
var parser = GetParser();
try
{
var fullyUpdated = false;
Movie lastMovie = null;
if (isRecent)
{
//lastReleaseInfo = _indexerStatusService.GetLastRssSyncReleaseInfo(Definition.Id);
}
for (int i = 0; i < pageableRequestChain.Tiers; i++)
{
var pageableRequests = pageableRequestChain.GetTier(i);
foreach (var pageableRequest in pageableRequests)
{
var pagedReleases = new List<Movie>();
foreach (var request in pageableRequest)
{
url = request.Url.FullUri;
var page = FetchPage(request, parser);
pagedReleases.AddRange(page);
if (isRecent && page.Any())
{
if (lastMovie == null)
{
fullyUpdated = true;
break;
}/*
var oldestReleaseDate = page.Select(v => v.PublishDate).Min();
if (oldestReleaseDate < lastReleaseInfo.PublishDate || page.Any(v => v.DownloadUrl == lastReleaseInfo.DownloadUrl))
{
fullyUpdated = true;
break;
}
if (pagedReleases.Count >= MaxNumResultsPerQuery &&
oldestReleaseDate < DateTime.UtcNow - TimeSpan.FromHours(24))
{
fullyUpdated = false;
break;
}*///update later
}
else if (pagedReleases.Count >= MaxNumResultsPerQuery)
{
break;
}
if (!IsFullPage(page))
{
break;
}
}
movies.AddRange(pagedReleases);
}
if (movies.Any())
{
break;
}
}
if (isRecent && !movies.Empty())
{
var ordered = movies.OrderByDescending(v => v.Title).ToList();
lastMovie = ordered.First();
//_indexerStatusService.UpdateRssSyncStatus(Definition.Id, lastReleaseInfo);
}
//_indexerStatusService.RecordSuccess(Definition.Id);
}
catch (WebException webException)
{
if (webException.Status == WebExceptionStatus.NameResolutionFailure ||
webException.Status == WebExceptionStatus.ConnectFailure)
{
//_indexerStatusService.RecordConnectionFailure(Definition.Id);
}
else
{
//_indexerStatusService.RecordFailure(Definition.Id);
}
if (webException.Message.Contains("502") || webException.Message.Contains("503") ||
webException.Message.Contains("timed out"))
{
_logger.Warn("{0} server is currently unavailable. {1} {2}", this, url, webException.Message);
}
else
{
_logger.Warn("{0} {1} {2}", this, url, webException.Message);
}
}
catch (HttpException httpException)
{
if ((int)httpException.Response.StatusCode == 429)
{
//_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1));
_logger.Warn("API Request Limit reached for {0}", this);
}
else
{
//_indexerStatusService.RecordFailure(Definition.Id);
_logger.Warn("{0} {1}", this, httpException.Message);
}
}
catch (RequestLimitReachedException)
{
//_indexerStatusService.RecordFailure(Definition.Id, TimeSpan.FromHours(1));
_logger.Warn("API Request Limit reached for {0}", this);
}
catch (ApiKeyException)
{
//_indexerStatusService.RecordFailure(Definition.Id);
_logger.Warn("Invalid API Key for {0} {1}", this, url);
}
catch (CloudFlareCaptchaException ex)
{
//_indexerStatusService.RecordFailure(Definition.Id);
if (ex.IsExpired)
{
_logger.Error(ex, "Expired CAPTCHA token for {0}, please refresh in indexer settings.", this);
}
else
{
_logger.Error(ex, "CAPTCHA token required for {0}, check indexer settings.", this);
}
}
catch (IndexerException ex)
{
//_indexerStatusService.RecordFailure(Definition.Id);
var message = string.Format("{0} - {1}", ex.Message, url);
_logger.Warn(ex, message);
}
catch (Exception feedEx)
{
//_indexerStatusService.RecordFailure(Definition.Id);
feedEx.Data.Add("FeedUrl", url);
_logger.Error(feedEx, "An error occurred while processing feed. " + url);
}
return movies;
}
protected virtual bool IsFullPage(IList<Movie> page)
{
return PageSize != 0 && page.Count >= PageSize;
}
protected virtual IList<Movie> FetchPage(NetImportRequest request, IParseNetImportResponse parser)
{
var response = FetchIndexerResponse(request);
return parser.ParseResponse(response).ToList().Select(m =>
{
m.RootFolderPath = ((NetImportDefinition) Definition).RootFolderPath;
m.ProfileId = ((NetImportDefinition) Definition).ProfileId;
m.Monitored = ((NetImportDefinition) Definition).ShouldMonitor;
return m;
}).ToList();
}
protected virtual NetImportResponse FetchIndexerResponse(NetImportRequest request)
{
_logger.Debug("Downloading List " + request.HttpRequest.ToString(false));
if (request.HttpRequest.RateLimit < RateLimit)
{
request.HttpRequest.RateLimit = RateLimit;
}
request.HttpRequest.AllowAutoRedirect = true;
return new NetImportResponse(request, _httpClient.Execute(request.HttpRequest));
}
protected override void Test(List<ValidationFailure> failures)
{
failures.AddIfNotNull(TestConnection());
}
protected virtual ValidationFailure TestConnection()
{
try
{
var parser = GetParser();
var generator = GetRequestGenerator();
var releases = FetchPage(generator.GetMovies().GetAllTiers().First().First(), parser);
if (releases.Empty())
{
return new ValidationFailure(string.Empty, "No results were returned from your list, please check your settings.");
}
}
catch (ApiKeyException)
{
_logger.Warn("List returned result for RSS URL, API Key appears to be invalid");
return new ValidationFailure("ApiKey", "Invalid API Key");
}
catch (RequestLimitReachedException)
{
_logger.Warn("Request limit reached");
}
catch (CloudFlareCaptchaException ex)
{
if (ex.IsExpired)
{
return new ValidationFailure("CaptchaToken", "CloudFlare CAPTCHA token expired, please Refresh.");
}
else
{
return new ValidationFailure("CaptchaToken", "Site protected by CloudFlare CAPTCHA. Valid CAPTCHA token required.");
}
}
catch (UnsupportedFeedException ex)
{
_logger.Warn(ex, "List feed is not supported");
return new ValidationFailure(string.Empty, "List feed is not supported: " + ex.Message);
}
catch (NetImportException ex)
{
_logger.Warn(ex, "Unable to connect to list");
return new ValidationFailure(string.Empty, "Unable to connect to indexer. " + ex.Message);
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to connect to list");
return new ValidationFailure(string.Empty, "Unable to connect to list, check the log for more details");
}
return null;
}
}
}
+16
View File
@@ -0,0 +1,16 @@
using System.Collections.Generic;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport
{
public interface INetImport : IProvider
{
bool Enabled { get; }
bool EnableAuto { get; }
IList<Movie> Fetch();
}
}
@@ -0,0 +1,9 @@
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.NetImport
{
public interface INetImportRequestGenerator
{
NetImportPageableRequestChain GetMovies();
}
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport
{
public interface IParseNetImportResponse
{
IList<Movie> ParseResponse(NetImportResponse netMovieImporterResponse);
}
}
@@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport
{
public abstract class NetImportBase<TSettings> : INetImport
where TSettings : IProviderConfig, new()
{
protected readonly IConfigService _configService;
protected readonly IParsingService _parsingService;
protected readonly Logger _logger;
public abstract string Name { get; }
public abstract bool Enabled { get; }
public abstract bool EnableAuto { get; }
public NetImportBase(IConfigService configService, IParsingService parsingService, Logger logger)
{
_configService = configService;
_parsingService = parsingService;
_logger = logger;
}
public Type ConfigContract => typeof(TSettings);
public virtual ProviderMessage Message => null;
public virtual IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
var config = (IProviderConfig)new TSettings();
yield return new NetImportDefinition
{
Name = this.Name,
Enabled = config.Validate().IsValid && Enabled,
EnableAuto = true,
ProfileId = 1,
Implementation = GetType().Name,
Settings = config
};
}
}
public virtual ProviderDefinition Definition { get; set; }
public virtual object RequestAction(string action, IDictionary<string, string> query) { return null; }
protected TSettings Settings => (TSettings)Definition.Settings;
public abstract IList<Movie> Fetch();
public ValidationResult Test()
{
var failures = new List<ValidationFailure>();
try
{
Test(failures);
}
catch (Exception ex)
{
_logger.Error(ex, "Test aborted due to exception");
failures.Add(new ValidationFailure(string.Empty, "Test was aborted due to an error: " + ex.Message));
}
return new ValidationResult(failures);
}
protected abstract void Test(List<ValidationFailure> failures);
public override string ToString()
{
return Definition.Name;
}
}
}
@@ -0,0 +1,36 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.NetImport
{
public class NetImportBaseSettingsValidator : AbstractValidator<NetImportBaseSettings>
{
public NetImportBaseSettingsValidator()
{
RuleFor(c => c.Link).NotEmpty();
}
}
public class NetImportBaseSettings : IProviderConfig
{
private static readonly NetImportBaseSettingsValidator Validator = new NetImportBaseSettingsValidator();
public NetImportBaseSettings()
{
Link = "http://rss.imdb.com/list/";
}
[FieldDefinition(0, Label = "Link", HelpText = "Link to the list of movies.")]
public string Link { get; set; }
public bool IsValid => !string.IsNullOrWhiteSpace(Link);
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}
@@ -0,0 +1,17 @@
using Marr.Data;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.NetImport
{
public class NetImportDefinition : ProviderDefinition
{
public bool Enabled { get; set; }
public bool EnableAuto { get; set; }
public bool ShouldMonitor { get; set; }
public int ProfileId { get; set; }
public LazyLoaded<Profile> Profile { get; set; }
public string RootFolderPath { get; set; }
public override bool Enable => Enabled;
}
}
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Composition;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.NetImport
{
public interface INetImportFactory : IProviderFactory<INetImport, NetImportDefinition>
{
List<INetImport> Enabled();
}
public class NetImportFactory : ProviderFactory<INetImport, NetImportDefinition>, INetImportFactory
{
private readonly INetImportRepository _providerRepository;
private readonly Logger _logger;
public NetImportFactory(INetImportRepository providerRepository,
IEnumerable<INetImport> providers,
IContainer container,
IEventAggregator eventAggregator,
Logger logger)
: base(providerRepository, providers, container, eventAggregator, logger)
{
_providerRepository = providerRepository;
_logger = logger;
}
protected override List<NetImportDefinition> Active()
{
return base.Active().Where(c => c.Enabled).ToList();
}
public override void SetProviderCharacteristics(INetImport provider, NetImportDefinition definition)
{
base.SetProviderCharacteristics(provider, definition);
}
public List<INetImport> Enabled()
{
var enabledImporters = GetAvailableProviders().Where(n => ((NetImportDefinition)n.Definition).Enabled);
var indexers = FilterBlockedIndexers(enabledImporters);
return indexers.ToList();
}
private IEnumerable<INetImport> FilterBlockedIndexers(IEnumerable<INetImport> importers)
{
foreach (var importer in importers)
{
yield return importer;
}
}
}
}
@@ -0,0 +1,25 @@
using System.Collections;
using System.Collections.Generic;
namespace NzbDrone.Core.NetImport
{
public class NetImportPageableRequest : IEnumerable<NetImportRequest>
{
private readonly IEnumerable<NetImportRequest> _enumerable;
public NetImportPageableRequest(IEnumerable<NetImportRequest> enumerable)
{
_enumerable = enumerable;
}
public IEnumerator<NetImportRequest> GetEnumerator()
{
return _enumerable.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return _enumerable.GetEnumerator();
}
}
}
@@ -0,0 +1,48 @@
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Core.NetImport
{
public class NetImportPageableRequestChain
{
private List<List<NetImportPageableRequest>> _chains;
public NetImportPageableRequestChain()
{
_chains = new List<List<NetImportPageableRequest>>();
_chains.Add(new List<NetImportPageableRequest>());
}
public int Tiers => _chains.Count;
public IEnumerable<NetImportPageableRequest> GetAllTiers()
{
return _chains.SelectMany(v => v);
}
public IEnumerable<NetImportPageableRequest> GetTier(int index)
{
return _chains[index];
}
public void Add(IEnumerable<NetImportRequest> request)
{
if (request == null) return;
_chains.Last().Add(new NetImportPageableRequest(request));
}
public void AddTier(IEnumerable<NetImportRequest> request)
{
AddTier();
Add(request);
}
public void AddTier()
{
if (_chains.Last().Count == 0) return;
_chains.Add(new List<NetImportPageableRequest>());
}
}
}
@@ -0,0 +1,20 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.NetImport
{
public interface INetImportRepository : IProviderRepository<NetImportDefinition>
{
}
public class NetImportRepository : ProviderRepository<NetImportDefinition>, INetImportRepository
{
public NetImportRepository(IMainDatabase database, IEventAggregator eventAggregator)
: base(database, eventAggregator)
{
}
}
}
@@ -0,0 +1,21 @@
using NzbDrone.Common.Http;
namespace NzbDrone.Core.NetImport
{
public class NetImportRequest
{
public HttpRequest HttpRequest { get; private set; }
public NetImportRequest(string url, HttpAccept httpAccept)
{
HttpRequest = new HttpRequest(url, httpAccept);
}
public NetImportRequest(HttpRequest httpRequest)
{
HttpRequest = httpRequest;
}
public HttpUri Url => HttpRequest.Url;
}
}
@@ -0,0 +1,24 @@
using NzbDrone.Common.Http;
namespace NzbDrone.Core.NetImport
{
public class NetImportResponse
{
private readonly NetImportRequest _netImport;
private readonly HttpResponse _httpResponse;
public NetImportResponse(NetImportRequest netImport, HttpResponse httpResponse)
{
_netImport = netImport;
_httpResponse = httpResponse;
}
public NetImportRequest Request => _netImport;
public HttpRequest HttpRequest => _httpResponse.Request;
public HttpResponse HttpResponse => _httpResponse;
public string Content => _httpResponse.Content;
}
}
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport
{
public interface IFetchNetImport
{
List<Movie> Fetch(int listId, bool onlyEnableAuto);
List<Movie> FetchAndFilter(int listId, bool onlyEnableAuto);
}
public class NetImportSearchService : IFetchNetImport, IExecute<NetImportSyncCommand>
{
private readonly Logger _logger;
private readonly INetImportFactory _netImportFactory;
private readonly IMovieService _movieService;
private readonly ISearchForNewMovie _movieSearch;
private readonly IRootFolderService _rootFolder;
public NetImportSearchService(INetImportFactory netImportFactory, IMovieService movieService,
ISearchForNewMovie movieSearch, IRootFolderService rootFolder, Logger logger)
{
_netImportFactory = netImportFactory;
_movieService = movieService;
_movieSearch = movieSearch;
_rootFolder = rootFolder;
_logger = logger;
}
public List<Movie> Fetch(int listId, bool onlyEnableAuto = false)
{
return MovieListSearch(listId, onlyEnableAuto);
}
public List<Movie> FetchAndFilter(int listId, bool onlyEnableAuto)
{
var movies = MovieListSearch(listId, onlyEnableAuto);
return movies.Where(x => !_movieService.MovieExists(x)).ToList();
}
public List<Movie> MovieListSearch(int listId, bool onlyEnableAuto = false)
{
var movies = new List<Movie>();
var importLists = _netImportFactory.GetAvailableProviders();
var lists = listId == 0 ? importLists : importLists.Where(n => ((NetImportDefinition)n.Definition).Id == listId);
if (onlyEnableAuto)
{
lists = importLists.Where(a => ((NetImportDefinition)a.Definition).EnableAuto);
}
foreach (var list in lists)
{
movies.AddRange(list.Fetch());
}
_logger.Debug("Found {0} movies from list(s) {1}", movies.Count, string.Join(", ", lists.Select(l => l.Definition.Name)));
return movies;
}
public void Execute(NetImportSyncCommand message)
{
var movies = FetchAndFilter(0, true);
_logger.Debug("Found {0} movies on your auto enabled lists not in your library", movies.Count);
foreach (var movie in movies)
{
var mapped = _movieSearch.MapMovieToTmdbMovie(movie);
if (mapped != null)
{
_movieService.AddMovie(mapped);
}
}
}
}
}
@@ -0,0 +1,12 @@
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Core.NetImport
{
public class NetImportSyncCommand : Command
{
public override bool SendUpdatesToClient => true;
public int listId = 0;
}
}
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.PassThePopcorn;
using NzbDrone.Core.Parser;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport.RSSImport
{
public class RSSImport : HttpNetImportBase<RSSImportSettings>
{
public override string Name => "RSSList";
public override bool Enabled => true;
public override bool EnableAuto => true;
public RSSImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger)
{ }
public override IEnumerable<ProviderDefinition> DefaultDefinitions
{
get
{
foreach (var def in base.DefaultDefinitions)
{
yield return def;
}
yield return new NetImportDefinition
{
Name = "IMDb Watchlist",
Enabled = Enabled,
EnableAuto = true,
ProfileId = 1,
Implementation = GetType().Name,
Settings = new RSSImportSettings { Link = "http://rss.imdb.com/list/YOURLISTID" },
};
}
}
public override INetImportRequestGenerator GetRequestGenerator()
{
return new RSSImportRequestGenerator() { Settings = Settings };
}
public override IParseNetImportResponse GetParser()
{
return new RSSImportParser(Settings);
}
}
}
@@ -0,0 +1,236 @@
using Newtonsoft.Json;
using NzbDrone.Core.NetImport.Exceptions;
using NzbDrone.Core.Tv;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.NetImport.RSSImport
{
public class RSSImportParser : IParseNetImportResponse
{
private readonly RSSImportSettings _settings;
private NetImportResponse _importResponse;
private readonly Logger _logger;
private static readonly Regex ReplaceEntities = new Regex("&[a-z]+;", RegexOptions.Compiled | RegexOptions.IgnoreCase);
public RSSImportParser(RSSImportSettings settings)
{
_settings = settings;
}
public virtual IList<Movie> ParseResponse(NetImportResponse importResponse)
{
_importResponse = importResponse;
var movies = new List<Movie>();
if (!PreProcess(importResponse))
{
return movies;
}
var document = LoadXmlDocument(importResponse);
var items = GetItems(document);
foreach (var item in items)
{
try
{
var reportInfo = ProcessItem(item);
movies.AddIfNotNull(reportInfo);
}
catch (Exception itemEx)
{
//itemEx.Data.Add("Item", item.Title());
_logger.Error(itemEx, "An error occurred while processing feed item from " + importResponse.Request.Url);
}
}
return movies;
}
protected virtual XDocument LoadXmlDocument(NetImportResponse indexerResponse)
{
try
{
var content = indexerResponse.Content;
content = ReplaceEntities.Replace(content, ReplaceEntity);
using (var xmlTextReader = XmlReader.Create(new StringReader(content), new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore, IgnoreComments = true }))
{
return XDocument.Load(xmlTextReader);
}
}
catch (XmlException ex)
{
var contentSample = indexerResponse.Content.Substring(0, Math.Min(indexerResponse.Content.Length, 512));
_logger.Debug("Truncated response content (originally {0} characters): {1}", indexerResponse.Content.Length, contentSample);
ex.Data.Add("ContentLength", indexerResponse.Content.Length);
ex.Data.Add("ContentSample", contentSample);
throw;
}
}
protected virtual string ReplaceEntity(Match match)
{
try
{
var character = WebUtility.HtmlDecode(match.Value);
return string.Concat("&#", (int)character[0], ";");
}
catch
{
return match.Value;
}
}
protected virtual Movie CreateNewMovie()
{
return new Movie();
}
protected virtual bool PreProcess(NetImportResponse indexerResponse)
{
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode);
}
if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/html") &&
indexerResponse.HttpRequest.Headers.Accept != null && !indexerResponse.HttpRequest.Headers.Accept.Contains("text/html"))
{
throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
}
return true;
}
protected Movie ProcessItem(XElement item)
{
var releaseInfo = CreateNewMovie();
releaseInfo = ProcessItem(item, releaseInfo);
//_logger.Trace("Parsed: {0}", releaseInfo.Title);
return PostProcess(item, releaseInfo);
}
protected virtual Movie ProcessItem(XElement item, Movie releaseInfo)
{
var result = Parser.Parser.ParseMovieTitle(GetTitle(item));
releaseInfo.Title = GetTitle(item);
if (result != null)
{
releaseInfo.Title = result.MovieTitle;
releaseInfo.Year = result.Year;
releaseInfo.ImdbId = result.ImdbId;
}
try
{
if (releaseInfo.ImdbId.IsNullOrWhiteSpace())
{
releaseInfo.ImdbId = GetImdbId(item);
}
}
catch (Exception)
{
_logger.Debug("Unable to extract Imdb Id :(.");
}
return releaseInfo;
}
protected virtual Movie PostProcess(XElement item, Movie releaseInfo)
{
return releaseInfo;
}
protected virtual string GetTitle(XElement item)
{
return item.TryGetValue("title", "Unknown");
}
protected virtual DateTime GetPublishDate(XElement item)
{
var dateString = item.TryGetValue("pubDate");
if (dateString.IsNullOrWhiteSpace())
{
throw new UnsupportedFeedException("Rss feed must have a pubDate element with a valid publish date.");
}
return XElementExtensions.ParseDate(dateString);
}
protected virtual string GetImdbId(XElement item)
{
var url = item.TryGetValue("link");
if (url.IsNullOrWhiteSpace())
{
return "";
}
return Parser.Parser.ParseImdbId(url);
}
protected IEnumerable<XElement> GetItems(XDocument document)
{
var root = document.Root;
if (root == null)
{
return Enumerable.Empty<XElement>();
}
var channel = root.Element("channel");
if (channel == null)
{
return Enumerable.Empty<XElement>();
}
return channel.Elements("item");
}
protected virtual string ParseUrl(string value)
{
if (value.IsNullOrWhiteSpace())
{
return null;
}
try
{
var url = _importResponse.HttpRequest.Url + new HttpUri(value);
return url.FullUri;
}
catch (Exception ex)
{
_logger.Debug(ex, string.Format("Failed to parse Url {0}, ignoring.", value));
return null;
}
}
}
}
@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.IndexerSearch.Definitions;
namespace NzbDrone.Core.NetImport.RSSImport
{
public class RSSImportRequestGenerator : INetImportRequestGenerator
{
public RSSImportSettings Settings { get; set; }
public virtual NetImportPageableRequestChain GetMovies()
{
var pageableRequests = new NetImportPageableRequestChain();
pageableRequests.Add(GetMovies(null));
return pageableRequests;
}
//public NetImportPageableRequestChain GetSearchRequests(MovieSearchCriteria searchCriteria)
//{
// return new NetImportPageableRequestChain();
//}
private IEnumerable<NetImportRequest> GetMovies(string searchParameters)
{
var request = new NetImportRequest($"{Settings.Link.Trim()}", HttpAccept.Rss);
yield return request;
}
}
}
@@ -0,0 +1,22 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.NetImport.RSSImport
{
public class RSSImportSettings : NetImportBaseSettings
{
//private const string helpLink = "https://imdb.com";
public RSSImportSettings()
{
Link = "http://rss.yoursite.com";
}
[FieldDefinition(0, Label = "RSS Link", HelpText = "Link to the rss feed of movies.")]
public new string Link { get; set; }
}
}
@@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Windows.Forms;
using System.Xml.Serialization;
namespace NzbDrone.Core.NetImport.StevenLu
{
public class StevenLuResponse
{
public string title { get; set; }
public string imdb_id { get; set; }
public string poster_url { get; set; }
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.PassThePopcorn;
using NzbDrone.Core.Parser;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport.StevenLu
{
public class StevenLuImport : HttpNetImportBase<StevenLuSettings>
{
public override string Name => "StevenLu";
public override bool Enabled => true;
public override bool EnableAuto => true;
public StevenLuImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger)
{ }
public override INetImportRequestGenerator GetRequestGenerator()
{
return new StevenLuRequestGenerator() { Settings = Settings };
}
public override IParseNetImportResponse GetParser()
{
return new StevenLuParser(Settings);
}
}
}
@@ -0,0 +1,82 @@
using Newtonsoft.Json;
using NzbDrone.Core.NetImport.Exceptions;
using NzbDrone.Core.Tv;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.NetImport.StevenLu
{
public class StevenLuParser : IParseNetImportResponse
{
private readonly StevenLuSettings _settings;
private NetImportResponse _importResponse;
private readonly Logger _logger;
public StevenLuParser(StevenLuSettings settings)
{
_settings = settings;
}
public IList<Tv.Movie> ParseResponse(NetImportResponse importResponse)
{
_importResponse = importResponse;
var movies = new List<Tv.Movie>();
if (!PreProcess(_importResponse))
{
return movies;
}
var jsonResponse = JsonConvert.DeserializeObject<List<StevenLuResponse>>(_importResponse.Content);
// no movies were return
if (jsonResponse == null)
{
return movies;
}
foreach (var item in jsonResponse)
{
movies.AddIfNotNull(new Tv.Movie()
{
Title = item.title,
ImdbId = item.imdb_id
});
}
return movies;
}
protected virtual bool PreProcess(NetImportResponse indexerResponse)
{
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode);
}
if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/json") &&
indexerResponse.HttpRequest.Headers.Accept != null && !indexerResponse.HttpRequest.Headers.Accept.Contains("text/json"))
{
throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
}
return true;
}
}
}
@@ -0,0 +1,28 @@
using NzbDrone.Common.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.NetImport.StevenLu
{
public class StevenLuRequestGenerator : INetImportRequestGenerator
{
public StevenLuSettings Settings { get; set; }
public virtual NetImportPageableRequestChain GetMovies()
{
var pageableRequests = new NetImportPageableRequestChain();
pageableRequests.Add(GetMovies(null));
return pageableRequests;
}
private IEnumerable<NetImportRequest> GetMovies(string searchParameters)
{
var request = new NetImportRequest($"{Settings.Link.Trim()}", HttpAccept.Json);
yield return request;
}
}
}
@@ -0,0 +1,22 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.NetImport.StevenLu
{
public class StevenLuSettings : NetImportBaseSettings
{
public StevenLuSettings()
{
Link = "https://s3.amazonaws.com/popular-movies/movies.json";
}
[FieldDefinition(0, Label = "URL", HelpText = "Don't change this unless you know what you are doing.")]
public new string Link { get; set; }
}
}
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Windows.Forms;
using System.Xml.Serialization;
namespace NzbDrone.Core.NetImport.Trakt
{
public class Ids
{
public int trakt { get; set; }
public string slug { get; set; }
public string imdb { get; set; }
public int tmdb { get; set; }
}
public class Movie
{
public string title { get; set; }
public int? year { get; set; }
public Ids ids { get; set; }
}
public class TraktResponse
{
public int rank { get; set; }
public string listed_at { get; set; }
public string type { get; set; }
public Movie movie { get; set; }
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.PassThePopcorn;
using NzbDrone.Core.Parser;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.NetImport.Trakt
{
public class TraktImport : HttpNetImportBase<TraktSettings>
{
public override string Name => "Trakt List";
public override bool Enabled => true;
public override bool EnableAuto => false;
public TraktImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger)
: base(httpClient, configService, parsingService, logger)
{ }
public override INetImportRequestGenerator GetRequestGenerator()
{
return new TraktRequestGenerator() { Settings = Settings };
}
public override IParseNetImportResponse GetParser()
{
return new TraktParser(Settings);
}
}
}
@@ -0,0 +1,9 @@
namespace NzbDrone.Core.NetImport.Trakt
{
public enum TraktListType
{
WatchList = 0,
Watched = 1,
CustomList = 2
}
}
@@ -0,0 +1,84 @@
using Newtonsoft.Json;
using NzbDrone.Core.NetImport.Exceptions;
using NzbDrone.Core.Tv;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Exceptions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.NetImport.Trakt
{
public class TraktParser : IParseNetImportResponse
{
private readonly TraktSettings _settings;
private NetImportResponse _importResponse;
private readonly Logger _logger;
public TraktParser(TraktSettings settings)
{
_settings = settings;
}
public IList<Tv.Movie> ParseResponse(NetImportResponse importResponse)
{
_importResponse = importResponse;
var movies = new List<Tv.Movie>();
if (!PreProcess(_importResponse))
{
return movies;
}
var jsonResponse = JsonConvert.DeserializeObject<List<TraktResponse>>(_importResponse.Content);
// no movies were return
if (jsonResponse == null)
{
return movies;
}
foreach (var movie in jsonResponse)
{
movies.AddIfNotNull(new Tv.Movie()
{
Title = movie.movie.title,
ImdbId = movie.movie.ids.imdb,
TmdbId = movie.movie.ids.tmdb,
Year = (movie.movie.year ?? 0)
});
}
return movies;
}
protected virtual bool PreProcess(NetImportResponse indexerResponse)
{
if (indexerResponse.HttpResponse.StatusCode != HttpStatusCode.OK)
{
throw new NetImportException(indexerResponse, "Indexer API call resulted in an unexpected StatusCode [{0}]", indexerResponse.HttpResponse.StatusCode);
}
if (indexerResponse.HttpResponse.Headers.ContentType != null && indexerResponse.HttpResponse.Headers.ContentType.Contains("text/json") &&
indexerResponse.HttpRequest.Headers.Accept != null && !indexerResponse.HttpRequest.Headers.Accept.Contains("text/json"))
{
throw new NetImportException(indexerResponse, "Indexer responded with html content. Site is likely blocked or unavailable.");
}
return true;
}
}
}
@@ -0,0 +1,46 @@
using NzbDrone.Common.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Core.NetImport.Trakt
{
public class TraktRequestGenerator : INetImportRequestGenerator
{
public TraktSettings Settings { get; set; }
public virtual NetImportPageableRequestChain GetMovies()
{
var pageableRequests = new NetImportPageableRequestChain();
pageableRequests.Add(GetMovies(null));
return pageableRequests;
}
private IEnumerable<NetImportRequest> GetMovies(string searchParameters)
{
var link = $"{Settings.Link.Trim()}{Settings.Username.Trim()}";
switch (Settings.ListType)
{
case (int)TraktListType.CustomList:
link = link + $"/lists/{Settings.Listname.Trim()}/items/movies";
break;
case (int)TraktListType.WatchList:
link = link + "/watchlist/movies";
break;
case (int)TraktListType.Watched:
link = link + "/watched/movies";
break;
}
var request = new NetImportRequest($"{link}", HttpAccept.Json);
request.HttpRequest.Headers.Add("trakt-api-version", "2");
request.HttpRequest.Headers.Add("trakt-api-key", "657bb899dcb81ec8ee838ff09f6e013ff7c740bf0ccfa54dd41e791b9a70b2f0");
yield return request;
}
}
}
@@ -0,0 +1,42 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.NetImport.Trakt
{
public class TraktSettingsValidator : AbstractValidator<TraktSettings>
{
public TraktSettingsValidator()
{
RuleFor(c => c.Link).ValidRootUrl();
RuleFor(c => c.Username).NotEmpty();
}
}
public class TraktSettings : NetImportBaseSettings
{
public TraktSettings()
{
Link = "https://api.trakt.tv/users/";
Username = "";
Listname = "";
}
[FieldDefinition(0, Label = "Trakt API URL", HelpText = "Link to to Trakt API URL, do not change unless you know what you are doing.")]
public new string Link { get; set; }
[FieldDefinition(1, Label = "Trakt List Type", Type = FieldType.Select, SelectOptions = typeof(TraktListType), HelpText = "Trakt list type, custom or watchlist")]
public int ListType { get; set; }
[FieldDefinition(2, Label = "Trakt Username", HelpText = "Trakt Username the list belongs to.")]
public string Username { get; set; }
[FieldDefinition(3, Label = "Trakt List Name", HelpText = "Required for Custom List")]
public string Listname { get; set; }
}
}
@@ -31,7 +31,7 @@ namespace NzbDrone.Core.Notifications.CustomScript
{ {
var movie = message.Movie; var movie = message.Movie;
var remoteMovie = message.RemoteMovie; var remoteMovie = message.RemoteMovie;
var releaseGroup = remoteMovie.ParsedEpisodeInfo.ReleaseGroup; var releaseGroup = remoteMovie.ParsedMovieInfo.ReleaseGroup;
var environmentVariables = new StringDictionary(); var environmentVariables = new StringDictionary();
environmentVariables.Add("Radarr_EventType", "Grab"); environmentVariables.Add("Radarr_EventType", "Grab");
@@ -56,6 +56,7 @@ namespace NzbDrone.Core.Notifications.CustomScript
environmentVariables.Add("Radarr_EventType", "Download"); environmentVariables.Add("Radarr_EventType", "Download");
environmentVariables.Add("Radarr_Movie_Id", movie.Id.ToString()); environmentVariables.Add("Radarr_Movie_Id", movie.Id.ToString());
environmentVariables.Add("Radarr_Movie_Title", movie.Title); environmentVariables.Add("Radarr_Movie_Title", movie.Title);
environmentVariables.Add("Radarr_Movie_Path", movie.Path);
environmentVariables.Add("Radarr_Movie_ImdbId", movie.ImdbId.ToString()); environmentVariables.Add("Radarr_Movie_ImdbId", movie.ImdbId.ToString());
environmentVariables.Add("Radarr_MovieFile_Id", movieFile.Id.ToString()); environmentVariables.Add("Radarr_MovieFile_Id", movieFile.Id.ToString());
environmentVariables.Add("Radarr_MovieFile_RelativePath", movieFile.RelativePath); environmentVariables.Add("Radarr_MovieFile_RelativePath", movieFile.RelativePath);
@@ -84,16 +85,6 @@ namespace NzbDrone.Core.Notifications.CustomScript
public override void OnRename(Series series) public override void OnRename(Series series)
{ {
var environmentVariables = new StringDictionary();
environmentVariables.Add("Radarr_EventType", "Rename");
environmentVariables.Add("Radarr_Series_Id", series.Id.ToString());
environmentVariables.Add("Radarr_Series_Title", series.Title);
environmentVariables.Add("Radarr_Series_Path", series.Path);
environmentVariables.Add("Radarr_Series_TvdbId", series.TvdbId.ToString());
environmentVariables.Add("Radarr_Series_Type", series.SeriesType.ToString());
ExecuteScript(environmentVariables);
} }
public override string Name => "Custom Script"; public override string Name => "Custom Script";
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Notifications.Email
public EmailSettingsValidator() public EmailSettingsValidator()
{ {
RuleFor(c => c.Server).NotEmpty(); RuleFor(c => c.Server).NotEmpty();
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
RuleFor(c => c.From).NotEmpty(); RuleFor(c => c.From).NotEmpty();
RuleFor(c => c.To).NotEmpty(); RuleFor(c => c.To).NotEmpty();
} }
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Notifications.Growl
public GrowlSettingsValidator() public GrowlSettingsValidator()
{ {
RuleFor(c => c.Host).ValidHost(); RuleFor(c => c.Host).ValidHost();
RuleFor(c => c.Port).GreaterThan(0); RuleFor(c => c.Port).InclusiveBetween(1, 65535);
} }
} }

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