From abc1505b6edd01118d0e0d3a48df271947b00f23 Mon Sep 17 00:00:00 2001 From: max Date: Sun, 28 Sep 2025 17:47:34 +0200 Subject: [PATCH] [CHANGE] Cookie import by netscape cookie txt format --- .../Components/Dialogs/AccountDialog.razor | 57 +++++++++---- .../Components/Dialogs/AccountDialog.razor.cs | 85 ++++++++----------- Manager.YouTube/Constants/CookieConstants.cs | 22 +++++ Manager.YouTube/Parsers/CookieTxtParser.cs | 53 ++++++++++++ 4 files changed, 151 insertions(+), 66 deletions(-) create mode 100644 Manager.YouTube/Constants/CookieConstants.cs create mode 100644 Manager.YouTube/Parsers/CookieTxtParser.cs 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 - -
+ + 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