Tesses.CMS Done enough
This commit is contained in:
parent
1b8a789600
commit
ba91f939fc
|
@ -93,6 +93,26 @@ public partial class App : Application
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static (string loginButtonText,LoggedInUserAccount? login) LoginData()
|
||||||
|
{
|
||||||
|
LoggedInUserAccount? login=null;
|
||||||
|
string loginText="Offline";
|
||||||
|
Task.Run(async()=>{
|
||||||
|
try {
|
||||||
|
Client.Users.LoginToken = Platform.Configuration.LoginToken;
|
||||||
|
login = await Client.Users.GetLoggedInUserAccountAsync();
|
||||||
|
loginText = login.LoggedIn ? login.ProperName : "Login";
|
||||||
|
} catch(Exception ex)
|
||||||
|
{
|
||||||
|
_=ex;
|
||||||
|
}
|
||||||
|
}).Wait();
|
||||||
|
|
||||||
|
return (loginText,login);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal static async Task<IImage> GetMovieThumbnailAsync(string username, string name)
|
internal static async Task<IImage> GetMovieThumbnailAsync(string username, string name)
|
||||||
{
|
{
|
||||||
//we need to cache the resource
|
//we need to cache the resource
|
||||||
|
@ -249,6 +269,15 @@ public partial class App : Application
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static bool HasDownloaded(UserAccount account,Movie movie)
|
||||||
|
{
|
||||||
|
UnixPath dir = Special.Root / "Downloads" / account.Username / "Movies" / movie.Name;
|
||||||
|
UnixPath filename = dir / movie.ProperName + ".mp4";
|
||||||
|
var fs = Platform.DownloadFilesystem;
|
||||||
|
if(fs is null) return false;
|
||||||
|
return fs.FileExists(filename);
|
||||||
|
}
|
||||||
|
|
||||||
internal static void StartDownload(UserAccount account, Movie movie)
|
internal static void StartDownload(UserAccount account, Movie movie)
|
||||||
{
|
{
|
||||||
string username = account.Username;
|
string username = account.Username;
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.002.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tesses.CMS.Avalonia", "Tesses.CMS.Avalonia.csproj", "{36061BA4-CB71-4B6C-8C0A-6482635EB893}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{36061BA4-CB71-4B6C-8C0A-6482635EB893}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{36061BA4-CB71-4B6C-8C0A-6482635EB893}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{36061BA4-CB71-4B6C-8C0A-6482635EB893}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{36061BA4-CB71-4B6C-8C0A-6482635EB893}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {B5F29959-050C-43EB-A56F-C7760E27834E}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,40 @@
|
||||||
|
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Tesses.CMS.Avalonia.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Tesses.CMS.Avalonia.Views.HomePages;
|
||||||
|
using Tesses.CMS.Avalonia.ViewModels.HomePages;
|
||||||
|
|
||||||
|
public partial class AccountViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
MainViewModel m;
|
||||||
|
|
||||||
|
public AccountViewModel(MainViewModel m)
|
||||||
|
{
|
||||||
|
this.m = m;
|
||||||
|
this.LogoutCommand = ReactiveCommand.CreateFromTask(LogoutAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IReactiveCommand LogoutCommand {get;init;}
|
||||||
|
|
||||||
|
public async Task LogoutAsync()
|
||||||
|
{
|
||||||
|
App.Client.Users.LoginToken = App.Platform.Configuration.LoginToken;
|
||||||
|
|
||||||
|
|
||||||
|
await App.Client.Users.LogoutAsync();
|
||||||
|
|
||||||
|
(m.LoginText,_) = App.LoginData();
|
||||||
|
m.CurrentPage = new HomePageViewModel();
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ public partial class HomePageViewModel : ViewModelBase
|
||||||
public HomePageViewModel()
|
public HomePageViewModel()
|
||||||
{
|
{
|
||||||
_curPage = new HomeUserListPageViewModel(this);
|
_curPage = new HomeUserListPageViewModel(this);
|
||||||
|
|
||||||
}
|
}
|
||||||
private ViewModelBase _curPage;
|
private ViewModelBase _curPage;
|
||||||
public ViewModelBase CurrentPage
|
public ViewModelBase CurrentPage
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
namespace Tesses.CMS.Avalonia.ViewModels.HomePages;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using global::Avalonia.Controls;
|
||||||
|
using Tesses.CMS.Avalonia.Models;
|
||||||
|
using Tesses.CMS.Avalonia.Views.HomePages;
|
||||||
|
using Tesses.CMS.Client;
|
||||||
|
|
||||||
|
public partial class HomeAboutMePageViewModel : ViewModelBase, IBackable
|
||||||
|
{
|
||||||
|
HomePageViewModel homePage;
|
||||||
|
HomeUserPageViewModel userPage;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _aboutMe;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _properName;
|
||||||
|
|
||||||
|
public HomeAboutMePageViewModel(HomePageViewModel homePage,HomeUserPageViewModel userPage)
|
||||||
|
{
|
||||||
|
App.Log("In HomeAboutMePageViewModel::ctor block begin");
|
||||||
|
this.homePage = homePage;
|
||||||
|
this.userPage = userPage;
|
||||||
|
this.AboutMe = userPage.Account.AboutMe;
|
||||||
|
this.ProperName = $"About {userPage.Account.ProperName}";
|
||||||
|
|
||||||
|
App.Log("In HomeAboutMePageViewModel::ctor block end");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewModelBase Back()
|
||||||
|
{
|
||||||
|
return userPage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,12 +44,17 @@ public partial class HomeMoviePageViewModel : ViewModelBase, IBackable
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _canWatch=false;
|
private bool _canWatch=false;
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _canDownload=false;
|
private bool _canDownload=false;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
|
|
||||||
private string _addOrRemoveFromFavorites="";
|
private string _addOrRemoveFromFavorites="";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _downloadButtonText="Download";
|
||||||
|
|
||||||
private bool _favoriteExists=false;
|
private bool _favoriteExists=false;
|
||||||
public bool FavoriteExists {
|
public bool FavoriteExists {
|
||||||
get=>_favoriteExists;
|
get=>_favoriteExists;
|
||||||
|
@ -66,6 +71,7 @@ public partial class HomeMoviePageViewModel : ViewModelBase, IBackable
|
||||||
|
|
||||||
public HomeMoviePageViewModel(HomePageViewModel home,HomeMovieListPageViewModel movieList,UserAccount account,MovieItem movie)
|
public HomeMoviePageViewModel(HomePageViewModel home,HomeMovieListPageViewModel movieList,UserAccount account,MovieItem movie)
|
||||||
{
|
{
|
||||||
|
this.DownloadCommand = ReactiveCommand.CreateFromTask(DownloadAsync);
|
||||||
this.account = account;
|
this.account = account;
|
||||||
username = account.Username;
|
username = account.Username;
|
||||||
this.home = home;
|
this.home = home;
|
||||||
|
@ -89,6 +95,11 @@ public partial class HomeMoviePageViewModel : ViewModelBase, IBackable
|
||||||
Media = JObject.FromObject(movie.Movie)
|
Media = JObject.FromObject(movie.Movie)
|
||||||
};
|
};
|
||||||
FavoriteExists = App.FavoritesExists(fav);
|
FavoriteExists = App.FavoritesExists(fav);
|
||||||
|
if(App.HasDownloaded(account,movie.Movie))
|
||||||
|
{
|
||||||
|
CanDownload = true;
|
||||||
|
DownloadButtonText = "Watch Downloaded";
|
||||||
|
}
|
||||||
|
|
||||||
Task.Run(async()=>{
|
Task.Run(async()=>{
|
||||||
var res=await App.Client.Movies.GetMovieContentMetadataAsync(username,movie.Movie.Name);
|
var res=await App.Client.Movies.GetMovieContentMetadataAsync(username,movie.Movie.Name);
|
||||||
|
@ -96,13 +107,19 @@ public partial class HomeMoviePageViewModel : ViewModelBase, IBackable
|
||||||
watchUrl = res.BrowserStream;
|
watchUrl = res.BrowserStream;
|
||||||
CanWatch = res.HasBrowserStream;
|
CanWatch = res.HasBrowserStream;
|
||||||
|
|
||||||
CanDownload = res.HasDownloadStream;
|
if(!App.HasDownloaded(account,movie.Movie))
|
||||||
|
{
|
||||||
|
CanDownload = res.HasDownloadStream;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}).Wait(0);
|
}).Wait(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public ViewModelBase Back()
|
public ViewModelBase Back()
|
||||||
{
|
{
|
||||||
return movieList;
|
return movieList;
|
||||||
|
@ -116,11 +133,18 @@ public partial class HomeMoviePageViewModel : ViewModelBase, IBackable
|
||||||
home.CurrentPage = new HomeMovieVideoPlayerViewModel(home,this,watchUrl);
|
home.CurrentPage = new HomeMovieVideoPlayerViewModel(home,this,watchUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[RelayCommand]
|
public IReactiveCommand DownloadCommand {get;init;}
|
||||||
public void Download()
|
public async Task DownloadAsync()
|
||||||
{
|
{
|
||||||
if(CanDownload)
|
if(App.HasDownloaded(account,movie))
|
||||||
App.StartDownload(account,movie);
|
{
|
||||||
|
await this.home.PlayDownloadedAsync(fav);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(CanDownload)
|
||||||
|
App.StartDownload(account,movie);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|
|
@ -41,6 +41,7 @@ public partial class HomeUserPageViewModel : ViewModelBase, IBackable
|
||||||
this.account = account;
|
this.account = account;
|
||||||
UserItems.Add(new UserPageItem("Movies",new HomeMovieListPageViewModel(homePage,this)));
|
UserItems.Add(new UserPageItem("Movies",new HomeMovieListPageViewModel(homePage,this)));
|
||||||
UserItems.Add(new UserPageItem("Shows",new HomeShowListPageViewModel(homePage,this)));
|
UserItems.Add(new UserPageItem("Shows",new HomeShowListPageViewModel(homePage,this)));
|
||||||
|
UserItems.Add(new UserPageItem("About",new HomeAboutMePageViewModel(homePage,this)));
|
||||||
}
|
}
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private UserPageItem? _selectedListItem;
|
private UserPageItem? _selectedListItem;
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
namespace Tesses.CMS.Avalonia.ViewModels;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Tesses.CMS.Avalonia.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ReactiveUI;
|
||||||
|
using Tesses.CMS.Avalonia.Views.HomePages;
|
||||||
|
using Tesses.CMS.Avalonia.ViewModels.HomePages;
|
||||||
|
|
||||||
|
public partial class LoginViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _email="";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _password="";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _failed=false;
|
||||||
|
|
||||||
|
MainViewModel m;
|
||||||
|
|
||||||
|
public LoginViewModel(MainViewModel m)
|
||||||
|
{
|
||||||
|
this.m = m;
|
||||||
|
this.LoginCommand = ReactiveCommand.CreateFromTask(LoginAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IReactiveCommand LoginCommand {get;init;}
|
||||||
|
|
||||||
|
public async Task LoginAsync()
|
||||||
|
{
|
||||||
|
Failed=false;
|
||||||
|
var tkn= await App.Client.Users.CreateTokenAsync(Email,Password);
|
||||||
|
if(tkn.Success)
|
||||||
|
{
|
||||||
|
App.Platform.Configuration.LoginToken = tkn.Cookie;
|
||||||
|
await App.Platform.WriteConfigurationAsync();
|
||||||
|
(m.LoginText,_) = App.LoginData();
|
||||||
|
m.CurrentPage = new HomePageViewModel();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Failed=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Tesses.CMS.Avalonia.Models;
|
using Tesses.CMS.Avalonia.Models;
|
||||||
|
using Tesses.CMS.Avalonia.Views;
|
||||||
|
using Tesses.CMS.Client;
|
||||||
|
|
||||||
public partial class MainViewModel : ViewModelBase
|
public partial class MainViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
@ -21,6 +23,8 @@ public partial class MainViewModel : ViewModelBase
|
||||||
SelectedListItem = Pages.First();
|
SelectedListItem = Pages.First();
|
||||||
this.Title = title;
|
this.Title = title;
|
||||||
|
|
||||||
|
(LoginText,_)= App.LoginData();
|
||||||
|
|
||||||
if(Environment.GetCommandLineArgs().Length > 1)
|
if(Environment.GetCommandLineArgs().Length > 1)
|
||||||
{
|
{
|
||||||
string path = Environment.GetCommandLineArgs()[1];
|
string path = Environment.GetCommandLineArgs()[1];
|
||||||
|
@ -55,7 +59,7 @@ public partial class MainViewModel : ViewModelBase
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Page? _selectedListItem;
|
private Page? _selectedListItem;
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _loginText="Login";
|
private string _loginText="";
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _title;
|
private string _title;
|
||||||
|
@ -81,6 +85,20 @@ public partial class MainViewModel : ViewModelBase
|
||||||
private void LoginAccount()
|
private void LoginAccount()
|
||||||
{
|
{
|
||||||
App.Log("Login button");
|
App.Log("Login button");
|
||||||
|
LoggedInUserAccount? data=null;
|
||||||
|
(LoginText,data)= App.LoginData();
|
||||||
|
|
||||||
|
if(data is not null)
|
||||||
|
{
|
||||||
|
if(data.LoggedIn)
|
||||||
|
{
|
||||||
|
CurrentPage = new AccountViewModel(this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentPage = new LoginViewModel(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SetHome()
|
internal void SetHome()
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels"
|
||||||
|
xmlns:v="clr-namespace:Tesses.CMS.Avalonia.Views"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Tesses.CMS.Avalonia.Views.AccountView"
|
||||||
|
x:DataType="vm:AccountViewModel">
|
||||||
|
<Design.DataContext>
|
||||||
|
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||||
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
|
<vm:AccountViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<StackPanel Margin="20">
|
||||||
|
<Button Command="{Binding LogoutCommand}">Logout</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
namespace Tesses.CMS.Avalonia.Views;
|
||||||
|
|
||||||
|
public partial class AccountView : UserControl
|
||||||
|
{
|
||||||
|
public AccountView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels.HomePages"
|
||||||
|
xmlns:v="clr-namespace:Tesses.CMS.Avalonia.Views"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Tesses.CMS.Avalonia.Views.HomePages.HomeAboutMePageView"
|
||||||
|
x:DataType="vm:HomeAboutMePageViewModel">
|
||||||
|
<Design.DataContext>
|
||||||
|
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||||
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
|
<vm:HomeAboutMePageViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<ScrollViewer>
|
||||||
|
<Grid RowDefinitions="Auto,*" Margin="20">
|
||||||
|
<TextBlock Text="{Binding ProperName}" Grid.Row="0" />
|
||||||
|
|
||||||
|
<v:InlineText Margin="0 20 0 0" Grid.Row="1" Text="{Binding AboutMe}" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,11 @@
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Tesses.CMS.Avalonia.Views.HomePages;
|
||||||
|
|
||||||
|
public partial class HomeAboutMePageView : UserControl
|
||||||
|
{
|
||||||
|
public HomeAboutMePageView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,Auto">
|
<Grid Grid.Row="1" ColumnDefinitions="Auto,Auto,Auto">
|
||||||
<Button Grid.Column="0" Command="{Binding WatchCommand}" Content="Watch" IsVisible="{Binding CanWatch}"/>
|
<Button Grid.Column="0" Command="{Binding WatchCommand}" Content="Watch" IsVisible="{Binding CanWatch}"/>
|
||||||
<Button Grid.Column="1" Command="{Binding DownloadCommand}" Content="Download" IsVisible="{Binding CanDownload}"/>
|
<Button Grid.Column="1" Command="{Binding DownloadCommand}" Content="{Binding DownloadButtonText}" IsVisible="{Binding CanDownload}"/>
|
||||||
<Button Grid.Column="2" Command="{Binding AddToFavoritesCommand}" Content="{Binding AddOrRemoveFromFavorites}"/>
|
<Button Grid.Column="2" Command="{Binding AddToFavoritesCommand}" Content="{Binding AddOrRemoveFromFavorites}"/>
|
||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -49,7 +49,9 @@ public class InlineText : WrapPanel
|
||||||
{
|
{
|
||||||
if(b.Length > 0)
|
if(b.Length > 0)
|
||||||
{
|
{
|
||||||
this.Children.Add(new TextBlock(){Text = b.ToString()});
|
var res = new TextBlock(){Text = $"\u200B{b.ToString()}\u200B",TextWrapping = TextWrapping.Wrap};
|
||||||
|
|
||||||
|
this.Children.Add(res);
|
||||||
b.Clear();
|
b.Clear();
|
||||||
}
|
}
|
||||||
StringBuilder b2 = new StringBuilder();
|
StringBuilder b2 = new StringBuilder();
|
||||||
|
@ -62,7 +64,7 @@ public class InlineText : WrapPanel
|
||||||
}
|
}
|
||||||
b2.Append(text[i]);
|
b2.Append(text[i]);
|
||||||
}
|
}
|
||||||
var tb = new TextBlock(){Text = b2.ToString()};
|
var tb = new TextBlock(){Text = $"\u200B{b2.ToString()}\u200B",TextWrapping = TextWrapping.Wrap};
|
||||||
tb.Foreground = new SolidColorBrush(Color.FromRgb(0,0,255));
|
tb.Foreground = new SolidColorBrush(Color.FromRgb(0,0,255));
|
||||||
tb.TextDecorations = TextDecorations.Underline;
|
tb.TextDecorations = TextDecorations.Underline;
|
||||||
|
|
||||||
|
@ -80,7 +82,7 @@ public class InlineText : WrapPanel
|
||||||
}
|
}
|
||||||
if(b.Length > 0)
|
if(b.Length > 0)
|
||||||
{
|
{
|
||||||
this.Children.Add(new TextBlock(){Text = b.ToString()});
|
this.Children.Add(new TextBlock(){Text = $"\u200B{b.ToString()}\u200B", TextWrapping = TextWrapping.Wrap});
|
||||||
b.Clear();
|
b.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:vm="clr-namespace:Tesses.CMS.Avalonia.ViewModels"
|
||||||
|
xmlns:v="clr-namespace:Tesses.CMS.Avalonia.Views"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Tesses.CMS.Avalonia.Views.LoginView"
|
||||||
|
x:DataType="vm:LoginViewModel">
|
||||||
|
<Design.DataContext>
|
||||||
|
<!-- This only sets the DataContext for the previewer in an IDE,
|
||||||
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
|
<vm:LoginViewModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
<StackPanel Margin="20">
|
||||||
|
<TextBlock Background="Red" IsVisible="{Binding Failed}">The Email or Password was incorrect, please try again.</TextBlock>
|
||||||
|
<TextBlock Margin="0 5" >Email:</TextBlock>
|
||||||
|
<TextBox Text="{Binding Email}" Watermark="Enter your email"/>
|
||||||
|
<TextBlock Margin="0 5" >Password:</TextBlock>
|
||||||
|
<TextBox Text="{Binding Password}" PasswordChar="*" Watermark="Enter your password"/>
|
||||||
|
<Button Command="{Binding LoginCommand}">Login</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
|
||||||
|
namespace Tesses.CMS.Avalonia.Views;
|
||||||
|
|
||||||
|
public partial class LoginView : UserControl
|
||||||
|
{
|
||||||
|
public LoginView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -256,7 +256,12 @@ namespace Tesses.CMS.Client
|
||||||
|
|
||||||
public async Task LogoutAsync()
|
public async Task LogoutAsync()
|
||||||
{
|
{
|
||||||
(await client.client.GetAsync("/logout")).Dispose();
|
(await client.client.GetAsync($"{client.rooturl}/logout")).Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<LoggedInUserAccount> GetLoggedInUserAccountAsync()
|
||||||
|
{
|
||||||
|
return JsonConvert.DeserializeObject<LoggedInUserAccount>(await client.client.GetStringAsync($"{client.rooturl}/api/v1/LoggedInUserAccount"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -280,11 +285,10 @@ namespace Tesses.CMS.Client
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
set{
|
set{
|
||||||
if(string.IsNullOrWhiteSpace(value))
|
if(client.client.DefaultRequestHeaders.Contains("Authorization"))
|
||||||
if(client.client.DefaultRequestHeaders.Contains("Authorization"))
|
|
||||||
client.client.DefaultRequestHeaders.Remove("Authorization");
|
client.client.DefaultRequestHeaders.Remove("Authorization");
|
||||||
else
|
if(!string.IsNullOrWhiteSpace(value))
|
||||||
client.client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",value);
|
client.client.DefaultRequestHeaders.Add("Authorization",$"Bearer {value}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,6 +301,26 @@ namespace Tesses.CMS.Client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class LoggedInUserAccount
|
||||||
|
{
|
||||||
|
[JsonProperty("logged_in")]
|
||||||
|
public bool LoggedIn {get;set;}=false;
|
||||||
|
|
||||||
|
[JsonProperty("username")]
|
||||||
|
public string UserName {get;set;}="";
|
||||||
|
|
||||||
|
[JsonProperty("proper_name")]
|
||||||
|
public string ProperName {get;set;}="";
|
||||||
|
[JsonProperty("is_admin")]
|
||||||
|
public bool IsAdmin {get;set;}=false;
|
||||||
|
|
||||||
|
[JsonProperty("is_invited")]
|
||||||
|
public bool IsInvited {get;set;}=false;
|
||||||
|
|
||||||
|
[JsonProperty("is_verified")]
|
||||||
|
public bool IsVerified {get;set;}=false;
|
||||||
|
}
|
||||||
|
|
||||||
public class LoginToken
|
public class LoginToken
|
||||||
{
|
{
|
||||||
[JsonProperty("success")]
|
[JsonProperty("success")]
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.002.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tesses.CMS.Client", "Tesses.CMS.Client.csproj", "{887B0982-1D90-413D-BFEE-72065D115DE0}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{887B0982-1D90-413D-BFEE-72065D115DE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{887B0982-1D90-413D-BFEE-72065D115DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{887B0982-1D90-413D-BFEE-72065D115DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{887B0982-1D90-413D-BFEE-72065D115DE0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {17F92768-0843-4A4F-B369-22B8120B4996}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.002.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tesses.CMS.Providers.LiteDb", "Tesses.CMS.Providers.LiteDb.csproj", "{D0D6C7FF-2B46-4909-9741-77DBDFD52D4D}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{D0D6C7FF-2B46-4909-9741-77DBDFD52D4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D0D6C7FF-2B46-4909-9741-77DBDFD52D4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D0D6C7FF-2B46-4909-9741-77DBDFD52D4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D0D6C7FF-2B46-4909-9741-77DBDFD52D4D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {45074933-DBC8-4448-B846-2287DFF81409}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -1,5 +1,5 @@
|
||||||
<h1>Change movie metadata</h1>
|
<h1>Change episode metadata</h1>
|
||||||
<form action="./edit" method="post" enctype="application/x-www-form-urlencoded">
|
<form action="./edit?csrf={{csrf}}" method="post" enctype="application/x-www-form-urlencoded">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="proper-name" class="form-label">Proper Name</label>
|
<label for="proper-name" class="form-label">Proper Name</label>
|
||||||
<input type="text" class="form-control" id="proper-name" name="proper_name" value="{{propername}}">
|
<input type="text" class="form-control" id="proper-name" name="proper_name" value="{{propername}}">
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
<h1>Upload episode files</h1>
|
<h1>Upload episode files</h1>
|
||||||
<form action="./upload" method="post" enctype="multipart/form-data">
|
<form action="./upload?csrf={{csrf2}}" method="post" enctype="multipart/form-data">
|
||||||
<h1>You should do these in order (not required but recomended because you can only upload one at a time, that is in one tab at least)</h1>
|
<h1>You should do these in order (not required but recomended because you can only upload one at a time, that is in one tab at least)</h1>
|
||||||
<select class="form-select" name="type" aria-label="Select resource">
|
<select class="form-select" name="type" aria-label="Select resource">
|
||||||
<option value="thumbnail" selected>Thumbnail (should be 120x214, the coverart for the episode)</option>
|
<option value="thumbnail" selected>Thumbnail (should be 120x214, the coverart for the episode)</option>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<h1>Change season metadata</h1>
|
<h1>Change season metadata</h1>
|
||||||
<form action="./edit" method="post" enctype="application/x-www-form-urlencoded">
|
<form action="./edit?csrf={{csrf}}" method="post" enctype="application/x-www-form-urlencoded">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="proper-name" class="form-label">Proper Name</label>
|
<label for="proper-name" class="form-label">Proper Name</label>
|
||||||
<input type="text" class="form-control" id="proper-name" name="proper_name" value="{{propername}}">
|
<input type="text" class="form-control" id="proper-name" name="proper_name" value="{{propername}}">
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
</form>
|
</form>
|
||||||
<br>
|
<br>
|
||||||
<h1>Add episode</h1>
|
<h1>Add episode</h1>
|
||||||
<form action="./addepisode" method="post" enctype="application/x-www-form-urlencoded">
|
<form action="./addepisode?csrf={{csrf2}}" method="post" enctype="application/x-www-form-urlencoded">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="name" class="form-label">Episode Name</label>
|
<label for="name" class="form-label">Episode Name</label>
|
||||||
<input type="text" class="form-control" id="name" name="name" aria-describedby="message">
|
<input type="text" class="form-control" id="name" name="name" aria-describedby="message">
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
</form>
|
</form>
|
||||||
<br>
|
<br>
|
||||||
<h1>Upload asset files</h1>
|
<h1>Upload asset files</h1>
|
||||||
<form action="./upload" method="post" enctype="multipart/form-data">
|
<form action="./upload?csrf={{csrf3}}" method="post" enctype="multipart/form-data">
|
||||||
<h1>You should do these in order (not required but recomended because you can only upload one at a time, that is in one tab at least)</h1>
|
<h1>You should do these in order (not required but recomended because you can only upload one at a time, that is in one tab at least)</h1>
|
||||||
<select class="form-select" name="type" aria-label="Select resource">
|
<select class="form-select" name="type" aria-label="Select resource">
|
||||||
<option value="thumbnail" selected>Thumbnail (should be 120x214, the coverart for the season (also used for when episode does not have one))</option>
|
<option value="thumbnail" selected>Thumbnail (should be 120x214, the coverart for the season (also used for when episode does not have one))</option>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<h1>Change show metadata</h1>
|
<h1>Change show metadata</h1>
|
||||||
<form action="./edit" method="post" enctype="application/x-www-form-urlencoded">
|
<form action="./edit?csrf={{csrf}}" method="post" enctype="application/x-www-form-urlencoded">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="proper-name" class="form-label">Proper Name</label>
|
<label for="proper-name" class="form-label">Proper Name</label>
|
||||||
<input type="text" class="form-control" id="proper-name" name="proper_name" value="{{propername}}">
|
<input type="text" class="form-control" id="proper-name" name="proper_name" value="{{propername}}">
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
</form>
|
</form>
|
||||||
<br>
|
<br>
|
||||||
<h1>Add season</h1>
|
<h1>Add season</h1>
|
||||||
<form action="./addseason" method="post" enctype="application/x-www-form-urlencoded">
|
<form action="./addseason?csrf={{csrf2}}" method="post" enctype="application/x-www-form-urlencoded">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="number" class="form-label">Season Number</label>
|
<label for="number" class="form-label">Season Number</label>
|
||||||
<input type="number" min="1" class="form-control" id="number" name="number" value="{{newseasonnumber}}">
|
<input type="number" min="1" class="form-control" id="number" name="number" value="{{newseasonnumber}}">
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
</form>
|
</form>
|
||||||
<br>
|
<br>
|
||||||
<h1>Upload asset files</h1>
|
<h1>Upload asset files</h1>
|
||||||
<form action="./upload" method="post" enctype="multipart/form-data">
|
<form action="./upload?csrf={{csrf3}}" method="post" enctype="multipart/form-data">
|
||||||
<h1>You should do these in order (not required but recomended because you can only upload one at a time, that is in one tab at least)</h1>
|
<h1>You should do these in order (not required but recomended because you can only upload one at a time, that is in one tab at least)</h1>
|
||||||
<select class="form-select" name="type" aria-label="Select resource">
|
<select class="form-select" name="type" aria-label="Select resource">
|
||||||
<option value="thumbnail" selected>Thumbnail (should be 120x214, the coverart for the show (also used for when season or episode does not have one))</option>
|
<option value="thumbnail" selected>Thumbnail (should be 120x214, the coverart for the show (also used for when season or episode does not have one))</option>
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
const end = document.getElementById('end');
|
const end = document.getElementById('end');
|
||||||
const text = document.getElementById('text');
|
const text = document.getElementById('text');
|
||||||
const _captions = document.getElementById('captions');
|
const _captions = document.getElementById('captions');
|
||||||
|
var csrf = "{{csrf}}";
|
||||||
function set_max()
|
function set_max()
|
||||||
{
|
{
|
||||||
begin.max = video_player.duration;
|
begin.max = video_player.duration;
|
||||||
|
@ -50,7 +51,7 @@
|
||||||
_delete.classList.add('btn');
|
_delete.classList.add('btn');
|
||||||
_delete.classList.add('btn-danger');
|
_delete.classList.add('btn-danger');
|
||||||
_delete.onclick = ()=>{
|
_delete.onclick = ()=>{
|
||||||
var index=captions.indexof(item);
|
var index=captions.indexOf(item);
|
||||||
if(index > -1)
|
if(index > -1)
|
||||||
{
|
{
|
||||||
captions.splice(index,1);
|
captions.splice(index,1);
|
||||||
|
@ -116,20 +117,25 @@
|
||||||
addEventListener("DOMContentLoaded", (event) => {loaded();});
|
addEventListener("DOMContentLoaded", (event) => {loaded();});
|
||||||
function save()
|
function save()
|
||||||
{
|
{
|
||||||
fetch("./subtitles?lang={{lang}}",{method: 'POST',
|
fetch(`./subtitles?lang={{lang}}&csrf=${csrf}`,{method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},body: JSON.stringify(captions)/*https://stackoverflow.com/a/29823632*/}).then(e=>{
|
},body: JSON.stringify(captions)/*https://stackoverflow.com/a/29823632*/}).then(e=>{
|
||||||
if(e.ok)
|
if(e.ok)
|
||||||
{
|
{
|
||||||
alert("Saved");
|
return e.json();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
alert("Failed");
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
e.close();
|
|
||||||
|
}).catch(e=>{
|
||||||
|
alert("Failed to save.");
|
||||||
|
}).then(e=>{
|
||||||
|
csrf=e.csrf;
|
||||||
|
alert("Success.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -932,17 +932,33 @@ namespace Tesses.CMS
|
||||||
{
|
{
|
||||||
foreach (var c in cookies)
|
foreach (var c in cookies)
|
||||||
{
|
{
|
||||||
var co = c.Split(new char[] { '=' }, 2);
|
foreach(var cE in c.Split(new string[]{"; "},StringSplitOptions.RemoveEmptyEntries))
|
||||||
if (co.Length == 2 && co[0] == "Session")
|
|
||||||
{
|
{
|
||||||
if (provider.ContainsSession(co[1]))
|
|
||||||
|
var co = cE.Split(new char[] { '=' }, 2);
|
||||||
|
if (co.Length == 2 && co[0] == "Session")
|
||||||
{
|
{
|
||||||
provider.DeleteSession(co[1]);
|
if (provider.ContainsSession(co[1]))
|
||||||
return;
|
{
|
||||||
|
provider.DeleteSession(co[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (ctx.RequestHeaders.TryGetFirst("Authorization", out var auth))
|
||||||
|
{
|
||||||
|
var co = auth.Split(new char[] { ' ' }, 2);
|
||||||
|
if (co.Length == 2 && co[0] == "Bearer")
|
||||||
|
{
|
||||||
|
if (provider.ContainsSession(co[1]))
|
||||||
|
{
|
||||||
|
provider.DeleteSession(co[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
HttpClient client = new HttpClient();
|
HttpClient client = new HttpClient();
|
||||||
|
@ -1141,35 +1157,40 @@ namespace Tesses.CMS
|
||||||
cookie = "";
|
cookie = "";
|
||||||
if (ctx.RequestHeaders.TryGetValue("Cookie", out var cookies))
|
if (ctx.RequestHeaders.TryGetValue("Cookie", out var cookies))
|
||||||
{
|
{
|
||||||
|
|
||||||
foreach (var c in cookies)
|
foreach (var c in cookies)
|
||||||
{
|
{
|
||||||
|
foreach(var cE in c.Split(new string[]{"; "},StringSplitOptions.RemoveEmptyEntries))
|
||||||
var co = c.Split(new char[] { '=' }, 2);
|
|
||||||
if (co.Length == 2 && co[0] == "Session")
|
|
||||||
{
|
{
|
||||||
cookie = co[1];
|
var co = cE.Split(new char[] { '=' }, 2);
|
||||||
long? account = provider.GetSession(cookie);
|
if (co.Length == 2 && co[0] == "Session")
|
||||||
|
|
||||||
|
|
||||||
if (account.HasValue)
|
|
||||||
{
|
{
|
||||||
if (requiresCSRF)
|
cookie = co[1];
|
||||||
|
long? account = provider.GetSession(cookie);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (account.HasValue)
|
||||||
{
|
{
|
||||||
if (ctx.QueryParams.TryGetFirst("csrf", out var csrf))
|
if (requiresCSRF)
|
||||||
{
|
{
|
||||||
if (IsValidCSRFAndDestroy(account.Value, cookie, csrf))
|
if (ctx.QueryParams.TryGetFirst("csrf", out var csrf))
|
||||||
{
|
{
|
||||||
return provider.GetUserById(account.Value);
|
if (IsValidCSRFAndDestroy(account.Value, cookie, csrf))
|
||||||
|
{
|
||||||
|
return provider.GetUserById(account.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
throw new InvalidCSRFException();
|
||||||
}
|
}
|
||||||
throw new InvalidCSRFException();
|
|
||||||
|
return provider.GetUserById(account.Value);
|
||||||
}
|
}
|
||||||
return provider.GetUserById(account.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (ctx.RequestHeaders.TryGetFirst("Authentication", out var auth))
|
else if (ctx.RequestHeaders.TryGetFirst("Authorization", out var auth))
|
||||||
{
|
{
|
||||||
var co = auth.Split(new char[] { ' ' }, 2);
|
var co = auth.Split(new char[] { ' ' }, 2);
|
||||||
if (co.Length == 2 && co[0] == "Bearer")
|
if (co.Length == 2 && co[0] == "Bearer")
|
||||||
|
@ -2094,7 +2115,7 @@ namespace Tesses.CMS
|
||||||
|
|
||||||
string user = usersPathValueServer.GetValue(ctx);
|
string user = usersPathValueServer.GetValue(ctx);
|
||||||
string show = showPathValueServer.GetValue(ctx);
|
string show = showPathValueServer.GetValue(ctx);
|
||||||
var me = GetAccount(ctx);
|
var me = GetAccount(ctx,out var cookie);
|
||||||
string seasonS = seasonPathValueServer.GetValue(ctx);
|
string seasonS = seasonPathValueServer.GetValue(ctx);
|
||||||
string episodeS = episodePathValueServer.GetValue(ctx);
|
string episodeS = episodePathValueServer.GetValue(ctx);
|
||||||
if (!int.TryParse(seasonS, out var season))
|
if (!int.TryParse(seasonS, out var season))
|
||||||
|
@ -2110,7 +2131,17 @@ namespace Tesses.CMS
|
||||||
if (me != null)
|
if (me != null)
|
||||||
{
|
{
|
||||||
if (_episode != null)
|
if (_episode != null)
|
||||||
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditEpisodeDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_episode.ProperName), Description = System.Web.HttpUtility.HtmlEncode(_episode.Description) })));
|
{
|
||||||
|
string csrf="";
|
||||||
|
string csrf2="";
|
||||||
|
|
||||||
|
if(me != null)
|
||||||
|
{
|
||||||
|
csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
csrf2 = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
}
|
||||||
|
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditEpisodeDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_episode.ProperName), Description = System.Web.HttpUtility.HtmlEncode(_episode.Description), csrf, csrf2 })));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2166,7 +2197,7 @@ namespace Tesses.CMS
|
||||||
int season = 1;
|
int season = 1;
|
||||||
if (!int.TryParse(seasonS, out season))
|
if (!int.TryParse(seasonS, out season))
|
||||||
season = 1;
|
season = 1;
|
||||||
var me = GetAccount(ctx);
|
var me = GetAccount(ctx,out var cookie);
|
||||||
|
|
||||||
var _season = provider.GetSeason(user, show, season);
|
var _season = provider.GetSeason(user, show, season);
|
||||||
|
|
||||||
|
@ -2178,7 +2209,18 @@ namespace Tesses.CMS
|
||||||
if (me != null)
|
if (me != null)
|
||||||
{
|
{
|
||||||
if (_season != null)
|
if (_season != null)
|
||||||
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditSeasonDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_season.ProperName), newseasonnumber = provider.EpisodeCount(user, show, season) + 1, Description = System.Web.HttpUtility.HtmlEncode(_season.Description) })));
|
{
|
||||||
|
string csrf="";
|
||||||
|
string csrf2="";
|
||||||
|
string csrf3="";
|
||||||
|
if(me != null)
|
||||||
|
{
|
||||||
|
csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
csrf2 = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
csrf3 = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
}
|
||||||
|
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditSeasonDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_season.ProperName), newseasonnumber = provider.EpisodeCount(user, show, season) + 1, Description = System.Web.HttpUtility.HtmlEncode(_season.Description), csrf,csrf2,csrf3 })));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2315,7 +2357,7 @@ namespace Tesses.CMS
|
||||||
{
|
{
|
||||||
string user = usersPathValueServer.GetValue(ctx);
|
string user = usersPathValueServer.GetValue(ctx);
|
||||||
string show = showPathValueServer.GetValue(ctx);
|
string show = showPathValueServer.GetValue(ctx);
|
||||||
var me = GetAccount(ctx);
|
var me = GetAccount(ctx,out var cookie);
|
||||||
var _show = provider.GetShow(user, show);
|
var _show = provider.GetShow(user, show);
|
||||||
|
|
||||||
if (me != null && me.Username != user && !me.IsAdmin)
|
if (me != null && me.Username != user && !me.IsAdmin)
|
||||||
|
@ -2326,7 +2368,18 @@ namespace Tesses.CMS
|
||||||
if (me != null)
|
if (me != null)
|
||||||
{
|
{
|
||||||
if (_show != null)
|
if (_show != null)
|
||||||
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditShowDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_show.ProperName), newseasonnumber = provider.SeasonCount(user, show) + 1, Description = System.Web.HttpUtility.HtmlEncode(_show.Description) })));
|
{
|
||||||
|
string csrf="";
|
||||||
|
string csrf2="";
|
||||||
|
string csrf3="";
|
||||||
|
if(me != null)
|
||||||
|
{
|
||||||
|
csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
csrf2 = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
csrf3 = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
}
|
||||||
|
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditShowDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_show.ProperName), newseasonnumber = provider.SeasonCount(user, show) + 1, Description = System.Web.HttpUtility.HtmlEncode(_show.Description) ,csrf,csrf2,csrf3 })));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2616,7 +2669,7 @@ namespace Tesses.CMS
|
||||||
{
|
{
|
||||||
string user = usersPathValueServer.GetValue(ctx);
|
string user = usersPathValueServer.GetValue(ctx);
|
||||||
string movie = moviePathValueServer.GetValue(ctx);
|
string movie = moviePathValueServer.GetValue(ctx);
|
||||||
var me = GetAccount(ctx,true);
|
var me = GetAccount(ctx,out var cookie,true);
|
||||||
var _movie = provider.GetMovie(user, movie);
|
var _movie = provider.GetMovie(user, movie);
|
||||||
if (me != null && me.Username != user && !me.IsAdmin)
|
if (me != null && me.Username != user && !me.IsAdmin)
|
||||||
{
|
{
|
||||||
|
@ -2642,12 +2695,13 @@ namespace Tesses.CMS
|
||||||
{
|
{
|
||||||
Subtitle.ToSrt(srtFile, json);
|
Subtitle.ToSrt(srtFile, json);
|
||||||
}
|
}
|
||||||
await ctx.SendTextAsync("Success");
|
var csrf=HttpUtility.UrlEncode(this.CreateCSRF(me.Id, cookie));
|
||||||
|
await ctx.SendJsonAsync(new{success=true,csrf});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.StatusCode = 400;
|
ctx.StatusCode = 400;
|
||||||
await ctx.SendTextAsync("Fail");
|
await ctx.SendJsonAsync(new{success=false});
|
||||||
}
|
}
|
||||||
private async Task SubtitlesEpisodeAsync(ServerContext ctx)
|
private async Task SubtitlesEpisodeAsync(ServerContext ctx)
|
||||||
{
|
{
|
||||||
|
@ -2661,7 +2715,7 @@ namespace Tesses.CMS
|
||||||
if (!int.TryParse(episodeS, out var episode))
|
if (!int.TryParse(episodeS, out var episode))
|
||||||
episode = 1;
|
episode = 1;
|
||||||
|
|
||||||
var me = GetAccount(ctx);
|
var me = GetAccount(ctx,out var cookie);
|
||||||
var _show = provider.GetMovie(user, show);
|
var _show = provider.GetMovie(user, show);
|
||||||
var _episode = provider.GetEpisode(user, show, season, episode);
|
var _episode = provider.GetEpisode(user, show, season, episode);
|
||||||
if (me != null && me.Username != user && !me.IsAdmin)
|
if (me != null && me.Username != user && !me.IsAdmin)
|
||||||
|
@ -2675,6 +2729,10 @@ namespace Tesses.CMS
|
||||||
string langDir = Path.Combine(path, user, "show", show, $"Season {season.ToString("D2")}", $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}-subtitles", lang);
|
string langDir = Path.Combine(path, user, "show", show, $"Season {season.ToString("D2")}", $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}-subtitles", lang);
|
||||||
string langFile = Path.Combine(langDir, $"{_episode.ProperName} S{season.ToString("D2")}E{episode.ToString("D2")}.json");
|
string langFile = Path.Combine(langDir, $"{_episode.ProperName} S{season.ToString("D2")}E{episode.ToString("D2")}.json");
|
||||||
string browserfile = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/S{season.ToString("D2")}E{episode.ToString("D2")}.mp4";
|
string browserfile = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/S{season.ToString("D2")}E{episode.ToString("D2")}.mp4";
|
||||||
|
string csrf="";
|
||||||
|
|
||||||
|
csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie));
|
||||||
|
|
||||||
string json = "";
|
string json = "";
|
||||||
bool hasjson = false;
|
bool hasjson = false;
|
||||||
if (File.Exists(langFile))
|
if (File.Exists(langFile))
|
||||||
|
@ -2682,7 +2740,7 @@ namespace Tesses.CMS
|
||||||
hasjson = true;
|
hasjson = true;
|
||||||
json = File.ReadAllText(langFile);
|
json = File.ReadAllText(langFile);
|
||||||
}
|
}
|
||||||
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageSubtitleEditor.RenderAsync(new { hasjson, json, lang, browserfile })));
|
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageSubtitleEditor.RenderAsync(new { hasjson, json, lang, browserfile, csrf })));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -2749,7 +2807,7 @@ namespace Tesses.CMS
|
||||||
{
|
{
|
||||||
string user = usersPathValueServer.GetValue(ctx);
|
string user = usersPathValueServer.GetValue(ctx);
|
||||||
string movie = moviePathValueServer.GetValue(ctx);
|
string movie = moviePathValueServer.GetValue(ctx);
|
||||||
var me = GetAccount(ctx);
|
var me = GetAccount(ctx,out var cookie);
|
||||||
var _movie = provider.GetMovie(user, movie);
|
var _movie = provider.GetMovie(user, movie);
|
||||||
if (me != null && me.Username != user && !me.IsAdmin)
|
if (me != null && me.Username != user && !me.IsAdmin)
|
||||||
{
|
{
|
||||||
|
@ -2769,7 +2827,8 @@ namespace Tesses.CMS
|
||||||
hasjson = true;
|
hasjson = true;
|
||||||
json = File.ReadAllText(langFile);
|
json = File.ReadAllText(langFile);
|
||||||
}
|
}
|
||||||
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageSubtitleEditor.RenderAsync(new { hasjson, json, lang, browserfile })));
|
var csrf=HttpUtility.UrlEncode(this.CreateCSRF(me.Id, cookie));
|
||||||
|
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageSubtitleEditor.RenderAsync(new { hasjson, json, lang, browserfile,csrf })));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -3993,7 +4052,7 @@ namespace Tesses.CMS
|
||||||
{
|
{
|
||||||
var user = provider.GetUserById(show.UserId);
|
var user = provider.GetUserById(show.UserId);
|
||||||
int count2 = provider.EpisodeCount(user.Username, show.Name, season);
|
int count2 = provider.EpisodeCount(user.Username, show.Name, season);
|
||||||
for (int j = 1; j < count2; j++)
|
for (int j = 1; j <= count2; j++)
|
||||||
{
|
{
|
||||||
yield return provider.GetEpisode(user.Username, show.Name, season, j);
|
yield return provider.GetEpisode(user.Username, show.Name, season, j);
|
||||||
}
|
}
|
||||||
|
@ -4172,6 +4231,7 @@ namespace Tesses.CMS
|
||||||
});
|
});
|
||||||
},new SwagmeDocumentation("Branding for server"));
|
},new SwagmeDocumentation("Branding for server"));
|
||||||
swagmeServer.Add("/Login", ApiLogin, new SwagmeDocumentation("Login to account", "<b>email</b>: the email of account</br><b>password</b>: the password of account</br><b>type</b>: json or cookie"), "POST", "Users");
|
swagmeServer.Add("/Login", ApiLogin, new SwagmeDocumentation("Login to account", "<b>email</b>: the email of account</br><b>password</b>: the password of account</br><b>type</b>: json or cookie"), "POST", "Users");
|
||||||
|
swagmeServer.Add("/LoggedInUserAccount",LoggedInAccountAsync,new SwagmeDocumentation("Get logged in account via authorization"),"GET","Users");
|
||||||
swagmeServer.Add("/GetPublicUsers", ApiGetPublicUsers, new SwagmeDocumentation("Get all public users"), "GET", "Users");
|
swagmeServer.Add("/GetPublicUsers", ApiGetPublicUsers, new SwagmeDocumentation("Get all public users"), "GET", "Users");
|
||||||
swagmeServer.Add("/Updates", (ctx) =>
|
swagmeServer.Add("/Updates", (ctx) =>
|
||||||
{
|
{
|
||||||
|
@ -4205,6 +4265,19 @@ namespace Tesses.CMS
|
||||||
return swagmeServer;
|
return swagmeServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task LoggedInAccountAsync(ServerContext ctx)
|
||||||
|
{
|
||||||
|
var account = this.GetAccount(ctx);
|
||||||
|
if(account is null)
|
||||||
|
{
|
||||||
|
await ctx.SendJsonAsync(new LoggedInUserAccount{LoggedIn=false});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await ctx.SendJsonAsync(new LoggedInUserAccount{LoggedIn=true,UserName = account.Username, ProperName = account.ProperName, IsAdmin = account.IsAdmin, IsInvited = account.IsInvited, IsVerified=account.IsVerified});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ApiLogin(ServerContext ctx)
|
private async Task ApiLogin(ServerContext ctx)
|
||||||
{
|
{
|
||||||
ctx.ParseBody();
|
ctx.ParseBody();
|
||||||
|
@ -4381,7 +4454,7 @@ namespace Tesses.CMS
|
||||||
if (type == "json")
|
if (type == "json")
|
||||||
{
|
{
|
||||||
List<Season> seasons = new List<Season>();
|
List<Season> seasons = new List<Season>();
|
||||||
for (int i = 1; i < provider.SeasonCount(s.UserId, s.Id); i++)
|
for (int i = 1; i <= provider.SeasonCount(s.UserId, s.Id); i++)
|
||||||
{
|
{
|
||||||
var season = provider.GetSeason(s.UserId, s.Id, i);
|
var season = provider.GetSeason(s.UserId, s.Id, i);
|
||||||
if (season != null)
|
if (season != null)
|
||||||
|
@ -4471,7 +4544,7 @@ namespace Tesses.CMS
|
||||||
if (type == "json")
|
if (type == "json")
|
||||||
{
|
{
|
||||||
List<Episode> episodes = new List<Episode>();
|
List<Episode> episodes = new List<Episode>();
|
||||||
for (int i = 1; i < provider.EpisodeCount(s.UserId, s.Id, season); i++)
|
for (int i = 1; i <= provider.EpisodeCount(s.UserId, s.Id, season); i++)
|
||||||
{
|
{
|
||||||
var episode = provider.GetEpisode(s.UserId, s.Id, season, i);
|
var episode = provider.GetEpisode(s.UserId, s.Id, season, i);
|
||||||
if (episode != null)
|
if (episode != null)
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Tesses.CMS
|
||||||
Season = SeasonNumber,
|
Season = SeasonNumber,
|
||||||
Episode = EpisodeNumber,
|
Episode = EpisodeNumber,
|
||||||
Description = System.Web.HttpUtility.HtmlEncode(Description),
|
Description = System.Web.HttpUtility.HtmlEncode(Description),
|
||||||
Thumbnail = File.Exists(Path.Combine(dir,user,"show",show,$"Season {SeasonNumber.ToString("D2")}",$"{EpisodeName} S{SeasonNumber.ToString("D2")}E{SeasonNumber.ToString("D2")}-thumbnail.jpg")) ? $"{configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{SeasonNumber.ToString("D2")}/{EpisodeName}%20S{SeasonNumber.ToString("D2")}E{SeasonNumber.ToString("D2")}-thumbnail.jpg" : File.Exists(Path.Combine(dir,user,"show",show,$"Season {SeasonNumber.ToString("D2")}","thumbnail.jpg")) ? $"{configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{SeasonNumber.ToString("D2")}/thumbnail.jpg" : $"{configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg"
|
Thumbnail = File.Exists(Path.Combine(dir,user,"show",show,$"Season {SeasonNumber.ToString("D2")}",$"{EpisodeName} S{SeasonNumber.ToString("D2")}E{EpisodeNumber.ToString("D2")}-thumbnail.jpg")) ? $"{configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{SeasonNumber.ToString("D2")}/{EpisodeName}%20S{SeasonNumber.ToString("D2")}E{EpisodeNumber.ToString("D2")}-thumbnail.jpg" : File.Exists(Path.Combine(dir,user,"show",show,$"Season {SeasonNumber.ToString("D2")}","thumbnail.jpg")) ? $"{configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{SeasonNumber.ToString("D2")}/thumbnail.jpg" : $"{configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Tesses.CMS
|
||||||
|
{
|
||||||
|
public class LoggedInUserAccount
|
||||||
|
{
|
||||||
|
[JsonProperty("logged_in")]
|
||||||
|
public bool LoggedIn {get;set;}=false;
|
||||||
|
|
||||||
|
[JsonProperty("username")]
|
||||||
|
public string UserName {get;set;}="";
|
||||||
|
|
||||||
|
[JsonProperty("proper_name")]
|
||||||
|
public string ProperName {get;set;}="";
|
||||||
|
|
||||||
|
[JsonProperty("is_admin")]
|
||||||
|
public bool IsAdmin {get;set;}=false;
|
||||||
|
|
||||||
|
[JsonProperty("is_invited")]
|
||||||
|
public bool IsInvited {get;set;}=false;
|
||||||
|
|
||||||
|
[JsonProperty("is_verified")]
|
||||||
|
public bool IsVerified {get;set;}=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.002.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tesses.CMS", "Tesses.CMS.csproj", "{2AB7647E-947B-43FD-8348-4F43B13DD9AA}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{2AB7647E-947B-43FD-8348-4F43B13DD9AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2AB7647E-947B-43FD-8348-4F43B13DD9AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2AB7647E-947B-43FD-8348-4F43B13DD9AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2AB7647E-947B-43FD-8348-4F43B13DD9AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {C4517F8F-92D8-4A81-8081-C8683339096F}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"Title": "TessesStudios",
|
||||||
|
"Urls": [{"Url":"https://tesses.net", "Text":"Tesses"}, {"Url": "https://jellyfin.site.tesses.net/", "Text": "Jellyfin (use guest)"}],
|
||||||
|
"Root": "CHANGE_TO_DOMAIN_NAME",
|
||||||
|
"Email":{
|
||||||
|
"Host": "CHANGE_TO_EMAIL_SMTP_SERVER",
|
||||||
|
"User": "CHANGE_TO_EMAIL_USERNAME",
|
||||||
|
"Pass": "CHNAGE_TO_EMAIL_PASS",
|
||||||
|
"Port": 587,
|
||||||
|
"Encryption": "StartTls",
|
||||||
|
"Email": "CHANGE_TO_EMAIL"
|
||||||
|
},
|
||||||
|
"Publish": "RequireInvite"
|
||||||
|
}
|
Loading…
Reference in New Issue