diff --git a/FoxTube.Core/FoxTube.Core.csproj b/FoxTube.Core/FoxTube.Core.csproj
index afc0e11..8b40f20 100644
--- a/FoxTube.Core/FoxTube.Core.csproj
+++ b/FoxTube.Core/FoxTube.Core.csproj
@@ -130,7 +130,6 @@
PackageReference
-
@@ -157,7 +156,7 @@
0.14.0
- 1.0.2
+ 1.0.3
1.45.0
@@ -184,10 +183,10 @@
10.1901.28001
- 2.3.200213001
+ 2.4.0
- 5.0.2
+ 5.0.3
diff --git a/FoxTube.Core/Models/SuspendedUser.cs b/FoxTube.Core/Models/SuspendedUser.cs
deleted file mode 100644
index d986cf3..0000000
--- a/FoxTube.Core/Models/SuspendedUser.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace FoxTube.Models
-{
- public class SuspendedUser
- {
- public string Name { get; set; }
- public string Email { get; set; }
- public string Avatar { get; set; }
- public string RefreshToken { get; set; }
- }
-}
\ No newline at end of file
diff --git a/FoxTube.Core/Models/User.cs b/FoxTube.Core/Models/User.cs
index b8b9f04..49f55b5 100644
--- a/FoxTube.Core/Models/User.cs
+++ b/FoxTube.Core/Models/User.cs
@@ -5,54 +5,59 @@ using Google.Apis.Services;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using System.Collections.Generic;
+using System.Threading.Tasks;
using YouTube;
+using YoutubeExplode;
namespace FoxTube.Models
{
public class User
{
- public Userinfoplus UserInfo { get; }
- public UserCredential Credential { get; }
+ public Userinfoplus UserInfo { get; private set; }
+ public UserCredential Credential { get; private set; }
public Channel Channel { get; private set; }
public List Subscriptions { get; } = new List();
- public ExtendedYouTubeService Service { get; }
- public User(UserCredential credential)
+ public ExtendedYouTubeService Service { get; private set; }
+ public YoutubeClient Client { get; private set; }
+
+ public static async Task GetUser(UserCredential credential)
{
- Credential = credential;
+ User user = new User();
+
+ user.Credential = credential;
BaseClientService.Initializer initializer = new BaseClientService.Initializer
{
ApplicationName = "FoxTube",
- HttpClientInitializer = Credential
+ HttpClientInitializer = credential
};
- Service = new ExtendedYouTubeService(initializer);
+ user.Service = new ExtendedYouTubeService(initializer);
- UserInfo = new Oauth2Service(initializer).Userinfo.Get().Execute();
+ user.UserInfo = await new Oauth2Service(initializer).Userinfo.Get().ExecuteAsync();
// TODO: Retrieve history and WL
- SubscriptionsResource.ListRequest subRequest = Service.Subscriptions.List("snippet");
+ SubscriptionsResource.ListRequest subRequest = user.Service.Subscriptions.List("snippet");
subRequest.Mine = true;
subRequest.MaxResults = 50;
subRequest.Order = SubscriptionsResource.ListRequest.OrderEnum.Relevance;
SubscriptionListResponse subResponse;
- string nextToken = null;
- Subscriptions.Clear();
do
{
- subRequest.PageToken = nextToken;
- subResponse = subRequest.Execute();
- foreach (Subscription s in subResponse.Items)
- Subscriptions.Add(s);
- nextToken = subResponse.NextPageToken;
+ subResponse = await subRequest.ExecuteAsync();
+ subRequest.PageToken = subResponse.NextPageToken;
- } while (!string.IsNullOrWhiteSpace(nextToken));
+ user.Subscriptions.AddRange(subResponse.Items);
- var request = Service.Channels.List("snippet,contentDetails,brandingSettings");
+ } while (!string.IsNullOrWhiteSpace(subRequest.PageToken));
+
+ var request = user.Service.Channels.List("snippet,contentDetails,brandingSettings");
request.Mine = true;
- Channel = request.Execute().Items[0];
+ user.Channel = request.Execute().Items[0];
+
+ return user;
}
}
}
diff --git a/FoxTube.Core/Services/Inbox.cs b/FoxTube.Core/Services/Inbox.cs
index 052ba36..8ba75b4 100644
--- a/FoxTube.Core/Services/Inbox.cs
+++ b/FoxTube.Core/Services/Inbox.cs
@@ -20,7 +20,7 @@ namespace FoxTube.Services
try
{
// TODO: Add backend
- HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Messages?toast=true&publishedAfter={storage.Values["Inbox.lastCheck"]}&lang={Settings.Language}&appVersion={Metrics.CurrentVersion}");
+ HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/FoxTube/Inbox?toast=true&publishedAfter={storage.Values["Inbox.lastCheck"]}&lang={Settings.Language}&appVersion={Metrics.CurrentVersion}");
storage.Values["Inbox.lastCheck"] = DateTime.UtcNow.Ticks;
if (response.StatusCode == HttpStatusCode.NoContent)
@@ -67,7 +67,7 @@ namespace FoxTube.Services
// TODO: Add backend
Settings.LastReviewedVersion = Metrics.CurrentVersion;
- HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Changelogs?toast=true&lang={Settings.Language}&version={Metrics.CurrentVersion}");
+ HttpResponseMessage response = await client.GetAsync($"https://xfox111.net/API/FoxTube/Changelog?lang={Settings.Language}&version={Metrics.CurrentVersion}");
if (response.StatusCode == HttpStatusCode.NoContent)
return;
diff --git a/FoxTube.Core/UserManagement.cs b/FoxTube.Core/UserManagement.cs
index 87d3f2d..7ce647e 100644
--- a/FoxTube.Core/UserManagement.cs
+++ b/FoxTube.Core/UserManagement.cs
@@ -1,11 +1,9 @@
using Google.Apis.Auth.OAuth2;
using Google.Apis.Oauth2.v2;
using System;
-using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using YouTube.Authorization;
-using System.Text.RegularExpressions;
using Windows.Security.Credentials;
using FoxTube.Models;
using YouTube;
@@ -13,58 +11,81 @@ using System.Threading;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
using Windows.UI.Xaml.Controls;
-using Windows.UI.Popups;
using FoxTube.Utils;
+using YoutubeExplode;
+using System.Linq;
+using Newtonsoft.Json;
+using Windows.Storage;
+using Google.Apis.Oauth2.v2.Data;
namespace FoxTube
{
public static class UserManagement
{
- static ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer
+ public const int MaxUsersCount = 2;
+
+ #region Private members
+ private static readonly ApplicationDataContainer storage = ApplicationData.Current.LocalSettings;
+
+ private static ExtendedYouTubeService _defaultService = new ExtendedYouTubeService(new Google.Apis.Services.BaseClientService.Initializer
{
ApplicationName = "FoxTube",
- ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0"
+ ApiKey = "AIzaSyBgHrCnrlzlVmk0cJKL8RqP9Y8x6XSuk_0",
//ApiKey = "AIzaSyD7tpbuvmYDv9h4udo9L_g3r0sLPFAnN00"
});
+ private static YoutubeClient _defaultYteClient = new YoutubeClient();
- static string[] Scopes { get; } = new string[]
+ private static string[] Scopes { get; } = new string[]
{
Oauth2Service.Scope.UserinfoProfile,
Oauth2Service.Scope.UserinfoEmail,
YouTubeService.Scope.YoutubeForceSsl
};
- static ClientSecrets ClientSecrets { get; } = new ClientSecrets
+ private static ClientSecrets[] ClientSecrets { get; } = new ClientSecrets[MaxUsersCount]
{
- ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
- ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
- //ClientId = "1096685398208-u95rcpkqb4e1ijfmb8jdq3jsg37l8igv.apps.googleusercontent.com",
- //ClientSecret = "IU5bbdjwvmx8ttJoXQ7e6JWd"
+ new ClientSecrets
+ {
+ ClientId = "349735264870-2ekqlm0a4mkg3mmrfcv90s3qp3o15dq0.apps.googleusercontent.com",
+ ClientSecret = "BkVZOAaCU2Zclf0Zlicg6y2_"
+ },
+ new ClientSecrets // DISABLED
+ {
+ ClientId = "1096685398208-u95rcpkqb4e1ijfmb8jdq3jsg37l8igv.apps.googleusercontent.com",
+ ClientSecret = "IU5bbdjwvmx8ttJoXQ7e6JWd"
+ }
};
+ #endregion
+ public static Userinfoplus[] Users { get; private set; } = new Userinfoplus[MaxUsersCount];
public static User CurrentUser { get; set; }
public static bool Authorized => CurrentUser != null;
public static ExtendedYouTubeService Service => CurrentUser?.Service ?? _defaultService;
+ public static YoutubeClient YoutubeClient => CurrentUser?.Client ?? _defaultYteClient;
public static event EventHandler UserStateUpdated;
public static event EventHandler SubscriptionsChanged;
public static async Task AddUser()
{
- Uri requestString = AuthorizationHelpers.FormQueryString(ClientSecrets, Scopes);
+ int queueIndex = Users.ToList().FindIndex(i => i == null);
+
+ if (queueIndex < 0)
+ throw new StackOverflowException("The maximum accounts limit is reached");
+
+ ClientSecrets secrets = ClientSecrets[queueIndex];
+
+ Uri requestString = AuthorizationHelpers.FormQueryString(secrets, Scopes);
WebAuthenticationResult result = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, requestString, AuthorizationHelpers.Endpoint);
switch (result.ResponseStatus)
{
case WebAuthenticationStatus.Success:
- string successCode = new Regex(@"(?<=code=)(.*?)(?=&)").Match(result.ResponseData).Value;
+ string successCode = AuthorizationHelpers.ParseSuccessCode(result.ResponseData);
- UserCredential credential = await AuthorizationHelpers.ExchangeToken(ClientSecrets, successCode);
- CurrentUser = new User(credential);
+ YouTube.Authorization.UserCredential credential = await AuthorizationHelpers.ExchangeToken(secrets, successCode);
- PasswordVault passwordVault = new PasswordVault();
- passwordVault.Add(new PasswordCredential("foxtube", CurrentUser.UserInfo.Id, credential.Token.RefreshToken));
- UserStateUpdated?.Invoke(null, true);
+ await LoadUser(credential, queueIndex);
return true;
case WebAuthenticationStatus.UserCancel:
break;
@@ -81,44 +102,104 @@ namespace FoxTube
("Error details", result.ResponseErrorDetail.ToString()));
break;
}
+
return false;
}
public static async Task Initialize()
{
- PasswordVault passwordVault = new PasswordVault();
- IReadOnlyList credentials;
- credentials = passwordVault.RetrieveAll();
+ Users = JsonConvert.DeserializeObject(storage.Values["UserManagement.Users"] as string);
+ int? lastUserIndex = storage.Values["UserManagement.LastUser"] as int?;
- if (credentials.Count == 0)
- return;
+ if (lastUserIndex.HasValue && Users[lastUserIndex.Value] != null ||
+ (lastUserIndex = Users.ToList().FindIndex(i => i != null)) > -1)
+ await SwitchUser(lastUserIndex.Value);
+ }
+
+ public static async Task Logout()
+ {
+ if (CurrentUser?.UserInfo?.Id == null)
+ return false;
try
{
- credentials[0].RetrievePassword();
- UserCredential credential = await AuthorizationHelpers.RestoreUser(ClientSecrets, credentials[0].Password);
- credentials[0].Password = credential.Token.RefreshToken;
- CurrentUser = new User(credential);
+ string userId = CurrentUser.UserInfo.Id;
+
+ PasswordVault passwordVault = new PasswordVault();
+ PasswordCredential credential = passwordVault.Retrieve("foxtube", userId);
+ passwordVault.Remove(credential);
+
+ await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None);
+
+ CurrentUser = null;
+ Users[Users.ToList().FindIndex(i => i.Id == userId)] = null;
+
+ storage.Values["UserManagement.Users"] = JsonConvert.SerializeObject(Users);
+ storage.Values["UserManagement.LastUser"] = null;
+
+ if (Users.Any(i => i != null))
+ await SwitchUser(Users.ToList().FindIndex(i => i != null));
+ else
+ UserStateUpdated?.Invoke(Users, false);
+
+ return true;
}
catch (Exception e)
{
- await new MessageDialog("It may be a bug or temporary server issues. Please, try again later", "Something went wrong...").ShowAsync();
+ Metrics.SendReport(new Exception("Failed to logout", e));
- Metrics.SendReport(new Exception("Refresh token exchange failed", e));
+ await new ContentDialog
+ {
+ Title = "Something went wrong...",
+ Content = "It may be a bug or temporary server issues. Please, try again later"
+ }.ShowAsync();
- foreach (PasswordCredential i in credentials)
- passwordVault.Remove(i);
+ return false;
}
}
- public static async Task Logout()
+ public static async Task SwitchUser(int userIndex)
+ {
+ Userinfoplus userInfo = Users[userIndex];
+
+ PasswordVault valut = new PasswordVault();
+ PasswordCredential vaultCredential = valut.Retrieve("foxtube", userInfo.Id);
+ if (vaultCredential == null)
+ throw new NullReferenceException("No user found to switch on");
+
+ vaultCredential.RetrievePassword();
+ string token = vaultCredential.Password;
+ YouTube.Authorization.UserCredential credential = await AuthorizationHelpers.RestoreUser(ClientSecrets[userIndex], token);
+
+ await LoadUser(credential, userIndex);
+ }
+
+ private static async Task LoadUser(YouTube.Authorization.UserCredential credential, int userIndex)
+ {
+ CurrentUser = await User.GetUser(credential);
+ Users[userIndex] = CurrentUser.UserInfo;
+
+ storage.Values["UserManagement.Users"] = JsonConvert.SerializeObject(Users);
+ storage.Values["UserManagement.LastUser"] = userIndex;
+
+ credential.RefreshTokenUpdated += (s, e) => UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken);
+ UpdateToken(CurrentUser.UserInfo.Id, credential.Token.RefreshToken);
+
+ UserStateUpdated?.Invoke(Users, true);
+ }
+
+ private static void UpdateToken(string id, string refreshToken)
{
PasswordVault passwordVault = new PasswordVault();
- PasswordCredential credential = passwordVault.Retrieve("foxtube", CurrentUser.UserInfo.Id);
- passwordVault.Remove(credential);
- await CurrentUser.Credential.RevokeTokenAsync(CancellationToken.None);
+ PasswordCredential vaultCredential = passwordVault.Retrieve("foxtube", id);
- UserStateUpdated?.Invoke(null, false);
+ if (vaultCredential == null)
+ {
+ vaultCredential = new PasswordCredential("foxtube", CurrentUser.UserInfo.Id, refreshToken);
+ passwordVault.Add(vaultCredential);
+ }
+ else
+ vaultCredential.Password = refreshToken;
}
}
-}
+}
\ No newline at end of file