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; using System.Net.Http; using System.Runtime.Serialization; 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); writer.WriteLine($"{begin.ToString("hh\\:mm\\:ss\\.fff")} --> {end.ToString("hh\\:mm\\:ss\\.fff")}"); 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.Count; i++) { writer.WriteLine(); writer.WriteLine(i + 1); subtitles[i].ToWebVTT(writer); } } public static void ToSrt(TextWriter writer, List subtitles) { for (int i = 0; i < subtitles.Count; i++) { if (i > 0) 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 ShowContentMetaData GetShowContentMetaData(string user, string show) { string showDir = Path.Combine(this.path, user, "show", show); var _show = new ShowContentMetaData() { Info = provider.GetShow(user, show), HasPoster = File.Exists(Path.Combine(path, user, "show", show, "poster.jpg")), HasThumbnail = File.Exists(Path.Combine(path, user, "show", show, "thumbnail.jpg")), HasShowTorrent = File.Exists(Path.Combine(path, user, "show", show, $"{show}.torrent")), HasShowWithExtrasTorrent = File.Exists(Path.Combine(path, user, "show", show, $"{show}_withextras.torrent")), PosterUrl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/poster.jpg", ThumbnailUrl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg", ShowTorrentUrl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/{show}.torrent", ShowWithExtrasTorrentUrl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/{show}_withextras.torrent" }; string extrasPath = Path.Combine(path, user, "show", show, "extras"); if (Directory.Exists(extrasPath)) FindExtras($"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/extras/", extrasPath, _show.ExtraStreams); return _show; } public SeasonContentMetaData GetSeasonContentMetaData(string user, string show, int season) { string showDir = Path.Combine(this.path, user, "show", show); string episodeDir = Path.Combine(showDir, $"Season {season.ToString("D2")}"); var _season0 = new SeasonContentMetaData() { Info = provider.GetSeason(user, show, season), HasPoster = File.Exists(Path.Combine(episodeDir, "poster.jpg")) ? true : File.Exists(Path.Combine(this.path, user, "show", show, "poster.jpg")), HasThumbnail = File.Exists(Path.Combine(episodeDir, "thumbnail.jpg")) ? true : File.Exists(Path.Combine(this.path, user, "show", show, "thumbnail.jpg")), PosterUrl = File.Exists(Path.Combine(episodeDir, "poster.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/poster.jpg" : $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/poster.jpg", ThumbnailUrl = File.Exists(Path.Combine(episodeDir, "thumbnail.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/thumbnail.jpg" : $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg", }; return _season0; } public EpisodeContentMetaData GetEpisodeContentMetaData(string user, string show, int season, int episode) { string episodeDir = Path.Combine(this.path, user, "show", show, $"Season {season.ToString("D2")}"); var _episode = provider.GetEpisode(user, show, season, episode); string name = $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}"; var _episode0 = new EpisodeContentMetaData() { HasPoster = File.Exists(Path.Combine(episodeDir, $"{name}-poster.jpg")) ? true : (File.Exists(Path.Combine(episodeDir, "poster.jpg")) ? true : File.Exists(Path.Combine(this.path, user, "show", show, "poster.jpg"))), HasThumbnail = File.Exists(Path.Combine(episodeDir, $"{name}-thumbnail.jpg")) ? true : (File.Exists(Path.Combine(episodeDir, "thumbnail.jpg")) ? true : File.Exists(Path.Combine(this.path, user, "show", show, "thumbnail.jpg"))), HasBrowserStream = File.Exists(Path.Combine(episodeDir, $"S{season.ToString("D2")}E{episode.ToString("D2")}.mp4")), HasDownloadStream = File.Exists(Path.Combine(episodeDir, $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}.mp4")), PosterUrl = File.Exists(Path.Combine(episodeDir, $"{name}-poster.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/{_episode.EpisodeName}%20S{season.ToString("D2")}E{episode.ToString("D2")}-poster.jpg" : File.Exists(Path.Combine(episodeDir, "poster.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/poster.jpg" : $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/poster.jpg", ThumbnailUrl = File.Exists(Path.Combine(episodeDir, $"{name}-thumbnail.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/{_episode.EpisodeName}%20S{season.ToString("D2")}E{episode.ToString("D2")}-thumbnail.jpg" : File.Exists(Path.Combine(episodeDir, "thumbnail.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/thumbnail.jpg" : $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg", BrowserStream = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/S{season.ToString("D2")}E{episode.ToString("D2")}.mp4", DownloadStream = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/{_episode.EpisodeName}%20S{season.ToString("D2")}E{episode.ToString("D2")}.mp4", Info = provider.GetEpisode(user, show, season, episode) }; GetEpisodeSubtitleStreams(_episode0.SubtitlesStreams, user, show, season, episode, _episode.EpisodeName); return _episode0; } public void GetEpisodeSubtitleStreams(List subtitleStreams, string user, string show, int season, int episode, string episode_name) { string subDir = Path.Combine(path, user, "show", show, $"Season {season.ToString("D2")}", $"{episode_name} S{season.ToString("D2")}E{episode.ToString("D2")}-subtitles"); GetSubtitleStreams(subtitleStreams, subDir, $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/{episode_name}%20S{season.ToString("D2")}E{episode.ToString("D2")}-subtitles", $"{episode_name}%20S{season.ToString("D2")}E{episode.ToString("D2")}"); } public void GetMovieSubtitleStreams(List subtitleStreams, string user, string movie) { string subDir = Path.Combine(path, user, "movie", movie, "subtitles"); GetSubtitleStreams(subtitleStreams, subDir, $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/{movie}/subtitles", movie); } public void GetSubtitleStreams(List subtitleStreams, string subDir, string subUrl, string file) { if (Directory.Exists(subDir)) foreach (var language in Directory.GetDirectories(subDir)) { string languageCode = Path.GetFileName(language); //en-US for english subtitleStreams.Add(new SubtitleStream() { LanguageCode = languageCode, VttUrl = $"{subUrl}/{languageCode}/{file}.vtt", SrtUrl = $"{subUrl}/{languageCode}/{file}.srt" }); } } public AlbumContentMetaData GetAlbumContentMetadata(string user, string album) { var _albumMeta = provider.GetAlbum(user, album); List browserStreams = new List(); List downloadStreams = new List(); if (_albumMeta != null) { int trackNumber = 1; foreach (var track in _albumMeta.Tracks) { string baseurl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}"; string browserurl = $"{baseurl}/{HttpUtility.UrlPathEncode(track)}.mp3"; string downloadurl = $"{baseurl}/{trackNumber.ToString("D2")}%20{HttpUtility.UrlPathEncode(_albumMeta.AlbumArtist)}%20-%20{HttpUtility.UrlPathEncode(track)}.flac"; if (File.Exists(Path.Combine(path, user, "album", album, $"{track}.mp3"))) browserStreams.Add(new Track { Url = browserurl, Name = track, TrackNumber = trackNumber }); if (File.Exists(Path.Combine(path, user, "album", album, $"{trackNumber.ToString("D2")} {_albumMeta.AlbumArtist} - {track}.flac"))) downloadStreams.Add(new Track { Url = downloadurl, Name = track, TrackNumber = trackNumber }); trackNumber++; } } var _album = new AlbumContentMetaData() { HasPoster = File.Exists(Path.Combine(path, user, "album", album, "poster.jpg")), HasThumbnail = File.Exists(Path.Combine(path, user, "album", album, "thumbnail.jpg")), HasAlbumTorrent = File.Exists(Path.Combine(path, user, "album", album, $"{album}.torrent")), HasAlbumWithExtrasTorrent = File.Exists(Path.Combine(path, user, "album", album, $"{album}_withextras.torrent")), PosterUrl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/poster.jpg", ThumbnailUrl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/thumbnail.jpg", AlbumTorrentUrl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/{album}.torrent", AlbumWithExtrasTorrentUrl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/{album}_withextras.torrent", DownloadStreams = downloadStreams, BrowserStreams = browserStreams, Info = _albumMeta }; string extrasPath = Path.Combine(path, user, "album", album, "extras"); if (Directory.Exists(extrasPath)) FindExtras($"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/extras/", extrasPath, _album.ExtraStreams); return _album; } public MovieContentMetaData GetMovieContentMetaData(string user, string movie) { var _movie = new MovieContentMetaData() { HasPoster = File.Exists(Path.Combine(path, user, "movie", movie, "poster.jpg")), HasThumbnail = File.Exists(Path.Combine(path, user, "movie", movie, "thumbnail.jpg")), HasBrowserStream = File.Exists(Path.Combine(path, user, "movie", movie, "browser.mp4")), HasDownloadStream = File.Exists(Path.Combine(path, user, "movie", movie, $"{movie}.mp4")), HasMovieTorrent = File.Exists(Path.Combine(path, user, "movie", movie, $"{movie}.torrent")), HasMovieWithExtrasTorrent = File.Exists(Path.Combine(path, user, "movie", movie, $"{movie}_withextras.torrent")), 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" }; GetMovieSubtitleStreams(_movie.SubtitlesStreams, user, movie); 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 pageEpisode; Template pageShows; Template pageMovie; Template pageAlbum; Template pageMusicPlayer; private Template pageAlbums; Template pageMovies; Template pageWatchMovie; Template pageWatchEpisode; Template pageUpload; Template pageUsers; Template pageEditMovieDetails; Template pageEditShowDetails; Template pageEditSeasonDetails; Template pageEditEpisodeDetails; Template pageEditAlbumDetails; Template pageExtrasViewer; Template pageEditUser; private Template pageTracklist; Template aboutUser; Template pageSubtitleLangList; Template pageSubtitleEditor; Template pageMailingList; Template pageVerifyEmail; Template pageManage; Template pageEmailMovie; Template pageWebhook; private Template siteWebMan; RouteServer routeServer; PathValueServer usersPathValueServer; PathValueServer moviePathValueServer; PathValueServer albumPathValueServer; PathValueServer showPathValueServer; PathValueServer seasonPathValueServer; PathValueServer episodePathValueServer; MountableServer showMountableServer; MountableServer usersMountableServer; MountableServer seasonMountableServer; RouteServer movieRouteServer; RouteServer showRouteServer; RouteServer seasonRouteServer; RouteServer episodeRouteServer; private RouteServer albumRouteServer; IContentProvider provider; string path; private event Action SendEvents; 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(); episodeRouteServer = new RouteServer(); albumRouteServer = new RouteServer(); moviePathValueServer = new PathValueServer(movieRouteServer); showMountableServer = new MountableServer(showRouteServer); showPathValueServer = new PathValueServer(showMountableServer); albumPathValueServer = new PathValueServer(albumRouteServer); seasonMountableServer = new MountableServer(seasonRouteServer); seasonPathValueServer = new PathValueServer(seasonMountableServer); episodePathValueServer = new PathValueServer(episodeRouteServer); 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")); pageAlbum = Template.Parse(AssetProvider.ReadAllText("/AlbumPage.html")); pageMusicPlayer = Template.Parse(AssetProvider.ReadAllText("/MusicPlayerPage.html")); pageAlbums = Template.Parse(AssetProvider.ReadAllText("/AlbumsPage.html")); pageMovies = Template.Parse(AssetProvider.ReadAllText("/MoviesPage.html")); pageUsers = Template.Parse(AssetProvider.ReadAllText("/UsersPage.html")); pageWatchMovie = Template.Parse(AssetProvider.ReadAllText("/WatchMovie.html")); pageWatchEpisode = Template.Parse(AssetProvider.ReadAllText("/WatchEpisode.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")); pageEditEpisodeDetails = Template.Parse(AssetProvider.ReadAllText("/EditEpisodeDetails.html")); pageEditAlbumDetails = Template.Parse(AssetProvider.ReadAllText("/EditAlbumDetails.html")); pageExtrasViewer = Template.Parse(AssetProvider.ReadAllText("/ExtrasViewer.html")); pageEditUser = Template.Parse(AssetProvider.ReadAllText("/AccountEditor.html")); pageTracklist = Template.Parse(AssetProvider.ReadAllText("/Tracklist.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")); pageEpisode = Template.Parse(AssetProvider.ReadAllText("/EpisodePage.html")); pageShows = Template.Parse(AssetProvider.ReadAllText("/ShowsPage.html")); pageMailingList = Template.Parse(AssetProvider.ReadAllText("/MailingList.html")); pageEmailMovie = Template.Parse(AssetProvider.ReadAllText("/EmailMovie.html")); pageWebhook = Template.Parse(AssetProvider.ReadAllText("/Webhook.html")); siteWebMan = Template.Parse("/site.webmanifest"); 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("/site.webmanifest", SiteWebManifestAsync, "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 () => { Stopwatch watch = new Stopwatch(); watch.Start(); while (Running) { if (tasks.TryDequeue(out var item)) { await item(); } await Task.Delay(TimeSpan.FromSeconds(0.125)); if (watch.Elapsed.TotalMinutes > 10) { ClearExpiredCSRF(); watch.Restart(); } } }).Wait(0); } private void ClearExpiredCSRF() { lock (CsrfTokens) { List csrfs = new List(); var dtNow = DateTime.Now; foreach (var item in CsrfTokens) { if (item.Expires < dtNow) csrfs.Add(item); } foreach (var item in csrfs) { CsrfTokens.Remove(item); } } } private async Task SiteWebManifestAsync(ServerContext ctx) { await ctx.SendTextAsync(await siteWebMan.RenderAsync(new { rooturl = $"{Configuration.Root.TrimEnd('/')}/", title = Configuration.Title }), "application/manifest+json"); } public async Task ManagePostAsync(ServerContext ctx) { ctx.ParseBody(); var account = GetAccount(ctx, true); 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, true); 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, out var cookie, false); List users = new List(); if (account != null && account.IsAdmin) { int i = 0; foreach (var user in provider.GetUsers()) { if (account.Id != user.Id) { string csrf = ""; string csrf2 = ""; if(account != null) { csrf=HttpUtility.UrlEncode(CreateCSRF(account.Id, cookie)); csrf2=HttpUtility.HtmlAttributeEncode(CreateCSRF(account.Id, cookie)); } string impersonate = $"{Configuration.Root.TrimEnd('/')}/impersonate?user={user.Username}&csrf={csrf}"; users.Add(new { csrf = csrf2, 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(ctx, 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(ctx, 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(ctx, "

Your account has been verified, click login to login.

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

Failed to verify account

")); } } private async Task AccountPostAsync(ServerContext ctx) { ctx.ParseBody(); var account = GetAccount(ctx,true); 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(ctx, 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(ctx, await AssetProvider.ReadAllTextAsync("/LoginPage.html"), false, false, true)); } public async Task SignupAsync(ServerContext ctx) { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await AssetProvider.ReadAllTextAsync("/SignupPage.html"), false, false, true)); } 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(ctx, 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; } } } } } HttpClient client = new HttpClient(); private async Task SendEvent(EventType type, string userpropername, string username, string name, string propername, string description, string body) { CMSEvent evt = new CMSEvent() { Type = type, Username = username, Name = name, ProperName = propername, UserProperName = userpropername, Description = description, Body = body }; try { SendEvents?.Invoke(evt); } catch (Exception ex) { _ = ex; } foreach (var user in provider.GetUsers()) { foreach (var wh in user.Webhooks) { if (wh.Username != username) continue; if ((type == EventType.MovieCreate || type == EventType.MovieUpdate) && wh.EnabledMovies) { switch (wh.Type) { case WebHookType.Ntfy: //ntfy is the easiest { string con = $"Movie {propername} by {userpropername} was {(type == EventType.MovieCreate ? "created" : "updated")}"; try { await SendNtfyAsync($"{Configuration.Root.TrimEnd('/')}/user/{username}/movie/{name}/", wh.Url, wh.Key, wh.Priority, con, body); //(await client.PostAsync(wh.Url,new StringContent(con))).Dispose(); } catch (Exception ex) { _ = ex; } } break; case WebHookType.Gotify: { string con = $"Movie {propername} by {userpropername} was {(type == EventType.MovieCreate ? "created" : "updated")}"; await SendGotifyAsync(wh.Url, wh.Key, wh.Priority, con, body); } break; case WebHookType.Other: (await client.PostAsync(wh.Url, new StringContent(JsonConvert.SerializeObject(new { key = wh.Key, data = evt }), Encoding.UTF8, "application/json"))).Dispose(); break; } } if ((type == EventType.ShowCreate || type == EventType.ShowUpdate) && wh.EnabledShows) { switch (wh.Type) { case WebHookType.Ntfy: //ntfy is the easiest { string con = $"Show {propername} by {userpropername} was {(type == EventType.ShowCreate ? "created" : "updated")}"; try { await SendNtfyAsync($"{Configuration.Root.TrimEnd('/')}/user/{username}/show/{name}/", wh.Url, wh.Key, wh.Priority, con, body); } catch (Exception ex) { _ = ex; } } break; case WebHookType.Gotify: { string con = $"Show {propername} by {userpropername} was {(type == EventType.ShowCreate ? "created" : "updated")}"; await SendGotifyAsync(wh.Url, wh.Key, wh.Priority, con, body); } break; case WebHookType.Other: (await client.PostAsync(wh.Url, new StringContent(JsonConvert.SerializeObject(new { key = wh.Key, data = evt }), Encoding.UTF8, "application/json"))).Dispose(); break; } } if ((type == EventType.AlbumCreate || type == EventType.AlbumUpdate) && wh.EnabledShows) { switch (wh.Type) { case WebHookType.Ntfy: //ntfy is the easiest { string con = $"Album {propername} by {userpropername} was {(type == EventType.AlbumCreate ? "created" : "updated")}"; try { await SendNtfyAsync($"{Configuration.Root.TrimEnd('/')}/user/{username}/album/{name}/", wh.Url, wh.Key, wh.Priority, con, body); } catch (Exception ex) { _ = ex; } } break; case WebHookType.Gotify: { string con = $"Album {propername} by {userpropername} was {(type == EventType.AlbumCreate ? "created" : "updated")}"; await SendGotifyAsync(wh.Url, wh.Key, wh.Priority, con, body); } break; case WebHookType.Other: (await client.PostAsync(wh.Url, new StringContent(JsonConvert.SerializeObject(new { key = wh.Key, data = evt }), Encoding.UTF8, "application/json"))).Dispose(); break; } } } } } private async Task SendNtfyAsync(string cmsurl, string url, string key, int priority, string con, string body) { try { using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url)) { request.Headers.Add("Title", con); request.Headers.Add("Click", cmsurl); request.Headers.Add("Priority", priority.ToString()); if (!string.IsNullOrWhiteSpace(key)) { request.Headers.Add("Authorization", $"Bearer {key}"); } request.Content = new StringContent(body); (await client.SendAsync(request)).Dispose(); } } catch (Exception ex) { _ = ex; } } private async Task SendGotifyAsync(string url, string key, int prio, string con, string body) { try { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, $"{url.TrimEnd('/')}/message"); request.Headers.Add("X-Gotify-Key", key); request.Content = new StringContent(JsonConvert.SerializeObject(new { message = body, title = con, priority = prio }), Encoding.UTF8, "application/json"); (await client.SendAsync(request)).Dispose(); } catch (Exception ex) { _ = ex; } } private string CreateCSRF(long account, string cookie) { lock (CsrfTokens) { CSRF csrf = new CSRF(account, cookie); CsrfTokens.Add(csrf); return csrf.CSRFToken; } } List CsrfTokens { get; set; } = new List(); private UserAccount GetAccount(ServerContext ctx, bool requiresCSRF = false) { return GetAccount(ctx, out var s, requiresCSRF); } bool IsValidCSRFAndDestroy(long account, string cookie, string csrf) { lock (CsrfTokens) { var now = DateTime.Now; foreach (var token in CsrfTokens) { if (token.Expires > now && cookie == token.Cookie && csrf == token.CSRFToken && account == token.UserId) { CsrfTokens.Remove(token); return true; } } return false; } } private UserAccount GetAccount(ServerContext ctx, out string cookie, bool requiresCSRF = false) { 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) { if (requiresCSRF) { if (ctx.QueryParams.TryGetFirst("csrf", out var csrf)) { if (IsValidCSRFAndDestroy(account.Value, cookie, csrf)) { return provider.GetUserById(account.Value); } } throw new InvalidCSRFException(); } return provider.GetUserById(account.Value); } } } } else if (ctx.RequestHeaders.TryGetFirst("Authentication", out var auth)) { var co = auth.Split(new char[] { ' ' }, 2); if (co.Length == 2 && co[0] == "Bearer") { long? account = provider.GetSession(co[1]); 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(ctx,"

You can't upload content

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

You can't upload content

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

You can't upload content

")); continue; } users.Add(user.Scriban()); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageUsers.RenderAsync(new { Users = users, rooturl = $"{Configuration.Root.TrimEnd('/')}/" }))); } const string badC = "/\\\"&,?|:;*@!# "; const string badCKeepSpace = "/\\\"&,?|:;*@!#"; private string FixStringKeepSpace(string str) { StringBuilder b = new StringBuilder(); foreach (var item in str) { if (char.IsControl(item)) { continue; } if (item >= 127) continue; if (badCKeepSpace.Contains(item.ToString())) continue; b.Append(item.ToString()); } return b.ToString(); } 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, true); if (account != null) { if (!account.IsAdmin && Configuration.Publish == CMSPublish.Admin) { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

You can't upload content

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

You can't upload content

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

You can't upload content

")); return; } name = FixString(name); switch (type) { case "movie": var movie = provider.CreateMovie(account.Username, name, proper_name, description.Replace("\r", "")); ScheduleTask(async () => { await this.Creator.EmailMovieAsync(provider, account, movie, false); await SendEvent(EventType.MovieCreate, account.ProperName, account.Username, movie.Name, movie.ProperName, movie.Description, ""); }); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{account.Username}/movie/{name}/edit"); break; case "show": var show = provider.CreateShow(account.Username, name, proper_name, description); ScheduleTask(async () => { await this.Creator.EmailShowAsync(provider, account, show, false); await SendEvent(EventType.ShowCreate, account.ProperName, account.Username, show.Name, show.ProperName, show.Description, ""); }); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{account.Username}/show/{name}/edit"); break; case "album": var album = provider.CreateAlbum(account.Username, name, proper_name, description); ScheduleTask(async () => { await this.Creator.EmailAlbumAsync(provider, account, album, false); await SendEvent(EventType.AlbumCreate, account.ProperName, account.Username, album.Name, album.ProperName, album.Description, ""); }); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{account.Username}/album/{name}/edit"); break; } } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/login"); } } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/"); } } public async Task UploadPage1(ServerContext ctx) { var account = GetAccount(ctx, out var cookie); if (account != null) { var csrf = HttpUtility.HtmlAttributeEncode(CreateCSRF(account.Id, cookie)); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await RenderUpload1Async(csrf), false, false, false, true)); } } 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(user, item.Name); movies.Add(item.Scriban(data.ThumbnailUrl)); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageMovies.RenderAsync(new { Movies = movies }))); } private void RegisterUsersPath() { RouteServer routeServer = new RouteServer(); routeServer.Add("/", UserPageAsync); routeServer.Add("/about", AboutAsync); routeServer.Add("/mailinglist", MailingListAsync); routeServer.Add("/mailinglist", MailingListPostAsync, "POST"); routeServer.Add("/webhooks", Webhooks); routeServer.Add("/update_webhook", UpdateWebhook, "POST"); routeServer.Add("/create_webhook", CreateWebhook, "POST"); usersMountableServer.Mount("/", routeServer); usersMountableServer.Mount("/movies/", Movies()); usersMountableServer.Mount("/movie/", moviePathValueServer); usersMountableServer.Mount("/shows/", Shows()); usersMountableServer.Mount("/show/", showPathValueServer); usersMountableServer.Mount("/albums/", Albums()); usersMountableServer.Mount("/album/", albumPathValueServer); RegisterMoviePath(); RegisterShowPath(); RegisterAlbumPath(); } private async Task CreateWebhook(ServerContext ctx) { ctx.ParseBody(); string username = usersPathValueServer.GetValue(ctx); var theAccount = provider.GetUserAccount(username); var account = GetAccount(ctx,true); if (account != null) { if (!ctx.QueryParams.TryGetFirst("key", out var key)) key = ""; if (!ctx.QueryParams.TryGetFirstInt32("priority", out var priority)) priority = 2; bool enablemovies = ctx.QueryParams.GetFirstBoolean("enablemovies"); bool enableshows = ctx.QueryParams.GetFirstBoolean("enableshows"); bool enablealbums = ctx.QueryParams.GetFirstBoolean("enablealbums"); bool enablesoftware = ctx.QueryParams.GetFirstBoolean("enablesoftware"); bool enableother = ctx.QueryParams.GetFirstBoolean("enableother"); if (ctx.QueryParams.TryGetFirst("name", out var name) && ctx.QueryParams.TryGetFirst("url", out var url) && ctx.QueryParams.TryGetFirst("type", out var type)) { WebHook hook = new WebHook { Username = username, Priority = priority, Url = url, WebhookName = name, Type = type == "ntfy" ? WebHookType.Ntfy : (type == "gotify" ? WebHookType.Gotify : WebHookType.Other), Key = key, EnabledMovies = enablemovies, EnabledShows = enableshows, EnabledAlbums = enablealbums, EnabledSoftware = enablesoftware, EnabledOther = enableother }; account.Webhooks.Add(hook); provider.UpdateUser(account); } await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{username}/webhooks"); } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/login"); } } private async Task UpdateWebhook(ServerContext ctx) { ctx.ParseBody(); string username = usersPathValueServer.GetValue(ctx); var theAccount = provider.GetUserAccount(username); var account = GetAccount(ctx,true); if (account != null) { if (!ctx.QueryParams.TryGetFirst("key", out var key)) key = ""; if (!ctx.QueryParams.TryGetFirstInt32("priority", out var priority)) priority = 2; bool enablemovies = ctx.QueryParams.GetFirstBoolean("enablemovies"); bool enableshows = ctx.QueryParams.GetFirstBoolean("enableshows"); bool enablealbums = ctx.QueryParams.GetFirstBoolean("enablealbums"); bool enablesoftware = ctx.QueryParams.GetFirstBoolean("enablesoftware"); bool enableother = ctx.QueryParams.GetFirstBoolean("enableother"); if (ctx.QueryParams.TryGetFirst("name", out var name) && ctx.QueryParams.TryGetFirst("url", out var url)) { foreach (var item in account.Webhooks) { if (item.WebhookName == name && item.Username == username) { if (ctx.QueryParams.TryGetFirst("action", out var val) && val == "Delete") { account.Webhooks.Remove(item); provider.UpdateUser(account); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{username}/webhooks"); return; } item.EnabledMovies = enablemovies; item.EnabledShows = enableshows; item.EnabledAlbums = enablealbums; item.EnabledSoftware = enablesoftware; item.EnabledOther = enableother; item.Key = key; item.Priority = priority; item.Url = url; provider.UpdateUser(account); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{username}/webhooks"); return; } } await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{username}/webhooks"); } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/login"); } } } private async Task Webhooks(ServerContext ctx) { string username = usersPathValueServer.GetValue(ctx); var theAccount = provider.GetUserAccount(username); var account = GetAccount(ctx); if (account != null) { List webhooks = new List(); foreach (var item in account.Webhooks) { if (item.Username != username) continue; webhooks.Add(new { name = item.WebhookName, ntfy = item.Type == WebHookType.Ntfy, gotify = item.Type == WebHookType.Gotify, priority = item.Priority, url = item.Url, key = item.Key, enablemovies = item.EnabledMovies, enableshows = item.EnabledShows, enablealbums = item.EnabledAlbums, enablesoftware = item.EnabledSoftware, enableother = item.EnabledOther }); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageWebhook.RenderAsync(new { webhooks }))); } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/login"); } } private async Task MailingListPostAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); var theAccount = provider.GetUserAccount(user); var account = GetAccount(ctx,true); if (account != null) { ctx.ParseBody(); MailEntry entry = null; foreach (var item in theAccount.AccountsToMail) { if (item.UserId == account.Id) { entry = item; break; } } if (entry == null) { entry = new MailEntry() { UserId = account.Id }; theAccount.AccountsToMail.Add(entry); } entry.EnableMovies = ctx.QueryParams.ContainsKey("enablemovies"); entry.EnableShows = ctx.QueryParams.ContainsKey("enableshows"); entry.EnableSingles = ctx.QueryParams.ContainsKey("enablesingles"); entry.EnableAlbums = ctx.QueryParams.ContainsKey("enablealbums"); entry.EnableSoftware = ctx.QueryParams.ContainsKey("enablesoftware"); entry.EnableOther = ctx.QueryParams.ContainsKey("enableother"); entry.EnableUpdates = ctx.QueryParams.ContainsKey("enableupdates"); provider.UpdateUser(theAccount); } await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/"); } private async Task MailingListAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); var theAccount = provider.GetUserAccount(user); var account = GetAccount(ctx); if (account != null) { MailEntry entry = new MailEntry() { UserId = account.Id }; foreach (var item in theAccount.AccountsToMail) { if (item.UserId == account.Id) { entry = item; break; } } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageMailingList.RenderAsync(new { enablemovies = entry.EnableMovies, enableshows = entry.EnableShows, enablesingles = entry.EnableSingles, enablealbums = entry.EnableAlbums, enablesoftware = entry.EnableSoftware, enableother = entry.EnableOther, enableupdates = entry.EnableUpdates }))); return; } await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/login"); } private async Task EditAlbumPagePostAsync(ServerContext ctx) { ctx.ParseBody(); string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); var _album = provider.GetAlbum(user, album); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { if (_album != null) { if (ctx.QueryParams.TryGetFirst("proper_name", out var proper_name) && ctx.QueryParams.TryGetFirst("description", out var description) && ctx.QueryParams.TryGetFirst("album_artist", out var album_artst) && ctx.QueryParams.TryGetFirst("year", out var yearS) && int.TryParse(yearS, out var year)) { _album.ProperName = proper_name; _album.Description = description.Replace("\r", ""); _album.AlbumArtist = FixStringKeepSpace(album_artst); _album.Year = year; provider.UpdateAlbum(_album); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

