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; using MailKit.Net.Smtp; using MimeKit; namespace Tesses.CMS { 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 subtitles) { writer.WriteLine("WEBVTT"); for(int i = 0;i subtitles) { for(int i = 0;i0) writer.WriteLine(); writer.WriteLine(i+1); subtitles[i].ToSrt(writer); } } } public class CMSServer { 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 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;} Template pageShell; Template pageIndex; Template pageDevcenter; Template pageShow; Template pageSeason; Template pageShows; Template pageMovie; Template pageMovies; Template pageWatchMovie; Template pageUpload; Template pageUsers; Template pageEditMovieDetails; Template pageEditShowDetails; Template pageEditSeasonDetails; Template pageExtrasViewer; Template pageEditUser; Template aboutUser; Template pageSubtitleLangList; Template pageSubtitleEditor; private Template pageVerifyEmail; private Template pageManage; RouteServer routeServer; PathValueServer usersPathValueServer; PathValueServer moviePathValueServer; PathValueServer showPathValueServer; PathValueServer seasonPathValueServer; MountableServer showMountableServer; MountableServer usersMountableServer; MountableServer seasonMountableServer; RouteServer movieRouteServer; RouteServer showRouteServer; RouteServer seasonRouteServer; IContentProvider provider; string path; public CMSServer(string configpath,IContentProvider provider) { this.provider=provider; usersMountableServer=new MountableServer(); usersPathValueServer = new PathValueServer(usersMountableServer); movieRouteServer=new RouteServer(); showRouteServer = new RouteServer(); seasonRouteServer=new RouteServer(); moviePathValueServer = new PathValueServer(movieRouteServer); showMountableServer=new MountableServer(showRouteServer); showPathValueServer=new PathValueServer(showMountableServer); seasonMountableServer = new MountableServer(seasonRouteServer); seasonPathValueServer=new PathValueServer(seasonMountableServer); string configJson = Path.Combine(configpath,"config.json"); if(File.Exists(configJson)) { Configuration = JsonConvert.DeserializeObject(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" }); } Creator =new EmailCreator(Configuration); 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")); 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")); 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); routeServer.Add("/logout",LogoutAsync); routeServer.Add("/login",LoginPostAsync,"POST"); routeServer.Add("/signup",SignupAsync); routeServer.Add("/signup",SignupPostAsync,"POST"); 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); RegisterUsersPath(); Task.Factory.StartNew(async()=>{ while(Running) { if(tasks.TryDequeue(out var item)) { await item(); } await Task.Delay(TimeSpan.FromSeconds(0.125)); } }).Wait(0); } 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 users = new List(); 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,"

Your account has been verified, click login to login.

Login")); } } if(failed) { await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Failed to verify account

")); } } 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('/')}/"); } 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("

Email is taken

"); } if(nameTaken) { b.AppendLine("

Name is taken

"); } if(properNameTaken) { b.AppendLine("

Proper Name is taken"); } if(passwordNotGoodEnough) { b.AppendLine("

Password not good enough

"); } if(passwordDontMatch) { b.AppendLine("

Passwords don't match

"); } await ctx.SendTextAsync(b.ToString()); } else { provider.CreateUser(Configuration,name,proper_name,email,password); var account=provider.GetUserAccount(name); 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=email}))); } } } } 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; } 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); } while(provider.ContainsSession(cookie)); provider.CreateSession(cookie,a.Id); ctx.ResponseHeaders.Add("Set-Cookie",$"Session={cookie}; Path=/"); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/"); return; } } ctx.StatusCode=401; await ctx.SendTextAsync("

Incorrect

Home | Login"); } } 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; } } } } } private UserAccount GetAccount(ServerContext ctx) { return GetAccount(ctx,out var s); } private UserAccount GetAccount(ServerContext ctx,out string cookie) { cookie=""; 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") { cookie = co[1]; long? account = provider.GetSession(cookie); if(account.HasValue) return provider.GetUserById(account.Value); } } } return null; } private async Task UsersAsync(ServerContext ctx) { List users=new List(); foreach(var user in provider.GetUsers()) { if(!user.IsAdmin && Configuration.Publish == CMSPublish.Admin) { //await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You can't upload content

")); continue; } if(!(user.IsAdmin || user.IsInvited) && Configuration.Publish == CMSPublish.RequireInvite) { //await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You can't upload content

")); continue; } if(!(user.IsAdmin || user.IsInvited || user.IsVerified)) { //await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You can't upload content

")); continue; } 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; } 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,"

You can't upload content

")); return; } if(!(account.IsAdmin || account.IsInvited) && Configuration.Publish == CMSPublish.RequireInvite) { await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You can't upload content

")); return; } if(!(account.IsAdmin || account.IsInvited || account.IsVerified)) { await ctx.SendTextAsync(await RenderHtmlAsync(false,"

You can't upload content

")); return; } name = FixString(name); switch(type) { case "movie": provider.CreateMovie(account.Username,name,proper_name,description.Replace("\r","")); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{account.Username}/movie/{name}/edit"); break; 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 movies=new List(); 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,"

You are unauthorized to edit this

")); } } 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 episodes=new List(); 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","
")}; } 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,"

You are unauthorized to edit this

")); } } 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 seasons=new List(); 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","
")}; } 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 shows=new List(); 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{HttpUtility.HtmlEncode(b2.ToString())}"); } 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","
")}))); } } private async Task UserPageAsync(ServerContext ctx) { await ctx.SendTextAsync(await RenderHtmlAsync(false,await AssetProvider.ReadAllTextAsync("/UserPage.html"))); } ConcurrentQueue> tasks=new ConcurrentQueue>(); 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>(); 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 languages = new List(); 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); 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)}"); } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/movie/{movie}/extras?path={System.Web.HttpUtility.UrlEncode(parent)}"); } } } private async Task ExtrasMoviePageAsync(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(!ctx.QueryParams.TryGetFirst("path",out var __path)) { __path="/"; } __path = __path.TrimStart('/'); __path=SanitizePath(__path); string extrasPath = Path.Combine(path,user,"movie",movie,"extras"); if(__path.Length > 0) { extrasPath=Path.Combine(extrasPath,__path); } List paths=new List(); 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}); } await ctx.SendTextAsync(await RenderHtmlAsync(false,await pageExtrasViewer.RenderAsync(new{Extras=paths,Path=$"/{__path}",Parent=__path,Editable=me != null}))); } private string SanitizePath(string path) { if(path.Length == 0) return ""; List paths=new List(); foreach(var item in path.Replace('\\','/').Split('/')) { if(item == "." || item == "..") continue; paths.Add(item); } return string.Join("/",paths); } private class PieceStream : Stream { public const int PieceLength = 16*1024; public int CurpieceOffset {get;private set;} =0; public List Pieces {get;private set;}=new List(); 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 paths = new List(); if(File.Exists(Path.Combine(movieDir,$"{movie}.mp4"))) paths.Add(new string[]{$"{movie}.mp4"}); if(File.Exists(Path.Combine(movieDir,"thumbnail.jpg"))) paths.Add(new string[]{"thumbnail.jpg"}); if(File.Exists(Path.Combine(movieDir,"poster.jpg"))) paths.Add(new string[]{"poster.jpg"}); if(Directory.Exists(subtitles)) GetExtras(paths,new string[]{"subtitles"},subtitles); List lengths = new List(); foreach(var _path in paths.Select(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 paths2 = new List(); GetExtras(paths2,new string[]{"extras"},extrasDir); foreach(var _path in paths2.Select(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 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); } } 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,"

