const BUILD_TIME = comptime DateTime.NowEpoch; var TYTDResources = [ {path="/beer.min.css",value=embed("beer.min.css")}, {path="/beer.min.js",value=embed("beer.min.js")}, {path="/material-symbols-outlined.woff2",value=embed("material-symbols-outlined.woff2")}, {path="/material-symbols-rounded.woff2",value=embed("material-symbols-rounded.woff2")}, {path="/material-symbols-sharp.woff2",value=embed("material-symbols-sharp.woff2")}, {path="/material-symbols-subset.woff2",value=embed("material-symbols-subset.woff2")}, {path="/htmx.min.js",value=embed("htmx.min.js")}, {path="/favicon.ico",value=embed("favicon.ico")}, {path="/tytd.svg",value=embed("tytd.svg")}, {path="/tytd-128.png",value=embed("tytd-128.png")}, {path="/tytd-192.png",value=embed("tytd-192.png")}, {path="/tytd-256.png",value=embed("tytd-256.png")}, {path="/tytd-384.png",value=embed("tytd-384.png")}, {path="/tytd-512.png",value=embed("tytd-512.png")}, {path="/tytd-1024.png",value=embed("tytd-1024.png")}, {path="/loading-indicator.svg",value=embed("loading-indicator.svg")}, {path="/theme.css",value=embed("theme.css")}, {path="/video.min.js",value=embed("video.min.js")}, {path="/video-js.css",value=embed("video-js.css")}, {path="/wavy.svg",value=embed("wavy.svg")}, {path="/site.webmanifest",value=embed("site.webmanifest")}, {path="/offline-progress.html",value=embed("offline-progress.html")}, {path="/offline.html",value=embed("offline.html")}, {path="/offline.js", value=embed("offline.js")}, ]; const fileNames = []; each(var item : TYTDResources) { fileNames.Add(item.path); } const service_worker_str = embed("service_worker.js").ToString().Replace("[\"<@ASSETS@>\"]",Json.Encode(fileNames)).Replace("<@BUILD_TIME@>",BUILD_TIME.ToString()); var times=1; class TYTDApp { private OOBE_STATE = { tag = "UnknownPC", pollHours = 3, pollMinutes = 0, pollSeconds = 0, enablePlugins = true }; private TYTD; public TYTDApp() { Console.WriteLine($"Built at {new DateTime(BUILD_TIME).ToString()}"); var tytdfs = new SubdirFilesystem(FS.Local, GetTYTDDir()); this.TYTD = new TYTD.Downloader(tytdfs,FS.MakeFull(GetTYTDDir())); this.TYTD.Start(); } public Handle(ctx) { if(ctx.Path == "/service_worker.js" || fileNames.Contains(ctx.Path)) { const inm=ctx.RequestHeaders.TryGetFirst("If-None-Match"); if(TypeIsString(inm)) { const strs = inm.Split(", "); each(var item : strs) { if(item == $"W/\"{BUILD_TIME}\"" || item == $"\"{BUILD_TIME}\"") { ctx.StatusCode = 304; ctx.WriteHeaders(); return true; } } } ctx.WithHeader("ETag",$"W/\"{BUILD_TIME}\""); } if(ctx.Path == "/service_worker.js") { ctx.WithMimeType(Net.Http.MimeType("service_worker.js")).SendText(service_worker_str); return true; } if(ctx.Path == "/api/v1/auth") { if(ctx.Method == "POST") { const req=ctx.ReadJson(); const result = this.TYTD.Auth(req.username, req.password); if(result) { ctx.SendJson({ success = true, flags = result.flags }); } else { ctx.SendJson({ success=false }); } return true; } return false; } if(ctx.Path == "/api/v1/login") { if(ctx.Method == "POST") { const req=ctx.ReadJson(); const result = this.TYTD.Login(req.username, req.password, false); if(result) { ctx.SendJson({ success = true, token = result }); } else { ctx.SendJson({ success=false }); } return true; } else { ctx.StatusCode=307; ctx.SendRedirect("/login"); return true; } } if(ctx.Path == "/login") { var incorrect=false; const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/"; const username = ctx.QueryParams.TryGetFirst("username") ?? ""; const password = ctx.QueryParams.TryGetFirst("password") ?? ""; if(ctx.Method == "POST") { const result = this.TYTD.Login(username, password,true); if(result) { ctx.StatusCode = 303; var date = new DateTime(DateTime.NowEpoch + UserFlags.Expires); ctx.WithHeader("Set-Cookie",$"Session={result}; SameSite=Lax; Expires={date.ToHttpDate()}; HttpOnly").SendRedirect(redirect); return true; } else incorrect=true; } ctx.WithMimeType("text/html").SendText(Pages.Login(redirect, incorrect)); return true; } const loggedIn = this.TYTD.IsLoggedIn(ctx); if(!loggedIn) { each(var file : TYTDResources) { if(ctx.Path == file.path) { ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value); return true; } } if(ctx.Path == "/progress") { ctx.WithMimeType("text/html").SendText(

You have been logged out

Login
); return true; } if(ctx.Path == "/sso") { const app = ctx.QueryParams.TryGetFirst("app") ?? ""; const token = ctx.QueryParams.TryGetFirst("token") ?? ""; const path = $"/sso?app={Net.Http.UrlEncode(app)}&token={Net.Http.UrlEncode(token)}"; ctx.StatusCode = 307; ctx.SendRedirect($"/login?redirect={Net.Http.UrlEncode(path)}"); return true; } ctx.StatusCode = 307; ctx.SendRedirect($"/login?redirect={Net.Http.UrlEncode(ctx.Path)}"); return true; } if(this.TYTD.Config.OobeState == "oobe") { each(var file : TYTDResources) { if(ctx.Path == file.path) { ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value); return true; } } if(ctx.Path == "/oobe") { if(ctx.Method == "POST") { const from = ctx.QueryParams.TryGetFirstInt("from"); switch(from) { case 1: { const enablePlugins = ctx.QueryParams.GetFirstBoolean("enablePlugins"); const tag = ctx.QueryParams.TryGetFirst("tag") ?? "UnknownPC"; const pollHours = ctx.QueryParams.TryGetFirstInt("pollHours") ?? 3; const pollMinutes = ctx.QueryParams.TryGetFirstInt("pollMinutes") ?? 0; const pollSeconds = ctx.QueryParams.TryGetFirstInt("pollSeconds") ?? 0; this.OOBE_STATE.tag = tag; this.OOBE_STATE.pollHours = pollHours; this.OOBE_STATE.pollMinutes = pollMinutes; this.OOBE_STATE.pollSeconds = pollSeconds; this.OOBE_STATE.enablePlugins = enablePlugins; const page = this.OOBE_STATE.enablePlugins ? "/oobe?page=2" : "/oobe?page=3"; ctx.StatusCode = 303; ctx.SendRedirect(page); return true; } break; case 3: { const createUser = (ctx.QueryParams.TryGetFirst("user") ?? "no")=="yes"; var seconds = this.OOBE_STATE.pollSeconds; seconds += this.OOBE_STATE.pollMinutes * 60; seconds += this.OOBE_STATE.pollHours * 3600; this.TYTD.Config.TYTDTag = this.OOBE_STATE.tag; this.TYTD.Config.BellTimer = seconds; this.TYTD.Config.EnablePlugins = this.OOBE_STATE.enablePlugins; this.TYTD.Config.OobeState = "welcome"; this.TYTD.SaveConfig(); if(createUser) { ctx.StatusCode = 303; ctx.SendRedirect("/newuser"); return true; } else { ctx.StatusCode = 303; ctx.SendRedirect("/"); return true; } } break; } ctx.WithMimeType("text/html").SendText(Components.ShellSimple("First Setup",

Malformed data

)); return true; } const page = ctx.QueryParams.TryGetFirstInt("page"); switch(page) { case 1: ctx.WithMimeType("text/html").SendText(Pages.OobePage1(this.OOBE_STATE)); break; case 2: ctx.WithMimeType("text/html").SendText(Pages.OobePage2(this.TYTD,ctx)); break; case 3: ctx.WithMimeType("text/html").SendText(Pages.OobePage3()); break; default: ctx.WithMimeType("text/html").SendText(Components.ShellSimple("First Setup",

Setup page not found

)); break; } return true; } if(ctx.Path != "/package-manage" && ctx.Path != "/api/v1/plugin-thumbnail.png") { ctx.StatusCode = 307; ctx.SendRedirect($"/oobe?page=1"); return true; } } if(ctx.Path == "/welcome") { const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/settings"; if(ctx.QueryParams.GetFirstBoolean("closePopup")) { if(this.TYTD.Config.OobeState == "welcome") { this.TYTD.Config.OobeState = "finished"; this.TYTD.SaveConfig(); } ctx.StatusCode = 307; ctx.SendRedirect(redirect); return true; } ctx.WithMimeType("text/html").SendText(Pages.Welcome(redirect)); return true; } if(this.TYTD.Config.OobeState == "welcome") { each(var file : TYTDResources) { if(ctx.Path == file.path) { ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value); return true; } } if(ctx.Path != "/newuser") { ctx.StatusCode = 307; ctx.SendRedirect($"/welcome?redirect={Net.Http.UrlEncode(ctx.Path)}"); return true; } } if(ctx.Path == "/") { ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD)); return true; } else if(ctx.Path == "/sso") { const app = ctx.QueryParams.TryGetFirst("app"); const token = ctx.QueryParams.TryGetFirst("token"); if(TypeIsString(app) && TypeIsString(token)) { const sso = this.TYTD.GetSSO(app); if(TypeIsDictionary(sso)) { const user = this.TYTD.WhoAmI(ctx); if(TypeIsDictionary(user) && user.flags != 0) { const postJson = { username = user.username, flags = user.flags, token = token, sso_app_key = sso.sso_app_key }; const resp = Net.Http.MakeRequest(sso.service_auth_post, { Method = "POST", Body = Net.Http.TextHttpRequestBody(postJson.ToString(),"application/json") }); if(resp.StatusCode >= 200 && resp.StatusCode <= 299) { ctx.StatusCode = 307; ctx.SendRedirect($"{sso.service_auth_redirect}{Net.Http.UrlEncode(token)}"); return true; } } } } ctx.StatusCode = 401; return false; } else if(ctx.Path == "/passwd") { var incorrect=false; const password = ctx.QueryParams.TryGetFirst("password") ?? ""; const newpassword = ctx.QueryParams.TryGetFirst("newpassword") ?? ""; const confirm = ctx.QueryParams.TryGetFirst("newpassword") ?? ""; if(ctx.Method == "POST") { this.TYTD.Passwd(); } ctx.WithMimeType("text/html").SendText(Pages.ChangePassword(redirect, incorrect)); } else if(ctx.Path == "/newuser") { var error = null; if(ctx.Method == "POST") { const username = ctx.QueryParams.TryGetFirst("username"); const password = ctx.QueryParams.TryGetFirst("password"); const confirm = ctx.QueryParams.TryGetFirst("confirm"); const isAdmin = ctx.QueryParams.GetFirstBoolean("isAdmin"); const canUsePlugins = ctx.QueryParams.GetFirstBoolean("canUsePlugins"); const canManagePlugins = ctx.QueryParams.GetFirstBoolean("canManagePlugins"); const canDownloadDB = ctx.QueryParams.GetFirstBoolean("canDownloadDB"); const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/"; var flags = 0; if(isAdmin) flags |= UserFlags.AdminFlag; if(canUsePlugins) flags |= UserFlags.PluginFlag; if(canManagePlugins) flags |= UserFlags.ManagePluginFlag; if(canDownloadDB) flags |= UserFlags.DatabaseFlag; if(password != confirm) { error = "Passwords do not match"; } else if(!TypeIsString(password)) { error = "Password is not defined"; } else if(!TypeIsString(username)) { error = "Username not defined"; } else { error = this.TYTD.CreateAccount(ctx, username, password, flags); } if(!TypeIsString(error)) { ctx.StatusCode=303; ctx.SendRedirect(redirect); } } ctx.WithMimeType("text/html").SendText(Pages.NewUser(error)); return true; } else if(ctx.Path == "/logout") { this.TYTD.Logout(ctx); ctx.StatusCode = 307; ctx.SendRedirect("/"); return true; } else if(ctx.Path == "/api") { ctx.WithMimeType("text/html").SendText(Pages.Api()); return true; } else if(ctx.Path == "/api-v1") { ctx.WithMimeType("text/html").SendText(Pages.ApiV1()); return true; } else if(ctx.Path == "/embed") { var v = ctx.QueryParams.TryGetFirst("v"); if(TypeIsString(v)) { const video = this.TYTD.GetVideo(v); if(TypeIsDefined(video)) { ctx.WithMimeType("text/html").SendText(Pages.VideoEmbed(v, video.title)); return true; } } return false; } else if(ctx.Path == "/api/v1/register_sso") { if(ctx.Method!="POST") { ctx.WithMimeType("application/json").SendJson({ success=false, reason = "Method must be post", type = "method" }); return true; } if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx))) { ctx.WithMimeType("application/json").SendJson({ success=false, reason = "You are either not logged in or not admin", type ="auth" }); return true; } const json = ctx.ReadJson(); const resp = this.TYTD.RegisterSSO(json); ctx.WithMimeType("application/json").SendJson( resp ); return true; } else if(ctx.Path == "/api/v1/download") { var v = ctx.QueryParams.TryGetFirst("v"); var inline = ctx.QueryParams.GetFirstBoolean("inline"); var res = ctx.QueryParams.TryGetFirst("res"); if(TypeOf(res) != "String") res = Resolution.LowVideo; var path = this.TYTD.GetVideoPath(v,res); if(path != null && this.TYTD.Storage.FileExists(path)) { var info = this.TYTD.GetVideo(v); var filename = $"{info.title}-{info.videoId}-{res}{path.GetExtension()}"; var strm = this.TYTD.Storage.OpenFile(path,"rb"); ctx.WithMimeType(Net.Http.MimeType(path.ToString())).WithContentDisposition(filename,inline).SendStream(strm); strm.Close(); return true; } return false; } else if(ctx.Path == "/api/v1/progress.json") { var jo = { CurrentVideoProgress = this.TYTD.CurrentVideoProgress, CurrentVideo = this.TYTD.CurrentVideo }; ctx.WithMimeType("application/json").SendJson(jo); return true; } else if(ctx.Path == "/api/v1/add") { if(ctx.Method=="POST") { const json = ctx.ReadJson(); each(var item : json) { this.TYTD.DownloadItem(item.url,item.res); } ctx.StatusCode=204; ctx.WriteHeaders(); return true; } } else if(ctx.Path == "/api/v1/video.json") { var id = ctx.QueryParams.TryGetFirst("v"); if(TypeOf(id)!="String") id = ""; ctx.WithMimeType("application/json").SendJson(this.TYTD.GetVideo(id)); return true; } else if(ctx.Path == "/api/v1/playlist.json") { var id = ctx.QueryParams.TryGetFirst("id"); var page = ctx.QueryParams.TryGetFirstInt("page"); var count = ctx.QueryParams.TryGetFirstInt("count"); if(TypeOf(page)!="Long") page = 1; if(TypeOf(count)!="Long") count = 20; if(TypeOf(id)!="String") id = ""; ctx.WithMimeType("application/json").SendJson(this.TYTD.GetPlaylistContents(id,page-1,count)); return true; } else if(ctx.Path == "/api/v1/channel.json") { var id = ctx.QueryParams.TryGetFirst("id"); var page = ctx.QueryParams.TryGetFirstInt("page"); var count = ctx.QueryParams.TryGetFirstInt("count"); if(TypeOf(page)!="Long") page = 1; if(TypeOf(count)!="Long") count = 20; if(TypeOf(id)!="String") id = ""; ctx.WithMimeType("application/json").SendJson(this.TYTD.GetChannelContents(id,page-1,count)); return true; } else if(ctx.Path == "/api/v1/downloads.json") { var q = ctx.QueryParams.TryGetFirst("q"); var type = ctx.QueryParams.TryGetFirst("type"); var page = ctx.QueryParams.TryGetFirstInt("page"); var count = ctx.QueryParams.TryGetFirstInt("count"); if(TypeOf(page)!="Long") page = 1; if(TypeOf(count)!="Long") count = 20; if(TypeOf(type)!="String") type = "videos"; if(TypeOf(q)!="String") q = ""; var result = { entries = [] }; switch(type) { case "videos": result.entries = this.TYTD.GetVideos(q,page-1,count); break; case "playlists": result.entries = this.TYTD.GetPlaylists(q,page-1,count); break; case "channels": result.entries = this.TYTD.GetChannels(q,page-1,count); break; } ctx.WithMimeType("application/json").SendJson(result); return true; } else if(ctx.Path == "/api/v1/database.db") { if(!this.TYTD.SendDatabase(ctx)) { ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized","

You are not authorized to download the database

",3,/"api/v1/database.db")); } return true; } else if(ctx.Path == "/api/v1/personal_tmp_link") { const name = ctx.QueryParams.TryGetFirst("name"); if(TypeIsString(name)) { const ents = this.TYTD.GetPersonalListTempUrl(name); if(TypeIsString(ents)) { ctx.WithMimeType("text/plain").SendText(ents); return true; } } } else if(ctx.Path == "/api/v1/manifest.json") { const v = ctx.QueryParams.TryGetFirst("v"); if(TypeIsString(v)) { const resp=this.TYTD.ManifestRequest(v); Console.WriteLine(resp); ctx.WithMimeType("application/json").SendJson(resp); return true; } } else if(ctx.Path == "/api/v1/personal") { /* GET /api/v1/personal GET /api/v1/personal?name=NAME DELETE /api/v1/personal name=NAME id=THEID //If this does not exist delete the entire list POST /api/v1/personal id=THEID or description= name= */ if(ctx.Method == "GET") { var name = ctx.QueryParams.TryGetFirst("name"); if(TypeOf(name) == "String") { var page = ctx.QueryParams.TryGetFirstInt("page") ?? 1; var count = ctx.QueryParams.TryGetFirstInt("count") ?? 20; var json = { description = this.TYTD.GetPersonalListDescription(name), items = this.TYTD.GetPersonalListContents(name,page-1,count) }; ctx.WithMimeType("application/json").SendJson(json); return true; } else { var json = { items = this.TYTD.GetPersonalLists() }; ctx.WithMimeType("application/json").SendJson(json); return true; } } else if(ctx.Method == "POST") { //{name="",id="",description=""} var j = ctx.ReadJson(); if(TypeOf(j.name) == "String") { var idHas = TypeOf(j.id) == "String" && j.id != ""; var descHas = TypeOf(j.description) == "String" && j.description != ""; if(idHas && descHas) { ctx.WithMimeType("application/json").SendJson({success=false,reason="You provided both a description (to set the list description) and id (to add a item) (you can only provide one)"}); } else if(idHas) { this.TYTD.AddToPersonalList(j.name, j.id); ctx.WithMimeType("application/json").SendJson({success=true}); } else if(descHas) { this.TYTD.SetPersonalListDescription(j.name,j.description); ctx.WithMimeType("application/json").SendJson({success=true}); } else { ctx.WithMimeType("application/json").SendJson({success=false,reason="You must provide either a description (to set the list description) or id (to add a item)"}); } } else { ctx.WithMimeType("application/json").SendJson({success=false, reason="Need a name"}); } return true; } else if(ctx.Method == "DELETE") { var j = ctx.ReadJson(); /* public RemoveFromPersonalList(name,id) public RemovePersonalList(name) */ if(TypeOf(j.name) == "String") { if(TypeOf(j.id) == "String" && j.id != "") { this.TYTD.RemoveFromPersonalList(j.name,j.id); } else { this.TYTD.RemovePersonalList(j.name); } ctx.WithMimeType("application/json").SendJson({success=true}); } else { ctx.WithMimeType("application/json").SendJson({success=false, reason="Need a name"}); } return true; } } else if(ctx.Path == "/subscribe") { var id = ctx.QueryParams.TryGetFirst("id"); var bell = ctx.QueryParams.TryGetFirst("bell"); var type = ctx.QueryParams.TryGetFirst("type"); switch(type) { case "subscribe": this.TYTD.SetSubscriptionBell(id, SubscriptionBell.BellLow); break; case "unsubscribe": this.TYTD.SetSubscriptionBell(id, null); break; case "bell": this.TYTD.SetSubscriptionBell(id, bell); break; } ctx.WithMimeType("text/html").SendText(Components.Subscribe(this.TYTD,id)); return true; } else if(ctx.Path == "/whoami") { const row = this.TYTD.WhoAmI(ctx); ctx.WithMimeType("text/html").SendText(Pages.WhoAmI(row)); return true; } else if(ctx.Path == "/edit-personal-description") { var name = ctx.QueryParams.TryGetFirst("name"); if(ctx.Method == "GET") { ctx.WithMimeType("text/html").SendText(Components.PersonalListDescription(this.TYTD,name,true)); return true; } else if(ctx.Method == "POST") { var description =ctx.QueryParams.TryGetFirst("description"); this.TYTD.SetPersonalListDescription(name,description); ctx.WithMimeType("text/html").SendText(Components.PersonalListDescription(this.TYTD,name,false)); return true; } } else if(ctx.Path == "/downloads") { ctx.WithMimeType("text/html").SendText(Pages.Downloads(this.TYTD,ctx)); return true; } else if(ctx.Path == "/list") { ctx.WithMimeType("text/html").SendText(Pages.List(this.TYTD,ctx)); return true; } else if(ctx.Path == "/settings") { ctx.WithMimeType("text/html").SendText(Pages.Settings(this.TYTD,ctx)); return true; } else if(ctx.Path == "/edituser") { if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx))) { ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",

You can{"'"}t modify admin settings as you are not an admin

,3)); return true; } const user = ctx.QueryParams.TryGetFirst("user"); if(TypeIsString(user)) { var userObj = null; const whoami = this.TYTD.WhoAmI(ctx); if(ctx.Method == "POST") { if(whoami.username != user) { const isAdmin = ctx.QueryParams.GetFirstBoolean("isAdmin"); const canUsePlugins = ctx.QueryParams.GetFirstBoolean("canUsePlugins"); const canManagePlugins = ctx.QueryParams.GetFirstBoolean("canManagePlugins"); const canDownloadDB = ctx.QueryParams.GetFirstBoolean("canDownloadDB"); var flags = 0; if(isAdmin) flags |= UserFlags.AdminFlag; if(canUsePlugins) flags |= UserFlags.PluginFlag; if(canManagePlugins) flags |= UserFlags.ManagePluginFlag; if(canDownloadDB) flags |= UserFlags.DatabaseFlag; this.TYTD.Mutex.Lock(); const db = this.TYTD.OpenDB(); Sqlite.Exec(db, $"UPDATE users SET flags = {flags} WHERE username = {Sqlite.Escape(user)}"); Sqlite.Close(db); this.TYTD.Mutex.Unlock(); } } if(ctx.Method == "DELETE") { if(whoami.username != user) { this.TYTD.Mutex.Lock(); const db = this.TYTD.OpenDB(); Sqlite.Exec(db,$"DELETE FROM users WHERE username = {Sqlite.Escape(user)}"); Sqlite.Close(db); this.TYTD.Mutex.Unlock(); ctx.StatusCode = 200; ctx.WithHeader("HX-Location", "/edituser"); ctx.SendText("Redirect"); } else { ctx.StatusCode = 401; ctx.WriteHeaders(); } return true; } if(whoami.username != user) { this.TYTD.Mutex.Lock(); const db = this.TYTD.OpenDB(); userObj = Sqlite.Exec(db,$"SELECT * FROM users WHERE username = {Sqlite.Escape(user)}"); Sqlite.Close(db); this.TYTD.Mutex.Unlock(); if(TypeIsList(userObj) && userObj.Length == 1) userObj = userObj[0]; else userObj=null; } ctx.WithMimeType("text/html").SendText(Pages.EditUser(userObj)); } else { ctx.WithMimeType("text/html").SendText(Pages.EditUserList(this.TYTD, ctx)); } return true; } else if(ctx.Path == "/admin") { if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx))) { ctx.WithMimeType("text/html").SendText(Components.Shell("Unauthorized",

You can{"'"}t modify admin settings as you are not an admin

,3)); return true; } if(ctx.Method == "POST") { /* hours minutes seconds tag enablePlugins */ var enablePlugins = ctx.QueryParams.GetFirstBoolean("enablePlugins"); var tag = ctx.QueryParams.TryGetFirst("tag"); var hours = ctx.QueryParams.TryGetFirstInt("hours"); var minutes = ctx.QueryParams.TryGetFirstInt("minutes"); var seconds = ctx.QueryParams.TryGetFirstInt("seconds"); if(TypeOf(tag) == "String" && TypeOf(hours) == "Long" && TypeOf(minutes) == "Long" && TypeOf(seconds) == "Long") { seconds += minutes * 60; seconds += hours * 3600; this.TYTD.Config.TYTDTag = tag; this.TYTD.Config.BellTimer = seconds; this.TYTD.Config.EnablePlugins = enablePlugins; this.TYTD.SaveConfig(); } } ctx.WithMimeType("text/html").SendText(Pages.Admin(this.TYTD,ctx)); return true; } else if(ctx.Path == "/watch" || ctx.Path == "/video") { ctx.WithMimeType("text/html").SendText(Pages.VideoInfo(this.TYTD,ctx)); return true; } else if(ctx.Path == "/watch_videos") { const video_ids = ctx.QueryParams.TryGetFirst("video_ids"); if(TypeIsString(video_ids)) { if(ctx.Method=="GET") { ctx.WithMimeType("text/html").SendText(Pages.YouTubeAnonyPlaylist(video_ids)); return true; } if(ctx.Method=="POST") { const name = ctx.QueryParams.TryGetFirst("name"); if(TypeIsString(name)) { const nameParts=video_ids.Split(","); Console.WriteLine(nameParts); each(var item : nameParts) { this.TYTD.AddToPersonalList(name,item); } Console.WriteLine(name); ctx.SendRedirect($"/list?name={Net.Http.UrlEncode(name)}",303); return true; } } } } else if(ctx.Path == "/playlist") { ctx.WithMimeType("text/html").SendText(Pages.PlaylistInfo(this.TYTD,ctx)); return true; } else if(ctx.Path == "/channel") { ctx.WithMimeType("text/html").SendText(Pages.ChannelInfo(this.TYTD,ctx)); return true; } else if(ctx.Path == "/add") { var url = ctx.QueryParams.TryGetFirst("url"); var action =ctx.QueryParams.TryGetFirst("action"); var res = ctx.QueryParams.TryGetFirst("res"); if(action == "add") { this.TYTD.DownloadItem(url,res); } else if(action == "info") { ctx.ResponseHeaders.SetValue("HX-Redirect", TYTD.PageRedirect(url)); } else if(action == "download") { ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}"); } else if(action == "play") { ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}&inline=true"); } ctx.WithMimeType("text/html").SendText(Components.Add()); return true; } else if(ctx.Path == "/add-video") { var url = ctx.QueryParams.TryGetFirst("url"); var action =ctx.QueryParams.TryGetFirst("action"); var res = ctx.QueryParams.TryGetFirst("res"); if(action == "add") { this.TYTD.DownloadItem(url,res); } else if(action == "download") { ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}"); } else if(action == "play") { ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}&inline=true"); } ctx.WithMimeType("text/html").SendText(Components.AddVideo(url)); return true; } else if(ctx.Path == "/add-to-list") { var id = ctx.QueryParams.TryGetFirst("id"); if(TypeOf(id) != "String") id = ""; var name = ctx.QueryParams.TryGetFirst("name"); if(TypeOf(name) != "String") name = ""; if(name == "") { ctx.WithMimeType("text/html").SendText(Components.CreateNewList(this.TYTD,id)); } else { this.TYTD.AddToPersonalList(name,id); ctx.WithMimeType("text/html").SendText(Components.AddToPersonalList(this.TYTD,id)); } return true; } else if(ctx.Path == "/progress") { ctx.WithMimeType("text/html").SendText(Components.Progress(this.TYTD)); return true; } else if(ctx.Path == "/queue-size") { ctx.WithMimeType("text/html").SendText(Components.QueueSZ(this.TYTD)); return true; } else if(ctx.Path == "/plugins") { ctx.WithMimeType("text/html").SendText(Pages.Plugins(this.TYTD,ctx)); return true; } else if(ctx.Path == "/plugins-download") { if(!UserFlags.CanManagePlugins(this.TYTD.IsLoggedIn(ctx))) return false; ctx.WithMimeType("text/html").SendText(Pages.DownloadPlugins(this.TYTD,ctx)); return true; } else if(ctx.Path == "/package-manage" && ctx.Method == "POST") { if(!UserFlags.CanManagePlugins(this.TYTD.IsLoggedIn(ctx))) return false; var data = { name = ctx.QueryParams.TryGetFirst("name"), version = ctx.QueryParams.TryGetFirst("version"), action = ctx.QueryParams.TryGetFirst("action") }; var mustConfirm = false; switch(data.action) { case "install": { var v = Version.Parse(data.version); if(v != null) this.TYTD.PackageInstall(data.name,v); } break; case "uninstall": { mustConfirm = true; } break; case "confirm": { this.TYTD.PackageUninstall(data.name); } break; } ctx.WithMimeType("text/html").SendText(Components.InstallButton(this.TYTD, data.name, data.version, mustConfirm)); return true; } else if(ctx.Path == "/api/v1/video-thumbnail") { var v = ctx.QueryParams.TryGetFirst("v"); var res = ctx.QueryParams.TryGetFirst("res"); if(TypeOf(res) != "String") res = "0"; var thu = this.TYTD.GetVideoThumbnail(v,res); if(thu == null) return false; ctx.WithMimeType("image/jpg").SendBytes(thu); return true; } else if(ctx.Path == "/api/v1/playlist-thumbnail") { var id = ctx.QueryParams.TryGetFirst("id"); var res = ctx.QueryParams.TryGetFirst("res"); if(TypeOf(res) != "String") res = "0"; var thu = this.TYTD.GetPlaylistThumbnail(id,res); if(thu == null) return false; ctx.WithMimeType("image/jpg").SendBytes(thu); return true; } else if(ctx.Path == "/api/v1/channel-thumbnail") { var id = ctx.QueryParams.TryGetFirst("id"); var res = ctx.QueryParams.TryGetFirst("res"); if(TypeOf(res) != "String") res = "0"; var thu = this.TYTD.GetChannelThumbnail(id,res); if(thu == null) return false; ctx.WithMimeType("image/jpg").SendBytes(thu); return true; } else if(ctx.Path == "/api/v1/plugin-thumbnail.png") { var name = ctx.QueryParams.TryGetFirst("name"); ctx.WithMimeType().SendBytes(this.TYTD.GetPluginThumbnail(name)); return true; } else if(ctx.Path.StartsWith("/plugin/")) { if(UserFlags.CanUsePlugins(this.TYTD.IsLoggedIn(ctx))) return this.TYTD.Servers.Handle(ctx); else { return false; } } else { each(var file : TYTDResources) { if(ctx.Path == file.path) { ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value); return true; } } } return false; } public Close() { this.TYTD.Stop(); } } func GetTYTDDir() { const dir = Env["TYTDDIR"]; if(TypeIsString(dir) && dir.Length > 0) return dir; return (Env.Videos / "TYTD2025").ToString(); } func WebAppMain(args) { if(args.Length == 2 && args[1] == "change-password") { Console.Write("Username: "); const username = Console.ReadLine(); Console.Write("Password: "); const echo = Console.Echo; Console.Echo = false; const password = Console.ReadLine(); Console.WriteLine(); Console.Write("Confirm: "); const confirm = Console.ReadLine(); Console.WriteLine(); Console.Echo = true; if(password != confirm) { Console.WriteLine("Passwords do not match!"); return null; } const salt = Crypto.RandomBytes(32, "TYTD2025"); const hash = Crypto.PBKDF2(password, salt, UserFlags.ITTR,64,384); FS.Local.CreateDirectory(GetTYTDDir()); const db = Sqlite.Open(GetTYTDDir()/"tytd.db"); const res = Sqlite.Exec(db, $"UPDATE users SET password_hash = {Sqlite.Escape(Crypto.Base64Encode(hash))}, password_salt = {Sqlite.Escape(Crypto.Base64Encode(salt))} WHERE username = {Sqlite.Escape(username)};"); if(TypeIsList(res)) { Console.WriteLine("Changed password successfully"); } else { Console.WriteLine($"Failed to change password: {res}"); } return null; } return new TYTDApp(); } func main(args) { var res = WebAppMain(args); Net.Http.ListenSimpleWithLoop(res,3255); res.Close(); return 0; }