Success

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

Failed

<- Back")); } private void RegisterAlbumPath() { albumRouteServer.Add("/", AlbumPageAsync); albumRouteServer.Add("/play", PlayAlbumAsync); albumRouteServer.Add("/edit", EditAlbumPageAsync); albumRouteServer.Add("/edit", EditAlbumPagePostAsync, "POST"); albumRouteServer.Add("/upload", UploadAlbumStreamAsync, "POST"); albumRouteServer.Add("/sendupdate", SendAlbumUpdateAsync, "POST"); albumRouteServer.Add("/edit_tracklist", EditTracklistAsync); albumRouteServer.Add("/edit_tracklist", EditTracklistPostAsync, "POST"); albumRouteServer.Add("/upload_extra", UploadAlbumExtraAsync, "POST"); albumRouteServer.Add("/extras", ExtrasAlbumPageAsync); albumRouteServer.Add("/mkdir", ExtrasAlbumMkdirAsync, "POST"); } private async Task EditTracklistPostAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var _album = provider.GetAlbum(user, album); var _user = provider.GetUserAccount(user); var me = GetAccount(ctx,true); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (_album != null && me != null && _user != null) { Directory.CreateDirectory(Path.Combine(path, user, "album", album)); var tmpFile = Path.Combine(path, user, "album", album, $"tmp{DateTime.Now.ToFileTime().ToString()}.bin"); foreach (var item in ctx.ParseBody((n, fn, ct) => File.Create(tmpFile))) item.Value.Dispose(); if (!ctx.QueryParams.TryGetFirst("operation", out var operation)) operation = ""; if (!ctx.QueryParams.TryGetFirstInt32("track_id", out var track_id)) track_id = 0; switch (operation) { case "Yes": { string oldtrack = _album.Tracks[track_id]; string flac = Path.Combine(path, user, "album", album, $"{(track_id + 1).ToString("D2")} {_album.AlbumArtist} - {oldtrack}.flac"); if (File.Exists(flac)) File.Delete(flac); string oldmp3 = Path.Combine(path, user, "album", album, $"{oldtrack}.mp3"); if (File.Exists(oldmp3)) File.Delete(oldmp3); _album.Tracks.RemoveAt(track_id); for (int i = track_id; i < _album.Tracks.Count; i++) { string oldflac = Path.Combine(path, user, "album", album, $"{(track_id + 2).ToString("D2")} {_album.AlbumArtist} - {oldtrack}.flac"); string newflac = Path.Combine(path, user, "album", album, $"{(track_id + 1).ToString("D2")} {_album.AlbumArtist} - {oldtrack}.flac"); if (File.Exists(oldflac)) File.Move(oldflac, newflac); } provider.UpdateAlbum(_album); } break; case "Delete": { string oldtrack = _album.Tracks[track_id]; string text = $"