Success

<- Back")); return; } } } await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Failed

<- Back")); } 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,"

Success

<- Back")); return; } } } await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Failed

<- Back")); } 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 paths = new List(); 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 lengths = new List(); foreach(var _path in paths.Select(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 paths2 = new List(); GetExtras(paths2,new string[]{"extras"},extrasDir); foreach(var _path in paths2.Select(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;iFile.Create(tmpFile))) item.Value.Dispose(); if(_movie != null) { if(ctx.QueryParams.TryGetFirst("type",out var type)) { switch(type) { case "thumbnail": string thumb = Path.Combine(path,user,"movie",movie,"thumbnail.jpg"); if(File.Exists(thumb)) File.Delete(thumb); File.Move(tmpFile,thumb); break; case "poster": string poster = Path.Combine(path,user,"movie",movie,"poster.jpg"); if(File.Exists(poster)) File.Delete(poster); File.Move(tmpFile,poster); break; case "movie": 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")}\""); break; } ScheduleTask(async()=>{ await GenerateBittorentFileMovieAsync(user,movie); }); await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Success

<- Back")); return; } } } await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Failed

<- Back")); } 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) { tasks.Enqueue(task); } public async Task EditMoviePagePostAsync(ServerContext ctx) { 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; _movie.Description = description.Replace("\r",""); provider.UpdateMovie(_movie); await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Success

<- Back")); return; } } } await ctx.SendTextAsync(await RenderHtmlAsync(false,"

Failed

<- Back")); } public async Task EditMoviePageAsync(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(_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,"

You are unauthorized to edit this

")); } } 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) { var data=GetMovieContentMetaData(Configuration,user,movie); List subtitles=new List(); 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}); } string thumb = data.ThumbnailUrl; value = new{ username=user, rooturl=$"{Configuration.Root.TrimEnd('/')}/", title=Configuration.Title,hasmovie=true,moviethumbnail=thumb,movieurl=movie,moviename=_movie.Name,moviedescription=_movie.Description,movieposter = data.PosterUrl, moviebrowserurl=data.BrowserStream,subtitles }; } 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")); 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","
")}; } 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"); swagmeServer.Add("/GetMovies",ApiGetMoviesAsync,new SwagmeDocumentation("Get a list of movies","user: the user of the movie
type: format of list (defaults to json): pls, wiimc, json, m3u8 or rss"),"GET","Movies"); 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"; var info =GetMovieContentMetaData(Configuration,user,movie); 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(); user = a.Username; } List movies=new List(); if(a != null) { movies.AddRange(provider.GetMovies(a.Id)); } if(type=="json") { await ctx.SendJsonAsync(movies); } if(type == "pls" || type == "wiimc") { StringBuilder b=new StringBuilder(); b.AppendLine("[Playlist]"); int i = 1; foreach(var item in movies) { 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"); 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 here
{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 RenderHtmlAsync(bool isHome,string body,params CMSNavUrl[] urls) { List _urls = new List(); 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 RenderDevcenterAsync() { return await pageDevcenter.RenderAsync(new{title=Configuration.Title,rooturl=$"{Configuration.Root.TrimEnd('/')}/"}); } private async Task RenderIndexAsync() { return await pageIndex.RenderAsync(new{title=Configuration.Title,rooturl=$"{Configuration.Root.TrimEnd('/')}/"}); } public async Task RenderUpload1Async() { return await pageUpload.RenderAsync(new{title=Configuration.Title,rooturl=$"{Configuration.Root.TrimEnd('/')}/"}); } public CMSConfiguration Configuration {get;set;}=new CMSConfiguration(); public IServer Server => routeServer; } 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}"); } } }