diff --git a/Manager.App/Components/Dialogs/AccountDialog.razor b/Manager.App/Components/Dialogs/AccountDialog.razor
index 38dfaee..e836e0b 100644
--- a/Manager.App/Components/Dialogs/AccountDialog.razor
+++ b/Manager.App/Components/Dialogs/AccountDialog.razor
@@ -27,27 +27,45 @@
- Import cookies
- @($"{ImportCookies.Count} cookie(s) imported")
-
-
+ Import cookies (Netscape Cookie format)
+
+
+
+
+
+ Upload cookie txt
+
+
+
+ Import
+
+
+ @if (MissingCookies.Any())
+ {
+
+ Some required cookies are not found, add the following cookie(s) to continue.
+
+ @foreach (var missingCookieName in MissingCookies)
+ {
+ @missingCookieName
+ }
+
+
+ }
- Import
-
-
+ Required Label="Cookies" Variant="Variant.Outlined"/>
+
-
+
+ Cookies
+
+ @($"{ImportCookies.Count} cookie(s)")
+
@@ -67,6 +85,11 @@
Immediate/>
+
+
+ @context.Item.Expires
+
+
diff --git a/Manager.App/Components/Dialogs/AccountDialog.razor.cs b/Manager.App/Components/Dialogs/AccountDialog.razor.cs
index 0876442..6b8349c 100644
--- a/Manager.App/Components/Dialogs/AccountDialog.razor.cs
+++ b/Manager.App/Components/Dialogs/AccountDialog.razor.cs
@@ -1,7 +1,12 @@
using System.Net;
+using System.Net.Mime;
+using System.Text;
using Manager.App.Models.Library;
using Manager.YouTube;
+using Manager.YouTube.Constants;
+using Manager.YouTube.Parsers;
using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Forms;
using MudBlazor;
namespace Manager.App.Components.Dialogs
@@ -12,12 +17,11 @@ namespace Manager.App.Components.Dialogs
[Parameter] public string DefaultUserAgent { get; set; } = "";
private ClientChannel? ClientChannel { get; set; }
private CookieCollection ImportCookies { get; set; } = [];
+ private IEnumerable MissingCookies => CookieConstants.RequiredCookiesNames.Where(req => !ImportCookies.Select(c => c.Name).ToHashSet().Contains(req)).ToList();
private bool _isLoading;
private AccountImportSteps _steps = AccountImportSteps.Authenticate;
- private bool _cookieImportTextValid;
private string _cookieText = "";
- private string _cookieDomain = ".youtube.com";
private bool CanSave()
{
@@ -70,13 +74,40 @@ namespace Manager.App.Components.Dialogs
}
await InvokeAsync(StateHasChanged);
}
-
- private void ParseCookies()
+
+ private async Task UploadFiles(IBrowserFile? file)
{
+ if (file == null)
+ {
+ SnackbarService.Add("File is null!", Severity.Error);
+ return;
+ }
+
+ if (file.ContentType != MediaTypeNames.Text.Plain)
+ {
+ SnackbarService.Add($"File uploaded with unsupported content type: {file.ContentType}", Severity.Warning);
+ return;
+ }
+
+ _isLoading = true;
+ var streamReader = new StreamReader(file.OpenReadStream(), Encoding.UTF8);
+ _cookieText = await streamReader.ReadToEndAsync();
+ _isLoading = false;
+ await InvokeAsync(StateHasChanged);
+ }
+
+ private async Task ParseCookies()
+ {
+ if (string.IsNullOrEmpty(_cookieText))
+ {
+ return;
+ }
+
try
{
ImportCookies.Clear();
- ImportCookies.Add(ParseCookieHeader(_cookieText, _cookieDomain));
+ var parsedCookies = await CookieTxtParser.ParseAsync(new MemoryStream(Encoding.UTF8.GetBytes(_cookieText)), CookieConstants.RequiredCookiesNames.ToHashSet());
+ ImportCookies.Add(parsedCookies);
_cookieText = string.Empty;
}
catch (Exception e)
@@ -94,50 +125,6 @@ namespace Manager.App.Components.Dialogs
_steps = AccountImportSteps.Authenticate;
StateHasChanged();
}
-
- private static string? ValidateCookieText(string text)
- {
- if (string.IsNullOrWhiteSpace(text))
- return "Cookies are required";
-
- var pairs = text.Split(';', StringSplitOptions.RemoveEmptyEntries);
- foreach (var pair in pairs)
- {
- if (!pair.Contains('=')) return "Invalid.";
- var key = pair[..pair.IndexOf('=')].Trim();
- if (string.IsNullOrEmpty(key)) return "Invalid.";
- }
-
- return null;
- }
-
- public static CookieCollection ParseCookieHeader(string cookieHeader, string domain = "")
- {
- var collection = new CookieCollection();
-
- if (string.IsNullOrWhiteSpace(cookieHeader))
- return collection;
-
- var cookies = cookieHeader.Split(';', StringSplitOptions.RemoveEmptyEntries);
-
- foreach (var cookieStr in cookies)
- {
- var parts = cookieStr.Split('=', 2);
- if (parts.Length != 2) continue;
-
- var name = parts[0].Trim();
- var value = parts[1].Trim();
-
- var cookie = new Cookie(name, value)
- {
- Path = "/",
- Domain = domain
- };
- collection.Add(cookie);
- }
-
- return collection;
- }
private async Task BuildClient()
{
diff --git a/Manager.YouTube/Constants/CookieConstants.cs b/Manager.YouTube/Constants/CookieConstants.cs
new file mode 100644
index 0000000..a537633
--- /dev/null
+++ b/Manager.YouTube/Constants/CookieConstants.cs
@@ -0,0 +1,22 @@
+namespace Manager.YouTube.Constants;
+
+public static class CookieConstants
+{
+ public static readonly IReadOnlyCollection RequiredCookiesNames = new HashSet
+ {
+ "SID",
+ "SIDCC",
+ "HSID",
+ "SSID",
+ "APISID",
+ "SAPISID",
+ "__Secure-1PAPISID",
+ "__Secure-1PSID",
+ "__Secure-1PSIDCC",
+ "__Secure-1PSIDTS",
+ "__Secure-3PAPISID",
+ "__Secure-3PSID",
+ "__Secure-3PSIDCC",
+ "__Secure-3PSIDTS"
+ };
+}
\ No newline at end of file
diff --git a/Manager.YouTube/Parsers/CookieTxtParser.cs b/Manager.YouTube/Parsers/CookieTxtParser.cs
new file mode 100644
index 0000000..c515596
--- /dev/null
+++ b/Manager.YouTube/Parsers/CookieTxtParser.cs
@@ -0,0 +1,53 @@
+using System.Net;
+
+namespace Manager.YouTube.Parsers;
+
+public static class CookieTxtParser
+{
+ public static async Task ParseAsync(Stream stream, HashSet? allowedCookies = null)
+ {
+ var cookieCollection = new CookieCollection();
+
+ using var reader = new StreamReader(stream);
+ while (await reader.ReadLineAsync() is { } line)
+ {
+ if (string.IsNullOrWhiteSpace(line) || line.StartsWith('#'))
+ {
+ continue;
+ }
+
+ var lineParts = line.Split('\t');
+ if (lineParts.Length < 7)
+ {
+ continue;
+ }
+
+ var domain = lineParts[0];
+ //var includeSubdomains = lineParts[1].Equals("TRUE", StringComparison.OrdinalIgnoreCase);
+ var path = lineParts[2];
+ var secure = lineParts[3].Equals("TRUE", StringComparison.OrdinalIgnoreCase);
+ var unixExpiry = long.TryParse(lineParts[4], out var exp) ? exp : 0;
+ var name = lineParts[5];
+ var value = lineParts[6];
+
+ if (!allowedCookies?.Contains(name) ?? false)
+ {
+ continue;
+ }
+
+ var cookie = new Cookie(name, value, path, domain)
+ {
+ Secure = secure
+ };
+
+ if (unixExpiry is > 0 and < int.MaxValue)
+ {
+ cookie.Expires = DateTimeOffset.FromUnixTimeSeconds(unixExpiry).DateTime;
+ }
+
+ cookieCollection.Add(cookie);
+ }
+
+ return cookieCollection;
+ }
+}
\ No newline at end of file