Delete Track: {oldtrack}?

Note the media files will be removed
No
"; await ctx.SendTextAsync(await RenderHtmlAsync(ctx, text)); return; } case "Upload": { string oldtrack = _album.Tracks[track_id]; string flac = Path.Combine(path, user, "album", album, $"{(track_id + 1).ToString("D2")} {_album.AlbumArtist} - {oldtrack}.flac"); File.Move(tmpFile, flac); ScheduleTask(async () => { await GenerateBittorentFileAlbumAsync(user, album); }); string oldmp3 = Path.Combine(path, user, "album", album, $"{oldtrack}.mp3"); ScheduleFFmpeg($"-y -i \"{flac}\" {Configuration.BrowserTranscodeMp3} \"{oldmp3}\""); } break; case "Rename": { if (track_id < _album.Tracks.Count) { if (!ctx.QueryParams.TryGetFirst("track_name", out var track_name)) track_name = $"Track {track_id.ToString("D2")}"; track_name = FixStringKeepSpace(track_name); string oldtrack = _album.Tracks[track_id]; string oldflac = Path.Combine(path, user, "album", album, $"{(track_id + 1).ToString("D2")} {_album.AlbumArtist} - {oldtrack}.flac"); string oldmp3 = Path.Combine(path, user, "album", album, $"{oldtrack}.mp3"); string newflac = Path.Combine(path, user, "album", album, $"{(track_id + 1).ToString("D2")} {_album.AlbumArtist} - {track_name}.flac"); string newmp3 = Path.Combine(path, user, "album", album, $"{track_name}.mp3"); if (oldflac != newflac && File.Exists(oldflac)) File.Move(oldflac, newflac); if (oldmp3 != newmp3 && File.Exists(oldmp3)) File.Move(oldmp3, newmp3); _album.Tracks[track_id] = track_name; provider.UpdateAlbum(_album); } } break; case "Add": { if (!ctx.QueryParams.TryGetFirst("track_name", out var track_name)) track_name = $"Track {track_id.ToString("D2")}"; _album.Tracks.Add(FixStringKeepSpace(track_name)); provider.UpdateAlbum(_album); } break; } if (File.Exists(tmpFile)) File.Delete(tmpFile); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/album/{album}/edit_tracklist"); } } private async Task EditTracklistAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var _album = provider.GetAlbum(user, album); var _user = provider.GetUserAccount(user); var me = GetAccount(ctx,out var cookie); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (_album != null && me != null && _user != null) { List tracks = new List(); string csrf=""; if(me != null) csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); for (int i = 0; i < _album.Tracks.Count; i++) { tracks.Add(new { index = i, name = HttpUtility.HtmlAttributeEncode(_album.Tracks[i]) }); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageTracklist.RenderAsync(new { tracks,csrf }))); } } private async Task PlayAlbumAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var _album = provider.GetAlbum(user, album); var _user = provider.GetUserAccount(user); var me = GetAccount(ctx); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (_album != null && _user != null) { List list = new List(); int i = 1; foreach (var item in _album.Tracks) { list.Add(new { artist = _album.AlbumArtist, album = _album.ProperName, name = item, art = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/thumbnail.jpg", url = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/{HttpUtility.UrlPathEncode(item)}.mp3", download = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/{i.ToString("D2")}%20{HttpUtility.UrlPathEncode(_album.AlbumArtist)}%20-%20{HttpUtility.UrlPathEncode(item)}.flac" }); i++; } object v = new { rooturl = $"{Configuration.Root.TrimEnd('/')}/", list = JsonConvert.SerializeObject(list) }; await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageMusicPlayer.RenderAsync(v))); } } private async Task AlbumPageAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var _album = provider.GetAlbum(user, album); var _user = provider.GetUserAccount(user); var me = GetAccount(ctx,out var cookie); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } object value; if (_album != null && _user != null) { string albumDir = Path.Combine(this.path, user, "album", album); bool torrent = File.Exists(Path.Combine(albumDir, $"{album}.torrent")); bool torrent_wextra = File.Exists(Path.Combine(albumDir, $"{album}_withextras.torrent")); bool extrasexists = Directory.Exists(Path.Combine(albumDir, "extras")) || me != null; string thumb = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/thumbnail.jpg"; string csrf=""; if(me != null) csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); value = new { csrf, extrasexists, torrentexists = torrent, torrentwextraexists = torrent_wextra, torrent = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/{album}.torrent", torrentwextra = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{album}/{album}_withextras.torrent", editable = me != null, userproper = HttpUtility.HtmlEncode(_user.ProperName), username = HttpUtility.HtmlEncode(user), rooturl = $"{Configuration.Root.TrimEnd('/')}/", title = Configuration.Title, hasalbum = true, albumthumbnail = thumb, albumproper = HttpUtility.HtmlEncode(_album.ProperName), albumname = HttpUtility.HtmlEncode(_album.Name), albumdescription = DescriptLinkUtils(_album.Description ?? "").Replace("\n", "
") }; } else { value = new { username = user, rooturl = $"{Configuration.Root.TrimEnd('/')}/", title = Configuration.Title, hasalbum = false }; } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageAlbum.RenderAsync(value))); } 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"); showRouteServer.Add("/upload_extra", UploadShowExtraAsync, "POST"); showRouteServer.Add("/extras", ExtrasShowPageAsync); showRouteServer.Add("/mkdir", ExtrasShowMkdirAsync, "POST"); showRouteServer.Add("/sendupdate", SendShowUpdateAsync, "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"); seasonMountableServer.Mount("/episode/", episodePathValueServer); RegisterEpisodePath(); } private void RegisterEpisodePath() { episodeRouteServer.Add("/", EpisodePageAsync); episodeRouteServer.Add("/edit", EditEpisodePageAsync); episodeRouteServer.Add("/edit", EditEpisodePagePostAsync, "POST"); episodeRouteServer.Add("/upload", UploadEpisodeStreamAsync, "POST"); episodeRouteServer.Add("/subtitles", SubtitlesEpisodeAsync); episodeRouteServer.Add("/subtitles", SubtitlesEpisodePostAsync, "POST"); episodeRouteServer.Add("/play", PlayEpisodePageAsync); } private async Task EpisodePageAsync(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); string seasonS = seasonPathValueServer.GetValue(ctx); string episodeS = episodePathValueServer.GetValue(ctx); if (!int.TryParse(seasonS, out var season)) season = 1; if (!int.TryParse(episodeS, out var episode)) episode = 1; var _season = provider.GetSeason(user, show, season); var _episode = provider.GetEpisode(user, show, season, episode); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } object value; if (_show != null && _user != null) { string episodeDir = Path.Combine(this.path, user, "show", show, $"Season {season.ToString("D2")}"); bool episodebrowserexists = File.Exists(Path.Combine(episodeDir, $"S{season.ToString("D2")}E{episode.ToString("D2")}.mp4")); string name = $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}"; bool episodeexists = File.Exists(Path.Combine(episodeDir, $"{name}.mp4")); string thumb = File.Exists(Path.Combine(episodeDir, $"{name}-thumbnail.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/{_episode.EpisodeName}%20S{season.ToString("D2")}E{episode.ToString("D2")}-thumbnail.jpg" : File.Exists(Path.Combine(episodeDir, "thumbnail.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/thumbnail.jpg" : $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg"; value = new { episodebrowserexists, episodeexists, downloadurl = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/{_episode.EpisodeName}%20S{season.ToString("D2")}E{episode.ToString("D2")}.mp4", editable = me != null, userproper = HttpUtility.HtmlEncode(_user.ProperName), username = HttpUtility.HtmlEncode(user), rooturl = $"{Configuration.Root.TrimEnd('/')}/", title = Configuration.Title, hasmovie = true, episodethumbnail = thumb, seasonproper = HttpUtility.HtmlEncode(_season.ProperName), showpropername = HttpUtility.HtmlEncode(_show.ProperName), episodeproperattr = HttpUtility.HtmlAttributeEncode(_episode.ProperName), episodeproper = HttpUtility.HtmlEncode(_episode.ProperName), episodename = HttpUtility.HtmlEncode(_episode.EpisodeName), episodedescription = DescriptLinkUtils(_episode.Description ?? "").Replace("\n", "
") }; } else { value = new { username = user, rooturl = $"{Configuration.Root.TrimEnd('/')}/", title = Configuration.Title, hasmovie = false }; } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEpisode.RenderAsync(value))); } private async Task UploadEpisodeStreamAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); string seasonS = seasonPathValueServer.GetValue(ctx); string episodeS = episodePathValueServer.GetValue(ctx); if (!int.TryParse(seasonS, out var season)) season = 1; if (!int.TryParse(episodeS, out var episode)) episode = 1; 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(); var _episode = provider.GetEpisode(user, show, season, episode); if (_episode != null) { if (ctx.QueryParams.TryGetFirst("type", out var type)) { switch (type) { case "thumbnail": string thumb = Path.Combine(path, user, "show", show, $"Season {season.ToString("D2")}", $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}-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 {season.ToString("D2")}", $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}-poster.jpg"); if (File.Exists(poster)) File.Delete(poster); File.Move(tmpFile, poster); break; case "movie": string movie = Path.Combine(path, user, "show", show, $"Season {season.ToString("D2")}", $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}.mp4"); if (File.Exists(movie)) File.Delete(movie); File.Move(tmpFile, movie); ScheduleFFmpeg($"-y -i \"{movie}\" {Configuration.BrowserTranscode} \"{Path.Combine(path, user, "show", show, $"Season {season.ToString("D2")}", $"S{season.ToString("D2")}E{episode.ToString("D2")}.mp4")}\""); break; } ScheduleTask(async () => { await GenerateBittorentFileShowAsync(user, show); }); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

