[CHANGE] Cookie import by netscape cookie txt format

This commit is contained in:
max
2025-09-28 17:47:34 +02:00
parent 2c125c24ae
commit abc1505b6e
4 changed files with 151 additions and 66 deletions

View File

@@ -27,27 +27,45 @@
<MudStack Row Spacing="2" Style="height: 100%"> <MudStack Row Spacing="2" Style="height: 100%">
<MudPaper Elevation="0" Outlined Class="pa-2" Style="width: 50%;"> <MudPaper Elevation="0" Outlined Class="pa-2" Style="width: 50%;">
<MudText>Import cookies</MudText> <MudText>Import cookies (Netscape Cookie format)</MudText>
<MudText Typo="Typo.caption">@($"{ImportCookies.Count} cookie(s) imported")</MudText> <MudStack Spacing="2">
<MudForm @bind-IsValid="@_cookieImportTextValid"> <MudStack Row Spacing="2">
<MudTextField @bind-Value="@_cookieDomain" Immediate Required Label="Domain" <MudFileUpload T="IBrowserFile" Accept=".txt" FilesChanged="UploadFiles">
RequiredError="Domain is required."/> <ActivatorContent>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.CloudUpload">
Upload cookie txt
</MudButton>
</ActivatorContent>
</MudFileUpload>
<MudButton Variant="Variant.Outlined"
OnClick="ParseCookies" Disabled="@(string.IsNullOrWhiteSpace(_cookieText))">Import
</MudButton>
</MudStack>
@if (MissingCookies.Any())
{
<MudPaper Class="pa-2" Elevation="0" Outlined>
<MudAlert Severity="Severity.Warning" Square Class="mb-2 mt-3">Some required cookies are not found, add the following cookie(s) to continue.</MudAlert>
<MudChipSet T="string" ReadOnly>
@foreach (var missingCookieName in MissingCookies)
{
<MudChip Variant="Variant.Text" Color="Color.Info">@missingCookieName</MudChip>
}
</MudChipSet>
</MudPaper>
}
<MudTextField Class="my-2" Lines="4" AutoGrow @bind-Value="@_cookieText" Immediate <MudTextField Class="my-2" Lines="4" AutoGrow @bind-Value="@_cookieText" Immediate
Required Label="Cookies" Variant="Variant.Outlined" Required Label="Cookies" Variant="Variant.Outlined"/>
Placeholder="EXAMPLE: Cookie1=Value1; Cookie2=Value2;" </MudStack>
Validation="@(new Func<string, string?>(ValidateCookieText))"/>
<MudButton Variant="Variant.Outlined" Disabled="@(!_cookieImportTextValid)"
OnClick="ParseCookies">Import
</MudButton>
</MudForm>
</MudPaper> </MudPaper>
<MudDataGrid Items="ImportCookies" Dense Elevation="0" Outlined Style="width: 50%;"> <MudDataGrid Items="ImportCookies" Dense Elevation="0" Outlined Style="width: 50%;">
<Header> <ToolBarContent>
<MudStack Class="ma-2"> <MudText>Cookies</MudText>
<MudText>Cookies</MudText> <MudSpacer />
</MudStack> <MudText Typo="Typo.caption">@($"{ImportCookies.Count} cookie(s)")</MudText>
</Header> </ToolBarContent>
<Columns> <Columns>
<TemplateColumn Title="Name"> <TemplateColumn Title="Name">
<CellTemplate> <CellTemplate>
@@ -67,6 +85,11 @@
Immediate/> Immediate/>
</CellTemplate> </CellTemplate>
</TemplateColumn> </TemplateColumn>
<TemplateColumn Title="Expires">
<CellTemplate>
@context.Item.Expires
</CellTemplate>
</TemplateColumn>
</Columns> </Columns>
</MudDataGrid> </MudDataGrid>
</MudStack> </MudStack>

View File

@@ -1,7 +1,12 @@
using System.Net; using System.Net;
using System.Net.Mime;
using System.Text;
using Manager.App.Models.Library; using Manager.App.Models.Library;
using Manager.YouTube; using Manager.YouTube;
using Manager.YouTube.Constants;
using Manager.YouTube.Parsers;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using MudBlazor; using MudBlazor;
namespace Manager.App.Components.Dialogs namespace Manager.App.Components.Dialogs
@@ -12,12 +17,11 @@ namespace Manager.App.Components.Dialogs
[Parameter] public string DefaultUserAgent { get; set; } = ""; [Parameter] public string DefaultUserAgent { get; set; } = "";
private ClientChannel? ClientChannel { get; set; } private ClientChannel? ClientChannel { get; set; }
private CookieCollection ImportCookies { get; set; } = []; private CookieCollection ImportCookies { get; set; } = [];
private IEnumerable<string> MissingCookies => CookieConstants.RequiredCookiesNames.Where(req => !ImportCookies.Select(c => c.Name).ToHashSet().Contains(req)).ToList();
private bool _isLoading; private bool _isLoading;
private AccountImportSteps _steps = AccountImportSteps.Authenticate; private AccountImportSteps _steps = AccountImportSteps.Authenticate;
private bool _cookieImportTextValid;
private string _cookieText = ""; private string _cookieText = "";
private string _cookieDomain = ".youtube.com";
private bool CanSave() private bool CanSave()
{ {
@@ -70,13 +74,40 @@ namespace Manager.App.Components.Dialogs
} }
await InvokeAsync(StateHasChanged); 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 try
{ {
ImportCookies.Clear(); 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; _cookieText = string.Empty;
} }
catch (Exception e) catch (Exception e)
@@ -94,50 +125,6 @@ namespace Manager.App.Components.Dialogs
_steps = AccountImportSteps.Authenticate; _steps = AccountImportSteps.Authenticate;
StateHasChanged(); 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() private async Task BuildClient()
{ {

View File

@@ -0,0 +1,22 @@
namespace Manager.YouTube.Constants;
public static class CookieConstants
{
public static readonly IReadOnlyCollection<string> RequiredCookiesNames = new HashSet<string>
{
"SID",
"SIDCC",
"HSID",
"SSID",
"APISID",
"SAPISID",
"__Secure-1PAPISID",
"__Secure-1PSID",
"__Secure-1PSIDCC",
"__Secure-1PSIDTS",
"__Secure-3PAPISID",
"__Secure-3PSID",
"__Secure-3PSIDCC",
"__Secure-3PSIDTS"
};
}

View File

@@ -0,0 +1,53 @@
using System.Net;
namespace Manager.YouTube.Parsers;
public static class CookieTxtParser
{
public static async Task<CookieCollection> ParseAsync(Stream stream, HashSet<string>? 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;
}
}