tesses-cms/Tesses.CMS/Class1.cs

5420 lines
246 KiB
C#

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<Subtitle> subtitles)
{
writer.WriteLine("WEBVTT");
for (int i = 0; i < subtitles.Count; i++)
{
writer.WriteLine();
writer.WriteLine(i + 1);
subtitles[i].ToWebVTT(writer);
}
}
public static void ToSrt(TextWriter writer, List<Subtitle> subtitles)
{
for (int i = 0; i < subtitles.Count; i++)
{
if (i > 0) writer.WriteLine();
writer.WriteLine(i + 1);
subtitles[i].ToSrt(writer);
}
}
}
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<SubtitleStream> 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<SubtitleStream> 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<SubtitleStream> 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<Track> browserStreams = new List<Track>();
List<Track> downloadStreams = new List<Track>();
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<ExtraDataStream> extraStreams)
{
foreach (var d in Directory.GetDirectories(v))
{
var dirname = Path.GetFileName(d);
string url = $"{extras}{dirname}/";
ExtraDataStream dataStream = new ExtraDataStream();
dataStream.IsDir = true;
dataStream.Name = dirname;
dataStream.Url = url;
FindExtras(url, d, dataStream.Items);
extraStreams.Add(dataStream);
}
foreach (var f in Directory.GetFiles(v))
{
var filename = Path.GetFileName(f);
var url = $"{extras}{filename}";
ExtraDataStream dataStream = new ExtraDataStream();
dataStream.IsDir = false;
dataStream.Name = filename;
dataStream.Url = url;
extraStreams.Add(dataStream);
}
}
EmailCreator Creator { get; set; }
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<object> 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<CMSConfiguration>(File.ReadAllText(configJson));
}
if (Configuration.BittorrentTrackers.Count == 0)
{
Configuration.BittorrentTrackers.AddRange(new string[]{
"https://t1.hloli.org:443/announce",
"http://1337.abcvg.info:80/announce",
"http://tracker.renfei.net:8080/announce",
"http://tracker.gbitt.info:80/announce",
"http://p2p.0g.cx:6969/announce",
"https://tracker.foreverpirates.co:443/announce"
});
}
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<CSRF> csrfs = new List<CSRF>();
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<object> users = new List<object>();
if (account != null && account.IsAdmin)
{
int i = 0;
foreach (var user in provider.GetUsers())
{
if (account.Id != user.Id)
{
string 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, "<h1>Your account has been verified, click login to login.</h1><a href=\"./login\" class=\"btn btn-primary\">Login</a>"));
}
}
if (failed)
{
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed to verify account</h1>"));
}
}
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("<h1>Email is taken</h1>");
}
if (nameTaken)
{
b.AppendLine("<h1>Name is taken</h1>");
}
if (properNameTaken)
{
b.AppendLine("<h1>Proper Name is taken");
}
if (passwordNotGoodEnough)
{
b.AppendLine("<h1>Password not good enough</h1>");
}
if (passwordDontMatch)
{
b.AppendLine("<h1>Passwords don't match</h1>");
}
await ctx.SendTextAsync(b.ToString());
}
else
{
provider.CreateUser(Configuration, name, proper_name, email, password);
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("<h1>Incorrect</h1><a href=\"./\">Home</a> | <a href=\"./login\">Login</a>");
}
}
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<CSRF> CsrfTokens { get; set; } = new List<CSRF>();
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<object> users = new List<object>();
foreach (var user in provider.GetUsers())
{
if (!user.IsAdmin && Configuration.Publish == CMSPublish.Admin)
{
//await ctx.SendTextAsync(await RenderHtmlAsync(ctx,"<h1>You can't upload content</h1>"));
continue;
}
if (!(user.IsAdmin || user.IsInvited) && Configuration.Publish == CMSPublish.RequireInvite)
{
//await ctx.SendTextAsync(await RenderHtmlAsync(ctx,"<h1>You can't upload content</h1>"));
continue;
}
if (!(user.IsAdmin || user.IsInvited || user.IsVerified))
{
//await ctx.SendTextAsync(await RenderHtmlAsync(ctx,"<h1>You can't upload content</h1>"));
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, "<h1>You can't upload content</h1>"));
return;
}
if (!(account.IsAdmin || account.IsInvited) && Configuration.Publish == CMSPublish.RequireInvite)
{
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>You can't upload content</h1>"));
return;
}
if (!(account.IsAdmin || account.IsInvited || account.IsVerified))
{
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>You can't upload content</h1>"));
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<object> movies = new List<object>();
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<object> webhooks = new List<object>();
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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 = $"<h1>Delete Track: {oldtrack}?</h1><span>Note the media files will be removed</span><form method=\"POST\" action=\"./edit_tracklist\"><input type=\"hidden\" name=\"track_id\" value=\"{track_id}\"><input type=\"submit\" name=\"operation\" value=\"Yes\" class=\"btn btn-danger\" value=\"Yes\"><a href=\"./edit_tracklist\" class=\"btn btn-primary\">No</a></form>";
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<object> tracks = new List<object>();
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<object> list = new List<object>();
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", "<br>")
};
}
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", "<br>")
};
}
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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, "<h1>You are unauthorized to edit this</h1>"));
}
}
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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, "<h1>You are unauthorized to edit this</h1>"));
}
}
private async Task SeasonPageAsync(ServerContext ctx)
{
string user = usersPathValueServer.GetValue(ctx);
string show = showPathValueServer.GetValue(ctx);
string season = seasonPathValueServer.GetValue(ctx);
var _show = provider.GetShow(user, show);
var _user = provider.GetUserAccount(user);
if (!int.TryParse(season, out var seasonNo))
seasonNo = 1;
var _season = provider.GetSeason(user, show, seasonNo);
var me = GetAccount(ctx);
if (me != null && me.Username != user && !me.IsAdmin)
{
me = null;
}
object value;
if (_show != null && _user != null && _season != null)
{
string showDir = Path.Combine(this.path, user, "show", show);
string thumb = File.Exists(Path.Combine(showDir, $"Season {_season.SeasonNumber.ToString("D2")}", "thumbnail.jpg")) ? $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/Season%20{_season.SeasonNumber.ToString("D2")}/thumbnail.jpg" : $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/{show}/thumbnail.jpg";
List<object> episodes = new List<object>();
for (int i = 1; i <= provider.EpisodeCount(user, show, seasonNo); i++)
{
var item = provider.GetEpisode(user, show, seasonNo, i);
if (item != null)
//var data = GetMovieContentMetaData(Configuration,user,item.Name);
episodes.Add(item.Scriban(Configuration, path, user, show));
}
value = new
{
episodes,
editable = me != null,
userproper = HttpUtility.HtmlEncode(_user.ProperName),
username = HttpUtility.HtmlEncode(user),
rooturl = $"{Configuration.Root.TrimEnd('/')}/",
title = Configuration.Title,
hasshow = true,
seasonthumbnail = thumb,
seasonproper = HttpUtility.HtmlEncode(_season.ProperName),
showproper = HttpUtility.HtmlEncode(_show.ProperName),
showname = HttpUtility.HtmlEncode(_show.Name),
seasondescription = DescriptLinkUtils(_season.Description ?? "").Replace("\n", "<br>")
};
}
else
{
value = new
{
username = user,
rooturl = $"{Configuration.Root.TrimEnd('/')}/",
title = Configuration.Title,
hasshow = false
};
}
await ctx.SendTextAsync(await RenderHtmlAsync(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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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, "<h1>You are unauthorized to edit this</h1>"));
}
}
private async Task ShowPageAsync(ServerContext ctx)
{
string user = usersPathValueServer.GetValue(ctx);
string show = showPathValueServer.GetValue(ctx);
var _show = provider.GetShow(user, show);
var _user = provider.GetUserAccount(user);
var me = GetAccount(ctx,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<object> seasons = new List<object>();
for (int i = 1; i <= provider.SeasonCount(user, show); i++)
{
var item = provider.GetSeason(user, show, i);
if (item != null)
//var data = GetMovieContentMetaData(Configuration,user,item.Name);
seasons.Add(item.Scriban(Configuration, path, user, show));
}
value = new
{
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", "<br>")
};
}
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<object> shows = new List<object>();
foreach (var item in provider.GetShows(user))
{
//var data = GetMovieContentMetaData(Configuration,user,item.Name);
shows.Add(item.Scriban(Configuration, user));
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await pageShows.RenderAsync(new { Shows = shows })));
}
private async Task AlbumsAsync(ServerContext ctx)
{
string user = usersPathValueServer.GetValue(ctx);
List<object> albums = new List<object>();
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($"<a href=\"{HttpUtility.HtmlAttributeEncode(b2.ToString())}\">{HttpUtility.HtmlEncode(b2.ToString())}</a>");
}
else
{
b.Append(HttpUtility.HtmlEncode(url[i]));
}
}
return b.ToString();
}
private async Task AboutAsync(ServerContext ctx)
{
var username = usersPathValueServer.GetValue(ctx);
var accountData = provider.GetUserAccount(username);
if (accountData != null)
{
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await aboutUser.RenderAsync(new { Propername = HttpUtility.HtmlEncode(accountData.ProperName), Aboutme = DescriptLinkUtils(accountData.AboutMe ?? "").Replace("\n", "<br>") })));
}
}
private async Task UserPageAsync(ServerContext ctx)
{
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, await AssetProvider.ReadAllTextAsync("/UserPage.html")));
}
ConcurrentQueue<Func<Task>> tasks = new ConcurrentQueue<Func<Task>>();
private void RegisterMoviePath()
{
movieRouteServer.Add("/", MoviePageAsync);
movieRouteServer.Add("/play", PlayMoviePageAsync);
movieRouteServer.Add("/edit", EditMoviePageAsync);
movieRouteServer.Add("/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<List<Subtitle>>();
string langDir = Path.Combine(path, user, "movie", movie, "subtitles", lang);
Directory.CreateDirectory(langDir);
string langFile = Path.Combine(langDir, $"{movie}.json");
string vtt = Path.Combine(langDir, $"{movie}.vtt");
string srt = Path.Combine(langDir, $"{movie}.srt");
File.WriteAllText(langFile, JsonConvert.SerializeObject(json));
using (var vttFile = File.CreateText(vtt))
{
Subtitle.ToWebVTT(vttFile, json);
}
using (var srtFile = File.CreateText(srt))
{
Subtitle.ToSrt(srtFile, json);
}
await ctx.SendTextAsync("Success");
return;
}
}
ctx.StatusCode = 400;
await ctx.SendTextAsync("Fail");
}
private async Task 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<object> languages = new List<object>();
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<List<Subtitle>>();
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<object> languages = new List<object>();
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<object> paths = new List<object>();
if (__path.Length > 0)
{
string up = Path.GetDirectoryName($"/{__path}").TrimStart('/');
string path = $"./extras?path={System.Web.HttpUtility.UrlEncode(up)}";
paths.Add(new { Path = path, Type = "[PARENT]", Name = "Up" });
}
else
{
paths.Add(new { Path = "./", Type = "[PARENT]", Name = "Up" });
}
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<object> paths = new List<object>();
if (__path.Length > 0)
{
string up = Path.GetDirectoryName($"/{__path}").TrimStart('/');
string path = $"./extras?path={System.Web.HttpUtility.UrlEncode(up)}";
paths.Add(new { Path = path, Type = "[PARENT]", Name = "Up" });
}
else
{
paths.Add(new { Path = "./", Type = "[PARENT]", Name = "Up" });
}
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<object> paths = new List<object>();
if (__path.Length > 0)
{
string up = Path.GetDirectoryName($"/{__path}").TrimStart('/');
string path = $"./extras?path={System.Web.HttpUtility.UrlEncode(up)}";
paths.Add(new { Path = path, Type = "[PARENT]", Name = "Up" });
}
else
{
paths.Add(new { Path = "./", Type = "[PARENT]", Name = "Up" });
}
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<string> paths = new List<string>();
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<byte[]> Pieces { get; private set; } = new List<byte[]>();
public byte[] CalculateCurPiece()
{
using (var sha1 = SHA1.Create())
{
return sha1.ComputeHash(Curpiece, 0, CurpieceOffset);
}
}
public byte[] Curpiece = new byte[PieceLength];
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
long len;
public override long Length => len;
public bool HasPiece => CurpieceOffset > 0;
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public override void Flush()
{
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
}
public override void Write(byte[] buffer, int offset, int count)
{
for (int i = 0; i < count; i++)
{
byte curByte = buffer[i + offset];
if (CurpieceOffset < PieceLength)
{
Curpiece[CurpieceOffset++] = curByte;
}
else
{
Pieces.Add(this.CalculateCurPiece());
CurpieceOffset = 0;
Curpiece[CurpieceOffset++] = curByte;
}
}
len += count;
}
}
private async Task 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<string[]> paths = new List<string[]>();
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<long> lengths = new List<long>();
foreach (var _path in paths.Select<string[], string>(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<string[]> paths2 = new List<string[]>();
GetExtras(paths2, new string[] { "extras" }, extrasDir);
foreach (var _path in paths2.Select<string[], string>(e => Path.Combine(e)))
using (var movieStrm = File.OpenRead(Path.Combine(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<string[]> paths = new List<string[]>();
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<long> lengths = new List<long>();
foreach (var _path in paths.Select<string[], string>(e => Path.Combine(e)))
using (var movieStrm = File.OpenRead(Path.Combine(movieDir, _path)))
{
lengths.Add(movieStrm.Length);
await movieStrm.CopyToAsync(strm);
}
Torrent torrent = new Torrent();
torrent.AnnounceList.AddRange(Configuration.BittorrentTrackers);
torrent.CreationDate = DateTime.Now;
torrent.UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/";
torrent.Info.PieceLength = PieceStream.PieceLength;
torrent.Info.Name = movie;
foreach (var piece in strm.Pieces)
{
torrent.Info.Pieces.Add(piece);
}
if (strm.HasPiece) torrent.Info.Pieces.Add(strm.CalculateCurPiece());
for (int i = 0; i < paths.Count; i++)
{
var _path = paths[i];
var len = lengths[i];
torrent.Info.Files.Add(new TorrentInfoFile() { Path = _path, Length = len });
}
string torrentFile = Path.Combine(movieDir, $"{movie}.torrent");
using (var file = File.Create(torrentFile))
{
await torrent.ToTorrentFile().WriteToStreamAsync(file);
}
if (Directory.Exists(extrasDir))
{
List<string[]> paths2 = new List<string[]>();
GetExtras(paths2, new string[] { "extras" }, extrasDir);
foreach (var _path in paths2.Select<string[], string>(e => Path.Combine(e)))
using (var movieStrm = File.OpenRead(Path.Combine(movieDir, _path)))
{
lengths.Add(movieStrm.Length);
await movieStrm.CopyToAsync(strm);
}
paths.AddRange(paths2);
torrent = new Torrent();
torrent.AnnounceList.AddRange(Configuration.BittorrentTrackers);
torrent.CreationDate = DateTime.Now;
torrent.UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/movie/";
torrent.Info.PieceLength = PieceStream.PieceLength;
torrent.Info.Name = movie;
foreach (var piece in strm.Pieces)
{
torrent.Info.Pieces.Add(piece);
}
if (strm.HasPiece) torrent.Info.Pieces.Add(strm.CalculateCurPiece());
for (int i = 0; i < paths.Count; i++)
{
var _path = paths[i];
var len = lengths[i];
torrent.Info.Files.Add(new TorrentInfoFile() { Path = _path, Length = len });
}
torrentFile = Path.Combine(movieDir, $"{movie}_withextras.torrent");
using (var file = File.Create(torrentFile))
{
await torrent.ToTorrentFile().WriteToStreamAsync(file);
}
}
}
private void GetExtras(List<string[]> paths2, string[] torrentPath, string extrasDir)
{
foreach (var dir in Directory.GetDirectories(extrasDir))
{
string dirname = Path.GetFileName(dir);
string[] path = new string[torrentPath.Length + 1];
Array.Copy(torrentPath, path, torrentPath.Length);
path[path.Length - 1] = dirname;
GetExtras(paths2, path, dir);
}
foreach (var file in Directory.GetFiles(extrasDir))
{
string filename = Path.GetFileName(file);
string[] path = new string[torrentPath.Length + 1];
Array.Copy(torrentPath, path, torrentPath.Length);
path[path.Length - 1] = filename;
paths2.Add(path);
}
}
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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<string[]> paths = new List<string[]>();
for (int i = 1; i <= provider.SeasonCount(user, show); i++)
{
var season = provider.GetSeason(user, show, i);
if (season != null)
{
string season_name = $"Season {i.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<long> lengths = new List<long>();
foreach (var _path in paths.Select<string[], string>(e => Path.Combine(e)))
using (var movieStrm = File.OpenRead(Path.Combine(showDir, _path)))
{
lengths.Add(movieStrm.Length);
await movieStrm.CopyToAsync(strm);
}
Torrent torrent = new Torrent();
torrent.AnnounceList.AddRange(Configuration.BittorrentTrackers);
torrent.CreationDate = DateTime.Now;
torrent.UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/";
torrent.Info.PieceLength = PieceStream.PieceLength;
torrent.Info.Name = show;
foreach (var piece in strm.Pieces)
{
torrent.Info.Pieces.Add(piece);
}
if (strm.HasPiece) torrent.Info.Pieces.Add(strm.CalculateCurPiece());
for (int i = 0; i < paths.Count; i++)
{
var _path = paths[i];
var len = lengths[i];
torrent.Info.Files.Add(new TorrentInfoFile() { Path = _path, Length = len });
}
string torrentFile = Path.Combine(showDir, $"{show}.torrent");
using (var file = File.Create(torrentFile))
{
await torrent.ToTorrentFile().WriteToStreamAsync(file);
}
if (Directory.Exists(extrasDir))
{
List<string[]> paths2 = new List<string[]>();
GetExtras(paths2, new string[] { "extras" }, extrasDir);
foreach (var _path in paths2.Select<string[], string>(e => Path.Combine(e)))
using (var movieStrm = File.OpenRead(Path.Combine(showDir, _path)))
{
lengths.Add(movieStrm.Length);
await movieStrm.CopyToAsync(strm);
}
paths.AddRange(paths2);
torrent = new Torrent();
torrent.AnnounceList.AddRange(Configuration.BittorrentTrackers);
torrent.CreationDate = DateTime.Now;
torrent.UrlList = $"{Configuration.Root.TrimEnd('/')}/content/{user}/show/";
torrent.Info.PieceLength = PieceStream.PieceLength;
torrent.Info.Name = show;
foreach (var piece in strm.Pieces)
{
torrent.Info.Pieces.Add(piece);
}
if (strm.HasPiece) torrent.Info.Pieces.Add(strm.CalculateCurPiece());
for (int i = 0; i < paths.Count; i++)
{
var _path = paths[i];
var len = lengths[i];
torrent.Info.Files.Add(new TorrentInfoFile() { Path = _path, Length = len });
}
torrentFile = Path.Combine(showDir, $"{show}_withextras.torrent");
using (var file = File.Create(torrentFile))
{
await torrent.ToTorrentFile().WriteToStreamAsync(file);
}
}
}
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
private void ScheduleFFmpeg(string command)
{
ScheduleTask(async () =>
{
using (Process process = new Process())
{
process.StartInfo.Arguments = command;
process.StartInfo.FileName = "ffmpeg";
if (process.Start())
{
await Task.Run(process.WaitForExit);
}
}
});
}
public void ScheduleTask(Func<Task> task)
{
tasks.Enqueue(task);
}
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, "<h1>Success</h1><a href=\"./edit\">&lt;- Back</a>"));
return;
}
}
}
await ctx.SendTextAsync(await RenderHtmlAsync(ctx, "<h1>Failed</h1><a href=\"./edit\">&lt;- Back</a>"));
}
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, "<h1>You are unauthorized to edit this</h1>"));
}
}
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, "<h1>You are unauthorized to edit this</h1>"));
}
}
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<object> subtitles = new List<object>();
foreach (var subtitle in data.SubtitlesStreams)
{
string languageName = "Unknown";
string langCode = "";
foreach (var lang in Languages)
{
if (lang.LangCode == subtitle.LanguageCode)
{
languageName = lang.LangTitle;
langCode = lang.LangCodeVideo;
}
}
subtitles.Add(new { langcode = langCode, name = languageName, file = subtitle.VttUrl });
}
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<object> subtitles = new List<object>();
foreach (var subtitle in data.SubtitlesStreams)
{
string languageName = "Unknown";
string langCode = "";
foreach (var lang in Languages)
{
if (lang.LangCode == subtitle.LanguageCode)
{
languageName = lang.LangTitle;
langCode = lang.LangCodeVideo;
}
}
subtitles.Add(new { langcode = langCode, name = languageName, file = subtitle.VttUrl });
}
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<Episode> GetEpisodesFromShows(IEnumerable<Show> shows)
{
foreach (var show in shows)
{
foreach (var episode in GetEpisodesFromShow(show))
yield return episode;
}
}
IEnumerable<Episode> 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<Episode> 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", "<br>")
};
}
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", "<b>email</b>: the email of account</br><b>password</b>: the password of account</br><b>type</b>: json or cookie"), "POST", "Users");
swagmeServer.Add("/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", "<b>user</b>: the user of the movies<br><b>type</b>: 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", "<b>user</b>: the user of the show<br><b>show</b>: the show name<br><b>season</b>: the season number starting from 1<br><b>episode</b>: the episode number starting from 1"), "GET", "Shows");
swagmeServer.Add("/GetShows", ApiGetShowsAsync, new SwagmeDocumentation("Get a list of shows", "<b>user</b>: the user of the shows<br><b>type</b>: 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", "<b>user</b>: the user of the show</br><b>show</b>: the show</br><b>type</b>: 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", "<b>user</b>: the user of the show</br><b>show</b>: the show</br><b>season</b>: the season<br><b>type</b>: 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", "<b>user</b>: the user of the albums<br><b>type</b>: 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<UserAccount> users = new List<UserAccount>();
foreach (var user in provider.GetUsers())
{
if (!user.IsAdmin && Configuration.Publish == CMSPublish.Admin)
{
//await ctx.SendTextAsync(await RenderHtmlAsync(ctx,"<h1>You can't upload content</h1>"));
continue;
}
if (!(user.IsAdmin || user.IsInvited) && Configuration.Publish == CMSPublish.RequireInvite)
{
//await ctx.SendTextAsync(await RenderHtmlAsync(ctx,"<h1>You can't upload content</h1>"));
continue;
}
if (!(user.IsAdmin || user.IsInvited || user.IsVerified))
{
//await ctx.SendTextAsync(await RenderHtmlAsync(ctx,"<h1>You can't upload content</h1>"));
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<Show> shows = new List<Show>();
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 <a href=\"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{HttpUtility.UrlEncode(show.Name)}/season/{item.SeasonNumber}/episode/{item.EpisodeNumber}/\">here</a><br>{item.Description}";
entry.LastUpdated = item.LastUpdated;
entry.Published = item.CreationTime;
await rss.Write(entry);
}
}
await ctx.SendTextAsync(sw.GetStringBuilder().ToString(), "application/rss+xml");
}
}
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<Season> seasons = new List<Season>();
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 <a href=\"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{HttpUtility.UrlEncode(s.Name)}/season/{item.SeasonNumber}/episode/{item.EpisodeNumber}/\">here</a><br>{item.Description}";
entry.LastUpdated = item.LastUpdated;
entry.Published = item.CreationTime;
await rss.Write(entry);
}
}
await ctx.SendTextAsync(sw.GetStringBuilder().ToString(), "application/rss+xml");
}
}
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<Episode> episodes = new List<Episode>();
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 <a href=\"{Configuration.Root.TrimEnd('/')}/user/{user}/show/{HttpUtility.UrlEncode(s.Name)}/season/{item.SeasonNumber}/episode/{item.EpisodeNumber}/\">here</a><br>{item.Description}";
entry.LastUpdated = item.LastUpdated;
entry.Published = item.CreationTime;
await rss.Write(entry);
}
}
await ctx.SendTextAsync(sw.GetStringBuilder().ToString(), "application/rss+xml");
}
}
private 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($"<track label=\"{languageName}\" kind=\"subtitles\" srclang=\"{langCode}\" src=\"{subtitle.VttUrl}\"/>");
}
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<Movie> movies = new List<Movie>();
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 <a href=\"{Configuration.Root.TrimEnd('/')}/user/{user}/movie/{HttpUtility.UrlEncode(item.Name)}/\">here</a><br>{item.Description}";
entry.LastUpdated = item.LastUpdated;
entry.Published = item.CreationTime;
await rss.Write(entry);
}
}
await ctx.SendTextAsync(sw.GetStringBuilder().ToString(), "application/rss+xml");
}
}
private 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<Album> albums = new List<Album>();
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 <a href=\"{Configuration.Root.TrimEnd('/')}/user/{user}/album/{HttpUtility.UrlEncode(item.Name)}/\">here</a><br>{item.Description}";
entry.LastUpdated = item.LastUpdated;
entry.Published = item.CreationTime;
await rss.Write(entry);
}
}
await ctx.SendTextAsync(sw.GetStringBuilder().ToString(), "application/rss+xml");
}
}
private string IniEscape(string properName)
{
StringBuilder b = new StringBuilder();
foreach (var c in properName)
{
if (c == '\\' || c == ';' || c == '\"' || c == '\'' || c == '#' || c == '=' || c == ':')
{
b.Append($"\\{c}");
}
else if (c == '\0')
{
b.Append("\\0");
}
else if (c == '\b')
{
b.Append("\\b");
}
else if (c == '\a')
{
b.Append("\\a");
}
else if (c == '\t')
{
b.Append("\\t");
}
else if (c == '\r')
{
b.Append("\\r");
}
else if (c == '\n')
{
b.Append("\\n");
}
else if (c > sbyte.MaxValue)
{
b.Append($"\\x{((int)c).ToString("X4")}");
}
else
{
b.Append(c);
}
}
return b.ToString();
}
private async Task<string> RenderHtmlAsync(ServerContext ctx, string body, bool isHome = false, bool isDevcenter = false, bool isLogin = false, bool isUpload = false)
{
List<object> _urls = new List<object>();
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<string> RenderDevcenterAsync()
{
return await pageDevcenter.RenderAsync(new { title = Configuration.Title, rooturl = $"{Configuration.Root.TrimEnd('/')}/" });
}
private async Task<string> RenderIndexAsync()
{
return await pageIndex.RenderAsync(new { title = Configuration.Title, rooturl = $"{Configuration.Root.TrimEnd('/')}/" });
}
public async Task<string> RenderUpload1Async(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<bool> BeforeAsync(ServerContext ctx)
{
try
{
return await inside.BeforeAsync(ctx);
}
catch (InvalidCSRFException)
{
await ctx.SendTextAsync("<h1>CSRF token invalid</h1><p>Press back, refresh and try again</p>");
}
return true;
}
public async Task PostAsync(ServerContext ctx)
{
try
{
await inside.PostAsync(ctx);
}
catch (InvalidCSRFException)
{
await ctx.SendTextAsync("<h1>CSRF token invalid</h1><p>Press back, refresh and try again</p>");
}
}
public async Task OptionsAsync(ServerContext ctx)
{
try
{
await inside.OptionsAsync(ctx);
}
catch (InvalidCSRFException)
{
await ctx.SendTextAsync("<h1>CSRF token invalid</h1><p>Press back, refresh and try again</p>");
}
}
public async Task OtherAsync(ServerContext ctx)
{
try
{
await inside.OtherAsync(ctx);
}
catch (InvalidCSRFException)
{
await ctx.SendTextAsync("<h1>CSRF token invalid</h1><p>Press back, refresh and try again</p>");
}
}
public async Task GetAsync(ServerContext ctx)
{
try
{
await inside.GetAsync(ctx);
}
catch (InvalidCSRFException)
{
await ctx.SendTextAsync("<h1>CSRF token invalid</h1><p>Press back, refresh and try again</p>");
}
}
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<SubtitleStream> SubtitlesStreams { get; set; } = new List<SubtitleStream>();
}
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", "<br>");
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", "<br>");
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", "<br>");
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}");
}
}
}
}
}