Success

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

Failed

<- Back")); } 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 async Task EditSeasonPagePostAsync(ServerContext ctx) { ctx.ParseBody(); string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); string seasonS = seasonPathValueServer.GetValue(ctx); if (!int.TryParse(seasonS, out var season)) season = 1; var me = GetAccount(ctx,true); var _season = provider.GetSeason(user, show, season); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { if (_season != null) { if (ctx.QueryParams.TryGetFirst("proper_name", out var proper_name) && ctx.QueryParams.TryGetFirst("description", out var description)) { _season.ProperName = proper_name; _season.Description = description.Replace("\r", ""); provider.UpdateSeason(_season); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

Success

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

Failed

<- Back")); } public async Task EditEpisodePageAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); var me = GetAccount(ctx); string seasonS = seasonPathValueServer.GetValue(ctx); string episodeS = episodePathValueServer.GetValue(ctx); if (!int.TryParse(seasonS, out var season)) season = 1; if (!int.TryParse(episodeS, out var episode)) episode = 1; var _episode = provider.GetEpisode(user, show, season, episode); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { if (_episode != null) await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditEpisodeDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_episode.ProperName), Description = System.Web.HttpUtility.HtmlEncode(_episode.Description) }))); } else { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

You are unauthorized to edit this

")); } } private async Task EditEpisodePagePostAsync(ServerContext ctx) { ctx.ParseBody(); string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); string seasonS = seasonPathValueServer.GetValue(ctx); string episodeS = episodePathValueServer.GetValue(ctx); if (!int.TryParse(seasonS, out var season)) season = 1; if (!int.TryParse(episodeS, out var episode)) episode = 1; var me = GetAccount(ctx,true); var _episode = provider.GetEpisode(user, show, season, episode); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { if (_episode != null) { if (ctx.QueryParams.TryGetFirst("proper_name", out var proper_name) && ctx.QueryParams.TryGetFirst("description", out var description)) { _episode.ProperName = proper_name; _episode.Description = description.Replace("\r", ""); provider.UpdateEpisode(_episode); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

Success

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

Failed

