Target crosslang v0.0.11, add channel tags, descriptions and thumbnail downloading
Build and Deploy on Tag / 🔨 Build (push) Successful in 22s
Build and Deploy on Tag / 🔨 Build (push) Successful in 22s
This commit is contained in:
@@ -14,6 +14,6 @@
|
||||
"project_dependencies": [
|
||||
"..\/Tesses.YouTubeDownloader"
|
||||
],
|
||||
"version": "1.0.1.0-dev",
|
||||
"version": "1.0.2.0-dev",
|
||||
"compTime": "secure"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"route": "/api/v1/video.json",
|
||||
"route": "/api/v1/video",
|
||||
"method": "GET",
|
||||
"queryParams": [
|
||||
{
|
||||
@@ -12,7 +12,7 @@
|
||||
"description": "Returns json object like https://tesses.net/apps/tytd/2025/video-example.json or null if video doesn't exist"
|
||||
},
|
||||
{
|
||||
"route": "/api/v1/downloads.json",
|
||||
"route": "/api/v1/downloads",
|
||||
"method": "GET",
|
||||
"queryParams": [
|
||||
{
|
||||
|
||||
@@ -83,4 +83,9 @@ body.dark {
|
||||
top: 50vh;
|
||||
left: 50vw;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
img.circle.supersized {
|
||||
width: 160px; /* or whatever size you need */
|
||||
height: 160px;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ 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">
|
||||
<img class="circle supersized" src={$"./api/v1/channel-thumbnail?id={Net.Http.UrlEncode(item.channelId)}"}>
|
||||
</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>
|
||||
|
||||
@@ -40,6 +40,13 @@ func Components.Shell(title, html, page, $mypage)
|
||||
classStr="",
|
||||
badge=""
|
||||
},
|
||||
{
|
||||
text = "Subscriptions",
|
||||
icon = "subscriptions",
|
||||
href = (/"subscriptions").MakeRelative(mypage).ToString(),
|
||||
classStr="",
|
||||
badge=""
|
||||
},
|
||||
{
|
||||
text = "Plugins",
|
||||
icon = "extension",
|
||||
@@ -68,7 +75,7 @@ func Components.Shell(title, html, page, $mypage)
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TYTD - {title}</title>
|
||||
<title>{title} - TYTD2025</title>
|
||||
<link rel="stylesheet" href={(/"beer.min.css").MakeRelative(mypage).ToString()}>
|
||||
<link rel="stylesheet" href={(/"theme.css").MakeRelative(mypage).ToString()}>
|
||||
<link rel="manifest" href={(/"site.webmanifest").MakeRelative(mypage).ToString()}>
|
||||
|
||||
@@ -1,3 +1,34 @@
|
||||
func Components.SubscribePageBell(tytd, id)
|
||||
{
|
||||
var bell = tytd.GetSubscriptionBell(id);
|
||||
|
||||
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 class="min" id={$"subscribe-btn-{Net.Http.HtmlEncode(id)}"}>
|
||||
<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-{Net.Http.HtmlEncode(id)}"} hx-vals={Json.Encode({id,type="bell-subscriptions-page",bell=b.value})}>{b.name}</li>
|
||||
</each>
|
||||
</menu>
|
||||
</button>
|
||||
</div>
|
||||
</return>
|
||||
}
|
||||
|
||||
func Components.Subscribe(tytd,id)
|
||||
{
|
||||
var bell = tytd.GetSubscriptionBell(id);
|
||||
|
||||
@@ -540,16 +540,30 @@ class TYTDApp {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/progress_ex.json")
|
||||
else if(ctx.Path == "/api/v1/progress_ex" || ctx.Path == "/api/v1/progress_ex.json")
|
||||
{
|
||||
ctx.WithMimeType("application/json").SendJson(this.TYTD.GetProgressEx());
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/progress.json")
|
||||
else if(ctx.Path == "/api/v1/progress" || ctx.Path == "/api/v1/progress.json")
|
||||
{
|
||||
ctx.WithMimeType("application/json").SendJson(this.TYTD.GetProgress());
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/server")
|
||||
{
|
||||
ctx.WithMimeType("application/json").SendJson(
|
||||
{
|
||||
Service = "tytd2025",
|
||||
TYTD = {
|
||||
CrossLangVersion = VM.RuntimeVersion.ToString(),
|
||||
TYTDTag = this.TYTD.Config.TYTDTag,
|
||||
DownloaderVersion = Resolution.getResolutions.File.Version.ToString()
|
||||
}
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/add")
|
||||
{
|
||||
if(ctx.Method=="POST")
|
||||
@@ -564,14 +578,14 @@ class TYTDApp {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/video.json")
|
||||
else if(ctx.Path == "/api/v1/video" || 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")
|
||||
else if(ctx.Path == "/api/v1/playlist" || ctx.Path == "/api/v1/playlist.json")
|
||||
{
|
||||
var id = ctx.QueryParams.TryGetFirst("id");
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
@@ -582,7 +596,7 @@ class TYTDApp {
|
||||
ctx.WithMimeType("application/json").SendJson(this.TYTD.GetPlaylistContents(id,page-1,count));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/channel.json")
|
||||
else if(ctx.Path == "/api/v1/channel" || ctx.Path == "/api/v1/channel.json")
|
||||
{
|
||||
var id = ctx.QueryParams.TryGetFirst("id");
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
@@ -593,9 +607,9 @@ class TYTDApp {
|
||||
ctx.WithMimeType("application/json").SendJson(this.TYTD.GetChannelContents(id,page-1,count));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/queue.json")
|
||||
else if(ctx.Path == "/api/v1/queue" || ctx.Path == "/api/v1/queue.json")
|
||||
{
|
||||
if(ctx.Method == "GET")
|
||||
if(ctx.Method == "GET" || ctx.Method == "HEAD")
|
||||
{
|
||||
var page = ctx.QueryParams.TryGetFirstInt("page");
|
||||
var count = ctx.QueryParams.TryGetFirstInt("count");
|
||||
@@ -608,7 +622,33 @@ class TYTDApp {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/downloads.json")
|
||||
else if(ctx.Path == "/api/v1/subscriptions")
|
||||
{
|
||||
if(ctx.Method == "GET" || ctx.Method == "HEAD")
|
||||
{
|
||||
ctx.WithMimeType("application/json").SendJson(this.TYTD.GetSubscriptions());
|
||||
}
|
||||
else if(ctx.Method == "POST")
|
||||
{
|
||||
const json = ctx.ParseJson();
|
||||
|
||||
// bell: null => unsubscribe; absent => default to BellLow
|
||||
|
||||
const bell = json.bell == undefined ? SubscriptionBell.BellLow : json.bell;
|
||||
|
||||
const id = json.id ?? "";
|
||||
|
||||
|
||||
|
||||
this.TYTD.SetSubscriptionBell(id, bell);
|
||||
|
||||
|
||||
ctx.StatusCode = 204;
|
||||
ctx.WriteHeaders();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/downloads" || ctx.Path == "/api/v1/downloads.json")
|
||||
{
|
||||
var q = ctx.QueryParams.TryGetFirst("q");
|
||||
var type = ctx.QueryParams.TryGetFirst("type");
|
||||
@@ -661,7 +701,7 @@ class TYTDApp {
|
||||
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/manifest.json")
|
||||
else if(ctx.Path == "/api/v1/manifest" || ctx.Path == "/api/v1/manifest.json")
|
||||
{
|
||||
const v = ctx.QueryParams.TryGetFirst("v");
|
||||
if(TypeIsString(v))
|
||||
@@ -684,7 +724,7 @@ class TYTDApp {
|
||||
name=
|
||||
*/
|
||||
|
||||
if(ctx.Method == "GET")
|
||||
if(ctx.Method == "GET" || ctx.Method == "HEAD")
|
||||
{
|
||||
var name = ctx.QueryParams.TryGetFirst("name");
|
||||
if(TypeOf(name) == "String")
|
||||
@@ -716,7 +756,23 @@ class TYTDApp {
|
||||
{
|
||||
var idHas = TypeOf(j.id) == "String" && j.id != "";
|
||||
var descHas = TypeOf(j.description) == "String" && j.description != "";
|
||||
if(idHas && descHas)
|
||||
var mode = TypeIsString(j.mode) ? j.mode : "add";
|
||||
|
||||
if(mode == "delete")
|
||||
{
|
||||
if(descHas)
|
||||
{
|
||||
ctx.WithMimeType("application/json").SendJson({success=false,reason="You provided a description which doesn't make sense when deleting something."});
|
||||
}
|
||||
else if(idHas)
|
||||
{
|
||||
this.TYTD.RemoveFromPersonalList(j.name,j.id);
|
||||
}
|
||||
else {
|
||||
this.TYTD.RemovePersonalList(j.name);
|
||||
}
|
||||
}
|
||||
else 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)"});
|
||||
}
|
||||
@@ -765,12 +821,17 @@ class TYTDApp {
|
||||
}
|
||||
|
||||
}
|
||||
else if(ctx.Path == "/subscriptions")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Subscriptions(this.TYTD));
|
||||
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");
|
||||
|
||||
var id = ctx.BodyParams.TryGetFirst("id");
|
||||
var bell = ctx.BodyParams.TryGetFirst("bell");
|
||||
var type = ctx.BodyParams.TryGetFirst("type");
|
||||
|
||||
|
||||
switch(type)
|
||||
{
|
||||
@@ -781,9 +842,17 @@ class TYTDApp {
|
||||
case "unsubscribe":
|
||||
this.TYTD.SetSubscriptionBell(id, null);
|
||||
break;
|
||||
case "unsubscribe-subscriptions-page":
|
||||
this.TYTD.SetSubscriptionBell(id, null);
|
||||
ctx.WithHeader("HX-Location", "/subscriptions").WriteHeaders();
|
||||
return true;
|
||||
case "bell":
|
||||
this.TYTD.SetSubscriptionBell(id, bell);
|
||||
break;
|
||||
case "bell-subscriptions-page":
|
||||
this.TYTD.SetSubscriptionBell(id, bell);
|
||||
ctx.WithMimeType("text/html").SendText(Components.SubscribePageBell(this.TYTD,id));
|
||||
return true;
|
||||
}
|
||||
ctx.WithMimeType("text/html").SendText(Components.Subscribe(this.TYTD,id));
|
||||
return true;
|
||||
@@ -797,7 +866,7 @@ class TYTDApp {
|
||||
else if(ctx.Path == "/edit-personal-description")
|
||||
{
|
||||
var name = ctx.QueryParams.TryGetFirst("name");
|
||||
if(ctx.Method == "GET")
|
||||
if(ctx.Method == "GET" || ctx.Method == "HEAD")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Components.PersonalListDescription(this.TYTD,name,true));
|
||||
return true;
|
||||
@@ -968,7 +1037,7 @@ class TYTDApp {
|
||||
else if(ctx.Path == "/watch_videos")
|
||||
{
|
||||
|
||||
if(ctx.Method=="GET")
|
||||
if(ctx.Method=="GET" || ctx.Method == "HEAD")
|
||||
{
|
||||
const video_ids = ctx.QueryParams.TryGetFirst("video_ids");
|
||||
|
||||
@@ -1261,4 +1330,4 @@ func main(args)
|
||||
Net.Http.ListenSimpleWithLoop(res,3255);
|
||||
res.Close();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,5 +72,5 @@ func Pages.Admin(tytd,ctx)
|
||||
|
||||
</form>;
|
||||
|
||||
return Components.Shell("Admin Settings",html ,4);
|
||||
return Components.Shell("Admin Settings",html ,5);
|
||||
}
|
||||
@@ -8,5 +8,5 @@ func Pages.Api()
|
||||
</a>
|
||||
</ul>
|
||||
</null>;
|
||||
return Components.Shell("Api",html,5);
|
||||
return Components.Shell("Api",html,6);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ func Pages.ApiV1()
|
||||
</fieldset>
|
||||
</each>
|
||||
</null>;
|
||||
return Components.Shell("Api V1",html,5);
|
||||
return Components.Shell("Api V1",html,6);
|
||||
}
|
||||
|
||||
var apiv1_routes = Json.Decode(embed("apiv1_routes.json").ToString());
|
||||
|
||||
@@ -45,5 +45,5 @@ const html = <null>
|
||||
</form>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Change your password",html,4);
|
||||
return Components.Shell("Change your password",html,5);
|
||||
}
|
||||
@@ -8,13 +8,21 @@ func Pages.ChannelInfo(tytd,ctx)
|
||||
var html = <h1>Could not find channel</h1>;
|
||||
var title = "N/A";
|
||||
|
||||
if(TypeOf(id) == "String")
|
||||
{
|
||||
id = TYTD.GetChannelId(id);
|
||||
if(TypeIsString(id))
|
||||
{
|
||||
|
||||
|
||||
|
||||
var res = tytd.GetChannelContents(id,page,20);
|
||||
|
||||
|
||||
html = <null>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img class="circle supersized" src={$"./api/v1/channel-thumbnail?id={Net.Http.UrlEncode(id)}"} width="144">
|
||||
|
||||
</div>
|
||||
<div class="max">
|
||||
<h1>{res.authorName}</h1>
|
||||
</div>
|
||||
@@ -22,6 +30,11 @@ func Pages.ChannelInfo(tytd,ctx)
|
||||
<raw(Components.Subscribe(tytd,id))>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details>
|
||||
<summary>Description <i>expand_more</i></summary>
|
||||
<plink(res.description)>
|
||||
</details>
|
||||
<each(var item : res.items)>
|
||||
<raw(Components.DownloadedVideo(item))>
|
||||
</each>
|
||||
@@ -35,5 +48,5 @@ func Pages.ChannelInfo(tytd,ctx)
|
||||
title = res.authorName;
|
||||
}
|
||||
|
||||
return Components.Shell($"Channel {title}",html,2);
|
||||
return Components.Shell(title,html,2);
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func Pages.EditUserList(tytd, ctx)
|
||||
</fieldset>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Edit users",html, 4);
|
||||
return Components.Shell("Edit users",html, 5);
|
||||
}
|
||||
|
||||
func Pages.EditUser(user)
|
||||
@@ -141,5 +141,5 @@ func Pages.EditUser(user)
|
||||
</if>;
|
||||
|
||||
|
||||
return Components.Shell("Edit user",html, 4);
|
||||
return Components.Shell("Edit user",html, 5);
|
||||
}
|
||||
@@ -81,5 +81,5 @@ func Pages.NewUser(error)
|
||||
</form>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("New User",html ,4);
|
||||
return Components.Shell("New User",html ,5);
|
||||
}
|
||||
@@ -75,5 +75,5 @@ func Pages.DownloadPlugins(tytd,ctx)
|
||||
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Download plugins",html ,3);
|
||||
return Components.Shell("Download plugins",html ,4);
|
||||
}
|
||||
@@ -20,5 +20,5 @@ func Pages.Plugins(tytd,ctx)
|
||||
</each>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Installed plugins", UserFlags.CanUsePlugins(userFlags) ? html : <h1>You can{"'"}t use plugins</h1> ,3);
|
||||
return Components.Shell("Installed plugins", UserFlags.CanUsePlugins(userFlags) ? html : <h1>You can{"'"}t use plugins</h1> ,4);
|
||||
}
|
||||
@@ -15,5 +15,5 @@ func Pages.Settings(tytd,ctx)
|
||||
|
||||
</fieldset>;
|
||||
|
||||
return Components.Shell("Settings",html ,4);
|
||||
return Components.Shell("Settings",html ,5);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
func Pages.Subscriptions(tytd)
|
||||
{
|
||||
const html = <null>
|
||||
<h1>Subscriptions</h1>
|
||||
<each(var item : tytd.GetSubscriptions())>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img class="circle supersized" src={$"./api/v1/channel-thumbnail?id={Net.Http.UrlEncode(item.channelId)}"}>
|
||||
</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 class="min">
|
||||
<button hx-post="./subscribe" hx-target="body" hx-push-url="true" hx-vals={Json.Encode({id=item.channelId,type="unsubscribe-subscriptions-page"})}>Unsubscribe</button>
|
||||
</div>
|
||||
<raw(Components.SubscribePageBell(tytd,item.channelId))>
|
||||
</div>
|
||||
</each>
|
||||
</null>;
|
||||
|
||||
return Components.Shell("Subscriptions",html ,3);
|
||||
}
|
||||
@@ -20,7 +20,17 @@ func Pages.VideoInfo(tytd,ctx)
|
||||
</div>
|
||||
<div class="min">
|
||||
<h4>{vi.title}</h4>
|
||||
<a class="underline" href={$"./channel?id={Net.Http.UrlEncode(vi.channelId)}"}>{vi.author}</a>
|
||||
<div class="row">
|
||||
<div class="min">
|
||||
<img class="circle" src={$"./api/v1/channel-thumbnail?id={Net.Http.UrlEncode(vi.channelId)}"} width="144">
|
||||
</div>
|
||||
<div class="min">
|
||||
<a class="underline" href={$"./channel?id={Net.Http.UrlEncode(vi.channelId)}"}>{vi.author}</a>
|
||||
</div>
|
||||
<div class="min">
|
||||
<raw(Components.Subscribe(tytd,vi.channelId))>
|
||||
</div>
|
||||
</div>
|
||||
<plink(vi.shortDescription)>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,5 +11,5 @@ func Pages.WhoAmI(row)
|
||||
<li>Can manage plugins: {UserFlags.CanManagePlugins(row.flags)}</li>
|
||||
</ul>
|
||||
</null>;
|
||||
return Components.Shell("Settings",html ,4);
|
||||
return Components.Shell("Settings",html ,5);
|
||||
}
|
||||
Reference in New Issue
Block a user