diff --git a/YouTubeScraper/.vs/YouTube.API/DesignTimeBuild/.dtbcache b/YouTubeScraper/.vs/YouTube.API/DesignTimeBuild/.dtbcache new file mode 100644 index 0000000..8b3fd24 Binary files /dev/null and b/YouTubeScraper/.vs/YouTube.API/DesignTimeBuild/.dtbcache differ diff --git a/YouTubeScraper/YouTube.API.Test/ClosedCaptionsTest.cs b/YouTubeScraper/YouTube.API.Test/ClosedCaptionsTest.cs index ef0aafc..5e0edfa 100644 --- a/YouTubeScraper/YouTube.API.Test/ClosedCaptionsTest.cs +++ b/YouTubeScraper/YouTube.API.Test/ClosedCaptionsTest.cs @@ -9,7 +9,7 @@ namespace YouTube.API.Test [Test] public void ValidCaptionsTest() { - YouTubeService service = new YouTubeService(); + ExtendedYouTubeService service = new ExtendedYouTubeService(); ClosedCaptionInfo info = service.VideoPlayback.List("VC5-YkjMHuw").Execute().ClosedCaptions.FirstOrDefault(); ClosedCaptionTrack track = service.Captions.Load(info).Execute(); Assert.IsNotNull(track); diff --git a/YouTubeScraper/YouTube.API.Test/DashManifestTest.cs b/YouTubeScraper/YouTube.API.Test/DashManifestTest.cs index 02d1c99..2f70f98 100644 --- a/YouTubeScraper/YouTube.API.Test/DashManifestTest.cs +++ b/YouTubeScraper/YouTube.API.Test/DashManifestTest.cs @@ -16,7 +16,7 @@ namespace YouTube.API.Test [Test] public void ValidManifestTest() { - YouTubeService service = new YouTubeService(); + ExtendedYouTubeService service = new ExtendedYouTubeService(); IReadOnlyList manifests = service.DashManifests.List("NkGbcQwWxqk").Execute(); foreach (var i in manifests) Console.WriteLine(i.Label); diff --git a/YouTubeScraper/YouTube.API.Test/VideoPlaybackTest.cs b/YouTubeScraper/YouTube.API.Test/VideoPlaybackTest.cs index 3a3af23..2717e6e 100644 --- a/YouTubeScraper/YouTube.API.Test/VideoPlaybackTest.cs +++ b/YouTubeScraper/YouTube.API.Test/VideoPlaybackTest.cs @@ -11,7 +11,7 @@ namespace YouTube.API.Test [Test] public void ValidVideoPlaybackTest() { - YouTubeService service = new YouTubeService(); + ExtendedYouTubeService service = new ExtendedYouTubeService(); VideoPlayback info = service.VideoPlayback.List("VC5-YkjMHuw").Execute(); Assert.NotNull(info); } diff --git a/YouTubeScraper/YouTube.API.Test/WatchLaterTest.cs b/YouTubeScraper/YouTube.API.Test/WatchLaterTest.cs index 1dc2c89..8849549 100644 --- a/YouTubeScraper/YouTube.API.Test/WatchLaterTest.cs +++ b/YouTubeScraper/YouTube.API.Test/WatchLaterTest.cs @@ -14,7 +14,7 @@ namespace YouTube.API.Test public class WatchLaterTest { const string testVideoId = "NkGbcQwWxqk"; - YouTubeService service; + ExtendedYouTubeService service; [SetUp] public void Setup() @@ -27,7 +27,7 @@ namespace YouTube.API.Test task.Wait(); UserCredential credential = task.Result; - service = new YouTubeService(new BaseClientService.Initializer + service = new ExtendedYouTubeService(new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "FoxTube" diff --git a/YouTubeScraper/YouTubeScraper/Authorization/AuthorizationHelpers.cs b/YouTubeScraper/YouTubeScraper/Authorization/AuthorizationHelpers.cs index 398132a..267726b 100644 --- a/YouTubeScraper/YouTubeScraper/Authorization/AuthorizationHelpers.cs +++ b/YouTubeScraper/YouTubeScraper/Authorization/AuthorizationHelpers.cs @@ -16,17 +16,15 @@ namespace YouTube.Authorization public static class AuthorizationHelpers { public static Uri Endpoint => "https://accounts.google.com/o/oauth2/approval".ToUri(); - public static string RedirectUrl => Uri.EscapeDataString(redirectUrl); - + const string refreshEndpoint = "https://oauth2.googleapis.com/token"; const string tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token"; - const string redirectUrl = "urn:ietf:wg:oauth:2.0:oob"; - public static Uri FormQueryString(ClientSecrets clientSecrets, params string[] scopes) + public static Uri FormQueryString(ClientSecrets clientSecrets, Uri redirectUri, params string[] scopes) { string clientId = Uri.EscapeDataString(clientSecrets.ClientId); string scopeStr = string.Join(' ', scopes); - return $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={RedirectUrl}&response_type=code&scope={scopeStr}".ToUri(); + return $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={redirectUri.AbsoluteUri}&response_type=code&scope={scopeStr}".ToUri(); } public static async Task ExchangeToken(ClientSecrets clientSecrets, string responseToken) @@ -36,7 +34,7 @@ namespace YouTube.Authorization Dictionary requestBody = new Dictionary { { "code", responseToken }, - { "redirect_uri", redirectUrl }, + //{ "redirect_uri", redirectUrl }, { "grant_type", "authorization_code" }, { "client_id", clientSecrets.ClientId }, { "client_secret", clientSecrets.ClientSecret } @@ -59,13 +57,53 @@ namespace YouTube.Authorization TokenType = responseData.token_type }; - AuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer() + AuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer { ClientSecrets = clientSecrets, Scopes = responseData.scope.ToString().Split(' ') }); - return new UserCredential(authorizationCodeFlow, "user", tokenResponse); + UserCredential credential = new UserCredential(authorizationCodeFlow, "user", tokenResponse); + + return credential; + } + + public static async Task RestoreUser(ClientSecrets clientSecrets, string refreshToken) + { + using HttpClient client = new HttpClient(); + + Dictionary requestBody = new Dictionary + { + { "client_id", clientSecrets.ClientId }, + { "client_secret", clientSecrets.ClientSecret }, + { "refresh_token", refreshToken }, + { "grant_type", "refresh_token" } + }; + + HttpResponseMessage response = await client.PostAsync(refreshEndpoint, new FormUrlEncodedContent(requestBody)); + + if (!response.IsSuccessStatusCode) + return null; + + string responseString = await response.Content.ReadAsStringAsync(); + dynamic responseData = JsonConvert.DeserializeObject(responseString); + + TokenResponse tokenResponse = new TokenResponse + { + AccessToken = responseData.access_token, + ExpiresInSeconds = responseData.expires_in, + RefreshToken = refreshToken, + TokenType = responseData.token_type + }; + + AuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer + { + ClientSecrets = clientSecrets + }); + + UserCredential credential = new UserCredential(authorizationCodeFlow, "user", tokenResponse); + + return credential; } } } diff --git a/YouTubeScraper/YouTubeScraper/YouTubeService.cs b/YouTubeScraper/YouTubeScraper/YouTubeService.cs index c8df870..5f22dc9 100644 --- a/YouTubeScraper/YouTubeScraper/YouTubeService.cs +++ b/YouTubeScraper/YouTubeScraper/YouTubeService.cs @@ -2,7 +2,7 @@ namespace YouTube { - public partial class YouTubeService : Google.Apis.YouTube.v3.YouTubeService + public partial class ExtendedYouTubeService : Google.Apis.YouTube.v3.YouTubeService { public DashManifestsResource DashManifests => new DashManifestsResource(this); public VideoPlaybackResource VideoPlayback => new VideoPlaybackResource(this); @@ -11,8 +11,8 @@ namespace YouTube public WatchLaterResource WatchLater => new WatchLaterResource(this); // TODO: Add Activities override for recomendations and subscriptions - public YouTubeService() : base() { } + public ExtendedYouTubeService() : base() { } - public YouTubeService(Initializer initializer) : base(initializer) { } + public ExtendedYouTubeService(Initializer initializer) : base(initializer) { } } }