2023-12-16 01:40:05 +00:00
using System ;
using System.IO ;
using System.Threading.Tasks ;
using Newtonsoft.Json ;
using Tesses.WebServer ;
using Scriban ;
using System.Collections.Generic ;
using Tesses.WebServer.Swagme ;
using System.Net ;
using System.Web ;
using System.Text ;
using System.Xml ;
using Microsoft.SyndicationFeed.Rss ;
using Microsoft.SyndicationFeed.Atom ;
using Microsoft.SyndicationFeed ;
using PlaylistsNET.Models ;
using PlaylistsNET.Content ;
using System.Security.Cryptography ;
using System.Collections.Concurrent ;
using System.Diagnostics ;
using System.Linq ;
2024-01-04 02:53:13 +00:00
using MailKit.Net.Smtp ;
using MimeKit ;
2023-12-16 01:40:05 +00:00
namespace Tesses.CMS
{
2024-01-04 02:53:13 +00:00
public class Subtitle
{
[JsonProperty("end")]
public double End { get ; set ; }
[JsonProperty("begin")]
public double Begin { get ; set ; }
[JsonProperty("text")]
public string Text { get ; set ; }
public void ToWebVTT ( TextWriter writer )
{
var begin = TimeSpan . FromSeconds ( Begin ) ;
var end = TimeSpan . FromSeconds ( End ) ;
try {
writer . WriteLine ( $"{begin.ToString(" hh \ \ : mm \ \ : ss \ \ . fff ")} --> {end.ToString(" hh \ \ : mm \ \ : ss \ \ . fff ")}" ) ;
} catch ( Exception ex )
{
Console . WriteLine ( ex ) ;
}
writer . WriteLine ( HttpUtility . HtmlEncode ( Text ) ) ;
}
public void ToSrt ( TextWriter writer )
{
var begin = TimeSpan . FromSeconds ( Begin ) ;
var end = TimeSpan . FromSeconds ( End ) ;
writer . WriteLine ( $"{begin.ToString(" hh \ \ : mm \ \ : ss \ \ , fff ")} --> {end.ToString(" hh \ \ : mm \ \ : ss \ \ , fff ")}" ) ;
writer . WriteLine ( Text ) ;
}
public static void ToWebVTT ( TextWriter writer , List < Subtitle > subtitles )
{
writer . WriteLine ( "WEBVTT" ) ;
for ( int i = 0 ; i < subtitles . Count ; i + + )
{
writer . WriteLine ( ) ;
writer . WriteLine ( i + 1 ) ;
subtitles [ i ] . ToWebVTT ( writer ) ;
}
}
public static void ToSrt ( TextWriter writer , List < Subtitle > subtitles )
{
for ( int i = 0 ; i < subtitles . Count ; i + + )
{
if ( i > 0 ) writer . WriteLine ( ) ;
writer . WriteLine ( i + 1 ) ;
subtitles [ i ] . ToSrt ( writer ) ;
}
}
}
2023-12-16 01:40:05 +00:00
public class CMSServer
{
2024-01-04 02:53:13 +00:00
public static readonly Language [ ] Languages = new Language [ ] {
new Language ( )
{
LangCode = "en-US" ,
LangCodeVideo = "en" ,
LangTitle = "English (US)"
} ,
new Language ( )
{
LangCode = "en-UK" ,
LangCodeVideo = "en" ,
LangTitle = "English (UK)"
} ,
new Language ( )
{
LangCode = "ar-AE" ,
LangCodeVideo = "ar" ,
LangTitle = "Arabic (AE)"
} ,
new Language ( )
{
LangCode = "zh-CN" ,
LangCodeVideo = "zh" ,
LangTitle = "Chinese (CN)"
} ,
new Language ( )
{
LangCode = "zh-TW" ,
LangCodeVideo = "zh" ,
LangTitle = "Chinese (TW)"
} ,
new Language ( )
{
LangCode = "cs-CZ" ,
LangCodeVideo = "cs" ,
LangTitle = "Czech (CZ)"
} ,
new Language ( )
{
LangCode = "da-DK" ,
LangCodeVideo = "da" ,
LangTitle = "Danish (DK)"
} ,
new Language ( )
{
LangCode = "in-ID" ,
LangCodeVideo = "in" ,
LangTitle = "Indonesian (ID)"
} ,
new Language ( )
{
LangCode = "ms-MY" ,
LangCodeVideo = "ms" ,
LangTitle = "Malaysian (MY)"
} ,
new Language ( )
{
LangCode = "nl-NL" ,
LangCodeVideo = "nl" ,
LangTitle = "Dutch (NL)"
} ,
new Language ( )
{
LangCode = "fr-FR" ,
LangCodeVideo = "fr" ,
LangTitle = "French (FR)"
} ,
new Language ( )
{
LangCode = "fi-FI" ,
LangCodeVideo = "fi" ,
LangTitle = "Finnish (FI)"
} ,
new Language ( )
{
LangCode = "de-DE" ,
LangCodeVideo = "de" ,
LangTitle = "German (DE)"
} ,
new Language ( )
{
LangCode = "it-IT" ,
LangCodeVideo = "it" ,
LangTitle = "Italian (IT)"
} ,
new Language ( )
{
LangCode = "ja-JP" ,
LangCodeVideo = "ja" ,
LangTitle = "Japanese (JP)"
} ,
new Language ( )
{
LangCode = "ko-KR" ,
LangCodeVideo = "ko" ,
LangTitle = "Korean (KR)"
} ,
new Language ( )
{
LangCode = "no-NO" ,
LangCodeVideo = "no" ,
LangTitle = "Norwegian (NO)"
} ,
new Language ( )
{
LangCode = "pl-PL" ,
LangCodeVideo = "pl" ,
LangTitle = "Polish (PL)"
} ,
new Language ( )
{
LangCode = "pt-BR" ,
LangCodeVideo = "pt" ,
LangTitle = "Portuguese (BR)"
} ,
new Language ( )
{
LangCode = "ro-RO" ,
LangCodeVideo = "ro" ,
LangTitle = "Romanian (RO)"
} ,
new Language ( )
{
LangCode = "ru-RU" ,
LangCodeVideo = "ru" ,
LangTitle = "Russian (RU)"
} ,
new Language ( )
{
LangCode = "es-ES" ,
LangCodeVideo = "es" ,
LangTitle = "Spanish (ES)"
} ,
new Language ( )
{
LangCode = "sv-SE" ,
LangCodeVideo = "sv" ,
LangTitle = "Swedish (SE)"
} ,
new Language ( )
{
LangCode = "th-TH" ,
LangCodeVideo = "th" ,
LangTitle = "Thai (TH)"
} ,
new Language ( )
{
LangCode = "tl-PH" ,
LangCodeVideo = "tl" ,
LangTitle = "Filipino (PH)"
} ,
new Language ( )
{
LangCode = "tr-TR" ,
LangCodeVideo = "tr" ,
LangTitle = "Turkish (TR)"
}
} ;
public class Language
{
public string LangCode { get ; set ; }
public string LangCodeVideo { get ; set ; }
public string LangTitle { get ; set ; }
}
public MovieContentMetaData GetMovieContentMetaData ( CMSConfiguration configuration , string user , string movie )
{
var _movie = new MovieContentMetaData ( ) {
PosterUrl = $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/poster.jpg" ,
ThumbnailUrl = $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/thumbnail.jpg" ,
BrowserStream = $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/browser.mp4" ,
DownloadStream = $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/{movie}.mp4" ,
MovieTorrentUrl = $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/{movie}.torrent" ,
MovieWithExtrasTorrentUrl = $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/{movie}_withextras.torrent"
} ;
string subDir = Path . Combine ( path , user , "movie" , movie , "subtitles" ) ;
if ( Directory . Exists ( subDir ) )
foreach ( var language in Directory . GetDirectories ( subDir ) )
{
string languageCode = Path . GetFileName ( language ) ; //en-US for english
_movie . SubtitlesStreams . Add ( new SubtitleStream ( ) { LanguageCode = languageCode , VttUrl = $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/subtitles/{languageCode}/{movie}.vtt" , SrtUrl = $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/subtitles/{languageCode}/{movie}.srt" } ) ;
}
string extrasPath = Path . Combine ( path , user , "movie" , movie , "extras" ) ;
if ( Directory . Exists ( extrasPath ) )
FindExtras ( $"{configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/extras/" , extrasPath , _movie . ExtraStreams ) ;
return _movie ;
}
private void FindExtras ( string extras , string v , List < ExtraDataStream > extraStreams )
{
foreach ( var d in Directory . GetDirectories ( v ) )
{
var dirname = Path . GetFileName ( d ) ;
string url = $"{extras}{dirname}/" ;
ExtraDataStream dataStream = new ExtraDataStream ( ) ;
dataStream . IsDir = true ;
dataStream . Name = dirname ;
dataStream . Url = url ;
FindExtras ( url , d , dataStream . Items ) ;
extraStreams . Add ( dataStream ) ;
}
foreach ( var f in Directory . GetFiles ( v ) )
{
var filename = Path . GetFileName ( f ) ;
var url = $"{extras}{filename}" ;
ExtraDataStream dataStream = new ExtraDataStream ( ) ;
dataStream . IsDir = false ;
dataStream . Name = filename ;
dataStream . Url = url ;
extraStreams . Add ( dataStream ) ;
}
}
EmailCreator Creator { get ; set ; }
2023-12-16 01:40:05 +00:00
Template pageShell ;
Template pageIndex ;
Template pageDevcenter ;
2024-01-04 02:53:13 +00:00
Template pageShow ;
Template pageSeason ;
Template pageShows ;
2023-12-16 01:40:05 +00:00
Template pageMovie ;
Template pageMovies ;
Template pageWatchMovie ;
Template pageUpload ;
Template pageUsers ;
Template pageEditMovieDetails ;
2024-01-04 02:53:13 +00:00
Template pageEditShowDetails ;
Template pageEditSeasonDetails ;
2023-12-16 01:40:05 +00:00
2024-01-04 02:53:13 +00:00
Template pageExtrasViewer ;
Template pageEditUser ;
Template aboutUser ;
Template pageSubtitleLangList ;
Template pageSubtitleEditor ;
private Template pageVerifyEmail ;
private Template pageManage ;
2023-12-16 01:40:05 +00:00
RouteServer routeServer ;
PathValueServer usersPathValueServer ;
PathValueServer moviePathValueServer ;
2024-01-04 02:53:13 +00:00
PathValueServer showPathValueServer ;
PathValueServer seasonPathValueServer ;
MountableServer showMountableServer ;
2023-12-16 01:40:05 +00:00
MountableServer usersMountableServer ;
2024-01-04 02:53:13 +00:00
MountableServer seasonMountableServer ;
2023-12-16 01:40:05 +00:00
RouteServer movieRouteServer ;
2024-01-04 02:53:13 +00:00
RouteServer showRouteServer ;
RouteServer seasonRouteServer ;
2023-12-16 01:40:05 +00:00
IContentProvider provider ;
string path ;
public CMSServer ( string configpath , IContentProvider provider )
{
this . provider = provider ;
usersMountableServer = new MountableServer ( ) ;
usersPathValueServer = new PathValueServer ( usersMountableServer ) ;
movieRouteServer = new RouteServer ( ) ;
2024-01-04 02:53:13 +00:00
showRouteServer = new RouteServer ( ) ;
seasonRouteServer = new RouteServer ( ) ;
2023-12-16 01:40:05 +00:00
moviePathValueServer = new PathValueServer ( movieRouteServer ) ;
2024-01-04 02:53:13 +00:00
showMountableServer = new MountableServer ( showRouteServer ) ;
showPathValueServer = new PathValueServer ( showMountableServer ) ;
seasonMountableServer = new MountableServer ( seasonRouteServer ) ;
seasonPathValueServer = new PathValueServer ( seasonMountableServer ) ;
2023-12-16 01:40:05 +00:00
string configJson = Path . Combine ( configpath , "config.json" ) ;
if ( File . Exists ( configJson ) )
{
Configuration = JsonConvert . DeserializeObject < CMSConfiguration > ( File . ReadAllText ( configJson ) ) ;
}
if ( Configuration . BittorrentTrackers . Count = = 0 )
{
Configuration . BittorrentTrackers . AddRange ( new string [ ] {
"https://t1.hloli.org:443/announce" ,
"http://1337.abcvg.info:80/announce" ,
"http://tracker.renfei.net:8080/announce" ,
"http://tracker.gbitt.info:80/announce" ,
"http://p2p.0g.cx:6969/announce" ,
"https://tracker.foreverpirates.co:443/announce"
} ) ;
}
2024-01-04 02:53:13 +00:00
Creator = new EmailCreator ( Configuration ) ;
2023-12-16 01:40:05 +00:00
pageShell = Template . Parse ( AssetProvider . ReadAllText ( "/PageShell.html" ) ) ;
pageIndex = Template . Parse ( AssetProvider . ReadAllText ( "/Index.html" ) ) ;
pageDevcenter = Template . Parse ( AssetProvider . ReadAllText ( "/Devcenter.html" ) ) ;
pageMovie = Template . Parse ( AssetProvider . ReadAllText ( "/MoviePage.html" ) ) ;
pageMovies = Template . Parse ( AssetProvider . ReadAllText ( "/MoviesPage.html" ) ) ;
pageUsers = Template . Parse ( AssetProvider . ReadAllText ( "/UsersPage.html" ) ) ;
pageWatchMovie = Template . Parse ( AssetProvider . ReadAllText ( "/WatchMovie.html" ) ) ;
pageUpload = Template . Parse ( AssetProvider . ReadAllText ( "/Upload.html" ) ) ;
pageEditMovieDetails = Template . Parse ( AssetProvider . ReadAllText ( "/EditMovieDetails.html" ) ) ;
2024-01-04 02:53:13 +00:00
pageEditShowDetails = Template . Parse ( AssetProvider . ReadAllText ( "/EditShowDetails.html" ) ) ;
pageEditSeasonDetails = Template . Parse ( AssetProvider . ReadAllText ( "/EditSeasonDetails.html" ) ) ;
pageExtrasViewer = Template . Parse ( AssetProvider . ReadAllText ( "/ExtrasViewer.html" ) ) ;
pageEditUser = Template . Parse ( AssetProvider . ReadAllText ( "/AccountEditor.html" ) ) ;
aboutUser = Template . Parse ( AssetProvider . ReadAllText ( "/AboutUser.html" ) ) ;
pageVerifyEmail = Template . Parse ( AssetProvider . ReadAllText ( "/VerifyEmailWeb.html" ) ) ;
pageManage = Template . Parse ( AssetProvider . ReadAllText ( "/ManageHtml.html" ) ) ;
pageSubtitleLangList = Template . Parse ( AssetProvider . ReadAllText ( "/SubtitleLangList.html" ) ) ;
pageSubtitleEditor = Template . Parse ( AssetProvider . ReadAllText ( "/SubtitleEditor.html" ) ) ;
pageShow = Template . Parse ( AssetProvider . ReadAllText ( "/ShowPage.html" ) ) ;
pageSeason = Template . Parse ( AssetProvider . ReadAllText ( "/SeasonPage.html" ) ) ;
pageShows = Template . Parse ( AssetProvider . ReadAllText ( "/ShowsPage.html" ) ) ;
2023-12-16 01:40:05 +00:00
MountableServer mountableServer = new MountableServer ( new AssetProvider ( ) ) ;
path = Path . Combine ( configpath , "content" ) ;
mountableServer . Mount ( "/content/" , new StaticServer ( path ) { AllowListingDirectories = true } ) ;
mountableServer . Mount ( "/api/v1/" , CreateSwagme ( ) ) ;
mountableServer . Mount ( "/user/" , usersPathValueServer ) ;
routeServer = new RouteServer ( mountableServer ) ;
routeServer . Add ( "/" , Index , "GET" ) ;
routeServer . Add ( "/devcenter" , Devcenter , "GET" ) ;
routeServer . Add ( "/upload" , UploadPage1 , "GET" ) ;
routeServer . Add ( "/upload" , Upload , "POST" ) ;
routeServer . Add ( "/users" , UsersAsync , "GET" ) ;
routeServer . Add ( "/login" , LoginAsync ) ;
2024-01-04 02:53:13 +00:00
routeServer . Add ( "/logout" , LogoutAsync ) ;
2023-12-16 01:40:05 +00:00
routeServer . Add ( "/login" , LoginPostAsync , "POST" ) ;
routeServer . Add ( "/signup" , SignupAsync ) ;
routeServer . Add ( "/signup" , SignupPostAsync , "POST" ) ;
2024-01-04 02:53:13 +00:00
routeServer . Add ( "/account" , AccountAsync ) ;
routeServer . Add ( "/account" , AccountPostAsync , "POST" ) ;
routeServer . Add ( "/verify" , VerifyAsync ) ;
routeServer . Add ( "/resend" , ResendVerification , "GET" ) ;
routeServer . Add ( "/manage" , ManageAsync ) ;
routeServer . Add ( "/manage" , ManagePostAsync , "POST" ) ;
routeServer . Add ( "/impersonate" , ImpersonateAsync ) ;
2023-12-16 01:40:05 +00:00
RegisterUsersPath ( ) ;
Task . Factory . StartNew ( async ( ) = > {
while ( Running )
{
if ( tasks . TryDequeue ( out var item ) )
{
await item ( ) ;
}
await Task . Delay ( TimeSpan . FromSeconds ( 0.125 ) ) ;
}
} ) . Wait ( 0 ) ;
2024-01-04 02:53:13 +00:00
}
public async Task ManagePostAsync ( ServerContext ctx )
{
ctx . ParseBody ( ) ;
var account = GetAccount ( ctx ) ;
if ( account ! = null & & account . IsAdmin )
{
if ( ctx . QueryParams . TryGetFirst ( "name" , out var name ) )
{
bool isinvited = ctx . QueryParams . ContainsKey ( "invited" ) ;
bool isverified = ctx . QueryParams . ContainsKey ( "verified" ) ;
bool isadmin = ctx . QueryParams . ContainsKey ( "admin" ) ;
var a = provider . GetUserAccount ( name ) ;
a . IsInvited = isinvited ;
a . IsVerified = isverified ;
a . IsAdmin = isadmin ;
provider . UpdateUser ( a ) ;
}
}
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/manage" ) ;
}
public async Task ImpersonateAsync ( ServerContext ctx )
{
var account = GetAccount ( ctx , out var cookie ) ;
if ( account ! = null & & account . IsAdmin )
{
if ( ctx . QueryParams . TryGetFirst ( "user" , out var user ) )
{
var _user = provider . GetUserAccount ( user ) ;
if ( _user ! = null )
{
provider . ChangeSession ( cookie , _user . Id ) ;
}
}
}
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/" ) ;
//impersonate
}
private async Task ManageAsync ( ServerContext ctx )
{
var account = GetAccount ( ctx ) ; List < object > users = new List < object > ( ) ;
if ( account ! = null & & account . IsAdmin )
{
int i = 0 ;
foreach ( var user in provider . GetUsers ( ) )
{
if ( account . Id ! = user . Id )
{
string impersonate = $"{Configuration.Root.TrimEnd('/')}/impersonate?user={user.Username}" ;
users . Add ( new { nameattr = HttpUtility . HtmlAttributeEncode ( user . Username ) , propername = HttpUtility . HtmlEncode ( user . ProperName ) , name = HttpUtility . HtmlEncode ( user . Username ) , isverified = user . IsVerified , isadmin = user . IsAdmin , isinvited = user . IsInvited , impersonate = impersonate , i = i } ) ;
i + + ;
}
}
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageManage . RenderAsync ( new { users = users } ) ) ) ;
}
private async Task ResendVerification ( ServerContext ctx )
{
var account = GetAccount ( ctx ) ;
if ( account ! = null ) {
if ( account . IsVerified )
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/" ) ;
else
{
var code = GetOrGenerateVerification ( account ) ;
await Creator . SendVerificationEmailAsync ( account , code ) ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageVerifyEmail . RenderAsync ( new { Email = account . Email } ) ) ) ;
}
}
}
private async Task VerifyAsync ( ServerContext ctx )
{
bool failed = true ;
if ( ctx . QueryParams . TryGetFirst ( "token" , out var token ) )
{
var item = provider . GetVerificationAccount ( token ) ;
if ( item . HasValue )
{
var a = provider . GetUserById ( item . Value ) ;
a . IsVerified = true ;
provider . UpdateUser ( a ) ;
failed = false ;
provider . DeleteVerificationCode ( token ) ;
}
if ( ! failed )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Your account has been verified, click login to login.</h1><a href=\"./login\" class=\"btn btn-primary\">Login</a>" ) ) ;
}
}
if ( failed )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Failed to verify account</h1>" ) ) ;
}
}
private async Task AccountPostAsync ( ServerContext ctx )
{
ctx . ParseBody ( ) ;
var account = GetAccount ( ctx ) ;
if ( account ! = null )
{
if ( ctx . QueryParams . TryGetFirst ( "about_me" , out var about_me ) & & ctx . QueryParams . TryGetFirst ( "proper_name" , out var proper_name ) )
{
account . AboutMe = about_me ;
account . ProperName = proper_name ;
provider . UpdateUser ( account ) ;
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/" ) ;
}
}
else
{
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/login" ) ;
}
}
private async Task AccountAsync ( ServerContext ctx )
{
var account = GetAccount ( ctx ) ;
if ( account ! = null )
{
//account page
var res = new { admin = account . IsAdmin , Notverified = ! account . IsVerified , Propername = HttpUtility . HtmlAttributeEncode ( account . ProperName ) , Aboutme = HttpUtility . HtmlEncode ( account . AboutMe ) } ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageEditUser . RenderAsync ( res ) ) ) ;
}
else
{
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/login" ) ;
}
}
private async Task LogoutAsync ( ServerContext ctx )
{
Logout ( ctx ) ;
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/" ) ;
}
2023-12-16 01:40:05 +00:00
public bool Running = true ;
public async Task LoginAsync ( ServerContext ctx )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await AssetProvider . ReadAllTextAsync ( "/LoginPage.html" ) ) ) ;
}
public async Task SignupAsync ( ServerContext ctx )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await AssetProvider . ReadAllTextAsync ( "/SignupPage.html" ) ) ) ;
}
public async Task SignupPostAsync ( ServerContext ctx )
{
ctx . ParseBody ( ) ;
if ( ctx . QueryParams . TryGetFirst ( "email" , out var email ) & & ctx . QueryParams . TryGetFirst ( "name" , out var name ) & & ctx . QueryParams . TryGetFirst ( "proper_name" , out var proper_name ) & & ctx . QueryParams . TryGetFirst ( "password" , out var password ) & & ctx . QueryParams . TryGetFirst ( "confirm_password" , out var confirm_password ) )
{
bool error = false , emailTaken = false , nameTaken = false , properNameTaken = false , passwordDontMatch = false , passwordNotGoodEnough = false ;
foreach ( var user in provider . GetUsers ( ) )
{
if ( user . Username = = name )
{
nameTaken = true ;
error = true ;
}
if ( user . Email = = email )
{
emailTaken = true ;
error = true ;
}
if ( user . ProperName = = proper_name )
{
properNameTaken = true ;
error = true ;
}
}
if ( password ! = confirm_password )
{
error = true ;
passwordDontMatch = true ;
}
if ( password . Length < 10 )
{
error = true ;
passwordNotGoodEnough = true ;
}
if ( error )
{
StringBuilder b = new StringBuilder ( ) ;
if ( emailTaken )
{
b . AppendLine ( "<h1>Email is taken</h1>" ) ;
}
if ( nameTaken )
{
b . AppendLine ( "<h1>Name is taken</h1>" ) ;
}
if ( properNameTaken )
{
b . AppendLine ( "<h1>Proper Name is taken" ) ;
}
if ( passwordNotGoodEnough )
{
b . AppendLine ( "<h1>Password not good enough</h1>" ) ;
}
if ( passwordDontMatch )
{
b . AppendLine ( "<h1>Passwords don't match</h1>" ) ;
}
await ctx . SendTextAsync ( b . ToString ( ) ) ;
}
else
{
provider . CreateUser ( Configuration , name , proper_name , email , password ) ;
2024-01-04 02:53:13 +00:00
var account = provider . GetUserAccount ( name ) ;
if ( account . IsVerified )
2023-12-16 01:40:05 +00:00
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/" ) ;
2024-01-04 02:53:13 +00:00
else
{
var code = GetOrGenerateVerification ( account ) ;
await Creator . SendVerificationEmailAsync ( account , code ) ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageVerifyEmail . RenderAsync ( new { Email = email } ) ) ) ;
}
2023-12-16 01:40:05 +00:00
}
}
}
2024-01-04 02:53:13 +00:00
private string GetOrGenerateVerification ( UserAccount account )
{
var now = DateTime . Now ;
byte [ ] bytes = new byte [ 32 ] ;
string token ;
using ( var rng = RandomNumberGenerator . Create ( ) )
do {
rng . GetBytes ( bytes ) ;
token = Convert . ToBase64String ( bytes ) ;
} while ( provider . ContainsVerificationCode ( token ) ) ;
provider . CreateVerificationCode ( token , account . Id ) ;
return token ;
}
2023-12-16 01:40:05 +00:00
public async Task LoginPostAsync ( ServerContext ctx )
{
ctx . ParseBody ( ) ;
if ( ctx . QueryParams . TryGetFirst ( "email" , out var email ) & & ctx . QueryParams . TryGetFirst ( "password" , out var password ) )
{
foreach ( var a in provider . GetUsers ( ) )
{
if ( a . Email ! = email ) continue ;
if ( a . Email = = email & & a . PasswordCorrect ( password ) )
{
//we got it
byte [ ] bytes = new byte [ 32 ] ;
string cookie ;
using ( var rng = RandomNumberGenerator . Create ( ) )
do {
rng . GetBytes ( bytes ) ;
cookie = Convert . ToBase64String ( bytes ) ;
2024-01-04 02:53:13 +00:00
} while ( provider . ContainsSession ( cookie ) ) ;
2023-12-16 01:40:05 +00:00
2024-01-04 02:53:13 +00:00
provider . CreateSession ( cookie , a . Id ) ;
2023-12-16 01:40:05 +00:00
ctx . ResponseHeaders . Add ( "Set-Cookie" , $"Session={cookie}; Path=/" ) ;
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/" ) ;
return ;
}
}
ctx . StatusCode = 401 ;
await ctx . SendTextAsync ( "<h1>Incorrect</h1><a href=\"./\">Home</a> | <a href=\"./login\">Login</a>" ) ;
}
}
2024-01-04 02:53:13 +00:00
private void Logout ( ServerContext ctx )
{
if ( ctx . RequestHeaders . TryGetValue ( "Cookie" , out var cookies ) )
{
foreach ( var c in cookies )
{
var co = c . Split ( new char [ ] { '=' } , 2 ) ;
if ( co . Length = = 2 & & co [ 0 ] = = "Session" )
{
if ( provider . ContainsSession ( co [ 1 ] ) )
{
provider . DeleteSession ( co [ 1 ] ) ;
return ;
}
}
}
}
}
2023-12-16 01:40:05 +00:00
private UserAccount GetAccount ( ServerContext ctx )
{
2024-01-04 02:53:13 +00:00
return GetAccount ( ctx , out var s ) ;
}
private UserAccount GetAccount ( ServerContext ctx , out string cookie )
{
cookie = "" ;
2023-12-16 01:40:05 +00:00
if ( ctx . RequestHeaders . TryGetValue ( "Cookie" , out var cookies ) )
{
foreach ( var c in cookies )
{
2024-01-04 02:53:13 +00:00
2023-12-16 01:40:05 +00:00
var co = c . Split ( new char [ ] { '=' } , 2 ) ;
if ( co . Length = = 2 & & co [ 0 ] = = "Session" )
{
2024-01-04 02:53:13 +00:00
cookie = co [ 1 ] ;
long? account = provider . GetSession ( cookie ) ;
if ( account . HasValue )
return provider . GetUserById ( account . Value ) ;
2023-12-16 01:40:05 +00:00
}
}
}
return null ;
}
private async Task UsersAsync ( ServerContext ctx )
{
List < object > users = new List < object > ( ) ;
foreach ( var user in provider . GetUsers ( ) )
{
2024-01-04 02:53:13 +00:00
if ( ! user . IsAdmin & & Configuration . Publish = = CMSPublish . Admin )
{
//await ctx.SendTextAsync(await RenderHtmlAsync(false,"<h1>You can't upload content</h1>"));
continue ;
}
if ( ! ( user . IsAdmin | | user . IsInvited ) & & Configuration . Publish = = CMSPublish . RequireInvite )
{
//await ctx.SendTextAsync(await RenderHtmlAsync(false,"<h1>You can't upload content</h1>"));
continue ;
}
if ( ! ( user . IsAdmin | | user . IsInvited | | user . IsVerified ) )
{
//await ctx.SendTextAsync(await RenderHtmlAsync(false,"<h1>You can't upload content</h1>"));
continue ;
}
2023-12-16 01:40:05 +00:00
users . Add ( user . Scriban ( ) ) ;
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageUsers . RenderAsync ( new {
Users = users ,
rooturl = $"{Configuration.Root.TrimEnd('/')}/"
} ) ) ) ;
}
const string badC = "/\\\"&,?|:;*@!# " ;
private string FixString ( string str )
{
StringBuilder b = new StringBuilder ( ) ;
foreach ( var item in str )
{
if ( char . IsControl ( item ) )
{
continue ;
}
2024-01-04 02:53:13 +00:00
2023-12-16 01:40:05 +00:00
if ( item > = 127 ) continue ;
if ( badC . Contains ( item . ToString ( ) ) ) continue ;
b . Append ( item . ToString ( ) ) ;
}
return b . ToString ( ) ;
}
private async Task Upload ( ServerContext ctx )
{
ctx . ParseBody ( ) ;
if ( ctx . QueryParams . TryGetFirst ( "name" , out var name ) & & ctx . QueryParams . TryGetFirst ( "proper_name" , out var proper_name ) & & ctx . QueryParams . TryGetFirst ( "type" , out var type ) & & ctx . QueryParams . TryGetFirst ( "description" , out var description ) )
{
var account = GetAccount ( ctx ) ;
if ( account ! = null )
{
if ( ! account . IsAdmin & & Configuration . Publish = = CMSPublish . Admin )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>You can't upload content</h1>" ) ) ;
return ;
}
if ( ! ( account . IsAdmin | | account . IsInvited ) & & Configuration . Publish = = CMSPublish . RequireInvite )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>You can't upload content</h1>" ) ) ;
return ;
}
if ( ! ( account . IsAdmin | | account . IsInvited | | account . IsVerified ) )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>You can't upload content</h1>" ) ) ;
return ;
}
name = FixString ( name ) ;
switch ( type )
{
case "movie" :
2024-01-04 02:53:13 +00:00
provider . CreateMovie ( account . Username , name , proper_name , description . Replace ( "\r" , "" ) ) ;
2023-12-16 01:40:05 +00:00
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/user/{account.Username}/movie/{name}/edit" ) ;
break ;
2024-01-04 02:53:13 +00:00
case "show" :
provider . CreateShow ( account . Username , name , proper_name , description ) ;
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/user/{account.Username}/show/{name}/edit" ) ;
break ;
}
}
else
{
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/login" ) ;
}
}
else
{
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/" ) ;
}
}
public async Task UploadPage1 ( ServerContext ctx )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await RenderUpload1Async ( ) , Configuration . RelativeNavUrl ( "Devcenter" , "devcenter" ) ) ) ;
}
public IServer Movies ( )
{
RouteServer routeServer = new RouteServer ( ) ;
routeServer . Add ( "/" , MoviesAsync ) ;
return routeServer ;
}
public async Task MoviesAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
List < object > movies = new List < object > ( ) ;
foreach ( var item in provider . GetMovies ( user ) )
{
var data = GetMovieContentMetaData ( Configuration , user , item . Name ) ;
movies . Add ( item . Scriban ( data . ThumbnailUrl ) ) ;
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageMovies . RenderAsync ( new { Movies = movies } ) ) ) ;
}
private void RegisterUsersPath ( )
{
RouteServer routeServer = new RouteServer ( ) ;
routeServer . Add ( "/" , UserPageAsync ) ;
routeServer . Add ( "/about" , AboutAsync ) ;
usersMountableServer . Mount ( "/" , routeServer ) ;
usersMountableServer . Mount ( "/movies/" , Movies ( ) ) ;
usersMountableServer . Mount ( "/movie/" , moviePathValueServer ) ;
usersMountableServer . Mount ( "/shows/" , Shows ( ) ) ;
usersMountableServer . Mount ( "/show/" , showPathValueServer ) ;
RegisterMoviePath ( ) ;
RegisterShowPath ( ) ;
}
private void RegisterShowPath ( )
{
showRouteServer . Add ( "/" , ShowPageAsync ) ;
showRouteServer . Add ( "/edit" , EditShowPageAsync ) ;
showRouteServer . Add ( "/edit" , EditShowPagePostAsync , "POST" ) ;
showRouteServer . Add ( "/upload" , UploadShowStreamAsync , "POST" ) ;
showRouteServer . Add ( "/addseason" , AddSeasonAsync , "POST" ) ;
showMountableServer . Mount ( "/season/" , seasonPathValueServer ) ;
RegisterSeasonPath ( ) ;
}
private void RegisterSeasonPath ( )
{
seasonRouteServer . Add ( "/" , SeasonPageAsync ) ;
seasonRouteServer . Add ( "/edit" , EditSeasonPageAsync ) ;
seasonRouteServer . Add ( "/edit" , EditSeasonPagePostAsync , "POST" ) ;
seasonRouteServer . Add ( "/addepisode" , AddEpisodeAsync , "POST" ) ;
seasonRouteServer . Add ( "/upload" , UploadSeasonStreamAsync , "POST" ) ;
}
private async Task AddEpisodeAsync ( ServerContext ctx )
{
ctx . ParseBody ( ) ;
string user = usersPathValueServer . GetValue ( ctx ) ;
string show = showPathValueServer . GetValue ( ctx ) ;
string season = seasonPathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _show = provider . GetShow ( user , show ) ;
int seasonNo = 1 ;
if ( ! int . TryParse ( season , out seasonNo ) )
seasonNo = 1 ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
int episode = 1 ;
if ( ctx . QueryParams . TryGetFirst ( "number" , out var number ) & & ! int . TryParse ( number , out episode ) )
episode = 1 ;
if ( ctx . QueryParams . TryGetFirst ( "proper_name" , out var proper_name ) & & ctx . QueryParams . TryGetFirst ( "description" , out var description ) & & ctx . QueryParams . TryGetFirst ( "name" , out var name ) )
{
provider . CreateEpisode ( user , show , seasonNo , episode , name , proper_name , description ) ;
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{show}/season/{seasonNo}/episode/{episode}/edit" ) ;
}
else
{
}
}
}
private Task EditSeasonPagePostAsync ( ServerContext ctx )
{
throw new NotImplementedException ( ) ;
}
private async Task EditSeasonPageAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string show = showPathValueServer . GetValue ( ctx ) ;
string seasonS = seasonPathValueServer . GetValue ( ctx ) ;
int season = 1 ;
if ( ! int . TryParse ( seasonS , out season ) )
season = 1 ;
var me = GetAccount ( ctx ) ;
var _season = provider . GetSeason ( user , show , season ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
if ( _season ! = null )
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , 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 ) } ) ) ) ;
}
else
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>You are unauthorized to edit this</h1>" ) ) ;
}
}
private async Task SeasonPageAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string show = showPathValueServer . GetValue ( ctx ) ;
string season = seasonPathValueServer . GetValue ( ctx ) ;
var _show = provider . GetShow ( user , show ) ;
var _user = provider . GetUserAccount ( user ) ;
if ( ! int . TryParse ( season , out var seasonNo ) )
seasonNo = 1 ;
var _season = provider . GetSeason ( user , show , seasonNo ) ;
var me = GetAccount ( ctx ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
object value ;
if ( _show ! = null & & _user ! = null & & _season ! = null )
{
string showDir = Path . Combine ( this . path , user , "show" , show ) ;
string thumb = File . Exists ( Path . Combine ( showDir , user , "show" , show , $"Season {_season.SeasonNumber.ToString(" D2 ")}" , "thumbnail.jpg" ) ) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{_season.SeasonNumber.ToString(" D2 ")}/thumbnail.jpg" : $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg" ;
List < object > episodes = new List < object > ( ) ;
for ( int i = 1 ; i < = provider . EpisodeCount ( user , show , seasonNo ) ; i + + )
{
var item = provider . GetEpisode ( user , show , seasonNo , i ) ;
if ( item ! = null )
//var data = GetMovieContentMetaData(Configuration,user,item.Name);
episodes . Add ( item . Scriban ( Configuration , path , user , show ) ) ;
}
value = new { episodes , editable = me ! = null , userproper = HttpUtility . HtmlEncode ( _user . ProperName ) , username = HttpUtility . HtmlEncode ( user ) , rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
title = Configuration . Title , hasshow = true , seasonthumbnail = thumb , seasonproper = HttpUtility . HtmlEncode ( _season . ProperName ) , showproper = HttpUtility . HtmlEncode ( _show . ProperName ) , showname = HttpUtility . HtmlEncode ( _show . Name ) , seasondescription = DescriptLinkUtils ( _season . Description ? ? "" ) . Replace ( "\n" , "<br>" ) } ;
}
else
{
value = new { username = user , rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
title = Configuration . Title , hasshow = false } ;
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageSeason . RenderAsync ( value ) ) ) ;
}
private async Task AddSeasonAsync ( ServerContext ctx )
{
ctx . ParseBody ( ) ;
string user = usersPathValueServer . GetValue ( ctx ) ;
string show = showPathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _show = provider . GetShow ( user , show ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
int season = 1 ;
if ( ctx . QueryParams . TryGetFirst ( "number" , out var number ) & & ! int . TryParse ( number , out season ) )
season = 1 ;
if ( ctx . QueryParams . TryGetFirst ( "proper_name" , out var proper_name ) & & ctx . QueryParams . TryGetFirst ( "description" , out var description ) )
{
provider . CreateSeason ( user , show , season , proper_name , description ) ;
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{show}/season/{season}/edit" ) ;
}
else
{
}
}
}
private Task EditShowPagePostAsync ( ServerContext ctx )
{
throw new NotImplementedException ( ) ;
}
private async Task EditShowPageAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string show = showPathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _show = provider . GetShow ( user , show ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
if ( _show ! = null )
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageEditShowDetails . RenderAsync ( new { Propername = System . Web . HttpUtility . HtmlAttributeEncode ( _show . ProperName ) , newseasonnumber = provider . SeasonCount ( user , show ) + 1 , Description = System . Web . HttpUtility . HtmlEncode ( _show . Description ) } ) ) ) ;
}
else
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>You are unauthorized to edit this</h1>" ) ) ;
}
}
private async Task ShowPageAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string show = showPathValueServer . GetValue ( ctx ) ;
var _show = provider . GetShow ( user , show ) ;
var _user = provider . GetUserAccount ( user ) ;
var me = GetAccount ( ctx ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
object value ;
if ( _show ! = null & & _user ! = null )
{
string showDir = Path . Combine ( this . path , user , "show" , show ) ;
bool torrent = File . Exists ( Path . Combine ( showDir , $"{show}.torrent" ) ) ;
bool torrent_wextra = File . Exists ( Path . Combine ( showDir , $"{show}_withextras.torrent" ) ) ;
bool extrasexists = Directory . Exists ( Path . Combine ( showDir , "extras" ) ) | | me ! = null ;
string thumb = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg" ;
List < object > seasons = new List < object > ( ) ;
for ( int i = 1 ; i < = provider . SeasonCount ( user , show ) ; i + + )
{
var item = provider . GetSeason ( user , show , i ) ;
if ( item ! = null )
//var data = GetMovieContentMetaData(Configuration,user,item.Name);
seasons . Add ( item . Scriban ( Configuration , path , user , show ) ) ;
}
value = new { seasons , extrasexists = extrasexists , torrentexists = torrent , torrentwextraexists = torrent_wextra , torrent = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/{show}.torrent" , torrentwextra = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/{show}_withextras.torrent" , editable = me ! = null , userproper = HttpUtility . HtmlEncode ( _user . ProperName ) , username = HttpUtility . HtmlEncode ( user ) , rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
title = Configuration . Title , hasshow = true , showthumbnail = thumb , showproper = HttpUtility . HtmlEncode ( _show . ProperName ) , showname = HttpUtility . HtmlEncode ( _show . Name ) , showdescription = DescriptLinkUtils ( _show . Description ? ? "" ) . Replace ( "\n" , "<br>" ) } ;
}
else
{
value = new { username = user , rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
title = Configuration . Title , hasshow = false } ;
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageShow . RenderAsync ( value ) ) ) ;
}
private IServer Shows ( )
{
RouteServer routeServer = new RouteServer ( ) ;
routeServer . Add ( "/" , ShowsAsync ) ;
return routeServer ;
}
private async Task ShowsAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
List < object > shows = new List < object > ( ) ;
foreach ( var item in provider . GetShows ( user ) )
{
//var data = GetMovieContentMetaData(Configuration,user,item.Name);
shows . Add ( item . Scriban ( Configuration , user ) ) ;
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageShows . RenderAsync ( new { Shows = shows } ) ) ) ;
}
private bool StartsWithAt ( string str , int indexof , string startsWith )
{
if ( str . Length - indexof < startsWith . Length ) return false ;
for ( int i = 0 ; i < startsWith . Length ; i + + )
{
if ( str [ i + indexof ] ! = startsWith [ i ] ) return false ;
}
return true ;
}
private string DescriptLinkUtils ( string url )
{
StringBuilder b = new StringBuilder ( ) ;
for ( int i = 0 ; i < url . Length ; i + + )
{
if ( StartsWithAt ( url , i , "http:" ) | | StartsWithAt ( url , i , "https:" ) | | StartsWithAt ( url , i , "ftp:" ) | | StartsWithAt ( url , i , "ftps:" ) | | StartsWithAt ( url , i , "sftp:" ) | | StartsWithAt ( url , i , "magnet:" ) )
{
StringBuilder b2 = new StringBuilder ( ) ;
for ( ; i < url . Length ; i + + )
{
if ( url [ i ] = = '\n' | | url [ i ] = = ' ' )
{
break ;
}
b2 . Append ( url [ i ] ) ;
2023-12-16 01:40:05 +00:00
}
2024-01-04 02:53:13 +00:00
b . Append ( $"<a href=\" { HttpUtility . HtmlAttributeEncode ( b2 . ToString ( ) ) } \ ">{HttpUtility.HtmlEncode(b2.ToString())}</a>" ) ;
}
else
{
b . Append ( HttpUtility . HtmlEncode ( url [ i ] ) ) ;
}
}
return b . ToString ( ) ;
}
private async Task AboutAsync ( ServerContext ctx )
{
var username = usersPathValueServer . GetValue ( ctx ) ;
var accountData = provider . GetUserAccount ( username ) ;
if ( accountData ! = null )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await aboutUser . RenderAsync ( new { Propername = HttpUtility . HtmlEncode ( accountData . ProperName ) , Aboutme = DescriptLinkUtils ( accountData . AboutMe ? ? "" ) . Replace ( "\n" , "<br>" ) } ) ) ) ;
}
}
private async Task UserPageAsync ( ServerContext ctx )
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await AssetProvider . ReadAllTextAsync ( "/UserPage.html" ) ) ) ;
}
ConcurrentQueue < Func < Task > > tasks = new ConcurrentQueue < Func < Task > > ( ) ;
private void RegisterMoviePath ( )
{
movieRouteServer . Add ( "/" , MoviePageAsync ) ;
movieRouteServer . Add ( "/play" , PlayMoviePageAsync ) ;
movieRouteServer . Add ( "/edit" , EditMoviePageAsync ) ;
movieRouteServer . Add ( "/edit" , EditMoviePagePostAsync , "POST" ) ;
movieRouteServer . Add ( "/upload" , UploadMovieStreamAsync , "POST" ) ;
movieRouteServer . Add ( "/upload_extra" , UploadMovieExtraAsync , "POST" ) ;
movieRouteServer . Add ( "/extras" , ExtrasMoviePageAsync ) ;
movieRouteServer . Add ( "/mkdir" , ExtrasMovieMkdirAsync , "POST" ) ;
movieRouteServer . Add ( "/subtitles" , SubtitlesMovieAsync ) ;
movieRouteServer . Add ( "/subtitles" , SubtitlesMoviePostAsync , "POST" ) ;
//http://192.168.0.158:62444/user/tesses/movie/MyGreatMovie/mkdir
}
private async Task SubtitlesMoviePostAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
if ( ctx . QueryParams . TryGetFirst ( "lang" , out var lang ) & & ! string . IsNullOrWhiteSpace ( lang ) )
{
var json = await ctx . ReadJsonAsync < List < Subtitle > > ( ) ;
string langDir = Path . Combine ( path , user , "movie" , movie , "subtitles" , lang ) ;
Directory . CreateDirectory ( langDir ) ;
string langFile = Path . Combine ( langDir , $"{movie}.json" ) ;
string vtt = Path . Combine ( langDir , $"{movie}.vtt" ) ;
string srt = Path . Combine ( langDir , $"{movie}.srt" ) ;
File . WriteAllText ( langFile , JsonConvert . SerializeObject ( json ) ) ;
using ( var vttFile = File . CreateText ( vtt ) )
{
Subtitle . ToWebVTT ( vttFile , json ) ;
}
using ( var srtFile = File . CreateText ( srt ) )
{
Subtitle . ToSrt ( srtFile , json ) ;
}
await ctx . SendTextAsync ( "Success" ) ;
return ;
}
}
ctx . StatusCode = 400 ;
await ctx . SendTextAsync ( "Fail" ) ;
}
private async Task SubtitlesMovieAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{ if ( ctx . QueryParams . TryGetFirst ( "lang" , out var lang ) & & ! string . IsNullOrWhiteSpace ( lang ) )
{
string langDir = Path . Combine ( path , user , "movie" , movie , "subtitles" , lang ) ;
string langFile = Path . Combine ( langDir , $"{movie}.json" ) ;
string browserfile = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/browser.mp4" ;
string json = "" ;
bool hasjson = false ;
if ( File . Exists ( langFile ) )
{
hasjson = true ;
json = File . ReadAllText ( langFile ) ;
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageSubtitleEditor . RenderAsync ( new { hasjson , json , lang , browserfile } ) ) ) ;
}
else
{
List < object > languages = new List < object > ( ) ;
foreach ( var item in Languages )
{
languages . Add ( new { code = item . LangCode , name = item . LangTitle } ) ;
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageSubtitleLangList . RenderAsync ( new { languages } ) ) ) ;
}
}
}
private async Task UploadMovieExtraAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
Directory . CreateDirectory ( Path . Combine ( path , user , "movie" , movie ) ) ;
var tmpFile = Path . Combine ( path , user , "movie" , movie , $"tmp{DateTime.Now.ToFileTime().ToString()}.bin" ) ;
string filename = "" ;
foreach ( var item in ctx . ParseBody ( ( n , fn , ct ) = > { filename = fn ; return File . Create ( tmpFile ) ; } ) )
item . Value . Dispose ( ) ;
if ( ctx . QueryParams . TryGetFirst ( "parent" , out var parent ) )
{
var _path = SanitizePath ( $"{parent.TrimStart('/').TrimEnd('/')}/{filename}" ) . TrimStart ( '/' ) ;
var _path2 = Path . Combine ( path , user , "movie" , movie , "extras" , _path ) ;
if ( File . Exists ( _path2 ) )
File . Delete ( _path2 ) ;
File . Move ( tmpFile , _path2 ) ;
ScheduleTask ( async ( ) = > {
await GenerateBittorentFileMovieAsync ( user , movie ) ;
} ) ;
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/user/{user}/movie/{movie}/extras?path={System.Web.HttpUtility.UrlEncode(parent)}" ) ;
}
}
}
private async Task ExtrasMovieMkdirAsync ( ServerContext ctx )
{
ctx . ParseBody ( ) ;
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
2023-12-16 01:40:05 +00:00
2024-01-04 02:53:13 +00:00
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( ctx . QueryParams . TryGetFirst ( "parent" , out var parent ) & & ctx . QueryParams . TryGetFirst ( "name" , out var name ) )
{
var _path = SanitizePath ( $"{parent.TrimStart('/').TrimEnd('/')}/{name}" ) . TrimStart ( '/' ) ;
var _path2 = Path . Combine ( path , user , "movie" , movie , "extras" , _path ) ;
if ( me ! = null )
{
Directory . CreateDirectory ( _path2 ) ;
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/user/{user}/movie/{movie}/extras?path={System.Web.HttpUtility.UrlEncode(_path)}" ) ;
2023-12-16 01:40:05 +00:00
}
else
{
2024-01-04 02:53:13 +00:00
await ctx . SendRedirectAsync ( $"{Configuration.Root.TrimEnd('/')}/user/{user}/movie/{movie}/extras?path={System.Web.HttpUtility.UrlEncode(parent)}" ) ;
2023-12-16 01:40:05 +00:00
}
}
}
2024-01-04 02:53:13 +00:00
private async Task ExtrasMoviePageAsync ( ServerContext ctx )
2023-12-16 01:40:05 +00:00
{
string user = usersPathValueServer . GetValue ( ctx ) ;
2024-01-04 02:53:13 +00:00
string movie = moviePathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
2023-12-16 01:40:05 +00:00
{
2024-01-04 02:53:13 +00:00
me = null ;
}
if ( ! ctx . QueryParams . TryGetFirst ( "path" , out var __path ) )
{
__path = "/" ;
2023-12-16 01:40:05 +00:00
}
2024-01-04 02:53:13 +00:00
__path = __path . TrimStart ( '/' ) ;
2023-12-16 01:40:05 +00:00
2024-01-04 02:53:13 +00:00
__path = SanitizePath ( __path ) ;
string extrasPath = Path . Combine ( path , user , "movie" , movie , "extras" ) ;
if ( __path . Length > 0 )
{
extrasPath = Path . Combine ( extrasPath , __path ) ;
}
2023-12-16 01:40:05 +00:00
2024-01-04 02:53:13 +00:00
List < object > paths = new List < object > ( ) ;
if ( __path . Length > 0 )
{
string up = Path . GetDirectoryName ( $"/{__path}" ) . TrimStart ( '/' ) ;
string path = $"./extras?path=/{System.Web.HttpUtility.UrlEncode(up)}" ;
paths . Add ( new { Path = path , Type = "[PARENT]" , Name = "Up" } ) ;
}
else {
paths . Add ( new { Path = "./" , Type = "[PARENT]" , Name = "Up" } ) ;
}
foreach ( var dir in Directory . EnumerateDirectories ( extrasPath ) )
{
string dirname = Path . GetFileName ( dir ) ;
string dirname_html = System . Web . HttpUtility . HtmlEncode ( dirname ) ;
string path = $"./extras?path=/{System.Web.HttpUtility.UrlEncode(__path.TrimEnd('/') + '/' + dirname)}" ;
paths . Add ( new { Path = path , Type = "[DIR]" , Name = dirname_html } ) ;
}
foreach ( var file in Directory . EnumerateFiles ( extrasPath ) )
{
string filename = Path . GetFileName ( file ) ;
string filename_html = System . Web . HttpUtility . HtmlEncode ( filename ) ;
string path = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/extras/{System.Web.HttpUtility.UrlPathEncode(__path.TrimEnd('/') + '/' + filename)}" ;
paths . Add ( new { Path = path , Type = "[FILE]" , Name = filename_html } ) ;
}
2023-12-16 01:40:05 +00:00
2024-01-04 02:53:13 +00:00
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageExtrasViewer . RenderAsync ( new { Extras = paths , Path = $"/{__path}" , Parent = __path , Editable = me ! = null } ) ) ) ;
}
2023-12-16 01:40:05 +00:00
2024-01-04 02:53:13 +00:00
private string SanitizePath ( string path )
2023-12-16 01:40:05 +00:00
{
2024-01-04 02:53:13 +00:00
if ( path . Length = = 0 ) return "" ;
List < string > paths = new List < string > ( ) ;
foreach ( var item in path . Replace ( '\\' , '/' ) . Split ( '/' ) )
{
if ( item = = "." | | item = = ".." ) continue ;
paths . Add ( item ) ;
}
return string . Join ( "/" , paths ) ;
2023-12-16 01:40:05 +00:00
}
private class PieceStream : Stream
{
public const int PieceLength = 16 * 1024 ;
public int CurpieceOffset { get ; private set ; } = 0 ;
public List < byte [ ] > Pieces { get ; private set ; } = new List < byte [ ] > ( ) ;
public byte [ ] CalculateCurPiece ( )
{
using ( var sha1 = SHA1 . Create ( ) )
{
return sha1 . ComputeHash ( Curpiece , 0 , CurpieceOffset ) ;
}
}
public byte [ ] Curpiece = new byte [ PieceLength ] ;
public override bool CanRead = > false ;
public override bool CanSeek = > false ;
public override bool CanWrite = > true ;
long len ;
public override long Length = > len ;
public bool HasPiece = > CurpieceOffset > 0 ;
public override long Position { get = > throw new NotImplementedException ( ) ; set = > throw new NotImplementedException ( ) ; }
public override void Flush ( )
{
}
public override int Read ( byte [ ] buffer , int offset , int count )
{
throw new NotImplementedException ( ) ;
}
public override long Seek ( long offset , SeekOrigin origin )
{
throw new NotImplementedException ( ) ;
}
public override void SetLength ( long value )
{
}
public override void Write ( byte [ ] buffer , int offset , int count )
{
for ( int i = 0 ; i < count ; i + + )
{
byte curByte = buffer [ i + offset ] ;
if ( CurpieceOffset < PieceLength )
{
Curpiece [ CurpieceOffset + + ] = curByte ;
}
else
{
Pieces . Add ( this . CalculateCurPiece ( ) ) ;
CurpieceOffset = 0 ;
Curpiece [ CurpieceOffset + + ] = curByte ;
}
}
len + = count ;
}
}
private async Task GenerateBittorentFileMovieAsync ( string user , string movie )
{
string movieDir = Path . Combine ( this . path , user , "movie" , movie ) ;
Directory . CreateDirectory ( movieDir ) ;
string extrasDir = Path . Combine ( movieDir , "extras" ) ;
string subtitles = Path . Combine ( movieDir , "subtitles" ) ;
PieceStream strm = new PieceStream ( ) ;
//calculate without extras
List < string [ ] > paths = new List < string [ ] > ( ) ;
2024-01-04 02:53:13 +00:00
if ( File . Exists ( Path . Combine ( movieDir , $"{movie}.mp4" ) ) )
2023-12-16 01:40:05 +00:00
paths . Add ( new string [ ] { $"{movie}.mp4" } ) ;
2024-01-04 02:53:13 +00:00
if ( File . Exists ( Path . Combine ( movieDir , "thumbnail.jpg" ) ) )
2023-12-16 01:40:05 +00:00
paths . Add ( new string [ ] { "thumbnail.jpg" } ) ;
2024-01-04 02:53:13 +00:00
if ( File . Exists ( Path . Combine ( movieDir , "poster.jpg" ) ) )
2023-12-16 01:40:05 +00:00
paths . Add ( new string [ ] { "poster.jpg" } ) ;
if ( Directory . Exists ( subtitles ) )
GetExtras ( paths , new string [ ] { "subtitles" } , subtitles ) ;
List < long > lengths = new List < long > ( ) ;
foreach ( var _path in paths . Select < string [ ] , string > ( e = > Path . Combine ( e ) ) )
using ( var movieStrm = File . OpenRead ( Path . Combine ( movieDir , _path ) ) )
{
lengths . Add ( movieStrm . Length ) ;
await movieStrm . CopyToAsync ( strm ) ;
}
Torrent torrent = new Torrent ( ) ;
torrent . AnnounceList . AddRange ( Configuration . BittorrentTrackers ) ;
torrent . CreationDate = DateTime . Now ;
torrent . UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/" ;
torrent . Info . PieceLength = PieceStream . PieceLength ;
torrent . Info . Name = movie ;
foreach ( var piece in strm . Pieces )
{
torrent . Info . Pieces . Add ( piece ) ;
}
if ( strm . HasPiece ) torrent . Info . Pieces . Add ( strm . CalculateCurPiece ( ) ) ;
for ( int i = 0 ; i < paths . Count ; i + + )
{
var _path = paths [ i ] ;
var len = lengths [ i ] ;
torrent . Info . Files . Add ( new TorrentInfoFile ( ) { Path = _path , Length = len } ) ;
}
string torrentFile = Path . Combine ( movieDir , $"{movie}.torrent" ) ;
using ( var file = File . Create ( torrentFile ) )
{
await torrent . ToTorrentFile ( ) . WriteToStreamAsync ( file ) ;
}
if ( Directory . Exists ( extrasDir ) )
{
List < string [ ] > paths2 = new List < string [ ] > ( ) ;
GetExtras ( paths2 , new string [ ] { "extras" } , extrasDir ) ;
foreach ( var _path in paths2 . Select < string [ ] , string > ( e = > Path . Combine ( e ) ) )
using ( var movieStrm = File . OpenRead ( Path . Combine ( movieDir , _path ) ) )
{
lengths . Add ( movieStrm . Length ) ;
await movieStrm . CopyToAsync ( strm ) ;
}
paths . AddRange ( paths2 ) ;
torrent = new Torrent ( ) ;
torrent . AnnounceList . AddRange ( Configuration . BittorrentTrackers ) ;
torrent . CreationDate = DateTime . Now ;
torrent . UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/" ;
torrent . Info . PieceLength = PieceStream . PieceLength ;
torrent . Info . Name = movie ;
foreach ( var piece in strm . Pieces )
{
torrent . Info . Pieces . Add ( piece ) ;
}
if ( strm . HasPiece ) torrent . Info . Pieces . Add ( strm . CalculateCurPiece ( ) ) ;
for ( int i = 0 ; i < paths . Count ; i + + )
{
var _path = paths [ i ] ;
var len = lengths [ i ] ;
torrent . Info . Files . Add ( new TorrentInfoFile ( ) { Path = _path , Length = len } ) ;
}
torrentFile = Path . Combine ( movieDir , $"{movie}_withextras.torrent" ) ;
using ( var file = File . Create ( torrentFile ) )
{
await torrent . ToTorrentFile ( ) . WriteToStreamAsync ( file ) ;
}
}
}
private void GetExtras ( List < string [ ] > paths2 , string [ ] torrentPath , string extrasDir )
{
foreach ( var dir in Directory . GetDirectories ( extrasDir ) )
{
string dirname = Path . GetFileName ( dir ) ;
string [ ] path = new string [ torrentPath . Length + 1 ] ;
Array . Copy ( torrentPath , path , torrentPath . Length ) ;
path [ path . Length - 1 ] = dirname ;
GetExtras ( paths2 , path , dir ) ;
}
foreach ( var file in Directory . GetFiles ( extrasDir ) )
{
string filename = Path . GetFileName ( file ) ;
string [ ] path = new string [ torrentPath . Length + 1 ] ;
Array . Copy ( torrentPath , path , torrentPath . Length ) ;
path [ path . Length - 1 ] = filename ;
paths2 . Add ( path ) ;
}
}
2024-01-04 02:53:13 +00:00
private async Task UploadSeasonStreamAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string show = showPathValueServer . GetValue ( ctx ) ;
string season = seasonPathValueServer . GetValue ( ctx ) ;
var _show = provider . GetShow ( user , show ) ;
var _user = provider . GetUserAccount ( user ) ;
if ( ! int . TryParse ( season , out var seasonNo ) )
seasonNo = 1 ;
var _season = provider . GetSeason ( user , show , seasonNo ) ;
var me = GetAccount ( ctx ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
Directory . CreateDirectory ( Path . Combine ( path , user , "show" , show , $"Season {seasonNo}" ) ) ;
var tmpFile = Path . Combine ( path , user , "show" , show , $"Season {seasonNo}" , $"tmp{DateTime.Now.ToFileTime().ToString()}.bin" ) ;
foreach ( var item in ctx . ParseBody ( ( n , fn , ct ) = > File . Create ( tmpFile ) ) )
item . Value . Dispose ( ) ;
if ( _show ! = null )
{
if ( ctx . QueryParams . TryGetFirst ( "type" , out var type ) )
{
switch ( type )
{
case "thumbnail" :
string thumb = Path . Combine ( path , user , "show" , show , $"Season {seasonNo}" , "thumbnail.jpg" ) ;
if ( File . Exists ( thumb ) ) File . Delete ( thumb ) ;
File . Move ( tmpFile , thumb ) ;
break ;
case "poster" :
string poster = Path . Combine ( path , user , "show" , show , $"Season {seasonNo}" , "poster.jpg" ) ;
if ( File . Exists ( poster ) ) File . Delete ( poster ) ;
File . Move ( tmpFile , poster ) ;
break ;
}
ScheduleTask ( async ( ) = > {
await GenerateBittorentFileShowAsync ( user , show ) ;
} ) ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Success</h1><a href=\"./edit\"><- Back</a>" ) ) ;
return ;
}
}
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Failed</h1><a href=\"./edit\"><- Back</a>" ) ) ;
}
private async Task UploadShowStreamAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string show = showPathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _show = provider . GetShow ( user , show ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
Directory . CreateDirectory ( Path . Combine ( path , user , "show" , show ) ) ;
var tmpFile = Path . Combine ( path , user , "show" , show , $"tmp{DateTime.Now.ToFileTime().ToString()}.bin" ) ;
foreach ( var item in ctx . ParseBody ( ( n , fn , ct ) = > File . Create ( tmpFile ) ) )
item . Value . Dispose ( ) ;
if ( _show ! = null )
{
if ( ctx . QueryParams . TryGetFirst ( "type" , out var type ) )
{
switch ( type )
{
case "thumbnail" :
string thumb = Path . Combine ( path , user , "show" , show , "thumbnail.jpg" ) ;
if ( File . Exists ( thumb ) ) File . Delete ( thumb ) ;
File . Move ( tmpFile , thumb ) ;
break ;
case "poster" :
string poster = Path . Combine ( path , user , "show" , show , "poster.jpg" ) ;
if ( File . Exists ( poster ) ) File . Delete ( poster ) ;
File . Move ( tmpFile , poster ) ;
break ;
}
ScheduleTask ( async ( ) = > {
await GenerateBittorentFileShowAsync ( user , show ) ;
} ) ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Success</h1><a href=\"./edit\"><- Back</a>" ) ) ;
return ;
}
}
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Failed</h1><a href=\"./edit\"><- Back</a>" ) ) ;
}
private async Task GenerateBittorentFileShowAsync ( string user , string show )
{
Console . WriteLine ( "Here" ) ;
string showDir = Path . Combine ( this . path , user , "show" , show ) ;
Directory . CreateDirectory ( showDir ) ;
string extrasDir = Path . Combine ( showDir , "extras" ) ;
PieceStream strm = new PieceStream ( ) ;
//calculate without extras
List < string [ ] > paths = new List < string [ ] > ( ) ;
for ( int i = 1 ; i < = provider . SeasonCount ( user , show ) ; i + + )
{
var season = provider . GetSeason ( user , show , i ) ;
if ( season ! = null )
{
string season_name = $"Season {i}" ;
if ( ! Directory . Exists ( Path . Combine ( showDir , season_name ) ) ) continue ;
if ( File . Exists ( Path . Combine ( showDir , season_name , "thumbnail.jpg" ) ) )
paths . Add ( new string [ ] { season_name , "thumbnail.jpg" } ) ;
if ( File . Exists ( Path . Combine ( showDir , season_name , "poster.jpg" ) ) )
paths . Add ( new string [ ] { season_name , "poster.jpg" } ) ;
for ( int j = 1 ; j < = provider . EpisodeCount ( user , show , i ) ; j + + )
{
var episode = provider . GetEpisode ( user , show , i , j ) ;
if ( episode ! = null )
{
string name = $"{episode.EpisodeName} S{episode.SeasonNumber.ToString(" D2 ")}E{episode.EpisodeNumber.ToString(" D2 ")}" ;
string video_name = $"{name}.mp4" ;
string subtitles_name = $"{name}-subtitles" ;
string thumbnail = $"{name}-thumbnail.jpg" ;
string poster = $"{name}-poster.jpg" ;
if ( File . Exists ( Path . Combine ( showDir , season_name , poster ) ) )
paths . Add ( new string [ ] { season_name , poster } ) ;
if ( File . Exists ( Path . Combine ( showDir , season_name , thumbnail ) ) )
paths . Add ( new string [ ] { season_name , thumbnail } ) ;
if ( File . Exists ( Path . Combine ( showDir , season_name , video_name ) ) )
paths . Add ( new string [ ] { season_name , video_name } ) ;
if ( Directory . Exists ( Path . Combine ( showDir , season_name , subtitles_name ) ) )
GetExtras ( paths , new string [ ] { season_name , subtitles_name } , Path . Combine ( showDir , season_name , subtitles_name ) ) ;
}
}
}
}
if ( File . Exists ( Path . Combine ( showDir , "thumbnail.jpg" ) ) )
paths . Add ( new string [ ] { "thumbnail.jpg" } ) ;
if ( File . Exists ( Path . Combine ( showDir , "poster.jpg" ) ) )
paths . Add ( new string [ ] { "poster.jpg" } ) ;
List < long > lengths = new List < long > ( ) ;
foreach ( var _path in paths . Select < string [ ] , string > ( e = > Path . Combine ( e ) ) )
using ( var movieStrm = File . OpenRead ( Path . Combine ( showDir , _path ) ) )
{
lengths . Add ( movieStrm . Length ) ;
await movieStrm . CopyToAsync ( strm ) ;
}
Torrent torrent = new Torrent ( ) ;
torrent . AnnounceList . AddRange ( Configuration . BittorrentTrackers ) ;
torrent . CreationDate = DateTime . Now ;
torrent . UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/" ;
torrent . Info . PieceLength = PieceStream . PieceLength ;
torrent . Info . Name = show ;
foreach ( var piece in strm . Pieces )
{
torrent . Info . Pieces . Add ( piece ) ;
}
if ( strm . HasPiece ) torrent . Info . Pieces . Add ( strm . CalculateCurPiece ( ) ) ;
for ( int i = 0 ; i < paths . Count ; i + + )
{
var _path = paths [ i ] ;
var len = lengths [ i ] ;
torrent . Info . Files . Add ( new TorrentInfoFile ( ) { Path = _path , Length = len } ) ;
}
string torrentFile = Path . Combine ( showDir , $"{show}.torrent" ) ;
using ( var file = File . Create ( torrentFile ) )
{
await torrent . ToTorrentFile ( ) . WriteToStreamAsync ( file ) ;
}
if ( Directory . Exists ( extrasDir ) )
{
List < string [ ] > paths2 = new List < string [ ] > ( ) ;
GetExtras ( paths2 , new string [ ] { "extras" } , extrasDir ) ;
foreach ( var _path in paths2 . Select < string [ ] , string > ( e = > Path . Combine ( e ) ) )
using ( var movieStrm = File . OpenRead ( Path . Combine ( showDir , _path ) ) )
{
lengths . Add ( movieStrm . Length ) ;
await movieStrm . CopyToAsync ( strm ) ;
}
paths . AddRange ( paths2 ) ;
torrent = new Torrent ( ) ;
torrent . AnnounceList . AddRange ( Configuration . BittorrentTrackers ) ;
torrent . CreationDate = DateTime . Now ;
torrent . UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/" ;
torrent . Info . PieceLength = PieceStream . PieceLength ;
torrent . Info . Name = show ;
foreach ( var piece in strm . Pieces )
{
torrent . Info . Pieces . Add ( piece ) ;
}
if ( strm . HasPiece ) torrent . Info . Pieces . Add ( strm . CalculateCurPiece ( ) ) ;
for ( int i = 0 ; i < paths . Count ; i + + )
{
var _path = paths [ i ] ;
var len = lengths [ i ] ;
torrent . Info . Files . Add ( new TorrentInfoFile ( ) { Path = _path , Length = len } ) ;
}
torrentFile = Path . Combine ( showDir , $"{show}_withextras.torrent" ) ;
using ( var file = File . Create ( torrentFile ) )
{
await torrent . ToTorrentFile ( ) . WriteToStreamAsync ( file ) ;
}
}
}
2023-12-16 01:40:05 +00:00
2024-01-04 02:53:13 +00:00
private async Task UploadMovieStreamAsync ( ServerContext ctx )
2023-12-16 01:40:05 +00:00
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
Directory . CreateDirectory ( Path . Combine ( path , user , "movie" , movie ) ) ;
var tmpFile = Path . Combine ( path , user , "movie" , movie , $"tmp{DateTime.Now.ToFileTime().ToString()}.bin" ) ;
foreach ( var item in ctx . ParseBody ( ( n , fn , ct ) = > File . Create ( tmpFile ) ) )
item . Value . Dispose ( ) ;
if ( _movie ! = null )
{
if ( ctx . QueryParams . TryGetFirst ( "type" , out var type ) )
{
switch ( type )
{
case "thumbnail" :
2024-01-04 02:53:13 +00:00
string thumb = Path . Combine ( path , user , "movie" , movie , "thumbnail.jpg" ) ;
if ( File . Exists ( thumb ) ) File . Delete ( thumb ) ;
File . Move ( tmpFile , thumb ) ;
2023-12-16 01:40:05 +00:00
break ;
case "poster" :
2024-01-04 02:53:13 +00:00
string poster = Path . Combine ( path , user , "movie" , movie , "poster.jpg" ) ;
if ( File . Exists ( poster ) ) File . Delete ( poster ) ;
File . Move ( tmpFile , poster ) ;
2023-12-16 01:40:05 +00:00
break ;
case "movie" :
2024-01-04 02:53:13 +00:00
string mov = Path . Combine ( path , user , "movie" , movie , $"{movie}.mp4" ) ;
if ( File . Exists ( mov ) ) File . Delete ( mov ) ;
File . Move ( tmpFile , mov ) ;
ScheduleFFmpeg ( $"-y -i \" { mov } \ " {Configuration.BrowserTranscode} \"{Path.Combine(path,user," movie ",movie," browser . mp4 ")}\"" ) ;
2023-12-16 01:40:05 +00:00
break ;
}
ScheduleTask ( async ( ) = > {
await GenerateBittorentFileMovieAsync ( user , movie ) ;
} ) ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Success</h1><a href=\"./edit\"><- Back</a>" ) ) ;
return ;
}
}
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Failed</h1><a href=\"./edit\"><- Back</a>" ) ) ;
}
private void ScheduleFFmpeg ( string command )
{
ScheduleTask ( async ( ) = > {
using ( Process process = new Process ( ) )
{
process . StartInfo . Arguments = command ;
process . StartInfo . FileName = "ffmpeg" ;
if ( process . Start ( ) )
{
await Task . Run ( process . WaitForExit ) ;
}
}
} ) ;
}
public void ScheduleTask ( Func < Task > task )
{
tasks . Enqueue ( task ) ;
}
2024-01-04 02:53:13 +00:00
public async Task EditMoviePagePostAsync ( ServerContext ctx )
2023-12-16 01:40:05 +00:00
{
ctx . ParseBody ( ) ;
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
if ( _movie ! = null )
{
if ( ctx . QueryParams . TryGetFirst ( "proper_name" , out var proper_name ) & & ctx . QueryParams . TryGetFirst ( "description" , out var description ) )
{
_movie . ProperName = proper_name ;
2024-01-04 02:53:13 +00:00
_movie . Description = description . Replace ( "\r" , "" ) ;
2023-12-16 01:40:05 +00:00
provider . UpdateMovie ( _movie ) ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Success</h1><a href=\"./edit\"><- Back</a>" ) ) ;
return ;
}
}
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>Failed</h1><a href=\"./edit\"><- Back</a>" ) ) ;
}
2024-01-04 02:53:13 +00:00
public async Task EditMoviePageAsync ( ServerContext ctx )
2023-12-16 01:40:05 +00:00
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var me = GetAccount ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
if ( me ! = null )
{
if ( _movie ! = null )
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageEditMovieDetails . RenderAsync ( new { Propername = System . Web . HttpUtility . HtmlAttributeEncode ( _movie . ProperName ) , Description = System . Web . HttpUtility . HtmlEncode ( _movie . Description ) } ) ) ) ;
}
else
{
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , "<h1>You are unauthorized to edit this</h1>" ) ) ;
}
}
private async Task PlayMoviePageAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
object value ;
if ( _movie ! = null )
{
2024-01-04 02:53:13 +00:00
var data = GetMovieContentMetaData ( Configuration , user , movie ) ;
List < object > subtitles = new List < object > ( ) ;
foreach ( var subtitle in data . SubtitlesStreams )
{
string languageName = "Unknown" ;
string langCode = "" ;
foreach ( var lang in Languages )
{
if ( lang . LangCode = = subtitle . LanguageCode )
{
languageName = lang . LangTitle ;
langCode = lang . LangCodeVideo ;
}
}
subtitles . Add ( new { langcode = langCode , name = languageName , file = subtitle . VttUrl } ) ;
}
2023-12-16 01:40:05 +00:00
string thumb = data . ThumbnailUrl ;
value = new { username = user , rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
2024-01-04 02:53:13 +00:00
title = Configuration . Title , hasmovie = true , moviethumbnail = thumb , movieurl = movie , moviename = _movie . Name , moviedescription = _movie . Description , movieposter = data . PosterUrl , moviebrowserurl = data . BrowserStream , subtitles
} ;
2023-12-16 01:40:05 +00:00
}
else
{
value = new { username = user , rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
title = Configuration . Title , hasmovie = false } ;
}
await ctx . SendTextAsync ( await pageWatchMovie . RenderAsync ( value ) ) ;
}
private async Task MoviePageAsync ( ServerContext ctx )
{
string user = usersPathValueServer . GetValue ( ctx ) ;
string movie = moviePathValueServer . GetValue ( ctx ) ;
var _movie = provider . GetMovie ( user , movie ) ;
var _user = provider . GetUserAccount ( user ) ;
var me = GetAccount ( ctx ) ;
if ( me ! = null & & me . Username ! = user & & ! me . IsAdmin )
{
me = null ;
}
object value ;
if ( _movie ! = null & & _user ! = null )
{
string movieDir = Path . Combine ( this . path , user , "movie" , movie ) ;
bool torrent = File . Exists ( Path . Combine ( movieDir , $"{movie}.torrent" ) ) ;
bool torrent_wextra = File . Exists ( Path . Combine ( movieDir , $"{movie}_withextras.torrent" ) ) ;
2024-01-04 02:53:13 +00:00
bool extrasexists = Directory . Exists ( Path . Combine ( movieDir , "extras" ) ) | | me ! = null ;
bool moviebrowserexists = File . Exists ( Path . Combine ( movieDir , "browser.mp4" ) ) ;
bool movieexists = File . Exists ( Path . Combine ( movieDir , $"{movie}.mp4" ) ) ;
string thumb = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/thumbnail.jpg" ;
value = new { extrasexists = extrasexists , moviebrowserexists = moviebrowserexists , movieexists = movieexists , downloadurl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/{movie}.mp4" , torrentexists = torrent , torrentwextraexists = torrent_wextra , torrent = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/{movie}.torrent" , torrentwextra = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/{movie}_withextras.torrent" , editable = me ! = null , userproper = HttpUtility . HtmlEncode ( _user . ProperName ) , username = HttpUtility . HtmlEncode ( user ) , rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
title = Configuration . Title , hasmovie = true , moviethumbnail = thumb , movieurl = movie , movieproperattr = HttpUtility . HtmlAttributeEncode ( _movie . ProperName ) , movieproper = HttpUtility . HtmlEncode ( _movie . ProperName ) , moviename = HttpUtility . HtmlEncode ( _movie . Name ) , moviedescription = DescriptLinkUtils ( _movie . Description ? ? "" ) . Replace ( "\n" , "<br>" ) } ;
2023-12-16 01:40:05 +00:00
}
else
{
value = new { username = user , rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
title = Configuration . Title , hasmovie = false } ;
}
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await pageMovie . RenderAsync ( value ) ) ) ;
}
private IServer CreateSwagme ( )
{
SwagmeServer swagmeServer = new SwagmeServer ( ) ;
swagmeServer . AbsoluteUrl = true ;
swagmeServer . Add ( "/MovieFile" , ApiMovieFileAsync , new SwagmeDocumentation ( "Get the movie resource" ) , "GET" , "Movies" ) ;
2024-01-04 02:53:13 +00:00
swagmeServer . Add ( "/GetMovies" , ApiGetMoviesAsync , new SwagmeDocumentation ( "Get a list of movies" , "<b>user</b>: the user of the movie<br><b>type</b>: format of list (defaults to json): pls, wiimc, json, m3u8 or rss" ) , "GET" , "Movies" ) ;
2023-12-16 01:40:05 +00:00
return swagmeServer ;
}
private async Task ApiMovieFileAsync ( ServerContext ctx )
{
if ( ctx . QueryParams . TryGetFirst ( "user" , out var user ) & & ctx . QueryParams . TryGetFirst ( "movie" , out var movie ) )
{
if ( ! ctx . QueryParams . TryGetFirst ( "type" , out var type ) )
type = "download" ;
2024-01-04 02:53:13 +00:00
var info = GetMovieContentMetaData ( Configuration , user , movie ) ;
2023-12-16 01:40:05 +00:00
switch ( type )
{
case "download" :
await ctx . SendRedirectAsync ( info . DownloadStream ) ;
break ;
case "browser" :
await ctx . SendRedirectAsync ( info . BrowserStream ) ;
break ;
case "thumbnail" :
await ctx . SendRedirectAsync ( info . ThumbnailUrl ) ;
break ;
case "poster" :
await ctx . SendRedirectAsync ( info . PosterUrl ) ;
break ;
case "torrent" :
await ctx . SendRedirectAsync ( info . MovieTorrentUrl ) ;
break ;
case "torrent_extra" :
await ctx . SendRedirectAsync ( info . MovieWithExtrasTorrentUrl ) ;
break ;
case "json" :
await ctx . SendJsonAsync ( info ) ;
break ;
}
}
}
private async Task ApiGetMoviesAsync ( ServerContext ctx )
{
UserAccount a ;
if ( ! ctx . QueryParams . TryGetFirst ( "type" , out var type ) ) type = "json" ;
if ( ctx . QueryParams . TryGetFirst ( "user" , out var user ) )
{
a = provider . GetUserAccount ( user ) ;
}
else
{
a = provider . GetFirstUser ( ) ;
2024-01-04 02:53:13 +00:00
user = a . Username ;
2023-12-16 01:40:05 +00:00
}
List < Movie > movies = new List < Movie > ( ) ;
if ( a ! = null )
{
movies . AddRange ( provider . GetMovies ( a . Id ) ) ;
}
if ( type = = "json" )
{
await ctx . SendJsonAsync ( movies ) ;
}
2024-01-04 02:53:13 +00:00
if ( type = = "pls" | | type = = "wiimc" )
2023-12-16 01:40:05 +00:00
{
StringBuilder b = new StringBuilder ( ) ;
b . AppendLine ( "[Playlist]" ) ;
int i = 1 ;
foreach ( var item in movies )
{
2024-01-04 02:53:13 +00:00
if ( type = = "wiimc" )
b . AppendLine ( $"File{i}={Configuration.Root.TrimEnd('/')}/content/{HttpUtility.UrlEncode(user)}/movie/{item.Name}/browser.mp4" ) ;
else
b . AppendLine ( $"File{i}={Configuration.Root.TrimEnd('/')}/api/v1/MovieFile?user={HttpUtility.UrlEncode(user)}&movie={HttpUtility.UrlEncode(item.Name)}&type=download" ) ;
2023-12-16 01:40:05 +00:00
b . AppendLine ( $"Length{i}=0" ) ;
b . AppendLine ( $"Title{i}={IniEscape(item.ProperName)}" ) ;
i + + ;
}
await ctx . SendTextAsync ( b . ToString ( ) , "audio/x-scpls" ) ;
}
if ( type = = "m3u8" )
{
M3uPlaylist playlist = new M3uPlaylist ( ) ;
foreach ( var item in movies )
{
M3uPlaylistEntry entry = new M3uPlaylistEntry ( ) ;
entry . Path = $"{Configuration.Root.TrimEnd('/')}/api/v1/MovieFile?user={HttpUtility.UrlEncode(user)}&movie={HttpUtility.UrlEncode(item.Name)}&type=download" ;
entry . Title = item . ProperName ;
playlist . PlaylistEntries . Add ( entry ) ;
}
M3uContent content = new M3uContent ( ) ;
await ctx . SendTextAsync ( content . ToText ( playlist ) , "application/x-mpegurl" ) ;
}
if ( type = = "rss" )
{
StringWriter sw = new StringWriter ( ) ;
using ( XmlWriter xmlWriter = XmlWriter . Create ( sw , new XmlWriterSettings ( ) { Async = true , Indent = false , OmitXmlDeclaration = true , Encoding = Encoding . UTF8 } ) )
{
var rss = new RssFeedWriter ( xmlWriter ) ;
await rss . WriteTitle ( $"{a.ProperName}'s Movies" ) ;
await rss . WriteGenerator ( "TessesCMS" ) ;
await rss . WriteValue ( "link" , $"{Configuration.Root.TrimEnd('/')}/" ) ;
foreach ( var item in movies )
{
AtomEntry entry = new AtomEntry ( ) ;
entry . Title = item . ProperName ;
entry . Description = $"View <a href=\" { Configuration . Root . TrimEnd ( '/' ) } / user / { user } / movie / { HttpUtility . UrlEncode ( item . Name ) } / \ ">here</a><br>{item.Description}" ;
entry . LastUpdated = item . LastUpdated ;
entry . Published = item . CreationTime ;
await rss . Write ( entry ) ;
}
}
await ctx . SendTextAsync ( sw . GetStringBuilder ( ) . ToString ( ) , "application/rss+xml" ) ;
}
}
private string IniEscape ( string properName )
{
StringBuilder b = new StringBuilder ( ) ;
foreach ( var c in properName )
{
if ( c = = '\\' | | c = = ';' | | c = = '\"' | | c = = '\'' | | c = = '#' | | c = = '=' | | c = = ':' )
{
b . Append ( $"\\{c}" ) ;
}
else if ( c = = '\0' )
{
b . Append ( "\\0" ) ;
}
else if ( c = = '\b' )
{
b . Append ( "\\b" ) ;
}
else if ( c = = '\a' )
{
b . Append ( "\\a" ) ;
}
else if ( c = = '\t' )
{
b . Append ( "\\t" ) ;
}
else if ( c = = '\r' )
{
b . Append ( "\\r" ) ;
} else if ( c = = '\n' )
{
b . Append ( "\\n" ) ;
}
else if ( c > sbyte . MaxValue )
{
b . Append ( $"\\x{((int)c).ToString(" X4 ")}" ) ;
}
else {
b . Append ( c ) ;
}
}
return b . ToString ( ) ;
}
private async Task < string > RenderHtmlAsync ( bool isHome , string body , params CMSNavUrl [ ] urls )
{
List < object > _urls = new List < object > ( ) ;
foreach ( var item in urls )
{
_urls . Add ( new { text = item . Text , url = item . Url , active = item . Active } ) ;
}
foreach ( var item in Configuration . Urls )
{
_urls . Add ( new { text = item . Text , url = item . Url , active = item . Active } ) ;
}
return await pageShell . RenderAsync ( new {
ishome = isHome ,
body = body ,
urls = _urls ,
rooturl = $"{Configuration.Root.TrimEnd('/')}/" ,
title = Configuration . Title
} ) ;
}
private async Task Index ( ServerContext ctx )
{
var account = GetAccount ( ctx ) ;
CMSNavUrl accountLink = account ! = null ? Configuration . RelativeNavUrl ( account . ProperName , "account" ) : Configuration . RelativeNavUrl ( "Login" , "login" ) ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( true , await RenderIndexAsync ( ) , Configuration . RelativeNavUrl ( "Devcenter" , "devcenter" ) , accountLink ) ) ;
}
private async Task Devcenter ( ServerContext ctx )
{
var link = Configuration . RelativeNavUrl ( "Devcenter" , "devcenter" ) ;
link . Active = true ;
await ctx . SendTextAsync ( await RenderHtmlAsync ( false , await RenderDevcenterAsync ( ) , link ) ) ;
}
private async Task < string > RenderDevcenterAsync ( )
{
return await pageDevcenter . RenderAsync ( new { title = Configuration . Title , rooturl = $"{Configuration.Root.TrimEnd('/')}/" } ) ;
}
private async Task < string > RenderIndexAsync ( )
{
return await pageIndex . RenderAsync ( new { title = Configuration . Title , rooturl = $"{Configuration.Root.TrimEnd('/')}/" } ) ;
}
public async Task < string > RenderUpload1Async ( )
{
return await pageUpload . RenderAsync ( new { title = Configuration . Title , rooturl = $"{Configuration.Root.TrimEnd('/')}/" } ) ;
}
public CMSConfiguration Configuration { get ; set ; } = new CMSConfiguration ( ) ;
public IServer Server = > routeServer ;
}
2024-01-04 02:53:13 +00:00
internal class EmailCreator
{
CMSConfiguration configuration ;
Template emailTemplate ;
Template verifyTemplate ;
public EmailCreator ( CMSConfiguration configuration )
{
this . configuration = configuration ;
emailTemplate = Template . Parse ( AssetProvider . ReadAllText ( "/EmailHtml.html" ) ) ;
verifyTemplate = Template . Parse ( AssetProvider . ReadAllText ( "/VerifyEmail.html" ) ) ;
}
private async Task SendEmailAsync ( string email , string emailHtml , string subject )
{
if ( InternetAddress . TryParse ( email , out var to ) & & InternetAddress . TryParse ( this . configuration . Email . Email , out var from ) )
using ( var smtp = new SmtpClient ( ) )
{
await smtp . ConnectAsync ( configuration . Email . Host , configuration . Email . Port , configuration . Email . Encryption ) ;
await smtp . AuthenticateAsync ( configuration . Email . User , configuration . Email . Pass ) ;
MimeMessage message = new MimeMessage ( ) ;
message . From . Add ( from ) ;
message . To . Add ( to ) ;
message . Body = new TextPart ( MimeKit . Text . TextFormat . Html ) { Text = await emailTemplate . RenderAsync ( new { Body = emailHtml , Websitename = HttpUtility . HtmlEncode ( configuration . Title ) , Websiteurl = HttpUtility . HtmlAttributeEncode ( $"{configuration.Root.TrimEnd('/')}/" ) } ) } ;
message . Subject = subject ;
await smtp . SendAsync ( message ) ;
}
}
public async Task SendVerificationEmailAsync ( UserAccount account , string verificationCode )
{
string verifyLink = $"{configuration.Root.TrimEnd('/')}/verify?token={HttpUtility.UrlEncode(verificationCode)}" ;
await SendEmailAsync ( account . Email , await verifyTemplate . RenderAsync ( new { verifyurllink = HttpUtility . HtmlAttributeEncode ( verifyLink ) , verifyurl = HttpUtility . HtmlEncode ( verifyLink ) , Propername = HttpUtility . HtmlEncode ( account . ProperName ) } ) , $"Verify email for {configuration.Title}" ) ;
}
}
2023-12-16 01:40:05 +00:00
}