<- Back")); } 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(ctx, await pageEditSeasonDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_season.ProperName), newseasonnumber = provider.EpisodeCount(user, show, season) + 1, Description = System.Web.HttpUtility.HtmlEncode(_season.Description) }))); } else { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

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, $"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(ctx, 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 async Task EditShowPagePostAsync(ServerContext ctx) { ctx.ParseBody(); string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); var _show = provider.GetShow(user, show); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { if (_show != null) { if (ctx.QueryParams.TryGetFirst("proper_name", out var proper_name) && ctx.QueryParams.TryGetFirst("description", out var description)) { _show.ProperName = proper_name; _show.Description = description.Replace("\r", ""); provider.UpdateShow(_show); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

Success

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

Failed

<- Back")); } 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(ctx, await pageEditShowDetails.RenderAsync(new { Propername = System.Web.HttpUtility.HtmlAttributeEncode(_show.ProperName), newseasonnumber = provider.SeasonCount(user, show) + 1, Description = System.Web.HttpUtility.HtmlEncode(_show.Description) }))); } else { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

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,out var cookie); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } object value; if (_show != null && _user != null) { string csrf=""; if(me != null) csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); 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 { csrf, seasons, 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(ctx, await pageShow.RenderAsync(value))); } private IServer Shows() { RouteServer routeServer = new RouteServer(); routeServer.Add("/", ShowsAsync); return routeServer; } private IServer Albums() { RouteServer routeServer = new RouteServer(); routeServer.Add("/", AlbumsAsync); 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(ctx, await pageShows.RenderAsync(new { Shows = shows }))); } private async Task AlbumsAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); List albums = new List(); foreach (var item in provider.GetAlbums(user)) { //var data = GetMovieContentMetaData(Configuration,user,item.Name); string thumb = $"{Configuration.Root.TrimEnd('/')}/content/{user}/album/{item.Name}/thumbnail.jpg"; albums.Add(item.Scriban(thumb)); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageAlbums.RenderAsync(new { Albums = albums }))); } private static 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; } internal static 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]); } b.Append($"{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(ctx, 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(ctx, 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("/sendupdate", SendMovieUpdateAsync, "POST"); 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 SendShowUpdateAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); var _user = provider.GetUserAccount(user); var _show = provider.GetShow(user, show); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { ctx.ParseBody(); if (!ctx.QueryParams.TryGetFirst("body", out var body)) { body = ""; } ScheduleTask(async () => { await Creator.EmailShowAsync(provider, _user, _show, true, body); await SendEvent(EventType.ShowUpdate, _user.ProperName, _user.Username, _show.Name, _show.ProperName, _show.Description, body); }); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{show}/"); } else { await ctx.SendTextAsync("Failed to send update"); } } private async Task SendMovieUpdateAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string movie = moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); var _user = provider.GetUserAccount(user); var _movie = provider.GetMovie(user, movie); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { ctx.ParseBody(); if (!ctx.QueryParams.TryGetFirst("body", out var body)) { body = ""; } ScheduleTask(async () => { await Creator.EmailMovieAsync(provider, _user, _movie, true, body); await SendEvent(EventType.MovieUpdate, _user.ProperName, _user.Username, _movie.Name, _movie.ProperName, _movie.Description, body); }); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/movie/{movie}/"); } else { await ctx.SendTextAsync("Failed to send update"); } } private async Task SendAlbumUpdateAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); var _user = provider.GetUserAccount(user); var _album = provider.GetAlbum(user, album); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { ctx.ParseBody(); if (!ctx.QueryParams.TryGetFirst("body", out var body)) { body = ""; } ScheduleTask(async () => { await Creator.EmailAlbumAsync(provider, _user, _album, true, body); await SendEvent(EventType.AlbumUpdate, _user.ProperName, _user.Username, _album.Name, _album.ProperName, _album.Description, body); }); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/album/{album}/"); } else { await ctx.SendTextAsync("Failed to send update"); } } private async Task SubtitlesMoviePostAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string movie = moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); 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 SubtitlesEpisodeAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); string seasonS = seasonPathValueServer.GetValue(ctx); string episodeS = episodePathValueServer.GetValue(ctx); if (!int.TryParse(seasonS, out var season)) season = 1; if (!int.TryParse(episodeS, out var episode)) episode = 1; var me = GetAccount(ctx); var _show = provider.GetMovie(user, show); var _episode = provider.GetEpisode(user, show, season, episode); 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, "show", show, $"Season {season.ToString("D2")}", $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}-subtitles", lang); string langFile = Path.Combine(langDir, $"{_episode.ProperName} S{season.ToString("D2")}E{episode.ToString("D2")}.json"); string browserfile = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{season.ToString("D2")}/S{season.ToString("D2")}E{episode.ToString("D2")}.mp4"; string json = ""; bool hasjson = false; if (File.Exists(langFile)) { hasjson = true; json = File.ReadAllText(langFile); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, 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(ctx, await pageSubtitleLangList.RenderAsync(new { languages }))); } } } private async Task SubtitlesEpisodePostAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); string seasonS = seasonPathValueServer.GetValue(ctx); string episodeS = episodePathValueServer.GetValue(ctx); if (!int.TryParse(seasonS, out var season)) season = 1; if (!int.TryParse(episodeS, out var episode)) episode = 1; var me = GetAccount(ctx,true); var _show = provider.GetMovie(user, show); var _episode = provider.GetEpisode(user, show, season, episode); 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, "show", show, $"Season {season.ToString("D2")}", $"{_episode.EpisodeName} S{season.ToString("D2")}E{episode.ToString("D2")}-subtitles", lang); Directory.CreateDirectory(langDir); string langFile = Path.Combine(langDir, $"{_episode.ProperName} S{season.ToString("D2")}E{episode.ToString("D2")}.json"); string vtt = Path.Combine(langDir, $"{_episode.ProperName} S{season.ToString("D2")}E{episode.ToString("D2")}.vtt"); string srt = Path.Combine(langDir, $"{_episode.ProperName} S{season.ToString("D2")}E{episode.ToString("D2")}.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(ctx, 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(ctx, await pageSubtitleLangList.RenderAsync(new { languages }))); } } } private async Task UploadAlbumExtraAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { Directory.CreateDirectory(Path.Combine(path, user, "album", album)); var tmpFile = Path.Combine(path, user, "album", album, $"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, "album", album, "extras", _path); if (File.Exists(_path2)) File.Delete(_path2); File.Move(tmpFile, _path2); ScheduleTask(async () => { await GenerateBittorentFileAlbumAsync(user, album); }); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/album/{album}/extras?path={System.Web.HttpUtility.UrlEncode(parent)}"); } } } private async Task UploadMovieExtraAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string movie = moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); 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 UploadShowExtraAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); 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"); 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, "show", show, "extras", _path); if (File.Exists(_path2)) File.Delete(_path2); File.Move(tmpFile, _path2); ScheduleTask(async () => { await GenerateBittorentFileShowAsync(user, show); }); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{show}/extras?path={System.Web.HttpUtility.UrlEncode(parent)}"); } } } private async Task ExtrasAlbumMkdirAsync(ServerContext ctx) { ctx.ParseBody(); string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); 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, "album", album, "extras", _path); if (me != null) { Directory.CreateDirectory(_path2); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/album/{album}/extras?path={System.Web.HttpUtility.UrlEncode(_path)}"); } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/album/{album}/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,true); 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 ExtrasShowMkdirAsync(ServerContext ctx) { ctx.ParseBody(); string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); 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, "show", show, "extras", _path); if (me != null) { Directory.CreateDirectory(_path2); await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{show}/extras?path={System.Web.HttpUtility.UrlEncode(_path)}"); } else { await ctx.SendRedirectAsync($"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{show}/extras?path={System.Web.HttpUtility.UrlEncode(parent)}"); } } } public async Task ExtrasShowPageAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); var me = GetAccount(ctx); 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, "show", show, "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" }); } if (Directory.Exists(extrasPath)) 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 }); } if (Directory.Exists(extrasPath)) 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}/show/{show}/extras/{System.Web.HttpUtility.UrlPathEncode(__path.TrimEnd('/') + '/' + filename)}"; paths.Add(new { Path = path, Type = "[FILE]", Name = filename_html }); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageExtrasViewer.RenderAsync(new { Extras = paths, Path = $"/{__path}", Parent = __path, Editable = me != null }))); } private async Task ExtrasAlbumPageAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var me = GetAccount(ctx,out var cookie); 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, "album", album, "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" }); } if (Directory.Exists(extrasPath)) 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 }); } if (Directory.Exists(extrasPath)) 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}/album/{album}/extras/{System.Web.HttpUtility.UrlPathEncode(__path.TrimEnd('/') + '/' + filename)}"; paths.Add(new { Path = path, Type = "[FILE]", Name = filename_html }); } string csrf=""; if(me != null) csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageExtrasViewer.RenderAsync(new {csrf, Extras = paths, Path = $"/{__path}", Parent = __path, Editable = me != null }))); } private async Task ExtrasMoviePageAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string movie = moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx,out var cookie); 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" }); } if (Directory.Exists(extrasPath)) 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 }); } if (Directory.Exists(extrasPath)) 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 }); } string csrf=""; if(me != null) csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageExtrasViewer.RenderAsync(new {csrf, 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 < 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 GenerateBittorentFileAlbumAsync(string user, string album) { var _album = provider.GetAlbum(user, album); string albumDir = Path.Combine(this.path, user, "album", album); Directory.CreateDirectory(albumDir); string extrasDir = Path.Combine(albumDir, "extras"); PieceStream strm = new PieceStream(); //calculate without extras List paths = new List(); int track = 1; foreach (var item in _album.Tracks) { string name = $"{track.ToString("D2")} {_album.AlbumArtist} - {item}.flac"; if (File.Exists(Path.Combine(albumDir, name))) paths.Add(new string[] { name }); track++; } if (File.Exists(Path.Combine(albumDir, "thumbnail.jpg"))) paths.Add(new string[] { "thumbnail.jpg" }); List lengths = new List(); foreach (var _path in paths.Select(e => Path.Combine(e))) using (var movieStrm = File.OpenRead(Path.Combine(albumDir, _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}/album/"; torrent.Info.PieceLength = PieceStream.PieceLength; torrent.Info.Name = album; 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(albumDir, $"{album}.torrent"); using (var file = File.Create(torrentFile)) { await torrent.ToTorrentFile().WriteToStreamAsync(file); } if (Directory.Exists(extrasDir)) { List 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(albumDir, _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}/album/"; torrent.Info.PieceLength = PieceStream.PieceLength; torrent.Info.Name = album; 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(albumDir, $"{album}_withextras.torrent"); using (var file = File.Create(torrentFile)) { await torrent.ToTorrentFile().WriteToStreamAsync(file); } } } 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 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 < 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 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 < 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 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,true); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { Directory.CreateDirectory(Path.Combine(path, user, "show", show, $"Season {seasonNo.ToString("D2")}")); var tmpFile = Path.Combine(path, user, "show", show, $"Season {seasonNo.ToString("D2")}", $"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.ToString("D2")}", "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.ToString("D2")}", "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(ctx, "

Success

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

Failed

<- Back")); } private async Task UploadShowStreamAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); 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(ctx, "

Success

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

Failed

<- Back")); } private async Task GenerateBittorentFileShowAsync(string user, string show) { 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.ToString("D2")}"; 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 < 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 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; 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); } } } private async Task UploadAlbumStreamAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); var _movie = provider.GetAlbum(user, album); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { Directory.CreateDirectory(Path.Combine(path, user, "album", album)); var tmpFile = Path.Combine(path, user, "album", album, $"tmp{DateTime.Now.ToFileTime().ToString()}.bin"); foreach (var item in ctx.ParseBody((n, fn, ct) => File.Create(tmpFile))) item.Value.Dispose(); if (_movie != null) { string thumb = Path.Combine(path, user, "album", album, "thumbnail.jpg"); if (File.Exists(thumb)) File.Delete(thumb); File.Move(tmpFile, thumb); ScheduleTask(async () => { await GenerateBittorentFileAlbumAsync(user, album); }); await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

Success

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

Failed

<- Back")); } private async Task UploadMovieStreamAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string movie = moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx,true); 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": 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(ctx, "

Success

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

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,true); 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(ctx, "

Success

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

Failed

<- Back")); } public async Task EditMoviePageAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string movie = moviePathValueServer.GetValue(ctx); var me = GetAccount(ctx,out var cookie); var _movie = provider.GetMovie(user, movie); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { if (_movie != null) { string csrf=""; string csrf2=""; if(me != null) { csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); csrf2 = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditMovieDetails.RenderAsync(new {csrf,csrf2, Propername = System.Web.HttpUtility.HtmlAttributeEncode(_movie.ProperName), Description = System.Web.HttpUtility.HtmlEncode(_movie.Description) }))); } } else { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

You are unauthorized to edit this

")); } } public async Task EditAlbumPageAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string album = albumPathValueServer.GetValue(ctx); var me = GetAccount(ctx,out var cookie); var _album = provider.GetAlbum(user, album); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { if (_album != null) { string csrf=""; string csrf2=""; if(me != null) { csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); csrf2 = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); } await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageEditAlbumDetails.RenderAsync(new { Year = _album.Year, Propername = System.Web.HttpUtility.HtmlAttributeEncode(_album.ProperName), Albumartist = System.Web.HttpUtility.HtmlAttributeEncode(_album.AlbumArtist), Description = System.Web.HttpUtility.HtmlEncode(_album.Description) ,csrf,csrf2}))); } } else { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "

You are unauthorized to edit this

")); } } private async Task PlayEpisodePageAsync(ServerContext ctx) { string user = usersPathValueServer.GetValue(ctx); string show = showPathValueServer.GetValue(ctx); string seasonS = seasonPathValueServer.GetValue(ctx); string episodeS = episodePathValueServer.GetValue(ctx); if (!int.TryParse(seasonS, out var season)) season = 1; if (!int.TryParse(episodeS, out var episode)) episode = 1; var _show = provider.GetShow(user, show); var _episode = provider.GetEpisode(user, show, season, episode); object value; if (_show != null) { var data = GetEpisodeContentMetaData(user, show, season, episode); 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 { curepisode = episode, curseason = season, nextepisodeapi = $"{Configuration.Root.TrimEnd('/')}/api/v1/NextEpisode?user={user}&show={show}", username = user, rooturl = $"{Configuration.Root.TrimEnd('/')}/", title = Configuration.Title, hasmovie = true, episodethumbnail = thumb, episodename = _show.Name, episodeposter = data.PosterUrl, episodebrowserurl = data.BrowserStream, subtitles }; } else { value = new { username = user, rooturl = $"{Configuration.Root.TrimEnd('/')}/", title = Configuration.Title, hasmovie = false }; } await ctx.SendTextAsync(await pageWatchEpisode.RenderAsync(value)); } 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(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)); } IEnumerable GetEpisodesFromShows(IEnumerable shows) { foreach (var show in shows) { foreach (var episode in GetEpisodesFromShow(show)) yield return episode; } } IEnumerable GetEpisodesFromShow(Show show) { var user = provider.GetUserById(show.UserId); int count = provider.SeasonCount(user.Username, show.Name); for (int i = 1; i <= count; i++) { int count2 = provider.EpisodeCount(user.Username, show.Name, i); for (int j = 1; j < count2; j++) { yield return provider.GetEpisode(user.Username, show.Name, i, j); } } } IEnumerable GetEpisodesFromSeason(Show show, int season) { var user = provider.GetUserById(show.UserId); int count2 = provider.EpisodeCount(user.Username, show.Name, season); for (int j = 1; j < count2; j++) { yield return provider.GetEpisode(user.Username, show.Name, season, j); } } 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,out var cookie); 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"; string csrf=""; if(me != null) csrf = HttpUtility.UrlEncode(CreateCSRF(me.Id,cookie)); value = new { csrf, extrasexists, moviebrowserexists, 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(ctx, await pageMovie.RenderAsync(value))); } private async Task ApiPutMovieExtraAsync(ServerContext ctx) { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("movie", out var movie)) { var me = GetAccount(ctx,true); 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"); await ctx.ReadToFileAsync(tmpFile); if (ctx.QueryParams.TryGetFirst("extra", out var extra)) { var _path = SanitizePath($"{extra.TrimStart('/')}").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.SendJsonAsync(new { success = true }); } } } } private async Task ApiPutShowExtraAsync(ServerContext ctx) { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("show", out var show)) { var me = GetAccount(ctx,true); 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"); await ctx.ReadToFileAsync(tmpFile); if (ctx.QueryParams.TryGetFirst("extra", out var extra)) { var _path = SanitizePath($"{extra.TrimStart('/')}").TrimStart('/'); var _path2 = Path.Combine(path, user, "show", show, "extras", _path); if (File.Exists(_path2)) File.Delete(_path2); File.Move(tmpFile, _path2); ScheduleTask(async () => { await GenerateBittorentFileShowAsync(user, show); }); await ctx.SendJsonAsync(new { success = true }); } } } } private async Task ApiPutAlbumExtraAsync(ServerContext ctx) { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("album", out var album)) { var me = GetAccount(ctx,true); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { Directory.CreateDirectory(Path.Combine(path, user, "album", album)); var tmpFile = Path.Combine(path, user, "album", album, $"tmp{DateTime.Now.ToFileTime().ToString()}.bin"); await ctx.ReadToFileAsync(tmpFile); if (ctx.QueryParams.TryGetFirst("extra", out var extra)) { var _path = SanitizePath($"{extra.TrimStart('/')}").TrimStart('/'); var _path2 = Path.Combine(path, user, "album", album, "extras", _path); if (File.Exists(_path2)) File.Delete(_path2); File.Move(tmpFile, _path2); ScheduleTask(async () => { await GenerateBittorentFileAlbumAsync(user, album); }); await ctx.SendJsonAsync(new { success = true }); } } } } private IServer CreateSwagme() { SwagmeServer swagmeServer = new SwagmeServer(); swagmeServer.AbsoluteUrl = true; swagmeServer.Add("/Branding",async(ctx)=>{ await ctx.SendJsonAsync(new { title = Configuration.Title }); },new SwagmeDocumentation("Branding for server")); swagmeServer.Add("/Login", ApiLogin, new SwagmeDocumentation("Login to account", "email: the email of account
password: the password of account
type: json or cookie"), "POST", "Users"); swagmeServer.Add("/GetPublicUsers", ApiGetPublicUsers, new SwagmeDocumentation("Get all public users"), "GET", "Users"); swagmeServer.Add("/Updates", (ctx) => { SendEvents events = new SendEvents(); void evthdlr(object data) { try { events.SendEvent(data); } catch (Exception ex) { _ = ex; } } SendEvents += evthdlr; ctx.ServerSentEvents(events); SendEvents -= evthdlr; }, new SwagmeDocumentation("Server sent events for updates"), "GET", "Users"); swagmeServer.Add("/MovieFile", ApiPutMovieFileAsync, new SwagmeDocumentation("Put the movie resource"), "PUT", "Movies"); swagmeServer.Add("/MovieFile", ApiMovieFileAsync, new SwagmeDocumentation("Get the movie resource"), "GET", "Movies"); swagmeServer.Add("/MovieExtra", ApiPutMovieExtraAsync, new SwagmeDocumentation("Put the movie extra"), "PUT", "Movies"); swagmeServer.Add("/GetMovies", ApiGetMoviesAsync, new SwagmeDocumentation("Get a list of movies", "user: the user of the movies
type: format of list (defaults to json): pls, wiimc, json, m3u8 or rss"), "GET", "Movies"); swagmeServer.Add("/ShowExtra", ApiPutShowExtraAsync, new SwagmeDocumentation("Put the show extra"), "PUT", "Shows"); swagmeServer.Add("/NextEpisode", ApiGetNextEpisodeAsync, new SwagmeDocumentation("Get the next episode if available", "user: the user of the show
show: the show name
season: the season number starting from 1
episode: the episode number starting from 1"), "GET", "Shows"); swagmeServer.Add("/GetShows", ApiGetShowsAsync, new SwagmeDocumentation("Get a list of shows", "user: the user of the shows
type: format of list (defaults to json): pls, wiimc, json, m3u8 or rss"), "GET", "Shows"); swagmeServer.Add("/GetSeasons", ApiGetSeasonsAsync, new SwagmeDocumentation("Get a list of episodes", "user: the user of the show
show: the show
type: format of list (defaults to json): pls, wiimc, json, m3u8 or rss"), "GET", "Shows"); swagmeServer.Add("/GetEpisodes", ApiGetEpisodesAsync, new SwagmeDocumentation("Get a list of episodes", "user: the user of the show
show: the show
season: the season
type: format of list (defaults to json): pls, wiimc, json, m3u8 or rss"), "GET", "Shows"); swagmeServer.Add("/ShowFile", ApiShowFileAsync, new SwagmeDocumentation("Get the show resource"), "GET", "Shows"); swagmeServer.Add("/SeasonFile", ApiSeasonFileAsync, new SwagmeDocumentation("Get the season resource"), "GET", "Shows"); swagmeServer.Add("/EpisodeFile", ApiEpisodeFileAsync, new SwagmeDocumentation("Get the episode resource"), "GET", "Shows"); swagmeServer.Add("/AlbumFile", ApiAlbumFileAsync, new SwagmeDocumentation(""), "GET", "Albums"); swagmeServer.Add("/AlbumFile", ApiPutAlbumFileAsync, new SwagmeDocumentation("Put the album resource"), "PUT", "Albums"); swagmeServer.Add("/AlbumExtra", ApiPutAlbumExtraAsync, new SwagmeDocumentation("Put the album extra"), "PUT", "Albums"); swagmeServer.Add("/GetAlbums", ApiGetAlbumsAsync, new SwagmeDocumentation("Get a list of albums", "user: the user of the albums
type: format of list (defaults to json): json or rss"), "GET", "Albums"); return swagmeServer; } private async Task ApiLogin(ServerContext ctx) { ctx.ParseBody(); if (ctx.QueryParams.TryGetFirst("email", out var email) && ctx.QueryParams.TryGetFirst("password", out var password) && ctx.QueryParams.TryGetFirst("type", out var type) && (type == "json" || type == "cookie")) { 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); if (type == "cookie") { ctx.ResponseHeaders.Add("Set-Cookie", $"Session={cookie}; Path=/"); await ctx.SendJsonAsync(new { success = true }); } else if (type == "json") { await ctx.SendJsonAsync(new { success = true, cookie }); } return; } } } await ctx.SendJsonAsync(new { success = false }); } private async Task ApiGetPublicUsers(ServerContext ctx) { List users = new List(); foreach (var user in provider.GetUsers()) { if (!user.IsAdmin && Configuration.Publish == CMSPublish.Admin) { //await ctx.SendTextAsync(await RenderHtmlAsync(ctx,"

You can't upload content

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

You can't upload content

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

You can't upload content

")); continue; } users.Add(user); } await ctx.SendJsonAsync(users); } private async Task ApiGetShowsAsync(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 shows = new List(); if (a != null) { shows.AddRange(provider.GetShows(a.Username)); } if (type == "json") { await ctx.SendJsonAsync(shows); } if (type == "pls" || type == "wiimc") { var episodes = GetEpisodesFromShows(shows); StringBuilder b = new StringBuilder(); b.AppendLine("[Playlist]"); int i = 1; foreach (var item in episodes) { var show = provider.GetShow(item.UserId, item.ShowId); if (type == "wiimc") b.AppendLine($"File{i}={Configuration.Root.TrimEnd('/')}/content/{HttpUtility.UrlEncode(user)}/show/{show.Name}/Season%20{item.SeasonNumber.ToString("D2")}/S{item.SeasonNumber.ToString("D2")}E{item.EpisodeNumber.ToString("D2")}.mp4"); else b.AppendLine($"File{i}={Configuration.Root.TrimEnd('/')}/api/v1/EpisodeFile?user={HttpUtility.UrlEncode(user)}&show={HttpUtility.UrlEncode(show.Name)}&season={item.SeasonNumber}&episode={item.EpisodeNumber}&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") { var episodes = GetEpisodesFromShows(shows); M3uPlaylist playlist = new M3uPlaylist(); foreach (var item in episodes) { M3uPlaylistEntry entry = new M3uPlaylistEntry(); var show = provider.GetShow(item.UserId, item.ShowId); entry.Path = $"{Configuration.Root.TrimEnd('/')}/api/v1/EpisodeFile?user={HttpUtility.UrlEncode(user)}&show={HttpUtility.UrlEncode(show.Name)}&season={item.SeasonNumber}&episode={item.EpisodeNumber}&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") { var episodes = GetEpisodesFromShows(shows); 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 episodes) { var show = provider.GetShow(item.UserId, item.ShowId); 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"); } } public async Task ApiGetSeasonsAsync(ServerContext ctx) { UserAccount a; Show s; if (!ctx.QueryParams.TryGetFirst("type", out var type)) type = "json"; if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("show", out var show)) { a = provider.GetUserAccount(user); s = provider.GetShow(user, show); } else { ctx.StatusCode = 404; await ctx.WriteHeadersAsync(); return; } if (type == "json") { List seasons = new List(); for (int i = 1; i < provider.SeasonCount(s.UserId, s.Id); i++) { var season = provider.GetSeason(s.UserId, s.Id, i); if (season != null) seasons.Add(season); } await ctx.SendJsonAsync(seasons); } if (type == "pls" || type == "wiimc") { var episodes = GetEpisodesFromShow(s); StringBuilder b = new StringBuilder(); b.AppendLine("[Playlist]"); int i = 1; foreach (var item in episodes) { if (type == "wiimc") b.AppendLine($"File{i}={Configuration.Root.TrimEnd('/')}/content/{HttpUtility.UrlEncode(user)}/show/{s.Name}/Season%20{item.SeasonNumber.ToString("D2")}/S{item.SeasonNumber.ToString("D2")}E{item.EpisodeNumber.ToString("D2")}.mp4"); else b.AppendLine($"File{i}={Configuration.Root.TrimEnd('/')}/api/v1/EpisodeFile?user={HttpUtility.UrlEncode(user)}&show={HttpUtility.UrlEncode(s.Name)}&season={item.SeasonNumber}&episode={item.EpisodeNumber}&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") { var episodes = GetEpisodesFromShow(s); M3uPlaylist playlist = new M3uPlaylist(); foreach (var item in episodes) { M3uPlaylistEntry entry = new M3uPlaylistEntry(); entry.Path = $"{Configuration.Root.TrimEnd('/')}/api/v1/EpisodeFile?user={HttpUtility.UrlEncode(user)}&show={HttpUtility.UrlEncode(s.Name)}&season={item.SeasonNumber}&episode={item.EpisodeNumber}&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") { var episodes = GetEpisodesFromShow(s); 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 episodes) { 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"); } } public async Task ApiGetEpisodesAsync(ServerContext ctx) { UserAccount a; Show s; if (!ctx.QueryParams.TryGetFirst("type", out var type)) type = "json"; if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("show", out var show) && ctx.QueryParams.TryGetFirstInt32("season", out var season)) { a = provider.GetUserAccount(user); s = provider.GetShow(user, show); } else { ctx.StatusCode = 404; await ctx.WriteHeadersAsync(); return; } if (type == "json") { List episodes = new List(); for (int i = 1; i < provider.EpisodeCount(s.UserId, s.Id, season); i++) { var episode = provider.GetEpisode(s.UserId, s.Id, season, i); if (episode != null) episodes.Add(episode); } await ctx.SendJsonAsync(episodes); } if (type == "pls" || type == "wiimc") { var episodes = GetEpisodesFromSeason(s, season); StringBuilder b = new StringBuilder(); b.AppendLine("[Playlist]"); int i = 1; foreach (var item in episodes) { if (type == "wiimc") b.AppendLine($"File{i}={Configuration.Root.TrimEnd('/')}/content/{HttpUtility.UrlEncode(user)}/show/{s.Name}/Season%20{item.SeasonNumber.ToString("D2")}/S{item.SeasonNumber.ToString("D2")}E{item.EpisodeNumber.ToString("D2")}.mp4"); else b.AppendLine($"File{i}={Configuration.Root.TrimEnd('/')}/api/v1/EpisodeFile?user={HttpUtility.UrlEncode(user)}&show={HttpUtility.UrlEncode(s.Name)}&season={item.SeasonNumber}&episode={item.EpisodeNumber}&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") { var episodes = GetEpisodesFromSeason(s, season); M3uPlaylist playlist = new M3uPlaylist(); foreach (var item in episodes) { M3uPlaylistEntry entry = new M3uPlaylistEntry(); entry.Path = $"{Configuration.Root.TrimEnd('/')}/api/v1/EpisodeFile?user={HttpUtility.UrlEncode(user)}&show={HttpUtility.UrlEncode(s.Name)}&season={item.SeasonNumber}&episode={item.EpisodeNumber}&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") { var episodes = GetEpisodesFromSeason(s, season); 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 episodes) { 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 async Task ApiGetNextEpisodeAsync(ServerContext ctx) { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("show", out var show) && ctx.QueryParams.TryGetFirst("season", out var seasonS) && ctx.QueryParams.TryGetFirst("episode", out var episodeS)) { if (!int.TryParse(seasonS, out var season)) season = 1; if (!int.TryParse(episodeS, out var episode)) episode = 1; var _show = provider.GetShow(user, show); var _episode = provider.GetEpisode(user, show, season, episode); bool hasnextepisode = false; int nextepisodeep = episode + 1; int nextepisodeseason = season; while (nextepisodeseason <= provider.SeasonCount(user, show)) { var daseason = provider.GetSeason(user, show, nextepisodeseason); if (daseason != null) { while (nextepisodeep <= provider.EpisodeCount(user, show, nextepisodeseason)) { if (File.Exists(Path.Combine(path, user, "show", show, $"Season {nextepisodeseason.ToString("D2")}", $"S{nextepisodeseason.ToString("D2")}E{nextepisodeep.ToString("D2")}.mp4"))) { hasnextepisode = true; break; } nextepisodeep++; } } if (hasnextepisode) { break; } nextepisodeep = 1; nextepisodeseason++; } if (hasnextepisode) { var _epn = provider.GetEpisode(user, show, nextepisodeseason, nextepisodeep); string next_episode_page_url = $"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{show}/season/{nextepisodeseason}/episode/{nextepisodeep}/play"; var data = GetEpisodeContentMetaData(user, show, nextepisodeseason, nextepisodeep); StringBuilder b = new StringBuilder(); 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; } } b.Append($""); } await ctx.SendJsonAsync(new { next_subtitles_html = b.ToString(), has_next_episode = true, next_episode_episode = nextepisodeep, next_episode_season = nextepisodeseason, next_episode_name = _epn.EpisodeName, next_episode_url = data.BrowserStream, next_poster_url = data.PosterUrl, next_episode_page_url }); return; } } await ctx.SendJsonAsync(new { has_next_episode = false }); } private async Task ApiPutAlbumFileAsync(ServerContext ctx) { try { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("album", out var album)) { if (!ctx.QueryParams.TryGetFirst("type", out var type)) type = "track"; if (!ctx.QueryParams.TryGetFirstInt32("track_id", out var track_id)) track_id = 1; var me = GetAccount(ctx,true); var _album = provider.GetAlbum(user, album); if (me != null && me.Username != user && !me.IsAdmin) { me = null; } if (me != null) { Directory.CreateDirectory(Path.Combine(path, user, "album", album)); var tmpFile = Path.Combine(path, user, "album", album, $"tmp{DateTime.Now.ToFileTime().ToString()}.bin"); await ctx.ReadToFileAsync(tmpFile); if (_album != null) { switch (type) { case "thumbnail": string thumb = Path.Combine(path, user, "album", album, "thumbnail.jpg"); if (File.Exists(thumb)) File.Delete(thumb); File.Move(tmpFile, thumb); break; case "poster": string poster = Path.Combine(path, user, "album", album, "poster.jpg"); if (File.Exists(poster)) File.Delete(poster); File.Move(tmpFile, poster); break; case "track": string oldtrack = _album.Tracks[track_id]; string flac = Path.Combine(path, user, "album", album, $"{(track_id + 1).ToString("D2")} {_album.AlbumArtist} - {oldtrack}.flac"); File.Move(tmpFile, flac); ScheduleTask(async () => { await GenerateBittorentFileAlbumAsync(user, album); }); string oldmp3 = Path.Combine(path, user, "album", album, $"{oldtrack}.mp3"); ScheduleFFmpeg($"-y -i \"{flac}\" {Configuration.BrowserTranscodeMp3} \"{oldmp3}\""); break; } ScheduleTask(async () => { await GenerateBittorentFileAlbumAsync(user, album); }); await ctx.SendJsonAsync(new { success = true }); return; } } } } catch (Exception ex) { Console.WriteLine(ex); } } private async Task ApiPutMovieFileAsync(ServerContext ctx) { try { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("movie", out var movie)) { if (!ctx.QueryParams.TryGetFirst("type", out var type)) type = "movie"; var me = GetAccount(ctx,true); 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"); await ctx.ReadToFileAsync(tmpFile); if (_movie != null) { 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.SendJsonAsync(new { success = true }); return; } } } } catch (Exception ex) { Console.WriteLine(ex); } } private async Task ApiAlbumFileAsync(ServerContext ctx) { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("album", out var album)) { if (!ctx.QueryParams.TryGetFirst("type", out var type)) type = "json"; var info = GetAlbumContentMetadata(user, album); switch (type) { case "thumbnail": await ctx.SendRedirectAsync(info.ThumbnailUrl); break; case "poster": await ctx.SendRedirectAsync(info.PosterUrl); break; case "torrent": await ctx.SendRedirectAsync(info.AlbumTorrentUrl); break; case "torrent_extra": await ctx.SendRedirectAsync(info.AlbumWithExtrasTorrentUrl); break; case "json": await ctx.SendJsonAsync(info); break; case "wiimc": case "pls": { StringBuilder b = new StringBuilder(); b.AppendLine("[Playlist]"); int i = 1; foreach (var item in info.DownloadStreams) { b.AppendLine($"File{i}={IniEscape(item.Url)}"); b.AppendLine($"Length{i}=0"); b.AppendLine($"Title{i}={IniEscape(item.Name)}"); i++; } await ctx.SendTextAsync(b.ToString(), "audio/x-scpls"); } break; case "m3u8": { M3uPlaylist playlist = new M3uPlaylist(); foreach (var item in info.DownloadStreams) { M3uPlaylistEntry entry = new M3uPlaylistEntry(); entry.Path = item.Url; entry.Title = item.Name; playlist.PlaylistEntries.Add(entry); } M3uContent content = new M3uContent(); await ctx.SendTextAsync(content.ToText(playlist), "application/x-mpegurl"); } break; case "browser": { if (ctx.QueryParams.TryGetFirstInt32("track_number", out var track_no)) { var res = info.BrowserStreams.Find(e => e.TrackNumber == track_no); if (res != null) await ctx.SendRedirectAsync(res.Url); else await ctx.SendNotFoundAsync(); } else await ctx.SendNotFoundAsync(); } break; case "download": { if (ctx.QueryParams.TryGetFirstInt32("track_number", out var track_no)) { var res = info.DownloadStreams.Find(e => e.TrackNumber == track_no); if (res != null) await ctx.SendRedirectAsync(res.Url); else await ctx.SendNotFoundAsync(); } else await ctx.SendNotFoundAsync(); } break; } } } 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(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 ApiEpisodeFileAsync(ServerContext ctx) { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("show", out var show) && ctx.QueryParams.TryGetFirstInt32("season", out var season) && ctx.QueryParams.TryGetFirstInt32("episode", out var episode)) { if (!ctx.QueryParams.TryGetFirst("type", out var type)) type = "download"; var info = GetEpisodeContentMetaData(user, show, season, episode); 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 "json": await ctx.SendJsonAsync(info); break; } } } private async Task ApiSeasonFileAsync(ServerContext ctx) { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("show", out var show) && ctx.QueryParams.TryGetFirstInt32("season", out var season)) { if (!ctx.QueryParams.TryGetFirst("type", out var type)) type = "json"; var info = GetSeasonContentMetaData(user, show, season); switch (type) { case "thumbnail": await ctx.SendRedirectAsync(info.ThumbnailUrl); break; case "poster": await ctx.SendRedirectAsync(info.PosterUrl); break; case "json": await ctx.SendJsonAsync(info); break; } } } private async Task ApiShowFileAsync(ServerContext ctx) { if (ctx.QueryParams.TryGetFirst("user", out var user) && ctx.QueryParams.TryGetFirst("show", out var show)) { if (!ctx.QueryParams.TryGetFirst("type", out var type)) type = "json"; var info = GetShowContentMetaData(user, show); switch (type) { case "thumbnail": await ctx.SendRedirectAsync(info.ThumbnailUrl); break; case "poster": await ctx.SendRedirectAsync(info.PosterUrl); break; case "torrent": await ctx.SendRedirectAsync(info.ShowTorrentUrl); break; case "torrent_extra": await ctx.SendRedirectAsync(info.ShowWithExtrasTorrentUrl); 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 async Task ApiGetAlbumsAsync(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 albums = new List(); if (a != null) { albums.AddRange(provider.GetAlbums(a.Id)); } if (type == "json") { await ctx.SendJsonAsync(albums); } 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 Albums"); await rss.WriteGenerator("TessesCMS"); await rss.WriteValue("link", $"{Configuration.Root.TrimEnd('/')}/"); foreach (var item in albums) { 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(ServerContext ctx, string body, bool isHome = false, bool isDevcenter = false, bool isLogin = false, bool isUpload = false) { List _urls = new List(); var account = GetAccount(ctx); CMSNavUrl accountLink = account != null ? Configuration.RelativeNavUrl(account.ProperName, "account") : Configuration.RelativeNavUrl("Login", "login"); accountLink.Active = isLogin; var dc = Configuration.RelativeNavUrl("Devcenter", "devcenter"); dc.Active = isDevcenter; var upload = Configuration.RelativeNavUrl("Upload", "upload"); upload.Active = isUpload; _urls.Add(upload); _urls.Add(dc); _urls.Add(accountLink); 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) { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await RenderIndexAsync(), true)); } private async Task Devcenter(ServerContext ctx) { await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await RenderDevcenterAsync(), false, true)); } 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(string csrf) { return await pageUpload.RenderAsync(new { title = Configuration.Title,csrf, rooturl = $"{Configuration.Root.TrimEnd('/')}/" }); } public CMSConfiguration Configuration { get; set; } = new CMSConfiguration(); public IServer Server => new CSRFCatcherServer(routeServer); } internal class CSRFCatcherServer : IServer { IServer inside; public CSRFCatcherServer(IServer server) { this.inside = server; } public async Task BeforeAsync(ServerContext ctx) { try { return await inside.BeforeAsync(ctx); } catch (InvalidCSRFException) { await ctx.SendTextAsync("

CSRF token invalid

Press back, refresh and try again

"); } return true; } public async Task PostAsync(ServerContext ctx) { try { await inside.PostAsync(ctx); } catch (InvalidCSRFException) { await ctx.SendTextAsync("

CSRF token invalid

Press back, refresh and try again

"); } } public async Task OptionsAsync(ServerContext ctx) { try { await inside.OptionsAsync(ctx); } catch (InvalidCSRFException) { await ctx.SendTextAsync("

CSRF token invalid

Press back, refresh and try again

"); } } public async Task OtherAsync(ServerContext ctx) { try { await inside.OtherAsync(ctx); } catch (InvalidCSRFException) { await ctx.SendTextAsync("

CSRF token invalid

Press back, refresh and try again

"); } } public async Task GetAsync(ServerContext ctx) { try { await inside.GetAsync(ctx); } catch (InvalidCSRFException) { await ctx.SendTextAsync("

CSRF token invalid

Press back, refresh and try again

"); } } public void AddCors(ServerContext ctx) { } } [Serializable] internal class InvalidCSRFException : Exception { public InvalidCSRFException() { } public InvalidCSRFException(string message) : base(message) { } public InvalidCSRFException(string message, Exception innerException) : base(message, innerException) { } protected InvalidCSRFException(SerializationInfo info, StreamingContext context) : base(info, context) { } } public class EpisodeContentMetaData { [JsonProperty("episode_info")] public Episode Info { get; set; } [JsonProperty("has_browser_stream")] public bool HasBrowserStream { get; set; } [JsonProperty("has_download_stream")] public bool HasDownloadStream { get; set; } [JsonProperty("has_poster")] public bool HasPoster { get; set; } [JsonProperty("has_thumbnail")] public bool HasThumbnail { get; set; } [JsonProperty("browser_stream")] public string BrowserStream { get; set; } [JsonProperty("download_stream")] public string DownloadStream { get; set; } [JsonProperty("poster_url")] public string PosterUrl { get; set; } [JsonProperty("thumbnail_url")] public string ThumbnailUrl { get; set; } [JsonProperty("subtitle_streams")] public List SubtitlesStreams { get; set; } = new List(); } internal class EmailCreator { CMSConfiguration configuration; Template emailTemplate; Template verifyTemplate; Template emailMovie; Template emailShow; Template emailAlbum; public EmailCreator(CMSConfiguration configuration) { this.configuration = configuration; emailTemplate = Template.Parse(AssetProvider.ReadAllText("/EmailHtml.html")); verifyTemplate = Template.Parse(AssetProvider.ReadAllText("/VerifyEmail.html")); emailMovie = Template.Parse(AssetProvider.ReadAllText("/EmailMovie.html")); emailShow = Template.Parse(AssetProvider.ReadAllText("/EmailShow.html")); emailAlbum = Template.Parse(AssetProvider.ReadAllText("/EmailAlbum.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}"); } public async Task EmailShowAsync(IContentProvider provider, UserAccount account, Show show, bool update = false, string body = "") { foreach (var uAccount in account.AccountsToMail) { if (!uAccount.EnableShows) continue; if (!uAccount.EnableShows && update) continue; var user = provider.GetUserById(uAccount.UserId); if (user != null) { bool hasmessage = false; string message = ""; if (!string.IsNullOrWhiteSpace(body)) { message = CMSServer.DescriptLinkUtils(body).Replace("\n", "
"); hasmessage = true; } await SendEmailAsync(user.Email, await emailShow.RenderAsync(new { hasmessage, message, propername = HttpUtility.HtmlEncode(user.ProperName), showuserproper = HttpUtility.HtmlEncode(account.ProperName), showproper = HttpUtility.HtmlEncode(show.ProperName), updated = update, showurl = $"{configuration.Root.TrimEnd('/')}/user/{account.Username}/show/{show.Name}/" }), $"{account.ProperName} {(update ? "updated" : "created")} the show {show.ProperName}"); } } } public async Task EmailMovieAsync(IContentProvider provider, UserAccount account, Movie movie, bool update = false, string body = "") { foreach (var uAccount in account.AccountsToMail) { if (!uAccount.EnableMovies) continue; if (!uAccount.EnableUpdates && update) continue; var user = provider.GetUserById(uAccount.UserId); if (user != null) { bool hasmessage = false; string message = ""; if (!string.IsNullOrWhiteSpace(body)) { message = CMSServer.DescriptLinkUtils(body).Replace("\n", "
"); hasmessage = true; } await SendEmailAsync(user.Email, await emailMovie.RenderAsync(new { hasmessage, message, propername = HttpUtility.HtmlEncode(user.ProperName), movieuserproper = HttpUtility.HtmlEncode(account.ProperName), movieproper = HttpUtility.HtmlEncode(movie.ProperName), updated = update, movieurl = $"{configuration.Root.TrimEnd('/')}/user/{account.Username}/movie/{movie.Name}/" }), $"{account.ProperName} {(update ? "updated" : "created")} the movie {movie.ProperName}"); } } } internal async Task EmailAlbumAsync(IContentProvider provider, UserAccount account, Album album, bool update = false, string body = "") { foreach (var uAccount in account.AccountsToMail) { if (!uAccount.EnableMovies) continue; if (!uAccount.EnableUpdates && update) continue; var user = provider.GetUserById(uAccount.UserId); if (user != null) { bool hasmessage = false; string message = ""; if (!string.IsNullOrWhiteSpace(body)) { message = CMSServer.DescriptLinkUtils(body).Replace("\n", "
"); hasmessage = true; } await SendEmailAsync(user.Email, await emailAlbum.RenderAsync(new { hasmessage, message, propername = HttpUtility.HtmlEncode(user.ProperName), albumuserproper = HttpUtility.HtmlEncode(account.ProperName), albumproper = HttpUtility.HtmlEncode(album.ProperName), updated = update, albumurl = $"{configuration.Root.TrimEnd('/')}/user/{account.Username}/album/{album.Name}/" }), $"{account.ProperName} {(update ? "updated" : "created")} the album {album.ProperName}"); } } } } }