using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Serialization; namespace Tesses.CMS.Client { public enum TessesCMSContentType { Movie, Show, Album, MusicVideo, SoftwareProject, Other } public class TessesCMSClient : IDisposable { private static HttpClient CreateHttpClient() { HttpClientHandler httpClientHandler=new HttpClientHandler(); httpClientHandler.AllowAutoRedirect=true; httpClientHandler.UseCookies=true; HttpClient client=new HttpClient(httpClientHandler); return client; } internal HttpClient client; internal string rooturl; public HttpClient Client => client; public string RootUrl { get=>$"{rooturl}/"; set { rooturl = value.TrimEnd('/'); } } bool ownsClient; public TessesCMSClient(string url,HttpClient client,bool ownsClient) { this.ownsClient=ownsClient; this.client = client; rooturl = url.TrimEnd('/'); } public TessesCMSClient(HttpClient client,bool ownsClient) : this("https://tessesstudios.com/",client,ownsClient) { } public TessesCMSClient(string url) : this(url,CreateHttpClient(),true) { } public TessesCMSClient() : this(CreateHttpClient(),true) { } public async Task GetBrandingAsync() { return JsonConvert.DeserializeObject(await client.GetStringAsync($"{rooturl}/api/v1/Branding")); } public void StartEvents(Action evt,CancellationToken token=default) { Task.Run(async()=>{ var clt=await client.GetSSEClientAsync($"{rooturl}/api/v1/Updates"); await foreach(var item in clt.ReadEventsAsync(token)) { evt(item.ParseJson()); } }).Wait(0); } public async Task CreateAsync(string urlname, string propername, string description, TessesCMSContentType type=TessesCMSContentType.Movie,CancellationToken token=default) { Dictionary kvp= new Dictionary { { "name", urlname }, { "proper_name", propername }, { "description", description }, { "type", type.ToString().ToLower() } }; using(var res=await client.PostAsync($"{rooturl}/upload",new FormUrlEncodedContent(kvp),token)) { return res.StatusCode != System.Net.HttpStatusCode.Unauthorized; } } public async Task UploadFilePutAsync(string url,string file,CancellationToken token=default,IProgress progress=null) { using(var f = File.OpenRead(file)) return await UploadFilePutAsync(url,f,token,progress); } public async Task UploadFilePutAsync(string url, Stream src,CancellationToken token=default,IProgress progress=null) { var request = new HttpRequestMessage(HttpMethod.Put,url); request.Content = new ProgressContent(src,progress); using(var res=await client.SendAsync(request,token)) { return res.StatusCode != System.Net.HttpStatusCode.Unauthorized; } } public async Task DownloadFileAsync(string url,string dest,CancellationToken token=default,IProgress progress=null) { using(var f = File.Open(dest,FileMode.OpenOrCreate,FileAccess.Write)) await DownloadFileAsync(url,f,token,progress); } public async Task DownloadFileAsync(string url,Stream dest,CancellationToken token=default,IProgress progress=null) { long offset=0; long total=0; if(dest.CanSeek) { offset=dest.Length; dest.Seek(offset,SeekOrigin.Begin); } HttpRequestMessage message=new HttpRequestMessage(HttpMethod.Get,url); if(offset > 0) { message.Headers.Range=new System.Net.Http.Headers.RangeHeaderValue(offset,null); } var resp=await client.SendAsync(message); if(resp.StatusCode == System.Net.HttpStatusCode.RequestedRangeNotSatisfiable) return; resp.EnsureSuccessStatusCode(); total = resp.StatusCode == System.Net.HttpStatusCode.PartialContent ? resp.Content.Headers.ContentLength.GetValueOrDefault() + offset : resp.Content.Headers.ContentLength.GetValueOrDefault(); int read=0; byte[] buffer=new byte[1024]; using(var srcStrm = await resp.Content.ReadAsStreamAsync()) { do { if(token.IsCancellationRequested) return; read = await srcStrm.ReadAsync(buffer,0,buffer.Length,token); if(token.IsCancellationRequested) return; await dest.WriteAsync(buffer,0,read,token); offset += read; if(total > 0) progress?.Report((double)offset / (double)total); } while(read>0); } resp.Dispose(); } public ShowClient Shows => new ShowClient(this); public MovieClient Movies => new MovieClient(this); public UserClient Users => new UserClient(this); public void Dispose() { if(this.ownsClient) this.client.Dispose(); } } public class Branding { [JsonProperty("title")] public string Title {get;set;}=""; } [JsonConverter(typeof(StringEnumConverter),typeof(SnakeCaseNamingStrategy))] public enum EventType { MovieCreate, MovieUpdate, ShowCreate, ShowUpdate } public class CMSEvent { [JsonProperty("eventtype")] public EventType Type {get;set;} [JsonProperty("username")] public string Username {get;set;}=""; [JsonProperty("userpropername")] public string UserProperName {get;set;}=""; [JsonProperty("name")] public string Name {get;set;}=""; [JsonProperty("propername")] public string ProperName {get;set;}=""; [JsonProperty("description")] public string Description {get;set;}=""; [JsonProperty("body")] public string Body {get;set;}=""; } internal class ProgressContent : HttpContent { private Stream src; private IProgress progress; public ProgressContent(Stream src, IProgress progress) { this.src = src; this.progress = progress; } protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) { int read = 0; byte[] buffer=new byte[1024]; double offset=0; double length = src.Length; do { read = await src.ReadAsync(buffer,0,buffer.Length); offset += read; await stream.WriteAsync(buffer,0,read); if(length != 0) progress?.Report(offset / length); } while(read != 0); } protected override bool TryComputeLength(out long length) { length = src.Length; return true; } } public class UserClient { TessesCMSClient client; internal UserClient(TessesCMSClient client) { this.client = client; } public async Task LogoutAsync() { (await client.client.GetAsync("/logout")).Dispose(); } public async Task CreateTokenAsync(string email,string password) { Dictionary us=new Dictionary(); us.Add("email",email); us.Add("password",password); us.Add("type","json"); using(var res=await client.client.PostAsync($"{client.rooturl}/api/v1/Login", new FormUrlEncodedContent(us))) return JsonConvert.DeserializeObject( await res.Content.ReadAsStringAsync()); } public string LoginToken { get { if(client.client.DefaultRequestHeaders.Contains("Authorization")) return client.client.DefaultRequestHeaders.Authorization.Parameter; return ""; } set{ if(string.IsNullOrWhiteSpace(value)) if(client.client.DefaultRequestHeaders.Contains("Authorization")) client.client.DefaultRequestHeaders.Remove("Authorization"); else client.client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",value); } } public async IAsyncEnumerable GetUsersAsync() { foreach(var user in JsonConvert.DeserializeObject>(await client.client.GetStringAsync($"{client.rooturl}/api/v1/GetPublicUsers"))) { yield return user; } } } public class LoginToken { [JsonProperty("success")] public bool Success {get;set;}=false; [JsonProperty("cookie")] public string Cookie {get;set;}=""; } public class ShowClient { TessesCMSClient client; internal ShowClient(TessesCMSClient client) { this.client = client; } public async IAsyncEnumerable GetShowsAsync(string user) { foreach(var item in JsonConvert.DeserializeObject>(await client.client.GetStringAsync($"{client.rooturl}/api/v1/GetShows?user={HttpUtility.UrlEncode(user)}"))) { yield return item; } } public async IAsyncEnumerable GetSeasonsAsync(string user,string show) { foreach(var item in JsonConvert.DeserializeObject>(await client.client.GetStringAsync($"{client.rooturl}/api/v1/GetSeasons?user={HttpUtility.UrlEncode(user)}&show={HttpUtility.UrlEncode(show)}"))) { yield return item; } } public async IAsyncEnumerable GetEpisodesAsync(string user,string show,int season) { foreach(var item in JsonConvert.DeserializeObject>(await client.client.GetStringAsync($"{client.rooturl}/api/v1/GetEpisodes?user={HttpUtility.UrlEncode(user)}&show={HttpUtility.UrlEncode(show)}&season={season}"))) { yield return item; } } public async IAsyncEnumerable GetEpisodesAsync(string user,string show) { await foreach(var season in GetSeasonsAsync(user,show)) { List episodes=new List(); await foreach(var episode in GetEpisodesAsync(user,show,season.SeasonNumber)) { episodes.Add(episode); } yield return new SeasonWithEpisodes(season,episodes); } } public async IAsyncEnumerable GetEpisodesAsync(string user) { await foreach(var show in GetShowsAsync(user)) { List seasons=new List(); await foreach(var episode in GetEpisodesAsync(user,show.Name)) { seasons.Add(episode); } yield return new ShowWithSeasonsAndEpisodes(show,seasons); } } public async Task GetShowContentMetadataAsync(string user,string show) { return JsonConvert.DeserializeObject(await client.client.GetStringAsync($"{client.rooturl.TrimEnd('/')}/api/v1/ShowFile?show={show}&user={user}&type=json")); } public async Task DownloadThumbnailAsync(string user,string show,string dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/show/{show}/thumbnail.jpg",dest,token,progress); } public async Task DownloadThumbnailAsync(string user,string show,Stream dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/show/{show}/thumbnail.jpg",dest,token,progress); } } public class MovieClient { TessesCMSClient client; internal MovieClient(TessesCMSClient client) { this.client = client; } public async IAsyncEnumerable GetMoviesAsync(string user) { foreach(var item in JsonConvert.DeserializeObject>(await client.client.GetStringAsync($"{client.rooturl}/api/v1/GetMovies?user={HttpUtility.UrlEncode(user)}"))) { yield return item; } } public async Task GetMovieContentMetadataAsync(string user,string movie) { return JsonConvert.DeserializeObject(await client.client.GetStringAsync($"{client.rooturl.TrimEnd('/')}/api/v1/MovieFile?movie={movie}&user={user}&type=json")); } public async Task UploadExtrasAsync(string user, string movie, string dir, CancellationToken token=default,IProgress progress=null) { List<(string localPath,string removePath)> items=new List<(string localPath, string removePath)>(); async Task EnumerateDir(string local, string remote) { if(!string.IsNullOrWhiteSpace(remote)) await CreateExtraDirectoryAsync(user,movie,remote,token); foreach(var dir in Directory.EnumerateDirectories(local)) { await EnumerateDir(dir,$"{remote}/{Path.GetFileName(dir)}"); } foreach(var file in Directory.EnumerateFiles(local)) { string name = $"{remote}/{Path.GetFileName(file)}"; items.Add((file,name)); } } await EnumerateDir(dir,""); for(int i = 0;i(e=>{ double j = i + e; progress?.Report(j / items.Count); })); } } public async Task UploadExtraAsync(string user, string movie,string extra, string src,CancellationToken token=default,IProgress progress=null) { string url = $"{client.rooturl}/api/v1/MovieExtra?user={user}&movie={movie}&extra={HttpUtility.UrlEncode(extra.TrimStart('/'))}"; return await client.UploadFilePutAsync(url,src,token,progress); } public async Task UploadExtraAsync(string user, string movie,string extra, Stream src,CancellationToken token=default,IProgress progress=null) { string url = $"{client.rooturl}/api/v1/MovieExtra?user={user}&movie={movie}&extra={HttpUtility.UrlEncode(extra.TrimStart('/'))}"; return await client.UploadFilePutAsync(url,src,token,progress); } public async Task UploadMovieAsync(string user, string movie, string src,CancellationToken token=default,IProgress progress=null) { string url = $"{client.rooturl}/api/v1/MovieFile?user={user}&movie={movie}&type=movie"; return await client.UploadFilePutAsync(url,src,token,progress); } public async Task UploadMovieAsync(string user, string movie, Stream src,CancellationToken token=default,IProgress progress=null) { string url = $"{client.rooturl}/api/v1/MovieFile?user={user}&movie={movie}&type=movie"; return await client.UploadFilePutAsync(url,src,token,progress); } public async Task UploadPosterAsync(string user, string movie, string src,CancellationToken token=default,IProgress progress=null) { string url = $"{client.rooturl}/api/v1/MovieFile?user={user}&movie={movie}&type=poster"; return await client.UploadFilePutAsync(url,src,token,progress); } public async Task UploadPosterAsync(string user, string movie, Stream src,CancellationToken token=default,IProgress progress=null) { string url = $"{client.rooturl}/api/v1/MovieFile?user={user}&movie={movie}&type=poster"; return await client.UploadFilePutAsync(url,src,token,progress); } public async Task UploadThumbnailAsync(string user, string movie, string src,CancellationToken token=default,IProgress progress=null) { string url = $"{client.rooturl}/api/v1/MovieFile?user={user}&movie={movie}&type=thumbnail"; return await client.UploadFilePutAsync(url,src,token,progress); } public async Task UploadThumbnailAsync(string user, string movie, Stream src,CancellationToken token=default,IProgress progress=null) { string url = $"{client.rooturl}/api/v1/MovieFile?user={user}&movie={movie}&type=thumbnail"; return await client.UploadFilePutAsync(url,src,token,progress); } public async Task CreateExtraDirectoryAsync(string user,string movie, string extraDir,CancellationToken token=default) { string parent=""; string name=Path.GetFileName(extraDir); try{ parent=Path.GetDirectoryName(extraDir.TrimStart('/')); } catch(Exception) { parent=""; } Dictionary kvp= new Dictionary { { "name", name }, { "parent", parent }, }; using(var res=await client.client.PostAsync($"{client.rooturl}/user/{user}/movie/{movie}/mkdir",new FormUrlEncodedContent(kvp),token)) { return res.StatusCode != System.Net.HttpStatusCode.Unauthorized; } } public async Task DownloadMovieAsync(string user,string movie,string dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/{movie}.mp4",dest,token,progress); } public async Task DownloadMovieAsync(string user,string movie,Stream dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/{movie}.mp4",dest,token,progress); } public async Task DownloadThumbnailAsync(string user,string movie,string dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/thumbnail.jpg",dest,token,progress); } public async Task DownloadThumbnailAsync(string user,string movie,Stream dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/thumbnail.jpg",dest,token,progress); } public async Task DownloadPosterAsync(string user,string movie,string dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/poster.jpg",dest,token,progress); } public async Task DownloadPosterAsync(string user,string movie,Stream dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/poster.jpg",dest,token,progress); } public async Task DownloadExtraAsync(string user,string movie,string path,string dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/extras/{HttpUtility.UrlPathEncode(path.TrimStart('/'))}",dest,token,progress); } public async Task DownloadExtraAsync(string user,string movie,string path,Stream dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/extras/{HttpUtility.UrlPathEncode(path.TrimStart('/'))}",dest,token,progress); } public async Task DownloadTorrentAsync(string user,string movie,string dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/{movie}.torrent",dest,token,progress); } public async Task DownloadTorrentAsync(string user,string movie,Stream dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/{movie}.torrent",dest,token,progress); } public async Task DownloadTorrentWithExtrasAsync(string user,string movie,string dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/{movie}_withextras.torrent",dest,token,progress); } public async Task DownloadTorrentWithExtrasAsync(string user,string movie,Stream dest,CancellationToken token=default,IProgress progress=null) { await client.DownloadFileAsync($"{client.rooturl}/content/{user}/movie/{movie}/{movie}_withextras.torrent",dest,token,progress); } public async Task DownloadExtrasAsync(string user,string movie, string dir, CancellationToken token=default,IProgress progress=null) { var r = await GetMovieContentMetadataAsync(user,movie); List<(string inDir,string outDir)> items=new List<(string inDir, string outDir)>(); void DownloadExtraDir(List files,string outDir,string inDir) { Directory.CreateDirectory(outDir); foreach(var item in files) { if(item.IsDir) { DownloadExtraDir(item.Items,Path.Combine(outDir,item.Name),$"{inDir}/{item.Name}"); } else { items.Add(($"{inDir}/{item.Name}",Path.Combine(outDir,item.Name))); } } } DownloadExtraDir(r.ExtraStreams,dir,""); for(int i = 0;i(e=>{ double j = i + e; progress?.Report(j / items.Count); })); } } public async Task CreateAsync(string urlname, string propername, string description) { return await client.CreateAsync(urlname,propername,description); } } }