Compare commits
4 Commits
2c125c24ae
...
b8d2573d78
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8d2573d78 | ||
|
|
a2a420d596 | ||
|
|
c170b8db1f | ||
|
|
abc1505b6e |
@@ -27,44 +27,66 @@
|
|||||||
|
|
||||||
<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>
|
||||||
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Name"
|
<MudText>@context.Item.Name</MudText>
|
||||||
Immediate/>
|
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="Domain">
|
<TemplateColumn Title="Domain">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Domain"
|
<MudText>@context.Item.Domain</MudText>
|
||||||
Immediate/>
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="Expires">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudText>@context.Item.Expires</MudText>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
<TemplateColumn Title="Value">
|
<TemplateColumn Title="Value">
|
||||||
<CellTemplate>
|
<CellTemplate>
|
||||||
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Value"
|
<MudTooltip Text="@context.Item.Value">
|
||||||
Immediate/>
|
<MudText Style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 250px;">@context.Item.Value</MudText>
|
||||||
|
</MudTooltip>
|
||||||
</CellTemplate>
|
</CellTemplate>
|
||||||
</TemplateColumn>
|
</TemplateColumn>
|
||||||
</Columns>
|
</Columns>
|
||||||
|
|||||||
@@ -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()
|
||||||
{
|
{
|
||||||
@@ -71,12 +75,39 @@ 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)
|
||||||
@@ -95,50 +126,6 @@ namespace Manager.App.Components.Dialogs
|
|||||||
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()
|
||||||
{
|
{
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ namespace Manager.App.Services;
|
|||||||
|
|
||||||
public interface ILibraryService
|
public interface ILibraryService
|
||||||
{
|
{
|
||||||
public Task<Result> FetchChannelImagesAsync(InnertubeChannel innertubeChannel);
|
|
||||||
public Task<Result> SaveClientAsync(ClientAccountEntity client, CancellationToken cancellationToken = default);
|
public Task<Result> SaveClientAsync(ClientAccountEntity client, CancellationToken cancellationToken = default);
|
||||||
public Task<Result<LibraryFile>> GetFileByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
public Task<Result<LibraryFile>> GetFileByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||||
public Task<Result<ChannelEntity>> GetChannelByIdAsync(string id, CancellationToken cancellationToken = default);
|
public Task<Result<ChannelEntity>> GetChannelByIdAsync(string id, CancellationToken cancellationToken = default);
|
||||||
|
|||||||
@@ -33,31 +33,6 @@ public class LibraryService : ILibraryService
|
|||||||
Directory.CreateDirectory(Path.Combine(_librarySettings.Path, LibraryConstants.Directories.SubDirChannels));
|
Directory.CreateDirectory(Path.Combine(_librarySettings.Path, LibraryConstants.Directories.SubDirChannels));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> FetchChannelImagesAsync(InnertubeChannel innertubeChannel)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
|
||||||
|
|
||||||
await AddWebImagesAsync(context, innertubeChannel.AvatarImages, innertubeChannel.Id, "avatars", LibraryConstants.FileTypes.ChannelAvatar, LibraryConstants.Directories.SubDirChannels);
|
|
||||||
await AddWebImagesAsync(context, innertubeChannel.BannerImages, innertubeChannel.Id, "banners", LibraryConstants.FileTypes.ChannelBanner, LibraryConstants.Directories.SubDirChannels);
|
|
||||||
|
|
||||||
if (!context.ChangeTracker.HasChanges())
|
|
||||||
{
|
|
||||||
_logger.LogInformation("No changes detected. Skipping.");
|
|
||||||
return Result.Success();
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
return HandleException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.Success();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task AddWebImagesAsync(LibraryDbContext context, List<WebImage> images, string foreignKey, string libSubDir, string fileType, string subDir)
|
private async Task AddWebImagesAsync(LibraryDbContext context, List<WebImage> images, string foreignKey, string libSubDir, string fileType, string subDir)
|
||||||
{
|
{
|
||||||
foreach (var image in images)
|
foreach (var image in images)
|
||||||
@@ -192,12 +167,6 @@ public class LibraryService : ILibraryService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var imagesResult = await FetchChannelImagesAsync(innertubeChannel);
|
|
||||||
if (!imagesResult.IsSuccess)
|
|
||||||
{
|
|
||||||
return ResultError.Fail("Failed to fetch channel images!");
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var channelResult = await GetChannelByIdAsync(innertubeChannel.Id, cancellationToken);
|
var channelResult = await GetChannelByIdAsync(innertubeChannel.Id, cancellationToken);
|
||||||
@@ -228,7 +197,7 @@ public class LibraryService : ILibraryService
|
|||||||
return ResultError.Error(e);
|
return ResultError.Error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.Channels.Any(c => c.Id == innertubeChannel.Id))
|
if (channelResult.IsSuccess)
|
||||||
{
|
{
|
||||||
context.Channels.Update(channelEntity);
|
context.Channels.Update(channelEntity);
|
||||||
}
|
}
|
||||||
@@ -237,6 +206,9 @@ public class LibraryService : ILibraryService
|
|||||||
context.Channels.Add(channelEntity);
|
context.Channels.Add(channelEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await AddWebImagesAsync(context, innertubeChannel.AvatarImages, innertubeChannel.Id, "avatars", LibraryConstants.FileTypes.ChannelAvatar, LibraryConstants.Directories.SubDirChannels);
|
||||||
|
await AddWebImagesAsync(context, innertubeChannel.BannerImages, innertubeChannel.Id, "banners", LibraryConstants.FileTypes.ChannelBanner, LibraryConstants.Directories.SubDirChannels);
|
||||||
|
|
||||||
var changed = await context.SaveChangesAsync(cancellationToken);
|
var changed = await context.SaveChangesAsync(cancellationToken);
|
||||||
return changed <= 0 ? ResultError.Fail("Failed to save channel!") : Result.Success();
|
return changed <= 0 ? ResultError.Fail("Failed to save channel!") : Result.Success();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ public class ClientService(IServiceScopeFactory scopeFactory, ILogger<ClientServ
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<HttpCookieEntity> httpCookies = [];
|
List<HttpCookieEntity> httpCookies = [];
|
||||||
httpCookies.AddRange(client.CookieContainer.GetAllCookies()
|
httpCookies.AddRange(client.CookieContainer.GetAllCookies().Where(c => c.Expires != DateTime.MinValue)
|
||||||
.ToList()
|
.ToList()
|
||||||
.Select(cookie => new HttpCookieEntity
|
.Select(cookie => new HttpCookieEntity
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class AuditInterceptor : SaveChangesInterceptor
|
|||||||
break;
|
break;
|
||||||
case EntityState.Modified:
|
case EntityState.Modified:
|
||||||
audits.AddRange(allowedProperties
|
audits.AddRange(allowedProperties
|
||||||
.Where(p => p.IsModified)
|
.Where(p => p.IsModified && !Equals(p.OriginalValue, p.CurrentValue))
|
||||||
.Select(p => CreateAudit(entry, p, entry.State, primaryKey))
|
.Select(p => CreateAudit(entry, p, entry.State, primaryKey))
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -82,7 +82,7 @@ public class AuditInterceptor : SaveChangesInterceptor
|
|||||||
EntityName = entry.Entity.GetType().Name,
|
EntityName = entry.Entity.GetType().Name,
|
||||||
EntityId = primaryKey ?? "Unknown",
|
EntityId = primaryKey ?? "Unknown",
|
||||||
PropertyName = prop.Metadata.Name,
|
PropertyName = prop.Metadata.Name,
|
||||||
OldValue = SerializeValue(prop.OriginalValue),
|
OldValue = changeType == EntityState.Added ? null : SerializeValue(prop.OriginalValue),
|
||||||
NewValue = SerializeValue(prop.CurrentValue),
|
NewValue = SerializeValue(prop.CurrentValue),
|
||||||
ModifiedUtc = DateTime.UtcNow,
|
ModifiedUtc = DateTime.UtcNow,
|
||||||
ChangedBy = "SYSTEM",
|
ChangedBy = "SYSTEM",
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -140,7 +140,6 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
return ResultError.Error(e, "Error while parsing JSON!");
|
return ResultError.Error(e, "Error while parsing JSON!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (State == null)
|
if (State == null)
|
||||||
{
|
{
|
||||||
return ResultError.Fail("Unable to parse client state!");
|
return ResultError.Fail("Unable to parse client state!");
|
||||||
@@ -148,7 +147,8 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
|
|
||||||
State.IsPremiumUser = clientStateResult.Value.Item2;
|
State.IsPremiumUser = clientStateResult.Value.Item2;
|
||||||
|
|
||||||
return Result.Success();
|
var cookieRotationResult = await RotateCookiesPageAsync();
|
||||||
|
return !cookieRotationResult.IsSuccess ? cookieRotationResult : Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<InnertubeChannel>> GetChannelByIdAsync(string channelId)
|
public async Task<Result<InnertubeChannel>> GetChannelByIdAsync(string channelId)
|
||||||
|
|||||||
Reference in New Issue
Block a user