[CHANGE] Cookie import by netscape cookie txt format
This commit is contained in:
@@ -27,27 +27,45 @@
|
||||
|
||||
<MudStack Row Spacing="2" Style="height: 100%">
|
||||
<MudPaper Elevation="0" Outlined Class="pa-2" Style="width: 50%;">
|
||||
<MudText>Import cookies</MudText>
|
||||
<MudText Typo="Typo.caption">@($"{ImportCookies.Count} cookie(s) imported")</MudText>
|
||||
<MudForm @bind-IsValid="@_cookieImportTextValid">
|
||||
<MudTextField @bind-Value="@_cookieDomain" Immediate Required Label="Domain"
|
||||
RequiredError="Domain is required."/>
|
||||
<MudText>Import cookies (Netscape Cookie format)</MudText>
|
||||
<MudStack Spacing="2">
|
||||
<MudStack Row Spacing="2">
|
||||
<MudFileUpload T="IBrowserFile" Accept=".txt" FilesChanged="UploadFiles">
|
||||
<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
|
||||
Required Label="Cookies" Variant="Variant.Outlined"
|
||||
Placeholder="EXAMPLE: Cookie1=Value1; Cookie2=Value2;"
|
||||
Validation="@(new Func<string, string?>(ValidateCookieText))"/>
|
||||
<MudButton Variant="Variant.Outlined" Disabled="@(!_cookieImportTextValid)"
|
||||
OnClick="ParseCookies">Import
|
||||
</MudButton>
|
||||
</MudForm>
|
||||
Required Label="Cookies" Variant="Variant.Outlined"/>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudDataGrid Items="ImportCookies" Dense Elevation="0" Outlined Style="width: 50%;">
|
||||
<Header>
|
||||
<MudStack Class="ma-2">
|
||||
<MudText>Cookies</MudText>
|
||||
</MudStack>
|
||||
</Header>
|
||||
<ToolBarContent>
|
||||
<MudText>Cookies</MudText>
|
||||
<MudSpacer />
|
||||
<MudText Typo="Typo.caption">@($"{ImportCookies.Count} cookie(s)")</MudText>
|
||||
</ToolBarContent>
|
||||
<Columns>
|
||||
<TemplateColumn Title="Name">
|
||||
<CellTemplate>
|
||||
@@ -67,6 +85,11 @@
|
||||
Immediate/>
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
<TemplateColumn Title="Expires">
|
||||
<CellTemplate>
|
||||
@context.Item.Expires
|
||||
</CellTemplate>
|
||||
</TemplateColumn>
|
||||
</Columns>
|
||||
</MudDataGrid>
|
||||
</MudStack>
|
||||
|
||||
@@ -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<string> 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()
|
||||
{
|
||||
|
||||
22
Manager.YouTube/Constants/CookieConstants.cs
Normal file
22
Manager.YouTube/Constants/CookieConstants.cs
Normal 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"
|
||||
};
|
||||
}
|
||||
53
Manager.YouTube/Parsers/CookieTxtParser.cs
Normal file
53
Manager.YouTube/Parsers/CookieTxtParser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user