mirror of
https://onedev.site.tesses.net/tytd2025
synced 2026-02-08 17:45:45 +00:00
First commit
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
func Components.AddToPersonalList(tytd,id)
|
||||
{
|
||||
|
||||
return <form hx-post="./add-to-list" hx-swap="outerHTML">
|
||||
<input type="hidden" name="id" value={id}>
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label suffix border fill">
|
||||
<select name="name">
|
||||
<option value="" selected>Create personal list</option>
|
||||
<each(var item : tytd.GetPersonalLists())>
|
||||
<option value={item.name}>Add to {item.name}</option>
|
||||
</each>
|
||||
</select>
|
||||
<label>Personal List</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button name="action" value="add"><i>add</i></button>
|
||||
</div>
|
||||
</form>;
|
||||
}
|
||||
|
||||
func Components.CreateNewList(tytd,id)
|
||||
{
|
||||
return <form hx-post="./add-to-list" hx-swap="outerHTML">
|
||||
<input type="hidden" name="id" value={id}>
|
||||
<div class="row">
|
||||
<div class="field label border max">
|
||||
<input type="text" name="name">
|
||||
<label>List Name</label>
|
||||
</div>
|
||||
|
||||
<button name="action" value="add"><i>add</i></button>
|
||||
</div>
|
||||
</form>;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
func Components.AddVideo(id)
|
||||
{
|
||||
return <form hx-post="./add-video" hx-swap="outerHTML">
|
||||
<input type="hidden" name="url" value={id}>
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label suffix border fill">
|
||||
<select name="res">
|
||||
<each(var item : Resolution.Resolutions)>
|
||||
<if(item.default)>
|
||||
<true>
|
||||
<option value={item.value} selected>{item.name}</option>
|
||||
</true>
|
||||
<false>
|
||||
<option value={item.value}>{item.name}</option>
|
||||
</false>
|
||||
</if>
|
||||
</each>
|
||||
</select>
|
||||
<label>Resolution</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button name="action" value="add"><i>add</i></button>
|
||||
<button name="action" value="download"><i>download</i></button>
|
||||
<button name="action" value="play"><i>play_arrow</i></button>
|
||||
</div>
|
||||
</form>;
|
||||
}
|
||||
33
Tesses.YouTubeDownloader.Server/src/components/add.tcross
Normal file
33
Tesses.YouTubeDownloader.Server/src/components/add.tcross
Normal file
@@ -0,0 +1,33 @@
|
||||
func Components.Add()
|
||||
{
|
||||
return <form hx-post="./add" hx-swap="outerHTML">
|
||||
<div class="field border fill">
|
||||
<input type="text" name="url">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label suffix border fill">
|
||||
<select name="res">
|
||||
<each(var item : Resolution.Resolutions)>
|
||||
<if(item.default)>
|
||||
<true>
|
||||
<option value={item.value} selected>{item.name}</option>
|
||||
</true>
|
||||
<false>
|
||||
<option value={item.value}>{item.name}</option>
|
||||
</false>
|
||||
</if>
|
||||
</each>
|
||||
</select>
|
||||
<label>Resolution</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button name="action" value="add"><i>add</i></button>
|
||||
<button name="action" value="info"><i>info</i></button>
|
||||
<button name="action" value="download"><i>download</i></button>
|
||||
<button name="action" value="play"><i>play_arrow</i></button>
|
||||
</div>
|
||||
</form>;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
func Components.DiscoverEntry(item)
|
||||
{
|
||||
switch(item.type)
|
||||
{
|
||||
case "video":
|
||||
return
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./video-thumbnail?v={Net.Http.UrlEncode(item.id)}"} width="144" width="120">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<a target="_blank" href={$"./video?v={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
|
||||
</div>
|
||||
<div class="min">
|
||||
<span>{item.views} • {item.uploaded}</span>
|
||||
</div>
|
||||
<div class="min">
|
||||
<each(var part : item.author)>
|
||||
<if(TypeOf(part.navigationEndpoint) == "Dictionary")>
|
||||
<true>
|
||||
<a target="_blank" href={$"./channel?id={Net.Http.UrlEncode(part.navigationEndpoint.browseEndpoint.browseId)}"}>{part.text}</a>
|
||||
|
||||
</true>
|
||||
<false>
|
||||
<span>{part.text}</span>
|
||||
</false>
|
||||
</if>
|
||||
</each>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
break;
|
||||
case "channel":
|
||||
return
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./channel-thumbnail?id={Net.Http.UrlEncode(item.id)}"} width="144" width="144">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<a target="_blank" href={$"./channel?id={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
|
||||
</div>
|
||||
<div class="min">
|
||||
<span>{item.subs}</span>
|
||||
</div>
|
||||
<div class ="min">
|
||||
<p class="min">{item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
break;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
func Components.DownloadedVideo(item)
|
||||
{
|
||||
return
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./api/v1/video-thumbnail?v={Net.Http.UrlEncode(item.videoId)}"} width="144" width="120">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<a hx-get={$"./video?v={Net.Http.UrlEncode(item.videoId)}"} hx-target="body" hx-push-url="true" href={$"./video?v={Net.Http.UrlEncode(item.videoId)}"}>{item.title}</a>
|
||||
</div>
|
||||
<div class="min">
|
||||
<span>{item.viewCountStr} (when downloaded)</span>
|
||||
</div>
|
||||
<div class="min">
|
||||
<a hx-get={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} href={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} hx-target="body" hx-push-url="true">{item.author}</a>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
|
||||
}
|
||||
func Components.DownloadedPlaylist(item)
|
||||
{
|
||||
var res = <div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./api/v1/playlist-thumbnail?id={Net.Http.UrlEncode(item.playlistId)}"} width="144" width="120">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<a hx-get={$"./playlist?id={Net.Http.UrlEncode(item.playlistId)}"} href={$"./playlist?id={Net.Http.UrlEncode(item.playlistId)}"} hx-target="body" hx-push-url="true">{item.title}</a>
|
||||
</div>
|
||||
<div class="min">
|
||||
<a hx-get={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} href={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} hx-target="body" hx-push-url="true">{item.channelTitle}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
return res;
|
||||
|
||||
}
|
||||
func Components.DownloadedChannel(item)
|
||||
{
|
||||
var res = <div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./api/v1/channel-thumbnail?id={Net.Http.UrlEncode(item.channelId)}"} width="144" width="120">
|
||||
</div>
|
||||
<div class="max">
|
||||
<a hx-get={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} href={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} hx-target="body" hx-push-url="true">{item.title}</a>
|
||||
</div>
|
||||
</div>;
|
||||
return res;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
func Components.MusicAlbum(item)
|
||||
{
|
||||
return <div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./album-art?id={Net.Http.UrlEncode(item.id)}"} width="200" height="200">
|
||||
</div>
|
||||
<div class="min">
|
||||
<a href={$"./music-album?id={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
func Components.MusicArtist(item)
|
||||
{
|
||||
return <a href={$"./music-artist?id={Net.Http.UrlEncode(item.id)}"}>{item.name}</a>;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
func Components.PackageItem(tytd,item)
|
||||
{
|
||||
|
||||
var html = <div class="row">
|
||||
<div class="min">
|
||||
<img width="64" height="64" src={item.thumb} alt="Package thumbnail">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<a target="_blank" href={item.url}>{item.name}</a>
|
||||
</div>
|
||||
<div class="min">
|
||||
<span>{item.version}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<raw(Components.InstallButton(tytd,item.name,item.version))>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
return html;
|
||||
}
|
||||
func Components.InstallButton(tytd, name, version, $confirm)
|
||||
{
|
||||
var id = Crypto.Base64Encode(new ByteArray($"{name}-{version}")).Replace("=","");
|
||||
|
||||
var version = Version.Parse(version);
|
||||
|
||||
if(version != null)
|
||||
{
|
||||
|
||||
var state = tytd.PackageState(name,version);
|
||||
return <div id={id}>
|
||||
<if(state == 0)>
|
||||
<true>
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="install",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Install</button>
|
||||
</true>
|
||||
<false>
|
||||
<if(confirm)>
|
||||
<true>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="confirm",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Yes Uninstall</button>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="cancel",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</true>
|
||||
<false>
|
||||
<if(state == 1)>
|
||||
<true>
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="uninstall",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Uninstall</button>
|
||||
</true>
|
||||
<false>
|
||||
<nav class="group split">
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="install",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML" class="left-round">
|
||||
<i>update</i>
|
||||
<span>Update</span>
|
||||
</button>
|
||||
<button hx-post="./package-manage" hx-vals={Json.Encode({action="uninstall",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML" class="right-round square">
|
||||
<i>delete</i>
|
||||
</button>
|
||||
</nav>
|
||||
</false>
|
||||
</if>
|
||||
</false>
|
||||
</if>
|
||||
|
||||
</false>
|
||||
</if>
|
||||
</div>;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
30
Tesses.YouTubeDownloader.Server/src/components/plugin.tcross
Normal file
30
Tesses.YouTubeDownloader.Server/src/components/plugin.tcross
Normal file
@@ -0,0 +1,30 @@
|
||||
func Components.InstalledPlugin(item)
|
||||
{
|
||||
|
||||
|
||||
var html = <div class="row">
|
||||
<div class="min">
|
||||
<img width="64" height="64" src={$"./api/v1/plugin-thumbnail.png?name={Net.Http.UrlEncode(item.pluginName)}"} alt="Package thumbnail">
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="col">
|
||||
<div class="min">
|
||||
<if(item.pluginObject.Server != undefined && item.pluginObject.Server != null)>
|
||||
<true>
|
||||
<a href={$"./plugin/{Net.Http.UrlPathEncode(item.pluginName)}/"}>{TypeOf(item.info.short_name_pretty) == "String" ? item.info.short_name_pretty : (TypeOf(item.info.short_name) == "String" ? item.info.short_name : item.name)}</a>
|
||||
</true>
|
||||
<false>
|
||||
<span>{TypeOf(item.info.short_name_pretty) == "String" ? item.info.short_name_pretty : (TypeOf(item.info.short_name) == "String" ? item.info.short_name : item.name)}</span>
|
||||
</false>
|
||||
</if>
|
||||
</div>
|
||||
<div class="min">
|
||||
<span>{item.version}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
return html;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
var progress=0;
|
||||
func Components.Progress(tytd)
|
||||
{
|
||||
var vid = tytd.CurrentVideo;
|
||||
|
||||
var html = <div hx-trigger="every 1500ms" hx-indicator="none" hx-get="./progress" hx-swap="outerHTML">
|
||||
<h2>{vid.Title}</h2>
|
||||
<h4>{vid.Channel}</h4>
|
||||
<progress value={tytd.CurrentVideoProgress * 100.0} max="100" min="0"></progress>
|
||||
</div>;
|
||||
progress++;
|
||||
return html;
|
||||
}
|
||||
68
Tesses.YouTubeDownloader.Server/src/components/shell.tcross
Normal file
68
Tesses.YouTubeDownloader.Server/src/components/shell.tcross
Normal file
@@ -0,0 +1,68 @@
|
||||
func Components.Shell(title, html, page, $mypage)
|
||||
{
|
||||
if(TypeOf(mypage) != "Path")
|
||||
mypage = /;
|
||||
|
||||
var index = (/).MakeRelative(mypage).ToString();
|
||||
if(index == "") index = "./";
|
||||
|
||||
var pages = [
|
||||
{
|
||||
text="Home",
|
||||
icon="home",
|
||||
href=index,
|
||||
classStr=""
|
||||
},
|
||||
{
|
||||
text = "Downloads",
|
||||
icon = "download",
|
||||
href=(/"downloads").MakeRelative(mypage).ToString(),
|
||||
classStr=""
|
||||
},
|
||||
{
|
||||
text = "Plugins",
|
||||
icon = "extension",
|
||||
href= (/"plugins").MakeRelative(mypage).ToString(),
|
||||
classStr=""
|
||||
},
|
||||
{
|
||||
text = "Settings",
|
||||
icon = "settings",
|
||||
href= (/"settings").MakeRelative(mypage).ToString(),
|
||||
classStr=""
|
||||
}
|
||||
];
|
||||
pages[page].classStr = "active";
|
||||
return <!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TYTD - {title}</title>
|
||||
<link rel="stylesheet" href={(/"beer.min.css").MakeRelative(mypage).ToString()}>
|
||||
<link rel="stylesheet" href={(/"theme.css").MakeRelative(mypage).ToString()}>
|
||||
<script src={(/"htmx.min.js").MakeRelative(mypage).ToString()} defer></script>
|
||||
<script type="module" src={(/"beer.min.js").MakeRelative(mypage).ToString()} defer></script>
|
||||
</head>
|
||||
<body hx-indicator="#loading-indicator">
|
||||
|
||||
<nav class="bottom">
|
||||
<each(var page : pages)>
|
||||
<a hx-get={page.href} hx-target="body" hx-push-url="true" class={page.classStr}>
|
||||
<i>{page.icon}</i>
|
||||
<div>{page.text}</div>
|
||||
</a>
|
||||
</each>
|
||||
</nav>
|
||||
|
||||
<main class="responsive">
|
||||
<div class="htmx-indicator shape loading-indicator extra" id="loading-indicator">
|
||||
<img class="responsive" src="./tytd.svg">
|
||||
</div>
|
||||
<raw(html)>
|
||||
</main>
|
||||
|
||||
|
||||
</body>
|
||||
</html>;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
func Components.Subscribe(tytd,id)
|
||||
{
|
||||
var bell = tytd.GetSubscriptionBell(id);
|
||||
var subscribed= bell != null;
|
||||
|
||||
var bellLogo = "notifications_off";
|
||||
switch(bell)
|
||||
{
|
||||
case SubscriptionBell.DownloadLow:
|
||||
case SubscriptionBell.DownloadHigh:
|
||||
bellLogo = "download";
|
||||
break;
|
||||
case SubscriptionBell.Bell:
|
||||
case SubscriptionBell.BellLow:
|
||||
case SubscriptionBell.BellHigh:
|
||||
bellLogo="notifications_active";
|
||||
break;
|
||||
}
|
||||
|
||||
<return>
|
||||
<div id="subscribe-btn">
|
||||
<if(subscribed)>
|
||||
<true>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<button hx-post="./subscribe" hx-swap="outerHTML" hx-target="#subscribe-btn" hx-vals={Json.Encode({id,type="unsubscribe"})}>Unsubscribe</button>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button class="circle">
|
||||
<i>{bellLogo}</i>
|
||||
<menu class="left no-wrap">
|
||||
<each(var b : SubscriptionBell.Bells)>
|
||||
<li hx-post="./subscribe" hx-swap="outerHTML" hx-target="#subscribe-btn" hx-vals={Json.Encode({id,type="bell",bell=b.value})}>{b.name}</li>
|
||||
</each>
|
||||
</menu>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</true>
|
||||
<false>
|
||||
<button hx-post="./subscribe" hx-swap="outerHTML" hx-target="#subscribe-btn" hx-vals={Json.Encode({id,type="subscribe"})}>Subscribe</button>
|
||||
</false>
|
||||
</if>
|
||||
</div>
|
||||
</return>
|
||||
}
|
||||
374
Tesses.YouTubeDownloader.Server/src/main.tcross
Normal file
374
Tesses.YouTubeDownloader.Server/src/main.tcross
Normal file
@@ -0,0 +1,374 @@
|
||||
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="/loading-indicator.svg",value=embed("loading-indicator.svg")},
|
||||
{path="/theme.css",value=embed("theme.css")}
|
||||
];
|
||||
var times=1;
|
||||
|
||||
class TYTDApp {
|
||||
private TYTD;
|
||||
|
||||
public TYTDApp()
|
||||
{
|
||||
|
||||
var tytdfs = new SubdirFilesystem(FS.Local, "TYTD");
|
||||
this.TYTD = new TYTD.Downloader(tytdfs,FS.MakeFull("TYTD"));
|
||||
this.TYTD.Start();
|
||||
|
||||
}
|
||||
public Handle(ctx)
|
||||
{
|
||||
if(ctx.Path == "/")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD));
|
||||
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)
|
||||
{
|
||||
var info = this.TYTD.GetVideo(v);
|
||||
var filename = $"{info.title}-{info.videoId}.{path.GetExtension()}";
|
||||
var strm = this.TYTD.Storage.OpenFile(path,"rb");
|
||||
ctx.WithMimeType(Net.Http.MimeType(path.ToString())).WithContentDisposition(filename,inline).SendStream(strm);
|
||||
strm.Close();
|
||||
}
|
||||
}
|
||||
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 == "/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 == "/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")
|
||||
{
|
||||
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.Settings(this.TYTD,ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/video")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.VideoInfo(this.TYTD,ctx));
|
||||
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 == "/plugins")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Plugins(this.TYTD,ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/plugins-download")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.DownloadPlugins(this.TYTD,ctx));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/package-manage" && ctx.Method == "POST")
|
||||
{
|
||||
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/"))
|
||||
{
|
||||
return this.TYTD.Servers.Handle(ctx);
|
||||
}
|
||||
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 WebAppMain(args)
|
||||
{
|
||||
return new TYTDApp();
|
||||
}
|
||||
func main(args)
|
||||
{
|
||||
var res = WebAppMain(args);
|
||||
Net.Http.ListenSimpleWithLoop(res,3255);
|
||||
res.Close();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
func Pages.ChannelInfo(tytd,ctx)
|
||||
{
|
||||
var id = ctx.QueryParams.TryGetFirst("id");
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
if(TypeOf(page) != "Long") page = 1;
|
||||
page--;
|
||||
|
||||
var html = <h1>Could not find channel</h1>;
|
||||
var title = "N/A";
|
||||
|
||||
if(TypeOf(id) == "String")
|
||||
{
|
||||
id = TYTD.GetChannelId(id);
|
||||
var res = tytd.GetChannelContents(id,page,20);
|
||||
|
||||
html = <null>
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<h1>{res.authorName}</h1>
|
||||
</div>
|
||||
<div class="min">
|
||||
<raw(Components.Subscribe(tytd,id))>
|
||||
</div>
|
||||
</div>
|
||||
<each(var item : res.items)>
|
||||
<raw(Components.DownloadedVideo(item))>
|
||||
</each>
|
||||
<footer class="row center-align">
|
||||
|
||||
<button hx-get={$"./channel?id={Net.Http.UrlEncode(id)}&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./channel?id={Net.Http.UrlEncode(id)}&type=videos&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
|
||||
|
||||
</footer>
|
||||
</null>;
|
||||
|
||||
title = res.authorName;
|
||||
}
|
||||
|
||||
return Components.Shell($"Channel {title}",html,1);
|
||||
}
|
||||
34
Tesses.YouTubeDownloader.Server/src/pages/discover.tcross
Normal file
34
Tesses.YouTubeDownloader.Server/src/pages/discover.tcross
Normal file
@@ -0,0 +1,34 @@
|
||||
func Pages.Discover(tytd,ctx)
|
||||
{
|
||||
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
var res = null;
|
||||
var q2 = "";
|
||||
if(TypeOf(q) == "String")
|
||||
{
|
||||
q2 = q;
|
||||
res = tytd.Discover(q);
|
||||
|
||||
}
|
||||
|
||||
var html = <null>
|
||||
<form hx-get="./discover" hx-target="body" hx-push-url="true">
|
||||
<div class="field large prefix round fill active">
|
||||
<i class="front">search</i>
|
||||
<input name="q" value={q2}>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
<if(res != null)>
|
||||
<true>
|
||||
<ul>
|
||||
<each(var item : res.items)>
|
||||
<li><raw(Components.DiscoverEntry(item))></li>
|
||||
</each>
|
||||
</ul>
|
||||
<button class="responsive">More</button>
|
||||
</true></null>;
|
||||
|
||||
|
||||
return Components.Shell($"Discover {q2}",html,1);
|
||||
}
|
||||
293
Tesses.YouTubeDownloader.Server/src/pages/downloads.tcross
Normal file
293
Tesses.YouTubeDownloader.Server/src/pages/downloads.tcross
Normal file
@@ -0,0 +1,293 @@
|
||||
func Pages.Downloads(tytd,ctx)
|
||||
{
|
||||
var type = ctx.QueryParams.TryGetFirst("type");
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
if(TypeOf(page) != "Long") page = 1;
|
||||
page--;
|
||||
|
||||
if(TypeOf(q) != "String")
|
||||
{
|
||||
q = "";
|
||||
}
|
||||
if(TypeOf(type) != "String")
|
||||
{
|
||||
type = "videos";
|
||||
}
|
||||
var html ="";
|
||||
|
||||
switch(type) {
|
||||
case "videos":
|
||||
{
|
||||
|
||||
var res = tytd.GetVideos(q,page,20);
|
||||
html = <null>
|
||||
<form hx-get="./downloads" hx-target="body" hx-push-url="true" class="s m">
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label suffix border round fill">
|
||||
<select name="type">
|
||||
<option value="videos" selected>Videos</option>
|
||||
<option value="playlists">Playlists</option>
|
||||
<option value="channels">Channels</option>
|
||||
<option value="personal">Personal List</option>
|
||||
</select>
|
||||
<label>Type</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button>Change</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<nav class="tabbed l">
|
||||
<a class="active" hx-get="./downloads?type=videos" hx-target="body" hx-push-url="true">
|
||||
<i>movie</i>
|
||||
<span>Videos</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=playlists" hx-target="body" hx-push-url="true">
|
||||
<i>list</i>
|
||||
<span>Playlists</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=channels" hx-target="body" hx-push-url="true">
|
||||
<i>person</i>
|
||||
<span>Channels</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true">
|
||||
<i>edit_note</i>
|
||||
<span>Personal Lists</span>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
</nav>
|
||||
<form hx-get="./downloads" hx-target="body" hx-push-url="true">
|
||||
<div class="field large prefix round fill active">
|
||||
<i class="front">search</i>
|
||||
<input name="q" value={q}>
|
||||
<input name="type" type="hidden" value="videos">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<each(var item : res)>
|
||||
<raw(Components.DownloadedVideo(item))>
|
||||
</each>
|
||||
<footer class="row center-align">
|
||||
|
||||
<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=videos&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=videos&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
|
||||
|
||||
</footer>
|
||||
|
||||
</null>;
|
||||
}
|
||||
break;
|
||||
case "playlists":
|
||||
{
|
||||
|
||||
var res = tytd.GetPlaylists(q,page,20);
|
||||
html = <null>
|
||||
<form hx-get="./downloads" hx-target="body" hx-push-url="true" class="s m">
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label suffix border round fill">
|
||||
<select name="type">
|
||||
<option value="videos">Videos</option>
|
||||
<option value="playlists" selected>Playlists</option>
|
||||
<option value="channels">Channels</option>
|
||||
<option value="personal">Personal List</option>
|
||||
|
||||
</select>
|
||||
<label>Type</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button>Change</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<nav class="tabbed l">
|
||||
<a hx-get="./downloads?type=videos" hx-target="body" hx-push-url="true">
|
||||
<i>movie</i>
|
||||
<span>Videos</span>
|
||||
</a>
|
||||
<a class="active" hx-get="./downloads?type=playlists" hx-target="body" hx-push-url="true">
|
||||
<i>list</i>
|
||||
<span>Playlists</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=channels" hx-target="body" hx-push-url="true">
|
||||
<i>person</i>
|
||||
<span>Channels</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true">
|
||||
<i>edit_note</i>
|
||||
<span>Personal Lists</span>
|
||||
</a>
|
||||
|
||||
|
||||
</nav>
|
||||
<form hx-get="./downloads" hx-target="body" hx-push-url="true">
|
||||
<div class="field large prefix round fill active">
|
||||
<i class="front">search</i>
|
||||
<input name="q" value={q}>
|
||||
<input name="type" type="hidden" value="playlists">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<each(var item : res)>
|
||||
<raw(Components.DownloadedPlaylist(item))>
|
||||
</each>
|
||||
<footer class="row center-align">
|
||||
|
||||
<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=playlists&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=playlists&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
|
||||
|
||||
</footer>
|
||||
|
||||
</null>;
|
||||
}
|
||||
break;
|
||||
case "channels":
|
||||
{
|
||||
|
||||
var res = tytd.GetChannels(q,page,20);
|
||||
html = <null>
|
||||
<form hx-get="./downloads" hx-target="body" hx-push-url="true" class="s m">
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label suffix border round fill">
|
||||
<select name="type">
|
||||
<option value="videos">Videos</option>
|
||||
<option value="playlists">Playlists</option>
|
||||
<option value="channels" selected>Channels</option>
|
||||
<option value="personal">Personal List</option>
|
||||
</select>
|
||||
<label>Type</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button>Change</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<nav class="tabbed l">
|
||||
<a hx-get="./downloads?type=videos" hx-target="body" hx-push-url="true">
|
||||
<i>movie</i>
|
||||
<span>Videos</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=playlists" hx-target="body" hx-push-url="true">
|
||||
<i>list</i>
|
||||
<span>Playlists</span>
|
||||
</a>
|
||||
<a class="active" hx-get="./downloads?type=channels" hx-target="body" hx-push-url="true">
|
||||
<i>person</i>
|
||||
<span>Channels</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true">
|
||||
<i>edit_note</i>
|
||||
<span>Personal Lists</span>
|
||||
</a>
|
||||
|
||||
|
||||
</nav>
|
||||
<form hx-get="./downloads" hx-target="body" hx-push-url="true">
|
||||
<div class="field large prefix round fill active">
|
||||
<i class="front">search</i>
|
||||
<input name="q" value={q}>
|
||||
<input name="type" type="hidden" value="channels">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<each(var item : res)>
|
||||
<raw(Components.DownloadedChannel(item))>
|
||||
</each>
|
||||
<footer class="row center-align">
|
||||
|
||||
<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=channels&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=channels&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
|
||||
|
||||
</footer>
|
||||
|
||||
</null>;
|
||||
}
|
||||
break;
|
||||
case "personal":
|
||||
{
|
||||
var res = tytd.GetPersonalLists();
|
||||
html = <null>
|
||||
<form hx-get="./downloads" hx-target="body" hx-push-url="true" class="s m">
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label suffix border round fill">
|
||||
<select name="type">
|
||||
<option value="videos">Videos</option>
|
||||
<option value="playlists">Playlists</option>
|
||||
<option value="channels">Channels</option>
|
||||
<option value="personal" selected>Personal List</option>
|
||||
</select>
|
||||
<label>Type</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button>Change</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<nav class="tabbed l">
|
||||
<a hx-get="./downloads?type=videos" hx-target="body" hx-push-url="true">
|
||||
<i>movie</i>
|
||||
<span>Videos</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=playlists" hx-target="body" hx-push-url="true">
|
||||
<i>list</i>
|
||||
<span>Playlists</span>
|
||||
</a>
|
||||
<a hx-get="./downloads?type=channels" hx-target="body" hx-push-url="true">
|
||||
<i>person</i>
|
||||
<span>Channels</span>
|
||||
</a>
|
||||
<a class="active" hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true">
|
||||
<i>edit_note</i>
|
||||
<span>Personal Lists</span>
|
||||
</a>
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
<each(var item : res)>
|
||||
<if(TypeOf(item.firstVideo) == "String")>
|
||||
<true>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img src={$"./api/v1/video-thumbnail?v={Net.Http.UrlEncode(item.firstVideo)}"} width="144" width="120">
|
||||
</div>
|
||||
<div class="max">
|
||||
<a href={$"./list?name={Net.Http.UrlEncode(item.name)}"}>{item.name}</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</true>
|
||||
<false>
|
||||
<div><a href={$"./list?name={Net.Http.UrlEncode(item.name)}"}>{item.name}</a></div>
|
||||
</false>
|
||||
</if>
|
||||
</each>
|
||||
|
||||
|
||||
</null>;
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return Components.Shell($"Downloads {q}",html,1);
|
||||
}
|
||||
11
Tesses.YouTubeDownloader.Server/src/pages/index.tcross
Normal file
11
Tesses.YouTubeDownloader.Server/src/pages/index.tcross
Normal file
@@ -0,0 +1,11 @@
|
||||
func Pages.Index(tytd)
|
||||
{
|
||||
|
||||
var html = <null>
|
||||
<raw(Components.Add())>
|
||||
<raw(Components.Progress(tytd))>
|
||||
</null>;
|
||||
|
||||
|
||||
return Components.Shell("Home",html,0);
|
||||
}
|
||||
36
Tesses.YouTubeDownloader.Server/src/pages/list.tcross
Normal file
36
Tesses.YouTubeDownloader.Server/src/pages/list.tcross
Normal file
@@ -0,0 +1,36 @@
|
||||
func Pages.List(tytd,ctx)
|
||||
{
|
||||
var name = ctx.QueryParams.TryGetFirst("name");
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
if(TypeOf(page) != "Long") page = 1;
|
||||
page--;
|
||||
if(TypeOf(name) != "String")
|
||||
{
|
||||
name = "";
|
||||
}
|
||||
|
||||
var res = tytd.GetPersonalListContents(name, page, 20);
|
||||
|
||||
var html = <null>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<button hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true"><i>arrow_back</i></button>
|
||||
</div>
|
||||
<div class="max">
|
||||
<h1>{name}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<each(var item : res)>
|
||||
<raw(Components.DownloadedVideo(item))>
|
||||
</each>
|
||||
<footer class="row center-align">
|
||||
|
||||
<button hx-get={$"./downloads?name={Net.Http.UrlEncode(name)}&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./downloads?name={Net.Http.UrlEncode(name)}&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
|
||||
|
||||
</footer>
|
||||
|
||||
</null>;
|
||||
|
||||
|
||||
return Components.Shell($"Personal List {name}",html,1);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
func Pages.Artist(music,ctx)
|
||||
{
|
||||
|
||||
var id = ctx.QueryParams.TryGetFirst("id");
|
||||
var res = music.GetArtistsAlbums(id);
|
||||
|
||||
if(TypeOf(id) != "String")
|
||||
{
|
||||
return Components.Shell("Error",<h1>Artist id not specified</h1>,pages);
|
||||
}
|
||||
|
||||
|
||||
var html = <null>
|
||||
<h1>{res.name}</h1>
|
||||
<ul>
|
||||
<each(var item : res.items)>
|
||||
<li><raw(Components.MusicAlbum(item))></li>
|
||||
</each>
|
||||
</ul>
|
||||
</null>;
|
||||
|
||||
|
||||
return Components.Shell($"Artist {res.name}",html,2);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
func Pages.Artists(music,ctx)
|
||||
{
|
||||
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
var res = null;
|
||||
var q2 = "";
|
||||
if(TypeOf(q) == "String")
|
||||
{
|
||||
q2 = q;
|
||||
res = music.GetArtists(q);
|
||||
|
||||
}
|
||||
|
||||
var html = <null>
|
||||
<form hx-get="./music-artists" hx-target="body" hx-push-url="true">
|
||||
<div class="field large prefix round fill active">
|
||||
<i class="front">search</i>
|
||||
<input name="q" value={q2}>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
<if(res != null)>
|
||||
<true>
|
||||
<ul>
|
||||
<each(var item : res)>
|
||||
<li><raw(Components.MusicArtist(item))></li>
|
||||
</each>
|
||||
</ul>
|
||||
</true></null>;
|
||||
|
||||
|
||||
return Components.Shell($"Search Artists {q2}",html,2);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
func Pages.Music(ctx)
|
||||
{
|
||||
var html = <ul>
|
||||
<div><a href="./music-artists">Artists</a></div>
|
||||
<div><a href="./music-downloaded">My Music</a></div>
|
||||
</ul>;
|
||||
return Components.Shell("Music",html,2);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
func Pages.PlaylistInfo(tytd,ctx)
|
||||
{
|
||||
var id = ctx.QueryParams.TryGetFirst("id");
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
if(TypeOf(page) != "Long") page = 1;
|
||||
page--;
|
||||
|
||||
var html = <h1>Could not find playlist</h1>;
|
||||
var title = "N/A";
|
||||
|
||||
if(TypeOf(id) == "String")
|
||||
{
|
||||
id = TYTD.GetPlaylistId(id);
|
||||
var res = tytd.GetPlaylistContents(id,page,20);
|
||||
|
||||
html = <null>
|
||||
<div class="col">
|
||||
<div class="max">
|
||||
<h1>{res.title}</h1>
|
||||
</div>
|
||||
<div class="max">
|
||||
<a href={$"./channel?id={Net.Http.UrlEncode(res.channelId)}"}>{res.channelTitle}</a>
|
||||
</div>
|
||||
</div>
|
||||
<each(var item : res.items)>
|
||||
<raw(Components.DownloadedVideo(item))>
|
||||
</each>
|
||||
<footer class="row center-align">
|
||||
|
||||
<button hx-get={$"./playlist?id={Net.Http.UrlEncode(id)}&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./playlist?id={Net.Http.UrlEncode(id)}&type=videos&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
|
||||
|
||||
</footer>
|
||||
</null>;
|
||||
|
||||
title = res.authorName;
|
||||
}
|
||||
|
||||
return Components.Shell($"Playlist {title}",html,1);
|
||||
}
|
||||
79
Tesses.YouTubeDownloader.Server/src/pages/plugins-dl.tcross
Normal file
79
Tesses.YouTubeDownloader.Server/src/pages/plugins-dl.tcross
Normal file
@@ -0,0 +1,79 @@
|
||||
func Pages.DownloadPlugins(tytd,ctx)
|
||||
{
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
if(TypeOf(q) != "String")
|
||||
{
|
||||
q = "";
|
||||
}
|
||||
var server = ctx.QueryParams.TryGetFirst("server");
|
||||
if(TypeOf(server) != "String") server = "https://cpkg.tesseslanguage.com/";
|
||||
var items=[];
|
||||
var items2 = [];
|
||||
|
||||
each(var item : tytd.PackageManager.Search(q,{server,type="lib",pluginHost="tytd2025"}))
|
||||
{
|
||||
items2.Add({
|
||||
name = item.packageName,
|
||||
version = item.version,
|
||||
url = $"{server}/package?name={Net.Http.UrlEncode(item.packageName)}",
|
||||
thumb = $"{server}/api/v1/package_icon.png?name={Net.Http.UrlEncode(item.packageName)}&version={Net.Http.UrlEncode(item.version)}"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
each(var item : tytd.PackageManager.GetPackageServers())
|
||||
{
|
||||
items.Add({
|
||||
active = items.Count == 0,
|
||||
url = item
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var html= <null><nav class="tabbed">
|
||||
<a hx-get="./plugins" hx-target="body" hx-push-url="true">
|
||||
<i>download_done</i>
|
||||
<span>Installed</span>
|
||||
</a>
|
||||
<a hx-get="./plugins-download" hx-target="body" class="active" hx-push-url="true">
|
||||
<i>download</i>
|
||||
<span>Download</span>
|
||||
</a>
|
||||
</nav>
|
||||
<form hx-get="./plugins-download" hx-target="body" hx-push-url="true">
|
||||
|
||||
<div class="field suffix border round">
|
||||
<select name="server">
|
||||
<each(var item : items)>
|
||||
<if(item.active)>
|
||||
<true>
|
||||
<option value={item.url} selected>{item.url}</option>
|
||||
</true>
|
||||
<false>
|
||||
<option value={item.url}>{item.url}</option>
|
||||
</false>
|
||||
</if>
|
||||
</each>
|
||||
</select>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
<div class="row no-space">
|
||||
<div class="field border left-round max">
|
||||
<input type="text" name="q" value={q}>
|
||||
</div>
|
||||
<button type="submit" class="large right-round min">Search</button>
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<each(var item : items2)>
|
||||
<raw(Components.PackageItem(tytd,item))>
|
||||
|
||||
</each>
|
||||
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Download plugins",html ,2);
|
||||
}
|
||||
21
Tesses.YouTubeDownloader.Server/src/pages/plugins.tcross
Normal file
21
Tesses.YouTubeDownloader.Server/src/pages/plugins.tcross
Normal file
@@ -0,0 +1,21 @@
|
||||
func Pages.Plugins(tytd,ctx)
|
||||
{
|
||||
var html= <null><nav class="tabbed">
|
||||
<a class="active" hx-get="./plugins" hx-target="body" hx-push-url="true">
|
||||
<i>download_done</i>
|
||||
<span>Installed</span>
|
||||
</a>
|
||||
<a hx-get="./plugins-download" hx-target="body" hx-push-url="true">
|
||||
<i>download</i>
|
||||
<span>Download</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<each(var item : tytd.Plugins)>
|
||||
<raw(Components.InstalledPlugin(item))>
|
||||
|
||||
</each>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Installed plugins",html ,2);
|
||||
}
|
||||
66
Tesses.YouTubeDownloader.Server/src/pages/settings.tcross
Normal file
66
Tesses.YouTubeDownloader.Server/src/pages/settings.tcross
Normal file
@@ -0,0 +1,66 @@
|
||||
func Pages.Settings(tytd,ctx)
|
||||
{
|
||||
var totalSecs = tytd.Config.BellTimer ?? 10800;
|
||||
var enablePlugins = tytd.Config.EnablePlugins ?? true;
|
||||
var hours = totalSecs / 3600;
|
||||
totalSecs -= hours * 3600;
|
||||
var minutes = totalSecs / 60;
|
||||
var seconds = totalSecs % 60;
|
||||
|
||||
|
||||
var html = <form hx-post="./settings" hx-target="body" >
|
||||
<div class="field middle-align">
|
||||
<nav>
|
||||
<div class="max">
|
||||
<h6>Enable plugins</h6>
|
||||
<div>Plugins allow you to extend tytd, but they have full access</div>
|
||||
</div>
|
||||
<label class="switch">
|
||||
<if(enablePlugins)>
|
||||
<true>
|
||||
<input name="enablePlugins" type="checkbox" checked>
|
||||
</true>
|
||||
<false>
|
||||
<input name="enablePlugins" type="checkbox">
|
||||
</false>
|
||||
</if>
|
||||
<span></span>
|
||||
</label>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="field label border">
|
||||
<input type="text" name="tag" value={tytd.TYTDTag}>
|
||||
<label>TYTD Tag</label>
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Subscriber Poll Rate</legend>
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label border">
|
||||
<input type="number" name="hours" value={hours}>
|
||||
<label>Hours</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="field label border">
|
||||
<input type="number" name="minutes" value={minutes}>
|
||||
<label>Minutes</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max">
|
||||
<div class="field label border">
|
||||
<input type="number" name="seconds" value={seconds}>
|
||||
<label>Seconds</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<footer>
|
||||
<button>Save</button>
|
||||
</footer>
|
||||
</form>;
|
||||
|
||||
return Components.Shell("Settings",html ,3);
|
||||
}
|
||||
30
Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross
Normal file
30
Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross
Normal file
@@ -0,0 +1,30 @@
|
||||
func Pages.VideoInfo(tytd,ctx)
|
||||
{
|
||||
var vid = ctx.QueryParams.TryGetFirst("v");
|
||||
tytd.PutVideoInfoIfNotExists(vid);
|
||||
var vi = tytd.GetVideo(vid);
|
||||
var html = <h1>Could not find video</h1>;
|
||||
|
||||
if(vi != null)
|
||||
{
|
||||
html = <div class="col">
|
||||
<div class="min">
|
||||
<img src={$"./api/v1/video-thumbnail?v={Net.Http.UrlEncode(vi.videoId)}"}>
|
||||
</div>
|
||||
<div class="min">
|
||||
<raw(Components.AddVideo($"https://www.youtube.com/watch?v={vi.videoId}"))>
|
||||
</div>
|
||||
<div class="min">
|
||||
<raw(Components.AddToPersonalList(tytd,vi.videoId))>
|
||||
</div>
|
||||
<div class="min">
|
||||
<h4>{vi.title}</h4>
|
||||
<a href={$"./channel?id={Net.Http.UrlEncode(vi.channelId)}"}>{vi.author}</a>
|
||||
<p>{vi.shortDescription}</p>
|
||||
</div>
|
||||
|
||||
</div>;
|
||||
}
|
||||
|
||||
return Components.Shell(vi != null ? vi.title : "Could not find video", html, 1);
|
||||
}
|
||||
Reference in New Issue
Block a user