[CHANGE] Reworked client creation
This commit is contained in:
@@ -24,7 +24,6 @@ public partial class AuthenticationHasher : ComponentBase
|
|||||||
|
|
||||||
private void Hash()
|
private void Hash()
|
||||||
{
|
{
|
||||||
var hashedValue= AuthenticationUtilities.GetSapisidHash(DatasyncId, SecureCookie, Origin, Time);
|
OutputHash = AuthenticationUtilities.GetSapisidHash(DatasyncId, SecureCookie, Origin, Time);
|
||||||
OutputHash = hashedValue ?? "Hash failed!";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -2,112 +2,195 @@
|
|||||||
|
|
||||||
<ForcedLoadingOverlay Visible="_isLoading"/>
|
<ForcedLoadingOverlay Visible="_isLoading"/>
|
||||||
|
|
||||||
|
@{
|
||||||
|
var client = PreparingClient?.YouTubeClient;
|
||||||
|
var clientState = client?.State;
|
||||||
|
var channel = PreparingClient?.Channel;
|
||||||
|
var avatar = channel?.AvatarImages.FirstOrDefault();
|
||||||
|
var banner = channel?.BannerImages.FirstOrDefault();
|
||||||
|
}
|
||||||
<MudDialog>
|
<MudDialog>
|
||||||
<TitleContent>
|
<TitleContent>
|
||||||
<MudText Typo="Typo.h6">Add new account</MudText>
|
<MudText Typo="Typo.h6">Add new account</MudText>
|
||||||
</TitleContent>
|
</TitleContent>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<MudStack Spacing="2">
|
@switch (_steps)
|
||||||
<MudStack Row Spacing="2" AlignItems="AlignItems.Start" Justify="Justify.SpaceEvenly" StretchItems="StretchItems.All">
|
{
|
||||||
|
case AccountImportSteps.Authenticate:
|
||||||
<MudStack Spacing="2">
|
<MudStack Spacing="2">
|
||||||
<MudTextField Label="UserAgent" Required @bind-Value="@Client.UserAgent"/>
|
<MudPaper Elevation="0" Outlined Class="pa-2">
|
||||||
</MudStack>
|
<MudSwitch @bind-Value="@IsAnonymous" Color="Color.Info">Anonymous client</MudSwitch>
|
||||||
<MudSimpleTable Bordered Dense Elevation="0" Outlined Square Hover>
|
<MudTextField @bind-Value="@DefaultUserAgent" Required Label="User agent"
|
||||||
<tbody>
|
HelperText="Use an WEB client user agent."/>
|
||||||
<tr>
|
</MudPaper>
|
||||||
<td>Account id:</td>
|
|
||||||
<td>@Client.Id</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Account name:</td>
|
|
||||||
<td>@Client.External.Channel?.ChannelName</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Account handle:</td>
|
|
||||||
<td>@Client.External.Channel?.Handle</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Logged in:</td>
|
|
||||||
<td style="@($"color: {(Client.External.State?.LoggedIn ?? false ? "green" : "red")}")">@Client.External.State?.LoggedIn</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>YouTube Premium:</td>
|
|
||||||
<td style="@($"color: {(Client.External.State?.IsPremiumUser ?? false ? "green" : "red")}")">@Client.External.State?.IsPremiumUser</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>User agent:</td>
|
|
||||||
<td>@Client.UserAgent</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>InnerTube client:</td>
|
|
||||||
<td>@Client.External.State?.InnerTubeClient</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>InnerTube client version:</td>
|
|
||||||
<td>@Client.External.State?.InnerTubeClientVersion</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>InnerTube API key:</td>
|
|
||||||
<td>@Client.External.State?.InnertubeApiKey</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Language:</td>
|
|
||||||
<td>@Client.External.State?.InnerTubeContext?.InnerTubeClient?.HLanguage</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</MudSimpleTable>
|
|
||||||
@{
|
|
||||||
var avatar = Client.External.Channel?.AvatarImages.FirstOrDefault();
|
|
||||||
}
|
|
||||||
@if (avatar != null)
|
|
||||||
{
|
|
||||||
<MudImage Src="@avatar.Url" Elevation="0" ObjectFit="ObjectFit.ScaleDown" Width="75"/>
|
|
||||||
}
|
|
||||||
</MudStack>
|
|
||||||
|
|
||||||
<MudPaper Elevation="0" Outlined Class="pa-2">
|
<MudStack Row Spacing="2" Style="height: 100%">
|
||||||
<MudText>Import cookies</MudText>
|
<MudPaper Elevation="0" Outlined Class="pa-2" Style="width: 50%;">
|
||||||
<MudForm @bind-IsValid="@_cookieTextValid" Disabled="@(Client.CookieContainer.Count != 0)">
|
<MudText>Import cookies</MudText>
|
||||||
<MudTextField @bind-Value="@_cookieDomain" Immediate Required Label="Domain" RequiredError="Domain is required."/>
|
<MudText Typo="Typo.caption">@($"{ImportCookies.Count} cookie(s) imported")</MudText>
|
||||||
<MudTextField Class="my-2" Lines="4" AutoGrow @bind-Value="@_cookieText" Immediate Required Label="Cookies" Placeholder="EXAMPLE: Cookie1=Value1; Cookie2=Value2;"
|
<MudForm @bind-IsValid="@_cookieImportTextValid" Disabled="@(IsAnonymous)">
|
||||||
Validation="@(new Func<string, string?>(ValidateCookieText))"/>
|
<MudTextField @bind-Value="@_cookieDomain" Immediate Required Label="Domain"
|
||||||
<MudButton Variant="Variant.Outlined" Disabled="@(!_cookieTextValid)" OnClick="ParseCookies">Load</MudButton>
|
RequiredError="Domain is required."/>
|
||||||
</MudForm>
|
<MudTextField Class="my-2" Lines="4" AutoGrow @bind-Value="@_cookieText" Immediate
|
||||||
</MudPaper>
|
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>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
<MudDataGrid Items="Client.CookieContainer.GetAllCookies()" Dense Elevation="0" Outlined>
|
<MudDataGrid Items="ImportCookies" Dense Elevation="0" Outlined Style="width: 50%;">
|
||||||
<Header>
|
<Header>
|
||||||
<MudStack Class="ma-2">
|
<MudStack Class="ma-2">
|
||||||
<MudText>Cookies</MudText>
|
<MudText>Cookies</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</Header>
|
||||||
|
<Columns>
|
||||||
|
<TemplateColumn Title="Name">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Name"
|
||||||
|
Immediate/>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="Domain">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Domain"
|
||||||
|
Immediate/>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
<TemplateColumn Title="Value">
|
||||||
|
<CellTemplate>
|
||||||
|
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Value"
|
||||||
|
Immediate/>
|
||||||
|
</CellTemplate>
|
||||||
|
</TemplateColumn>
|
||||||
|
</Columns>
|
||||||
|
</MudDataGrid>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</Header>
|
</MudStack>
|
||||||
<Columns>
|
break;
|
||||||
<TemplateColumn Title="Name">
|
case AccountImportSteps.Validate:
|
||||||
<CellTemplate>
|
<MudStack Spacing="3">
|
||||||
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Name" Immediate/>
|
<MudPaper Elevation="0">
|
||||||
</CellTemplate>
|
@if (banner != null)
|
||||||
</TemplateColumn>
|
{
|
||||||
<TemplateColumn Title="Domain">
|
<MudImage Src="@banner.Url" Height="250" Style="width: 100%;"/>
|
||||||
<CellTemplate>
|
}
|
||||||
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Domain" Immediate/>
|
else
|
||||||
</CellTemplate>
|
{
|
||||||
</TemplateColumn>
|
<div Class="account-banner" Style="height: 250px; width: 100%;"></div>
|
||||||
<TemplateColumn Title="Value">
|
}
|
||||||
<CellTemplate>
|
<MudStack Row Spacing="3" Class="px-4">
|
||||||
<MudTextField Variant="Variant.Text" @bind-Value="@context.Item.Value" Immediate/>
|
@if (avatar != null)
|
||||||
</CellTemplate>
|
{
|
||||||
</TemplateColumn>
|
<MudImage Src="@avatar.Url" Class="mt-n5" Height="100" Width="100"/>
|
||||||
<PropertyColumn Title="Expires" Property="x => x.Expires"/>
|
}
|
||||||
</Columns>
|
else
|
||||||
</MudDataGrid>
|
{
|
||||||
</MudStack>
|
<div Class="avatar-pattern mt-n6" Style="height: 100px; width: 100px;"></div>
|
||||||
|
}
|
||||||
|
<MudStack Spacing="0">
|
||||||
|
<MudText Typo="Typo.h5">@(channel?.ChannelName ?? client?.Id)</MudText>
|
||||||
|
<MudText Typo="Typo.caption">@(string.IsNullOrWhiteSpace(channel?.Description) ? "No description!" : channel.Description)</MudText>
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudSimpleTable Bordered Dense Elevation="0" Outlined Square Hover>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Account id:</td>
|
||||||
|
<td>@client?.Id</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Account name:</td>
|
||||||
|
<td>@channel?.ChannelName</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Account handle:</td>
|
||||||
|
<td>@channel?.Handle</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Logged in:</td>
|
||||||
|
<td style="@($"color: {(clientState?.LoggedIn ?? false ? "green" : "red")}")">@clientState?.LoggedIn</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>YouTube Premium:</td>
|
||||||
|
<td style="@($"color: {(clientState?.IsPremiumUser ?? false ? "green" : "red")}")">@clientState?.IsPremiumUser</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>User agent:</td>
|
||||||
|
<td>@client?.UserAgent</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>InnerTube client:</td>
|
||||||
|
<td>@clientState?.InnerTubeClient</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>InnerTube client version:</td>
|
||||||
|
<td>@clientState?.InnerTubeClientVersion</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>InnerTube API key:</td>
|
||||||
|
<td>@clientState?.InnertubeApiKey</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Language:</td>
|
||||||
|
<td>@clientState?.InnerTubeContext?.InnerTubeClient?.HLanguage</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</MudSimpleTable>
|
||||||
|
</MudStack>
|
||||||
|
break;
|
||||||
|
}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<MudStack Spacing="2" Row>
|
<MudStack Spacing="2" Row>
|
||||||
<MudButton Color="Color.Error" OnClick="() => MudDialog?.Cancel()" Variant="Variant.Outlined">Cancel</MudButton>
|
<MudButton Color="Color.Error" OnClick="() => MudDialog?.Cancel()" Variant="Variant.Outlined">Cancel
|
||||||
<MudButton Color="Color.Info" Variant="Variant.Outlined" OnClick="ValidateAccount" Disabled="@(!CanValidate())">Validate</MudButton>
|
</MudButton>
|
||||||
<MudButton Color="Color.Primary" Variant="Variant.Outlined" Disabled="@(!CanSave())" OnClick="OnSave">Save</MudButton>
|
<MudButton Color="Color.Info" OnClick="ClearPreparedClient" Variant="Variant.Outlined">Reset</MudButton>
|
||||||
|
<MudButton Color="Color.Primary" OnClick="OnNextStep" Disabled="@(!CanContinue())" Variant="Variant.Outlined">@(_steps == AccountImportSteps.Validate ? "Save" : "Next")</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</MudDialog>
|
</MudDialog>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.account-banner {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
/* Pattern background */
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
135deg,
|
||||||
|
#1976d2, /* MudBlazor Primary */
|
||||||
|
#1976d2 20px,
|
||||||
|
#1565c0 20px,
|
||||||
|
#1565c0 40px
|
||||||
|
);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-pattern {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
/* Patterned background */
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
#1976d2,
|
||||||
|
#1976d2 10px,
|
||||||
|
#1565c0 10px,
|
||||||
|
#1565c0 20px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</style>
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
using Manager.App.Models.Library;
|
||||||
using Manager.YouTube;
|
using Manager.YouTube;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using MudBlazor;
|
using MudBlazor;
|
||||||
@@ -9,27 +10,79 @@ namespace Manager.App.Components.Dialogs
|
|||||||
{
|
{
|
||||||
[CascadingParameter] private IMudDialogInstance? MudDialog { get; set; }
|
[CascadingParameter] private IMudDialogInstance? MudDialog { get; set; }
|
||||||
[Parameter] public string DefaultUserAgent { get; set; } = "";
|
[Parameter] public string DefaultUserAgent { get; set; } = "";
|
||||||
|
private bool IsAnonymous { get; set; }
|
||||||
public YouTubeClient Client { get; set; } = new();
|
private ClientPrep? PreparingClient { get; set; }
|
||||||
|
private CookieCollection ImportCookies { get; set; } = [];
|
||||||
private bool _isLoading;
|
private bool _isLoading;
|
||||||
|
private AccountImportSteps _steps = AccountImportSteps.Authenticate;
|
||||||
|
|
||||||
private bool _cookieTextValid;
|
private bool _cookieImportTextValid;
|
||||||
private string _cookieText = "";
|
private string _cookieText = "";
|
||||||
private string _cookieDomain = ".youtube.com";
|
private string _cookieDomain = ".youtube.com";
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
Client.UserAgent = DefaultUserAgent;
|
|
||||||
base.OnInitialized();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private bool CanSave()
|
||||||
|
{
|
||||||
|
if (IsAnonymous || PreparingClient?.YouTubeClient?.State?.LoggedIn == true)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanContinue()
|
||||||
|
{
|
||||||
|
switch (_steps)
|
||||||
|
{
|
||||||
|
case AccountImportSteps.Authenticate:
|
||||||
|
if (IsAnonymous || ImportCookies.Count != 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case AccountImportSteps.Validate:
|
||||||
|
if (IsAnonymous || PreparingClient?.YouTubeClient?.State?.LoggedIn == true)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnNextStep()
|
||||||
|
{
|
||||||
|
switch (_steps)
|
||||||
|
{
|
||||||
|
case AccountImportSteps.Authenticate:
|
||||||
|
if (CanContinue())
|
||||||
|
{
|
||||||
|
_steps = AccountImportSteps.Validate;
|
||||||
|
await BuildClient();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SnackbarService.Add("Cannot continue!", Severity.Warning);
|
||||||
|
break;
|
||||||
|
case AccountImportSteps.Validate:
|
||||||
|
if (CanSave())
|
||||||
|
{
|
||||||
|
MudDialog?.Close(DialogResult.Ok(PreparingClient));
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SnackbarService.Add("Cannot save!", Severity.Warning);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
private void ParseCookies()
|
private void ParseCookies()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var cookies = ParseCookieHeader(_cookieText, _cookieDomain);
|
ImportCookies.Clear();
|
||||||
Client.CookieContainer.Add(cookies);
|
ImportCookies.Add(ParseCookieHeader(_cookieText, _cookieDomain));
|
||||||
_cookieText = string.Empty;
|
_cookieText = string.Empty;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -39,6 +92,16 @@ namespace Manager.App.Components.Dialogs
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearPreparedClient()
|
||||||
|
{
|
||||||
|
PreparingClient?.YouTubeClient?.Dispose();
|
||||||
|
PreparingClient = null;
|
||||||
|
IsAnonymous = false;
|
||||||
|
ImportCookies.Clear();
|
||||||
|
_steps = AccountImportSteps.Authenticate;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private static string? ValidateCookieText(string text)
|
private static string? ValidateCookieText(string text)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
@@ -82,46 +145,41 @@ namespace Manager.App.Components.Dialogs
|
|||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanValidate()
|
private async Task BuildClient()
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(Client.UserAgent) || Client.CookieContainer.Count <= 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanSave()
|
|
||||||
{
|
|
||||||
if (Client.External.State == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(Client.Id))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Client.SapisidCookie != null && Client.External.State.LoggedIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ValidateAccount()
|
|
||||||
{
|
{
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
var result = await Client.BuildClientAsync();
|
PreparingClient = new ClientPrep();
|
||||||
if (!result.IsSuccess)
|
if (IsAnonymous)
|
||||||
{
|
{
|
||||||
SnackbarService.Add(result.Error?.Description ?? "Error validating account.", Severity.Error);
|
ImportCookies.Clear();
|
||||||
|
}
|
||||||
|
var clientResult = await YouTubeClient.CreateAsync(ImportCookies, DefaultUserAgent);
|
||||||
|
if (clientResult.IsSuccess)
|
||||||
|
{
|
||||||
|
PreparingClient.YouTubeClient = clientResult.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PreparingClient.YouTubeClient == null)
|
||||||
|
{
|
||||||
|
SnackbarService.Add("Failed to get client!", Severity.Error);
|
||||||
|
_isLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var accountResult = await PreparingClient.YouTubeClient.GetChannelByIdAsync(PreparingClient.YouTubeClient.Id);
|
||||||
|
if (accountResult.IsSuccess)
|
||||||
|
{
|
||||||
|
PreparingClient.Channel = accountResult.Value;
|
||||||
}
|
}
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
}
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
private void OnSave()
|
|
||||||
{
|
|
||||||
MudDialog?.Close(DialogResult.Ok(Client));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum AccountImportSteps
|
||||||
|
{
|
||||||
|
Authenticate,
|
||||||
|
Validate
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
@page "/Channels"
|
@page "/Channels"
|
||||||
@using Manager.App.Models.Settings
|
@using Manager.App.Models.Settings
|
||||||
|
@using Manager.App.Services.System
|
||||||
@using Microsoft.Extensions.Options
|
@using Microsoft.Extensions.Options
|
||||||
|
|
||||||
@inject ILibraryService LibraryService
|
@inject ILibraryService LibraryService
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Manager.App.Components.Dialogs;
|
using Manager.App.Components.Dialogs;
|
||||||
|
using Manager.App.Models.Library;
|
||||||
using Manager.Data.Entities.LibraryContext;
|
using Manager.Data.Entities.LibraryContext;
|
||||||
using Manager.YouTube;
|
using Manager.YouTube;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
@@ -28,6 +29,20 @@ public partial class Channels : ComponentBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = (YouTubeClient)result.Data;
|
var client = (ClientPrep)result.Data;
|
||||||
|
if (client == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*var savedResult = await ClientManager.SaveClientAsync(client);
|
||||||
|
if (!savedResult.IsSuccess)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Failed to store client: {savedResult.Error?.Description ?? "Unknown!"}", Severity.Error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Client {client.External.Channel?.Handle ?? client.Id} saved!", Severity.Success);
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -3,6 +3,7 @@ using DotBased.Logging.MEL;
|
|||||||
using DotBased.Logging.Serilog;
|
using DotBased.Logging.Serilog;
|
||||||
using Manager.App.Models.Settings;
|
using Manager.App.Models.Settings;
|
||||||
using Manager.App.Services;
|
using Manager.App.Services;
|
||||||
|
using Manager.App.Services.System;
|
||||||
using Manager.Data.Contexts;
|
using Manager.Data.Contexts;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@@ -25,6 +26,8 @@ public static class DependencyInjection
|
|||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddScoped<ILibraryService, LibraryService>();
|
builder.Services.AddScoped<ILibraryService, LibraryService>();
|
||||||
|
|
||||||
|
/*builder.Services.AddHostedService<ClientManager>();*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupSettings(this WebApplicationBuilder builder)
|
public static void SetupSettings(this WebApplicationBuilder builder)
|
||||||
|
10
Manager.App/Models/Library/ClientPrep.cs
Normal file
10
Manager.App/Models/Library/ClientPrep.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using Manager.YouTube;
|
||||||
|
using Manager.YouTube.Models.Innertube;
|
||||||
|
|
||||||
|
namespace Manager.App.Models.Library;
|
||||||
|
|
||||||
|
public class ClientPrep
|
||||||
|
{
|
||||||
|
public YouTubeClient? YouTubeClient { get; set; }
|
||||||
|
public Channel? Channel { get; set; }
|
||||||
|
}
|
@@ -7,6 +7,8 @@ namespace Manager.App.Services;
|
|||||||
|
|
||||||
public interface ILibraryService
|
public interface ILibraryService
|
||||||
{
|
{
|
||||||
|
public Task<Result<ChannelEntity>> GetChannelByIdAsync(string id, CancellationToken cancellationToken = default);
|
||||||
|
public Task<Result> SaveChannelAsync(ChannelEntity channel, CancellationToken cancellationToken = default);
|
||||||
public Task<Result<LibraryInformation>> GetLibraryInfoAsync(CancellationToken cancellationToken = default);
|
public Task<Result<LibraryInformation>> GetLibraryInfoAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
public Task<ListResult<ChannelEntity>> GetChannelAccountsAsync(int total = 20, int offset = 0, CancellationToken cancellationToken = default);
|
public Task<ListResult<ChannelEntity>> GetChannelAccountsAsync(int total = 20, int offset = 0, CancellationToken cancellationToken = default);
|
||||||
|
@@ -29,6 +29,53 @@ public class LibraryService : ILibraryService
|
|||||||
Directory.CreateDirectory(Path.Combine(_librarySettings.Path, SubDirChannels));
|
Directory.CreateDirectory(Path.Combine(_librarySettings.Path, SubDirChannels));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Result<ChannelEntity>> GetChannelByIdAsync(string id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
|
{
|
||||||
|
return ResultError.Fail("Channel id cannot be null or empty!");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
var channel = await context.Channels.Include(c => c.ClientAccount).FirstOrDefaultAsync(c => c.Id == id, cancellationToken);
|
||||||
|
if (channel == null)
|
||||||
|
{
|
||||||
|
return ResultError.Fail("Channel not found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return HandleException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result> SaveChannelAsync(ChannelEntity channel, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
if (context.Channels.Any(c => c.Id == channel.Id))
|
||||||
|
{
|
||||||
|
context.Channels.Update(channel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Channels.Add(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
var changed = await context.SaveChangesAsync(cancellationToken);
|
||||||
|
return changed <= 0 ? Result.Success() : ResultError.Fail("Failed to save channel!");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return ResultError.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Result<LibraryInformation>> GetLibraryInfoAsync(CancellationToken cancellationToken = default)
|
public async Task<Result<LibraryInformation>> GetLibraryInfoAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -57,7 +104,7 @@ public class LibraryService : ILibraryService
|
|||||||
{
|
{
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
var orderedAccounts = context.Channels.Include(x => x.ClientAccount).Where(x => x.ClientAccount != null).OrderBy(x => x.Id);
|
var orderedAccounts = context.Channels.Include(x => x.ClientAccount).Where(x => x.ClientAccount != null).OrderBy(x => x.Id);
|
||||||
return new ListResultReturn<ChannelEntity>(orderedAccounts.Skip(offset).Take(total).ToList(), orderedAccounts.Count());
|
return new ListResultReturn<ChannelEntity>(orderedAccounts.Skip(offset).Take(total).ToList(),orderedAccounts.Count());
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
|
using System.Net;
|
||||||
using DotBased.Monads;
|
using DotBased.Monads;
|
||||||
|
using Manager.App.Models.Library;
|
||||||
|
using Manager.Data.Entities.LibraryContext;
|
||||||
using Manager.YouTube;
|
using Manager.YouTube;
|
||||||
|
|
||||||
namespace Manager.App.Services.System;
|
namespace Manager.App.Services.System;
|
||||||
@@ -19,17 +22,118 @@ public class ClientManager : BackgroundService
|
|||||||
// Clear up
|
// Clear up
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> SaveClientAsync(YouTubeClient client)
|
public async Task<Result<ClientPrep>> PrepareClient()
|
||||||
{
|
{
|
||||||
return ResultError.Fail("Not implemented");
|
|
||||||
|
return ResultError.Fail("Not implemented!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*public async Task<Result> SaveClientAsync(YouTubeClient client, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(client.Id))
|
||||||
|
{
|
||||||
|
return ResultError.Fail("Client does not have an ID, cannot save to library database!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var channelResult = await libraryService.GetChannelByIdAsync(client.Id, cancellationToken);
|
||||||
|
|
||||||
|
ChannelEntity? channel;
|
||||||
|
if (channelResult.IsSuccess)
|
||||||
|
{
|
||||||
|
channel = channelResult.Value;
|
||||||
|
UpdateChannelEntity(channel, client);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
channel = CreateNewChannelFromClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
var saveResult = await libraryService.SaveChannelAsync(channel, cancellationToken);
|
||||||
|
return saveResult;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*private void UpdateChannelEntity(ChannelEntity channel, YouTubeClient client)
|
||||||
|
{
|
||||||
|
channel.Name = client.External.Channel?.ChannelName;
|
||||||
|
channel.Handle = client.External.Channel?.Handle;
|
||||||
|
channel.Description = client.External.Channel?.Description;
|
||||||
|
var clientAcc = channel.ClientAccount;
|
||||||
|
if (clientAcc != null)
|
||||||
|
{
|
||||||
|
clientAcc.UserAgent = clientAcc.UserAgent;
|
||||||
|
var currentCookies = client.CookieContainer.GetAllCookies();
|
||||||
|
foreach (var cookieEntity in clientAcc.HttpCookies.ToList())
|
||||||
|
{
|
||||||
|
var cookie = currentCookies[cookieEntity.Name];
|
||||||
|
if (cookie == null)
|
||||||
|
{
|
||||||
|
clientAcc.HttpCookies.Remove(cookieEntity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cookie.Domain.Equals(cookieEntity.Domain, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cookieEntity.Value = cookie.Value;
|
||||||
|
cookieEntity.Path = cookie.Path;
|
||||||
|
cookieEntity.Secure = cookie.Secure;
|
||||||
|
cookieEntity.HttpOnly = cookie.HttpOnly;
|
||||||
|
cookieEntity.ExpiresUtc = cookie.Expires == DateTime.MinValue ? null : cookie.Expires;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChannelEntity CreateNewChannelFromClient(YouTubeClient client)
|
||||||
|
{
|
||||||
|
var cookies = new List<HttpCookieEntity>();
|
||||||
|
foreach (var cookieObj in client.CookieContainer.GetAllCookies())
|
||||||
|
{
|
||||||
|
if (cookieObj is not Cookie cookie)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookieEntity = new HttpCookieEntity
|
||||||
|
{
|
||||||
|
ClientId = client.Id,
|
||||||
|
Name = cookie.Name,
|
||||||
|
Value = cookie.Value,
|
||||||
|
Domain = cookie.Domain,
|
||||||
|
Path = cookie.Path,
|
||||||
|
Secure = cookie.Secure,
|
||||||
|
HttpOnly = cookie.HttpOnly,
|
||||||
|
ExpiresUtc = cookie.Expires == DateTime.MinValue ? null : cookie.Expires
|
||||||
|
};
|
||||||
|
cookies.Add(cookieEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientAcc = new ClientAccountEntity
|
||||||
|
{
|
||||||
|
Id = client.Id,
|
||||||
|
UserAgent = client.UserAgent,
|
||||||
|
HttpCookies = cookies
|
||||||
|
};
|
||||||
|
|
||||||
|
var channel = new ChannelEntity
|
||||||
|
{
|
||||||
|
Id = client.Id,
|
||||||
|
Name = client.External.Channel?.ChannelName,
|
||||||
|
Handle = client.External.Channel?.Handle,
|
||||||
|
Description = client.External.Channel?.Description,
|
||||||
|
ClientAccount = clientAcc
|
||||||
|
};
|
||||||
|
return channel;
|
||||||
|
}*/
|
||||||
|
|
||||||
public async Task<Result<YouTubeClient>> LoadClientByIdAsync(string id)
|
public async Task<Result<YouTubeClient>> LoadClientByIdAsync(string id)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(id))
|
if (string.IsNullOrWhiteSpace(id))
|
||||||
{
|
{
|
||||||
return ResultError.Fail("Client ID is empty!");
|
return ResultError.Fail("Client ID is empty!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return ResultError.Fail("Not implemented");
|
return ResultError.Fail("Not implemented");
|
||||||
}
|
}
|
||||||
|
@@ -12,10 +12,6 @@ public class ChannelEntity : DateTimeBase
|
|||||||
public string? Handle { get; set; }
|
public string? Handle { get; set; }
|
||||||
[MaxLength(DataConstants.DbContext.DefaultDbDescriptionStringSize)]
|
[MaxLength(DataConstants.DbContext.DefaultDbDescriptionStringSize)]
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
public DateTime JoinedDate { get; set; }
|
|
||||||
public long Subscribers { get; set; }
|
|
||||||
public long TotalVideos { get; set; }
|
|
||||||
public long TotalViews { get; set; }
|
|
||||||
public List<MediaEntity> Media { get; set; } = [];
|
public List<MediaEntity> Media { get; set; } = [];
|
||||||
public List<PlaylistEntity> Playlists { get; set; } = [];
|
public List<PlaylistEntity> Playlists { get; set; } = [];
|
||||||
public ClientAccountEntity? ClientAccount { get; set; }
|
public ClientAccountEntity? ClientAccount { get; set; }
|
||||||
|
@@ -16,7 +16,5 @@ public class HttpCookieEntity : DateTimeBase
|
|||||||
public bool Secure { get; set; }
|
public bool Secure { get; set; }
|
||||||
public bool HttpOnly { get; set; }
|
public bool HttpOnly { get; set; }
|
||||||
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
|
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
|
||||||
public string? SameSite { get; set; }
|
|
||||||
[MaxLength(DataConstants.DbContext.DefaultDbStringSize)]
|
|
||||||
public required string ClientId { get; set; }
|
public required string ClientId { get; set; }
|
||||||
}
|
}
|
||||||
|
@@ -1,424 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Manager.Data.Contexts;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Manager.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(LibraryDbContext))]
|
|
||||||
[Migration("20250902141251_InitialLibrary")]
|
|
||||||
partial class InitialLibrary
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.19");
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.CaptionEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("MediaId")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("LanguageCode")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("MediaId", "LanguageCode");
|
|
||||||
|
|
||||||
b.ToTable("captions", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ChannelEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("JoinedDate")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<long>("Subscribers")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("TotalVideos")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("TotalViews")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("channels", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ClientAccountEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("UserAgent")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("client_accounts", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.HttpCookieEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("ClientId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Domain")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("ExpiresUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("HttpOnly")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Path")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("SameSite")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("Secure")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Value")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Name");
|
|
||||||
|
|
||||||
b.HasIndex("ClientId");
|
|
||||||
|
|
||||||
b.ToTable("http_cookies", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.Join.PlaylistMedia", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("PlaylistId")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("MediaId")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("PlaylistId", "MediaId");
|
|
||||||
|
|
||||||
b.HasIndex("MediaId");
|
|
||||||
|
|
||||||
b.ToTable("join_playlist_media", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("ChannelId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("ExternalState")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("IsDownloaded")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("State")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("UploadDateUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("ChannelId");
|
|
||||||
|
|
||||||
b.ToTable("media", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaFormatEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("MediaId")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("Itag")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("ApproxDurationMs")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int?>("AudioChannels")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("AudioSampleRate")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<long>("AverageBitrate")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("Bitrate")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("ContentLengthBytes")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<double?>("Framerate")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<int?>("Height")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("IsAdaptive")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("LastModifiedUnixEpoch")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<double?>("LoudnessDb")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<string>("MimeType")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Quality")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("QualityLabel")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int?>("Width")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("MediaId", "Itag");
|
|
||||||
|
|
||||||
b.ToTable("media_formats", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.PlaylistEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("ChannelId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("ChannelId");
|
|
||||||
|
|
||||||
b.ToTable("playlists", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.CaptionEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.MediaEntity", null)
|
|
||||||
.WithMany("Captions")
|
|
||||||
.HasForeignKey("MediaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ClientAccountEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.ChannelEntity", null)
|
|
||||||
.WithOne("ClientAccount")
|
|
||||||
.HasForeignKey("Manager.Data.Entities.LibraryContext.ClientAccountEntity", "Id")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.HttpCookieEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.ClientAccountEntity", null)
|
|
||||||
.WithMany("HttpCookies")
|
|
||||||
.HasForeignKey("ClientId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.Join.PlaylistMedia", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.MediaEntity", null)
|
|
||||||
.WithMany("PlaylistMedias")
|
|
||||||
.HasForeignKey("MediaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.PlaylistEntity", null)
|
|
||||||
.WithMany("PlaylistMedias")
|
|
||||||
.HasForeignKey("PlaylistId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.ChannelEntity", null)
|
|
||||||
.WithMany("Media")
|
|
||||||
.HasForeignKey("ChannelId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaFormatEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.MediaEntity", null)
|
|
||||||
.WithMany("Formats")
|
|
||||||
.HasForeignKey("MediaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.PlaylistEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.ChannelEntity", null)
|
|
||||||
.WithMany("Playlists")
|
|
||||||
.HasForeignKey("ChannelId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ChannelEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ClientAccount");
|
|
||||||
|
|
||||||
b.Navigation("Media");
|
|
||||||
|
|
||||||
b.Navigation("Playlists");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ClientAccountEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("HttpCookies");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Captions");
|
|
||||||
|
|
||||||
b.Navigation("Formats");
|
|
||||||
|
|
||||||
b.Navigation("PlaylistMedias");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.PlaylistEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("PlaylistMedias");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,259 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Manager.Data.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class InitialLibrary : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "channels",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
|
||||||
JoinedDate = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
Subscribers = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
TotalVideos = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
TotalViews = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_channels", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "client_accounts",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
UserAgent = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_client_accounts", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_client_accounts_channels_Id",
|
|
||||||
column: x => x.Id,
|
|
||||||
principalTable: "channels",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "media",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
Title = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
|
||||||
UploadDateUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
ChannelId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
ExternalState = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
IsDownloaded = table.Column<bool>(type: "INTEGER", nullable: false),
|
|
||||||
State = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_media", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_media_channels_ChannelId",
|
|
||||||
column: x => x.ChannelId,
|
|
||||||
principalTable: "channels",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "playlists",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
|
||||||
ChannelId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_playlists", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_playlists_channels_ChannelId",
|
|
||||||
column: x => x.ChannelId,
|
|
||||||
principalTable: "channels",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "http_cookies",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
Value = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
Domain = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
Path = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
ExpiresUtc = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
|
||||||
Secure = table.Column<bool>(type: "INTEGER", nullable: false),
|
|
||||||
HttpOnly = table.Column<bool>(type: "INTEGER", nullable: false),
|
|
||||||
SameSite = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
ClientId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_http_cookies", x => x.Name);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_http_cookies_client_accounts_ClientId",
|
|
||||||
column: x => x.ClientId,
|
|
||||||
principalTable: "client_accounts",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "captions",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
MediaId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
LanguageCode = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_captions", x => new { x.MediaId, x.LanguageCode });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_captions_media_MediaId",
|
|
||||||
column: x => x.MediaId,
|
|
||||||
principalTable: "media",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "media_formats",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
MediaId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
Itag = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
Quality = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
IsAdaptive = table.Column<bool>(type: "INTEGER", nullable: false),
|
|
||||||
MimeType = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
Bitrate = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
AverageBitrate = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
LastModifiedUnixEpoch = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
ContentLengthBytes = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
ApproxDurationMs = table.Column<long>(type: "INTEGER", nullable: false),
|
|
||||||
Width = table.Column<int>(type: "INTEGER", nullable: true),
|
|
||||||
Height = table.Column<int>(type: "INTEGER", nullable: true),
|
|
||||||
Framerate = table.Column<double>(type: "REAL", nullable: true),
|
|
||||||
QualityLabel = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
AudioChannels = table.Column<int>(type: "INTEGER", nullable: true),
|
|
||||||
AudioSampleRate = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
|
||||||
LoudnessDb = table.Column<double>(type: "REAL", nullable: true),
|
|
||||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_media_formats", x => new { x.MediaId, x.Itag });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_media_formats_media_MediaId",
|
|
||||||
column: x => x.MediaId,
|
|
||||||
principalTable: "media",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "join_playlist_media",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
PlaylistId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
MediaId = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
|
||||||
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false),
|
|
||||||
LastModifiedUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_join_playlist_media", x => new { x.PlaylistId, x.MediaId });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_join_playlist_media_media_MediaId",
|
|
||||||
column: x => x.MediaId,
|
|
||||||
principalTable: "media",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_join_playlist_media_playlists_PlaylistId",
|
|
||||||
column: x => x.PlaylistId,
|
|
||||||
principalTable: "playlists",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_http_cookies_ClientId",
|
|
||||||
table: "http_cookies",
|
|
||||||
column: "ClientId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_join_playlist_media_MediaId",
|
|
||||||
table: "join_playlist_media",
|
|
||||||
column: "MediaId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_media_ChannelId",
|
|
||||||
table: "media",
|
|
||||||
column: "ChannelId");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_playlists_ChannelId",
|
|
||||||
table: "playlists",
|
|
||||||
column: "ChannelId");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "captions");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "http_cookies");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "join_playlist_media");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "media_formats");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "client_accounts");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "playlists");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "media");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "channels");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,421 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using Manager.Data.Contexts;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace Manager.Data.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(LibraryDbContext))]
|
|
||||||
partial class LibraryDbContextModelSnapshot : ModelSnapshot
|
|
||||||
{
|
|
||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.19");
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.CaptionEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("MediaId")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("LanguageCode")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("MediaId", "LanguageCode");
|
|
||||||
|
|
||||||
b.ToTable("captions", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ChannelEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("JoinedDate")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<long>("Subscribers")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("TotalVideos")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("TotalViews")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("channels", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ClientAccountEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("UserAgent")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("client_accounts", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.HttpCookieEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("ClientId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Domain")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("ExpiresUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("HttpOnly")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Path")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("SameSite")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("Secure")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Value")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Name");
|
|
||||||
|
|
||||||
b.HasIndex("ClientId");
|
|
||||||
|
|
||||||
b.ToTable("http_cookies", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.Join.PlaylistMedia", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("PlaylistId")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("MediaId")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("PlaylistId", "MediaId");
|
|
||||||
|
|
||||||
b.HasIndex("MediaId");
|
|
||||||
|
|
||||||
b.ToTable("join_playlist_media", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("ChannelId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("ExternalState")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("IsDownloaded")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("State")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Title")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("UploadDateUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("ChannelId");
|
|
||||||
|
|
||||||
b.ToTable("media", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaFormatEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("MediaId")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("Itag")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("ApproxDurationMs")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<int?>("AudioChannels")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("AudioSampleRate")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<long>("AverageBitrate")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("Bitrate")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("ContentLengthBytes")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<double?>("Framerate")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<int?>("Height")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("IsAdaptive")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<long>("LastModifiedUnixEpoch")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<double?>("LoudnessDb")
|
|
||||||
.HasColumnType("REAL");
|
|
||||||
|
|
||||||
b.Property<string>("MimeType")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Quality")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("QualityLabel")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int?>("Width")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("MediaId", "Itag");
|
|
||||||
|
|
||||||
b.ToTable("media_formats", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.PlaylistEntity", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("ChannelId")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAtUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime>("LastModifiedUtc")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Name")
|
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("ChannelId");
|
|
||||||
|
|
||||||
b.ToTable("playlists", (string)null);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.CaptionEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.MediaEntity", null)
|
|
||||||
.WithMany("Captions")
|
|
||||||
.HasForeignKey("MediaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ClientAccountEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.ChannelEntity", null)
|
|
||||||
.WithOne("ClientAccount")
|
|
||||||
.HasForeignKey("Manager.Data.Entities.LibraryContext.ClientAccountEntity", "Id")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.HttpCookieEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.ClientAccountEntity", null)
|
|
||||||
.WithMany("HttpCookies")
|
|
||||||
.HasForeignKey("ClientId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.Join.PlaylistMedia", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.MediaEntity", null)
|
|
||||||
.WithMany("PlaylistMedias")
|
|
||||||
.HasForeignKey("MediaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.PlaylistEntity", null)
|
|
||||||
.WithMany("PlaylistMedias")
|
|
||||||
.HasForeignKey("PlaylistId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.ChannelEntity", null)
|
|
||||||
.WithMany("Media")
|
|
||||||
.HasForeignKey("ChannelId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaFormatEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.MediaEntity", null)
|
|
||||||
.WithMany("Formats")
|
|
||||||
.HasForeignKey("MediaId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.PlaylistEntity", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("Manager.Data.Entities.LibraryContext.ChannelEntity", null)
|
|
||||||
.WithMany("Playlists")
|
|
||||||
.HasForeignKey("ChannelId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ChannelEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("ClientAccount");
|
|
||||||
|
|
||||||
b.Navigation("Media");
|
|
||||||
|
|
||||||
b.Navigation("Playlists");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.ClientAccountEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("HttpCookies");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.MediaEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("Captions");
|
|
||||||
|
|
||||||
b.Navigation("Formats");
|
|
||||||
|
|
||||||
b.Navigation("PlaylistMedias");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Manager.Data.Entities.LibraryContext.PlaylistEntity", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("PlaylistMedias");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,35 +0,0 @@
|
|||||||
using Manager.YouTube.Models.Innertube;
|
|
||||||
|
|
||||||
namespace Manager.YouTube.Models;
|
|
||||||
|
|
||||||
public class ClientExternalData
|
|
||||||
{
|
|
||||||
public ClientState? State { get; set; }
|
|
||||||
public Channel? Channel { get; set; }
|
|
||||||
public List<string> DatasyncIds { get; set; } = [];
|
|
||||||
|
|
||||||
public string GetDatasyncId()
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(State?.WebPlayerContextConfig?.WebPlayerContext?.DatasyncId))
|
|
||||||
{
|
|
||||||
return State.WebPlayerContextConfig.WebPlayerContext.DatasyncId;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tempDatasyncId = "";
|
|
||||||
foreach (var datasyncId in DatasyncIds)
|
|
||||||
{
|
|
||||||
var split = datasyncId.Split("||", StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
switch (split.Length)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
case 2 when tempDatasyncId.Equals(split[1]):
|
|
||||||
continue;
|
|
||||||
case 2:
|
|
||||||
tempDatasyncId = split[1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tempDatasyncId;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -7,24 +7,18 @@ public static class NetworkService
|
|||||||
{
|
{
|
||||||
public const string Origin = "https://www.youtube.com";
|
public const string Origin = "https://www.youtube.com";
|
||||||
|
|
||||||
public static async Task<Result<string>> MakeRequestAsync(HttpRequestMessage request, YouTubeClient client)
|
public static async Task<Result<string>> MakeRequestAsync(HttpRequestMessage request, YouTubeClient client, bool skipAuthenticationHeader = false)
|
||||||
{
|
{
|
||||||
request.Headers.Add("Origin", Origin);
|
request.Headers.Add("Origin", Origin);
|
||||||
request.Headers.UserAgent.ParseAdd(client.UserAgent);
|
request.Headers.UserAgent.ParseAdd(client.UserAgent);
|
||||||
if (client.SapisidCookie != null)
|
if (client.SapisidCookie != null && !skipAuthenticationHeader)
|
||||||
{
|
{
|
||||||
request.Headers.Authorization = AuthenticationUtilities.GetSapisidHashHeader(client.External.GetDatasyncId(), client.SapisidCookie.Value, Origin);
|
request.Headers.Authorization = AuthenticationUtilities.GetSapisidHashHeader(client.GetDatasyncId(), client.SapisidCookie.Value, Origin);
|
||||||
}
|
|
||||||
|
|
||||||
var httpClient = client.GetHttpClient();
|
|
||||||
if (httpClient == null)
|
|
||||||
{
|
|
||||||
return ResultError.Fail("Failed getting http client!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await httpClient.SendAsync(request);
|
var response = await client.HttpClient.SendAsync(request);
|
||||||
var contentString = await response.Content.ReadAsStringAsync();
|
var contentString = await response.Content.ReadAsStringAsync();
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
|
@@ -10,16 +10,27 @@ public static class AuthenticationUtilities
|
|||||||
private const string HeaderScheme = "SAPISIDHASH";
|
private const string HeaderScheme = "SAPISIDHASH";
|
||||||
|
|
||||||
// Dave Thomas & windy for updated answer @ https://stackoverflow.com/a/32065323/9948300
|
// Dave Thomas & windy for updated answer @ https://stackoverflow.com/a/32065323/9948300
|
||||||
public static AuthenticationHeaderValue? GetSapisidHashHeader(string datasyncId, string sapisid, string origin)
|
public static AuthenticationHeaderValue GetSapisidHashHeader(string datasyncId, string sapisid, string origin)
|
||||||
{
|
{
|
||||||
var strHash = GetSapisidHash(datasyncId, sapisid, origin);
|
var strHash = GetSapisidHash(datasyncId, sapisid, origin);
|
||||||
return strHash == null ? null : new AuthenticationHeaderValue(HeaderScheme, strHash);
|
return new AuthenticationHeaderValue(HeaderScheme, strHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string? GetSapisidHash(string datasyncId, string sapisid, string origin, string? time = null)
|
public static string GetSapisidHash(string datasyncId, string sapisid, string origin, string? time = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(datasyncId) || string.IsNullOrWhiteSpace(sapisid) || string.IsNullOrWhiteSpace(origin))
|
if (string.IsNullOrWhiteSpace(datasyncId))
|
||||||
return null;
|
{
|
||||||
|
throw new ArgumentNullException(nameof(datasyncId));
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(sapisid))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(sapisid));
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(origin))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(origin));
|
||||||
|
}
|
||||||
|
|
||||||
datasyncId = datasyncId.Replace("||", "");
|
datasyncId = datasyncId.Replace("||", "");
|
||||||
sapisid = Uri.UnescapeDataString(sapisid);
|
sapisid = Uri.UnescapeDataString(sapisid);
|
||||||
if (string.IsNullOrWhiteSpace(time))
|
if (string.IsNullOrWhiteSpace(time))
|
||||||
|
@@ -4,7 +4,6 @@ using System.Text;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
using DotBased.Monads;
|
using DotBased.Monads;
|
||||||
using Manager.YouTube.Models;
|
|
||||||
using Manager.YouTube.Models.Innertube;
|
using Manager.YouTube.Models.Innertube;
|
||||||
using Manager.YouTube.Parsers;
|
using Manager.YouTube.Parsers;
|
||||||
using Manager.YouTube.Parsers.Json;
|
using Manager.YouTube.Parsers.Json;
|
||||||
@@ -16,44 +15,62 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
public string Id { get; private set; } = "";
|
public string Id { get; private set; } = "";
|
||||||
public string? UserAgent { get; set; }
|
public string? UserAgent { get; set; }
|
||||||
public CookieContainer CookieContainer { get; } = new() { PerDomainCapacity = 50 };
|
public CookieContainer CookieContainer { get; } = new() { PerDomainCapacity = 50 };
|
||||||
public ClientExternalData External { get; set; } = new();
|
public ClientState? State { get; private set; }
|
||||||
|
public List<string> DatasyncIds { get; } = [];
|
||||||
public Cookie? SapisidCookie => CookieContainer.GetAllCookies()["SAPISID"];
|
public Cookie? SapisidCookie => CookieContainer.GetAllCookies()["SAPISID"];
|
||||||
public HttpClient? GetHttpClient() => _httpClient;
|
public HttpClient HttpClient { get; }
|
||||||
|
|
||||||
private HttpClient? _httpClient;
|
private YouTubeClient(CookieCollection cookies, string userAgent)
|
||||||
|
|
||||||
public YouTubeClient()
|
|
||||||
{
|
{
|
||||||
SetupClient();
|
if (string.IsNullOrWhiteSpace(userAgent))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(userAgent));
|
||||||
|
}
|
||||||
|
UserAgent = userAgent;
|
||||||
|
if (cookies.Count == 0)
|
||||||
|
{
|
||||||
|
Id = $"anon_{Guid.NewGuid()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
CookieContainer.Add(cookies);
|
||||||
|
HttpClient = new HttpClient(GetHttpClientHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupClient()
|
public static async Task<Result<YouTubeClient>> CreateAsync(CookieCollection cookies, string userAgent)
|
||||||
{
|
{
|
||||||
_httpClient?.Dispose();
|
var client = new YouTubeClient(cookies, userAgent);
|
||||||
|
var clientInitializeResult = await client.FetchClientDataAsync();
|
||||||
|
if (!clientInitializeResult.IsSuccess)
|
||||||
|
{
|
||||||
|
return clientInitializeResult.Error ?? ResultError.Fail("Failed to initialize YouTube client!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpClientHandler GetHttpClientHandler()
|
||||||
|
{
|
||||||
var clientHandler = new HttpClientHandler
|
var clientHandler = new HttpClientHandler
|
||||||
{
|
{
|
||||||
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
|
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
|
||||||
UseCookies = true,
|
UseCookies = true,
|
||||||
CookieContainer = CookieContainer
|
CookieContainer = CookieContainer
|
||||||
};
|
};
|
||||||
_httpClient = new HttpClient(clientHandler);
|
return clientHandler;
|
||||||
_httpClient.DefaultRequestHeaders.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result> BuildClientAsync()
|
internal async Task<Result> FetchClientDataAsync()
|
||||||
{
|
{
|
||||||
if (External.State is not { LoggedIn: true })
|
if (State is not { LoggedIn: true })
|
||||||
{
|
{
|
||||||
var state = await GetClientStateAsync();
|
var state = await GetClientStateAsync();
|
||||||
if (!state.IsSuccess)
|
if (!state.IsSuccess)
|
||||||
{
|
{
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
External.State = state.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(External.State.WebPlayerContextConfig?.WebPlayerContext?.DatasyncId))
|
if (string.IsNullOrWhiteSpace(State?.WebPlayerContextConfig?.WebPlayerContext?.DatasyncId))
|
||||||
{
|
{
|
||||||
var datasyncResult = await GetDatasyncIds();
|
var datasyncResult = await GetDatasyncIds();
|
||||||
if (!datasyncResult.IsSuccess)
|
if (!datasyncResult.IsSuccess)
|
||||||
@@ -63,9 +80,9 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
|
|
||||||
foreach (var id in datasyncResult.Value)
|
foreach (var id in datasyncResult.Value)
|
||||||
{
|
{
|
||||||
if (External.DatasyncIds.Contains(id))
|
if (DatasyncIds.Contains(id))
|
||||||
continue;
|
continue;
|
||||||
External.DatasyncIds.Add(id);
|
DatasyncIds.Add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,18 +96,11 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
|
|
||||||
Id = accountInfoResult.Value;
|
Id = accountInfoResult.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelResult = await GetChannelByIdAsync(Id);
|
|
||||||
if (!channelResult.IsSuccess)
|
|
||||||
{
|
|
||||||
return channelResult.Error ?? ResultError.Fail("Failed to get channel.");
|
|
||||||
}
|
|
||||||
External.Channel = channelResult.Value;
|
|
||||||
|
|
||||||
return Result.Success();
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<ClientState>> GetClientStateAsync()
|
private async Task<Result> GetClientStateAsync()
|
||||||
{
|
{
|
||||||
var httpRequest = new HttpRequestMessage
|
var httpRequest = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
@@ -98,7 +108,7 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
RequestUri = new Uri(NetworkService.Origin)
|
RequestUri = new Uri(NetworkService.Origin)
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await NetworkService.MakeRequestAsync(httpRequest, this);
|
var result = await NetworkService.MakeRequestAsync(httpRequest, this, true);
|
||||||
if (!result.IsSuccess)
|
if (!result.IsSuccess)
|
||||||
{
|
{
|
||||||
return result.Error ?? ResultError.Fail("Request failed!");
|
return result.Error ?? ResultError.Fail("Request failed!");
|
||||||
@@ -110,10 +120,9 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
return clientStateResult.Error;
|
return clientStateResult.Error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientState? clientState;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
clientState = JsonSerializer.Deserialize<ClientState>(clientStateResult.Value.Item1);
|
State = JsonSerializer.Deserialize<ClientState>(clientStateResult.Value.Item1);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -121,19 +130,19 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (clientState == null)
|
if (State == null)
|
||||||
{
|
{
|
||||||
return ResultError.Fail("Unable to parse client state!");
|
return ResultError.Fail("Unable to parse client state!");
|
||||||
}
|
}
|
||||||
|
|
||||||
clientState.IsPremiumUser = clientStateResult.Value.Item2;
|
State.IsPremiumUser = clientStateResult.Value.Item2;
|
||||||
|
|
||||||
return clientState;
|
return Result.Success();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<Channel>> GetChannelByIdAsync(string channelId)
|
public async Task<Result<Channel>> GetChannelByIdAsync(string channelId)
|
||||||
{
|
{
|
||||||
if (External.State == null)
|
if (State == null)
|
||||||
{
|
{
|
||||||
return ResultError.Fail("No client state!");
|
return ResultError.Fail("No client state!");
|
||||||
}
|
}
|
||||||
@@ -143,12 +152,12 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
return ResultError.Fail("Channel id is empty!");
|
return ResultError.Fail("Channel id is empty!");
|
||||||
}
|
}
|
||||||
|
|
||||||
var serializedContext = JsonSerializer.SerializeToNode(External.State.InnerTubeContext);
|
var serializedContext = JsonSerializer.SerializeToNode(State.InnerTubeContext);
|
||||||
var payload = new JsonObject { { "context", serializedContext }, { "browseId", channelId } };
|
var payload = new JsonObject { { "context", serializedContext }, { "browseId", channelId } };
|
||||||
var requestMessage = new HttpRequestMessage
|
var requestMessage = new HttpRequestMessage
|
||||||
{
|
{
|
||||||
Method = HttpMethod.Post,
|
Method = HttpMethod.Post,
|
||||||
RequestUri = new Uri($"{NetworkService.Origin}/youtubei/v1/browse?key={External.State.InnertubeApiKey}"),
|
RequestUri = new Uri($"{NetworkService.Origin}/youtubei/v1/browse?key={State.InnertubeApiKey}"),
|
||||||
Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json)
|
Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json)
|
||||||
};
|
};
|
||||||
var responseResult = await NetworkService.MakeRequestAsync(requestMessage, this);
|
var responseResult = await NetworkService.MakeRequestAsync(requestMessage, this);
|
||||||
@@ -160,14 +169,39 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
return ChannelJsonParser.ParseJsonToChannelData(responseResult.Value);
|
return ChannelJsonParser.ParseJsonToChannelData(responseResult.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetDatasyncId()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(State?.WebPlayerContextConfig?.WebPlayerContext?.DatasyncId))
|
||||||
|
{
|
||||||
|
return State.WebPlayerContextConfig.WebPlayerContext.DatasyncId;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempDatasyncId = "";
|
||||||
|
foreach (var datasyncId in DatasyncIds)
|
||||||
|
{
|
||||||
|
var split = datasyncId.Split("||", StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
switch (split.Length)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
case 2 when tempDatasyncId.Equals(split[1]):
|
||||||
|
continue;
|
||||||
|
case 2:
|
||||||
|
tempDatasyncId = split[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempDatasyncId;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_httpClient?.Dispose();
|
HttpClient?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Result<string>> GetCurrentAccountIdAsync()
|
private async Task<Result<string>> GetCurrentAccountIdAsync()
|
||||||
{
|
{
|
||||||
if (External.State is not { LoggedIn: true })
|
if (State is not { LoggedIn: true })
|
||||||
{
|
{
|
||||||
return ResultError.Fail("Client not logged in!");
|
return ResultError.Fail("Client not logged in!");
|
||||||
}
|
}
|
||||||
@@ -177,7 +211,7 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
Method = HttpMethod.Post,
|
Method = HttpMethod.Post,
|
||||||
RequestUri = new Uri($"{NetworkService.Origin}/youtubei/v1/account/account_menu")
|
RequestUri = new Uri($"{NetworkService.Origin}/youtubei/v1/account/account_menu")
|
||||||
};
|
};
|
||||||
var serializedContext = JsonSerializer.SerializeToNode(External.State.InnerTubeContext);
|
var serializedContext = JsonSerializer.SerializeToNode(State.InnerTubeContext);
|
||||||
var payload = new JsonObject { { "context", serializedContext } };
|
var payload = new JsonObject { { "context", serializedContext } };
|
||||||
httpRequest.Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json);
|
httpRequest.Content = new StringContent(payload.ToJsonString(), Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||||
var responseResult = await NetworkService.MakeRequestAsync(httpRequest, this);
|
var responseResult = await NetworkService.MakeRequestAsync(httpRequest, this);
|
||||||
@@ -192,7 +226,7 @@ public sealed class YouTubeClient : IDisposable
|
|||||||
|
|
||||||
private async Task<Result<string[]>> GetDatasyncIds()
|
private async Task<Result<string[]>> GetDatasyncIds()
|
||||||
{
|
{
|
||||||
if (External.State is not { LoggedIn: true } || CookieContainer.Count == 0)
|
if (State is not { LoggedIn: true } || CookieContainer.Count == 0)
|
||||||
{
|
{
|
||||||
return ResultError.Fail("Client is not logged in, requires logged in client for this endpoint (/getDatasyncIdsEndpoint).");
|
return ResultError.Fail("Client is not logged in, requires logged in client for this endpoint (/getDatasyncIdsEndpoint).");
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user