From aa977eb2d59c8adad563a0a985b222a9eaea6cfd Mon Sep 17 00:00:00 2001 From: geogolem Date: Thu, 16 Feb 2017 21:26:38 -0500 Subject: [PATCH 1/2] fully functional traktAuthentication using api.couchpota.to with comments for when updated RadarrAPI is deployed --- package.json | 1 + .../Config/NetImportConfigResource.cs | 6 + .../Configuration/ConfigService.cs | 39 ++++++ .../Configuration/IConfigService.cs | 7 + .../NetImport/Trakt/TraktImport.cs | 5 +- .../NetImport/Trakt/TraktRequestGenerator.cs | 69 +++++++++- .../NetImport/Options/NetImportOptionsView.js | 124 +++++++++++++----- .../Options/NetImportOptionsViewTemplate.hbs | 20 ++- 8 files changed, 237 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index c3556ed7f..45f05450c 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "run-sequence": "1.1.1", "streamqueue": "1.1.0", "tar.gz": "0.1.1", + "url-search-params": "^0.6.1", "webpack": "1.12.0", "webpack-stream": "2.1.0" } diff --git a/src/NzbDrone.Api/Config/NetImportConfigResource.cs b/src/NzbDrone.Api/Config/NetImportConfigResource.cs index 6dae86e4d..942a2177d 100644 --- a/src/NzbDrone.Api/Config/NetImportConfigResource.cs +++ b/src/NzbDrone.Api/Config/NetImportConfigResource.cs @@ -8,6 +8,9 @@ namespace NzbDrone.Api.Config public int NetImportSyncInterval { get; set; } public string ListSyncLevel { get; set; } public string ImportExclusions { get; set; } + public string TraktAuthToken { get; set; } + public string TraktRefreshToken { get; set; } + public int TraktTokenExpiry { get; set; } } public static class NetImportConfigResourceMapper @@ -19,6 +22,9 @@ namespace NzbDrone.Api.Config NetImportSyncInterval = model.NetImportSyncInterval, ListSyncLevel = model.ListSyncLevel, ImportExclusions = model.ImportExclusions, + TraktAuthToken = model.TraktAuthToken, + TraktRefreshToken = model.TraktRefreshToken, + TraktTokenExpiry = model.TraktTokenExpiry, }; } } diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index c37d71a6c..5d7911e06 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -117,6 +117,45 @@ namespace NzbDrone.Core.Configuration set { SetValue("NetImportSyncInterval", value); } } + + public string TraktAuthToken + { + get { return GetValue("TraktAuthToken", string.Empty); } + + set { SetValue("TraktAuthToken", value); } + } + + public string TraktRefreshToken + { + get { return GetValue("TraktRefreshToken", string.Empty); } + + set {SetValue("TraktRefreshToken", value); } + } + + public int TraktTokenExpiry + { + get { return GetValueInt("TraktTokenExpiry", 0); } + + set { SetValue("TraktTokenExpiry", value); } + } + + public string NewTraktAuthToken + { + get {return GetValue("NewTraktAuthToken", string.Empty); } + set { SetValue("NewTraktAuthToken", value); } + } + + public string NewTraktRefreshToken + { + get {return GetValue("NewTraktRefreshToken", string.Empty); } + set { SetValue("NewTraktRefreshToken", value); } + } + + public int NewTraktTokenExpiry + { + get {return GetValueInt("NewTraktTokenExpiry", 0); } + set { SetValue("NewTraktTokenExpiry", value); } + } public string ListSyncLevel { diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index 7a4ca2625..f0f5e54f7 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -51,6 +51,12 @@ namespace NzbDrone.Core.Configuration int NetImportSyncInterval { get; set; } string ListSyncLevel { get; set; } string ImportExclusions { get; set; } + string TraktAuthToken { get; set; } + string TraktRefreshToken { get; set; } + int TraktTokenExpiry { get; set; } + string NewTraktAuthToken { get; set; } + string NewTraktRefreshToken {get; set; } + int NewTraktTokenExpiry { get; set; } //UI int FirstDayOfWeek { get; set; } @@ -60,6 +66,7 @@ namespace NzbDrone.Core.Configuration string LongDateFormat { get; set; } string TimeFormat { get; set; } bool ShowRelativeDates { get; set; } + bool EnableColorImpairedMode { get; set; } //Internal diff --git a/src/NzbDrone.Core/NetImport/Trakt/TraktImport.cs b/src/NzbDrone.Core/NetImport/Trakt/TraktImport.cs index 7cacb4084..3460dbba0 100644 --- a/src/NzbDrone.Core/NetImport/Trakt/TraktImport.cs +++ b/src/NzbDrone.Core/NetImport/Trakt/TraktImport.cs @@ -10,14 +10,15 @@ namespace NzbDrone.Core.NetImport.Trakt public override string Name => "Trakt List"; public override bool Enabled => true; public override bool EnableAuto => false; + public IConfigService _configService; public TraktImport(IHttpClient httpClient, IConfigService configService, IParsingService parsingService, Logger logger) : base(httpClient, configService, parsingService, logger) - { } + { _configService = configService; } public override INetImportRequestGenerator GetRequestGenerator() { - return new TraktRequestGenerator() { Settings = Settings }; + return new TraktRequestGenerator() { Settings = Settings, _configService=_configService }; } public override IParseNetImportResponse GetParser() diff --git a/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs b/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs index 50f9e2e2f..0405abcbd 100644 --- a/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs +++ b/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs @@ -1,10 +1,31 @@ using NzbDrone.Common.Http; +using System; using System.Collections.Generic; +using System.Net; +using System.IO; +using Newtonsoft.Json.Linq; +using NzbDrone.Core.Configuration; + namespace NzbDrone.Core.NetImport.Trakt { + public class refreshRequestResponse + { + //the next 3 lines can eventually be removed and replaced with the ones marked below + public bool success { get; set; } + public string oauth { get; set; } + public string refresh { get; set; } + /*//replace with the lines below when radarrAPI changes have been merged + public string access_token { get; set; } + public string token_type { get; set; } + public int expires_in { get; set; } + public string refresh_token { get; set; } + public string scope { get; set; }*/ + } + public class TraktRequestGenerator : INetImportRequestGenerator { + public IConfigService _configService; public TraktSettings Settings { get; set; } public virtual NetImportPageableRequestChain GetMovies() @@ -58,10 +79,56 @@ namespace NzbDrone.Core.NetImport.Trakt link = link + "/movies/watched/all" + filters; break; } + if (_configService.TraktRefreshToken != string.Empty) + { + //tokens were overwritten with something other than nothing + if (_configService.NewTraktTokenExpiry > _configService.TraktTokenExpiry) + { + //but our refreshedTokens are more current + _configService.TraktAuthToken = _configService.NewTraktAuthToken; + _configService.TraktRefreshToken = _configService.NewTraktRefreshToken; + _configService.TraktTokenExpiry = _configService.NewTraktTokenExpiry; + } + + Int32 unixTime= (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; + + if ( unixTime > _configService.TraktTokenExpiry) + { + //the next line should eventually be removed and replaced with the one commented out below it + var url = "https://api.couchpota.to/authorize/trakt_refresh?token="+_configService.TraktRefreshToken; + //var url = "https://radarr.aeonlucid.com/authorize/trakt_refresh?token="+_configService.TraktRefreshToken; + + HttpWebRequest rquest = (HttpWebRequest)WebRequest.Create(url); + string rsponseString = string.Empty; + using (HttpWebResponse rsponse = (HttpWebResponse)rquest.GetResponse()) + using (Stream stream = rsponse.GetResponseStream()) + using (StreamReader reader = new StreamReader(stream)) + { + rsponseString = reader.ReadToEnd(); + } + refreshRequestResponse j1 = Newtonsoft.Json.JsonConvert.DeserializeObject(rsponseString); + _configService.TraktAuthToken = j1.oauth; //eventually replace with j1.access_token + _configService.TraktRefreshToken = j1.refresh; //eventually replace with j1.refresh_token + + //lets have it expire in 8 weeks (4838400 seconds) + _configService.TraktTokenExpiry = unixTime + 4838400; + + //store the refreshed tokens in case they get overwritten by an old set of tokens + _configService.NewTraktAuthToken = _configService.TraktAuthToken; + _configService.NewTraktRefreshToken = _configService.TraktRefreshToken; + _configService.NewTraktTokenExpiry = _configService.TraktTokenExpiry; + } + } var request = new NetImportRequest($"{link}", HttpAccept.Json); request.HttpRequest.Headers.Add("trakt-api-version", "2"); - request.HttpRequest.Headers.Add("trakt-api-key", "657bb899dcb81ec8ee838ff09f6e013ff7c740bf0ccfa54dd41e791b9a70b2f0"); + //request.HttpRequest.Headers.Add("trakt-api-key", "657bb899dcb81ec8ee838ff09f6e013ff7c740bf0ccfa54dd41e791b9a70b2f0"); //radarr + //the line below should eventually be replaced with the one commented out above + request.HttpRequest.Headers.Add("trakt-api-key", "8a54ed7b5e1b56d874642770ad2e8b73e2d09d6e993c3a92b1e89690bb1c9014"); //couchpotato + if (_configService.TraktAuthToken != null) + { + request.HttpRequest.Headers.Add("Authorization", "Bearer " + _configService.TraktAuthToken); + } yield return request; } diff --git a/src/UI/Settings/NetImport/Options/NetImportOptionsView.js b/src/UI/Settings/NetImport/Options/NetImportOptionsView.js index 68cedfe77..3a65cac63 100644 --- a/src/UI/Settings/NetImport/Options/NetImportOptionsView.js +++ b/src/UI/Settings/NetImport/Options/NetImportOptionsView.js @@ -6,11 +6,45 @@ require('../../../Mixins/TagInput'); require('bootstrap'); require('bootstrap.tagsinput'); +//if ('searchParams' in HTMLAnchorElement.prototype) { +// var URLSearchParams = require('url-search-params-polyfill'); +//} + +var URLSearchParams = require('url-search-params'); + +var q = window.location; +var callback_url = q.protocol+'//'+q.hostname+(q.port ? ':' + q.port : '')+'/settings/netimport'; var view = Marionette.ItemView.extend({ - template : 'Settings/NetImport/Options/NetImportOptionsViewTemplate', + template : 'Settings/NetImport/Options/NetImportOptionsViewTemplate', + events : { + 'click .x-reset-trakt-tokens' : '_resetTraktTokens', + 'click .x-revoke-trakt-tokens' : '_revokeTraktTokens' + }, - ui : { - importExclusions : '.x-import-exclusions' + initialize : function() { + + }, + + onShow : function() { + var params = new URLSearchParams(window.location.search); + var oauth = params.get('oauth'); + var refresh=params.get('refresh'); + if (oauth && refresh){ + history.pushState('object', 'title', callback_url); + this.ui.authToken.val(oauth).trigger('change'); + this.ui.refreshToken.val(refresh).trigger('change'); + this.ui.tokenExpiry.val(Math.floor(Date.now() / 1000) + 4838400).trigger('change'); // this means the token will expire in 8 weeks (4838400 seconds) + //this.model.isSaved = false; + window.alert("Trakt Authentication Complete - Click Save to make the change take effect"); + } + if (this.ui.authToken.val() && this.ui.refreshToken.val()){ + this.ui.resetTokensButton.hide(); + this.ui.revokeTokensButton.show(); + } else { + this.ui.resetTokensButton.show(); + this.ui.revokeTokensButton.hide(); + } + }, onRender : function() { @@ -20,53 +54,83 @@ var view = Marionette.ItemView.extend({ /*itemText : function(item) { var uri; var text; - if (item.startsWith('tt')) { - uri = window.NzbDrone.ApiRoot + '/movies/lookup/imdb?imdbId='+item; - } - else { - uri = window.NzbDrone.ApiRoot + '/movies/lookup/tmdb?tmdbId='+item; - } - var promise = $.ajax({ - url : uri, - type : 'GET', - async : false, - }); - promise.success(function(response) { + if (item.startsWith('tt')) { + uri = window.NzbDrone.ApiRoot + '/movies/lookup/imdb?imdbId='+item; + } + else { + uri = window.NzbDrone.ApiRoot + '/movies/lookup/tmdb?tmdbId='+item; + } + var promise = $.ajax({ + url : uri, + type : 'GET', + async : false, + }); + promise.success(function(response) { text=response['title']+' ('+response['year']+')'; - }); + }); - promise.error(function(request, status, error) { + promise.error(function(request, status, error) { text=item; - }); + }); return text; }*/ - }); - this.ui.importExclusions.on('beforeItemAdd', function(event) { + }); + this.ui.importExclusions.on('beforeItemAdd', function(event) { var uri; - if (event.item.startsWith('tt')) { + if (event.item.startsWith('tt')) { uri = window.NzbDrone.ApiRoot + '/movies/lookup/imdb?imdbId='+event.item; } else { - uri = window.NzbDrone.ApiRoot + '/movies/lookup/tmdb?tmdbId='+event.item; + uri = window.NzbDrone.ApiRoot + '/movies/lookup/tmdb?tmdbId='+event.item; } var promise = $.ajax({ - url : uri, - type : 'GET', + url : uri, + type : 'GET', async : false, - }); + }); promise.success(function(response) { event.cancel=false; - }); + }); - promise.error(function(request, status, error) { + promise.error(function(request, status, error) { event.cancel = true; - window.alert(event.item+' is not a valid! Must be valid tt#### IMDB ID or #### TMDB ID'); - }); - }); + window.alert(event.item+' is not a valid! Must be valid tt#### IMDB ID or #### TMDB ID'); + }); + }); + }, + + ui : { + resetTraktTokens : '.x-reset-trakt-tokens', + authToken : '.x-trakt-auth-token', + refreshToken : '.x-trakt-refresh-token', + resetTokensButton : '.x-reset-trakt-tokens', + revokeTokensButton : '.x-revoke-trakt-tokens', + tokenExpiry : '.x-trakt-token-expiry', + importExclusions : '.x-import-exclusions' }, + _resetTraktTokens : function() { + if (window.confirm("Proceed to trakt.tv for authentication?\nYou will then be redirected back here.")){ + window.location='https://api.couchpota.to/authorize/trakt/?target='+callback_url; //this eventually can be removed and replaced with the line below + //window.location='https://radarr.aeonlucid.com/authorize/trakt?target='+callback_url; + //this.ui.resetTokensButton.hide(); + } + }, + + _revokeTraktTokens : function() { + if (window.confirm("Log out of trakt.tv?")){ + //TODO: need to implement this: http://docs.trakt.apiary.io/#reference/authentication-oauth/revoke-token/revoke-an-access_token + this.ui.authToken.val('').trigger('change'); + this.ui.refreshToken.val('').trigger('change'); + this.ui.tokenExpiry.val(0).trigger('change'); + this.ui.resetTokensButton.show(); + this.ui.revokeTokensButton.hide(); + window.alert("Logged out of Trakt.tv - Click Save to make the change take effect"); + } + } }); + AsModelBoundView.call(view); AsValidatedView.call(view); diff --git a/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs b/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs index 35f9bce5f..1cfc5b6dc 100644 --- a/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs +++ b/src/UI/Settings/NetImport/Options/NetImportOptionsViewTemplate.hbs @@ -41,4 +41,22 @@ - + Trakt Authentication +
+ +
+ + +
+
+
+ +
+ +
+
+ + +
+
+ From f138d4f67742111a77c7e62a1e178247f898b954 Mon Sep 17 00:00:00 2001 From: geogolem Date: Sat, 4 Mar 2017 21:45:22 -0500 Subject: [PATCH 2/2] an updated radarrAPI has been deployed --> this commit makes trakt authentication ready to be merged to the develop branch --- .../NetImport/Trakt/TraktRequestGenerator.cs | 21 ++++++------------- .../NetImport/Options/NetImportOptionsView.js | 5 ++--- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs b/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs index 0405abcbd..c724d3967 100644 --- a/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs +++ b/src/NzbDrone.Core/NetImport/Trakt/TraktRequestGenerator.cs @@ -11,16 +11,11 @@ namespace NzbDrone.Core.NetImport.Trakt { public class refreshRequestResponse { - //the next 3 lines can eventually be removed and replaced with the ones marked below - public bool success { get; set; } - public string oauth { get; set; } - public string refresh { get; set; } - /*//replace with the lines below when radarrAPI changes have been merged - public string access_token { get; set; } + public string access_token { get; set; } public string token_type { get; set; } public int expires_in { get; set; } public string refresh_token { get; set; } - public string scope { get; set; }*/ + public string scope { get; set; } } public class TraktRequestGenerator : INetImportRequestGenerator @@ -94,9 +89,7 @@ namespace NzbDrone.Core.NetImport.Trakt if ( unixTime > _configService.TraktTokenExpiry) { - //the next line should eventually be removed and replaced with the one commented out below it - var url = "https://api.couchpota.to/authorize/trakt_refresh?token="+_configService.TraktRefreshToken; - //var url = "https://radarr.aeonlucid.com/authorize/trakt_refresh?token="+_configService.TraktRefreshToken; + var url = "http://radarr.aeonlucid.com/v1/trakt/refresh?refresh="+_configService.TraktRefreshToken; HttpWebRequest rquest = (HttpWebRequest)WebRequest.Create(url); string rsponseString = string.Empty; @@ -107,8 +100,8 @@ namespace NzbDrone.Core.NetImport.Trakt rsponseString = reader.ReadToEnd(); } refreshRequestResponse j1 = Newtonsoft.Json.JsonConvert.DeserializeObject(rsponseString); - _configService.TraktAuthToken = j1.oauth; //eventually replace with j1.access_token - _configService.TraktRefreshToken = j1.refresh; //eventually replace with j1.refresh_token + _configService.TraktAuthToken = j1.access_token; + _configService.TraktRefreshToken = j1.refresh_token; //lets have it expire in 8 weeks (4838400 seconds) _configService.TraktTokenExpiry = unixTime + 4838400; @@ -122,9 +115,7 @@ namespace NzbDrone.Core.NetImport.Trakt var request = new NetImportRequest($"{link}", HttpAccept.Json); request.HttpRequest.Headers.Add("trakt-api-version", "2"); - //request.HttpRequest.Headers.Add("trakt-api-key", "657bb899dcb81ec8ee838ff09f6e013ff7c740bf0ccfa54dd41e791b9a70b2f0"); //radarr - //the line below should eventually be replaced with the one commented out above - request.HttpRequest.Headers.Add("trakt-api-key", "8a54ed7b5e1b56d874642770ad2e8b73e2d09d6e993c3a92b1e89690bb1c9014"); //couchpotato + request.HttpRequest.Headers.Add("trakt-api-key", "964f67b126ade0112c4ae1f0aea3a8fb03190f71117bd83af6a0560a99bc52e6"); //aeon if (_configService.TraktAuthToken != null) { request.HttpRequest.Headers.Add("Authorization", "Bearer " + _configService.TraktAuthToken); diff --git a/src/UI/Settings/NetImport/Options/NetImportOptionsView.js b/src/UI/Settings/NetImport/Options/NetImportOptionsView.js index 3a65cac63..13c5fc8e9 100644 --- a/src/UI/Settings/NetImport/Options/NetImportOptionsView.js +++ b/src/UI/Settings/NetImport/Options/NetImportOptionsView.js @@ -27,7 +27,7 @@ var view = Marionette.ItemView.extend({ onShow : function() { var params = new URLSearchParams(window.location.search); - var oauth = params.get('oauth'); + var oauth = params.get('access'); var refresh=params.get('refresh'); if (oauth && refresh){ history.pushState('object', 'title', callback_url); @@ -111,8 +111,7 @@ var view = Marionette.ItemView.extend({ _resetTraktTokens : function() { if (window.confirm("Proceed to trakt.tv for authentication?\nYou will then be redirected back here.")){ - window.location='https://api.couchpota.to/authorize/trakt/?target='+callback_url; //this eventually can be removed and replaced with the line below - //window.location='https://radarr.aeonlucid.com/authorize/trakt?target='+callback_url; + window.location='http://radarr.aeonlucid.com/v1/trakt/redirect?target='+callback_url; //this.ui.resetTokensButton.hide(); } },