mirror of
https://onedev.site.tesses.net/tytd2025
synced 2026-06-01 09:55:34 +00:00
1201 lines
44 KiB
Plaintext
1201 lines
44 KiB
Plaintext
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(
|
|
<div>
|
|
<h1>You have been logged out</h1>
|
|
<a href="./login">Login</a>
|
|
</div>
|
|
);
|
|
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", <h1>Malformed data</h1>));
|
|
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", <h1>Setup page not found</h1>));
|
|
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","<h1>You are not authorized to download the database</h1>",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",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,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",<h1>You can{"'"}t modify admin settings as you are not an admin</h1>,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;
|
|
} |