From c993977e65223129d81e28263284a28d2f3ddbf2 Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Wed, 30 Sep 2026 04:03:51 -0500 Subject: [PATCH] Target crosslang v0.0.11, add channel tags, descriptions and thumbnail downloading --- .gitea/workflows/work.yml | 4 +- .gitignore | 3 +- Tesses.YouTubeDownloader.Server/cross.json | 4 +- .../res/apiv1_routes.json | 4 +- Tesses.YouTubeDownloader.Server/res/theme.css | 5 + .../src/components/downloaded-entry.tcross | 2 +- .../src/components/shell.tcross | 9 +- .../src/components/subscribebtn.tcross | 31 ++++ .../src/main.tcross | 105 ++++++++++-- .../src/pages/admin.tcross | 2 +- .../src/pages/api.tcross | 2 +- .../src/pages/api/api-v1.tcross | 2 +- .../src/pages/changepassword.tcross | 2 +- .../src/pages/channel/info.tcross | 19 ++- .../src/pages/edituser.tcross | 4 +- .../src/pages/newaccount.tcross | 2 +- .../src/pages/plugins-dl.tcross | 2 +- .../src/pages/plugins.tcross | 2 +- .../src/pages/settings.tcross | 2 +- .../src/pages/subscriptions.tcross | 22 +++ .../src/pages/video/info.tcross | 12 +- .../src/pages/whoami.tcross | 2 +- Tesses.YouTubeDownloader/cross.json | 6 +- Tesses.YouTubeDownloader/src/Bell.tcross | 8 +- .../src/YouTubeDownloader.tcross | 161 +++++++++++++----- changeme.md => changelog.md | 2 + nfpm.yaml | 2 +- 27 files changed, 331 insertions(+), 90 deletions(-) create mode 100644 Tesses.YouTubeDownloader.Server/src/pages/subscriptions.tcross rename changeme.md => changelog.md (62%) diff --git a/.gitea/workflows/work.yml b/.gitea/workflows/work.yml index a263ac2..534c727 100644 --- a/.gitea/workflows/work.yml +++ b/.gitea/workflows/work.yml @@ -14,7 +14,7 @@ env: jobs: build-packages: name: "🔨 Build" - runs-on: fw13-ubuntu-latest + runs-on: msi-walmart container: image: "git.tesses.org/tesses50/linux-x64:latest" @@ -56,4 +56,4 @@ jobs: - \ No newline at end of file + diff --git a/.gitignore b/.gitignore index 4ac87b8..0ee450e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ TYTD artifacts *.pkg.tar.zst *.deb -*.rpm \ No newline at end of file +*.rpm +tmp/ \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/cross.json b/Tesses.YouTubeDownloader.Server/cross.json index a51b358..b14900a 100644 --- a/Tesses.YouTubeDownloader.Server/cross.json +++ b/Tesses.YouTubeDownloader.Server/cross.json @@ -14,6 +14,6 @@ "project_dependencies": [ "..\/Tesses.YouTubeDownloader" ], - "version": "1.0.1.0-dev", + "version": "1.0.2.0-dev", "compTime": "secure" -} \ No newline at end of file +} diff --git a/Tesses.YouTubeDownloader.Server/res/apiv1_routes.json b/Tesses.YouTubeDownloader.Server/res/apiv1_routes.json index b147ce0..cd473fa 100644 --- a/Tesses.YouTubeDownloader.Server/res/apiv1_routes.json +++ b/Tesses.YouTubeDownloader.Server/res/apiv1_routes.json @@ -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": [ { diff --git a/Tesses.YouTubeDownloader.Server/res/theme.css b/Tesses.YouTubeDownloader.Server/res/theme.css index cc31281..500b7b9 100644 --- a/Tesses.YouTubeDownloader.Server/res/theme.css +++ b/Tesses.YouTubeDownloader.Server/res/theme.css @@ -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; } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/components/downloaded-entry.tcross b/Tesses.YouTubeDownloader.Server/src/components/downloaded-entry.tcross index 45fbf95..359ac89 100644 --- a/Tesses.YouTubeDownloader.Server/src/components/downloaded-entry.tcross +++ b/Tesses.YouTubeDownloader.Server/src/components/downloaded-entry.tcross @@ -49,7 +49,7 @@ func Components.DownloadedChannel(item) { var res =
- +
{item.title} diff --git a/Tesses.YouTubeDownloader.Server/src/components/shell.tcross b/Tesses.YouTubeDownloader.Server/src/components/shell.tcross index d86ed39..aeea0be 100644 --- a/Tesses.YouTubeDownloader.Server/src/components/shell.tcross +++ b/Tesses.YouTubeDownloader.Server/src/components/shell.tcross @@ -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) - TYTD - {title} + {title} - TYTD2025 diff --git a/Tesses.YouTubeDownloader.Server/src/components/subscribebtn.tcross b/Tesses.YouTubeDownloader.Server/src/components/subscribebtn.tcross index b92d8d7..d613f44 100644 --- a/Tesses.YouTubeDownloader.Server/src/components/subscribebtn.tcross +++ b/Tesses.YouTubeDownloader.Server/src/components/subscribebtn.tcross @@ -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; + } + +
+ +
+
+} + func Components.Subscribe(tytd,id) { var bell = tytd.GetSubscriptionBell(id); diff --git a/Tesses.YouTubeDownloader.Server/src/main.tcross b/Tesses.YouTubeDownloader.Server/src/main.tcross index e842feb..be3cafa 100644 --- a/Tesses.YouTubeDownloader.Server/src/main.tcross +++ b/Tesses.YouTubeDownloader.Server/src/main.tcross @@ -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; -} \ No newline at end of file +} diff --git a/Tesses.YouTubeDownloader.Server/src/pages/admin.tcross b/Tesses.YouTubeDownloader.Server/src/pages/admin.tcross index d5694d8..304a666 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/admin.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/admin.tcross @@ -72,5 +72,5 @@ func Pages.Admin(tytd,ctx) ; - return Components.Shell("Admin Settings",html ,4); + return Components.Shell("Admin Settings",html ,5); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/api.tcross b/Tesses.YouTubeDownloader.Server/src/pages/api.tcross index 9607355..15356bb 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/api.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/api.tcross @@ -8,5 +8,5 @@ func Pages.Api() ; - return Components.Shell("Api",html,5); + return Components.Shell("Api",html,6); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/api/api-v1.tcross b/Tesses.YouTubeDownloader.Server/src/pages/api/api-v1.tcross index 1375cfd..02577d0 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/api/api-v1.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/api/api-v1.tcross @@ -21,7 +21,7 @@ func Pages.ApiV1() ; - return Components.Shell("Api V1",html,5); + return Components.Shell("Api V1",html,6); } var apiv1_routes = Json.Decode(embed("apiv1_routes.json").ToString()); diff --git a/Tesses.YouTubeDownloader.Server/src/pages/changepassword.tcross b/Tesses.YouTubeDownloader.Server/src/pages/changepassword.tcross index 98eb16b..e0ce608 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/changepassword.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/changepassword.tcross @@ -45,5 +45,5 @@ const html = ; - return Components.Shell("Change your password",html,4); + return Components.Shell("Change your password",html,5); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/channel/info.tcross b/Tesses.YouTubeDownloader.Server/src/pages/channel/info.tcross index 4049c44..48a5cd7 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/channel/info.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/channel/info.tcross @@ -8,13 +8,21 @@ func Pages.ChannelInfo(tytd,ctx) var html =

Could not find channel

; var title = "N/A"; - if(TypeOf(id) == "String") - { id = TYTD.GetChannelId(id); + if(TypeIsString(id)) + { + + + var res = tytd.GetChannelContents(id,page,20); + html =
+
+ + +

{res.authorName}

@@ -22,6 +30,11 @@ func Pages.ChannelInfo(tytd,ctx)
+ +
+ Description expand_more + +
@@ -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); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/edituser.tcross b/Tesses.YouTubeDownloader.Server/src/pages/edituser.tcross index 03f3d5e..24a2a36 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/edituser.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/edituser.tcross @@ -38,7 +38,7 @@ func Pages.EditUserList(tytd, ctx) ; - 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) ; - return Components.Shell("Edit user",html, 4); + return Components.Shell("Edit user",html, 5); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/newaccount.tcross b/Tesses.YouTubeDownloader.Server/src/pages/newaccount.tcross index 9da9521..82bf6b5 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/newaccount.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/newaccount.tcross @@ -81,5 +81,5 @@ func Pages.NewUser(error) ; - return Components.Shell("New User",html ,4); + return Components.Shell("New User",html ,5); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/plugins-dl.tcross b/Tesses.YouTubeDownloader.Server/src/pages/plugins-dl.tcross index 73ddcb9..568143f 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/plugins-dl.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/plugins-dl.tcross @@ -75,5 +75,5 @@ func Pages.DownloadPlugins(tytd,ctx) ; - return Components.Shell("Download plugins",html ,3); + return Components.Shell("Download plugins",html ,4); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/plugins.tcross b/Tesses.YouTubeDownloader.Server/src/pages/plugins.tcross index b3eef41..388e385 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/plugins.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/plugins.tcross @@ -20,5 +20,5 @@ func Pages.Plugins(tytd,ctx) ; - return Components.Shell("Installed plugins", UserFlags.CanUsePlugins(userFlags) ? html :

You can{"'"}t use plugins

,3); + return Components.Shell("Installed plugins", UserFlags.CanUsePlugins(userFlags) ? html :

You can{"'"}t use plugins

,4); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/settings.tcross b/Tesses.YouTubeDownloader.Server/src/pages/settings.tcross index e34589a..296ddb6 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/settings.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/settings.tcross @@ -15,5 +15,5 @@ func Pages.Settings(tytd,ctx) ; - return Components.Shell("Settings",html ,4); + return Components.Shell("Settings",html ,5); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/subscriptions.tcross b/Tesses.YouTubeDownloader.Server/src/pages/subscriptions.tcross new file mode 100644 index 0000000..3c51674 --- /dev/null +++ b/Tesses.YouTubeDownloader.Server/src/pages/subscriptions.tcross @@ -0,0 +1,22 @@ +func Pages.Subscriptions(tytd) +{ + const html = +

Subscriptions

+ +
+
+ +
+ +
+ +
+ +
+ +
; + + return Components.Shell("Subscriptions",html ,3); +} \ No newline at end of file diff --git a/Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross b/Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross index 1412a17..3d5b389 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross @@ -20,7 +20,17 @@ func Pages.VideoInfo(tytd,ctx)

{vi.title}

- {vi.author} +
+
+ +
+ +
+ +
+
diff --git a/Tesses.YouTubeDownloader.Server/src/pages/whoami.tcross b/Tesses.YouTubeDownloader.Server/src/pages/whoami.tcross index 6e2dc55..b221c5a 100644 --- a/Tesses.YouTubeDownloader.Server/src/pages/whoami.tcross +++ b/Tesses.YouTubeDownloader.Server/src/pages/whoami.tcross @@ -11,5 +11,5 @@ func Pages.WhoAmI(row)
  • Can manage plugins: {UserFlags.CanManagePlugins(row.flags)}
  • ; - return Components.Shell("Settings",html ,4); + return Components.Shell("Settings",html ,5); } \ No newline at end of file diff --git a/Tesses.YouTubeDownloader/cross.json b/Tesses.YouTubeDownloader/cross.json index 9fb9efd..027a14e 100644 --- a/Tesses.YouTubeDownloader/cross.json +++ b/Tesses.YouTubeDownloader/cross.json @@ -2,7 +2,7 @@ "info": { "description": "Download YouTube Videos (using CrossLang)", "maintainer": "Mike Nolan", - "repo":"https://onedev.site.tesses.net/tytd2025", + "repo":"https://git.tesses.org/tytd2025", "homepage": "https://tesses.net/apps/tytd/2025/", "type": "lib", "license": "GPLv3-or-later" @@ -14,6 +14,6 @@ } ], "name": "Tesses.YouTubeDownloader", - "version": "1.0.1.0-dev", + "version": "1.0.2.0-dev", "icon": "icon.png" -} \ No newline at end of file +} diff --git a/Tesses.YouTubeDownloader/src/Bell.tcross b/Tesses.YouTubeDownloader/src/Bell.tcross index b5a39ac..1d72cbd 100644 --- a/Tesses.YouTubeDownloader/src/Bell.tcross +++ b/Tesses.YouTubeDownloader/src/Bell.tcross @@ -20,10 +20,10 @@ class SubscriptionBell {name="Disabled",value=SubscriptionBell.Disabled}, {name="Notify",value=SubscriptionBell.Bell}, {name="Notify and Download (Low)", value=SubscriptionBell.BellLow}, - {name="Notify and Download (High)", value=SubscriptionBell.BellHigh}, - {name="Download (Low)", value=SubscriptionBell.DownloadLow}, - {name="Download (High)", value=SubscriptionBell.DownloadHigh} + // {name="Notify and Download (High)", value=SubscriptionBell.BellHigh}, + {name="Download (Low)", value=SubscriptionBell.DownloadLow} + // ,{name="Download (High)", value=SubscriptionBell.DownloadHigh} ]; } -} \ No newline at end of file +} diff --git a/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross b/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross index 0f82c68..3deb4f3 100644 --- a/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross +++ b/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross @@ -131,7 +131,7 @@ class TYTD.Downloader { }; this.BeforeQueued.Invoke(this, ent); if(ent.Cancel) { - this.LOG("Adding video canceled: {theVideoId}"); + this.LOG($"Adding video canceled: {theVideoId}"); return; } } @@ -199,6 +199,7 @@ class TYTD.Downloader { if(cid != null) { + DownloadChannelMetadataIfNotExists(cid); const ent = { Id = cid, @@ -451,23 +452,30 @@ class TYTD.Downloader { public GetChannelContents(id, offset, count) { id = TYTD.GetChannelId(id); + //title TEXT, description TEXT, tags TEXT if(id == null) return { authorName = "N/A", + description = "", + tags = [], items = [] }; this.Mutex.Lock(); var db = this.OpenDB(); var authorName = ""; + var description = ""; + var tags = []; var res = Sqlite.Exec(db, $"SELECT * FROM videos v WHERE (v.channelId = {Sqlite.Escape(id)}) LIMIT {count} OFFSET {offset*count};"); - if(TypeOf(res) != "List" || res.Length == 0) { - var res2 = Sqlite.Exec(db,"SELECT * FROM channels c WHERE c.channelId = {Sqlite.Escape(id)};"); - if(TypeOf(res2) == "List" && res2.Length > 0) - { - authorName = res2[0].title; - } + var res2 = Sqlite.Exec(db,$"SELECT * FROM channels c WHERE c.channelId = {Sqlite.Escape(id)};"); + + if(TypeOf(res2) == "List" && res2.Length > 0) + { + authorName = res2[0].title; + description = res2[0].description ?? ""; + tags = Json.Decode(res2[0].tags ?? "[]") ?? []; } - else { + else if(TypeOf(res) == "List" && res.Length > 0) + { authorName = res[0].author; } Sqlite.Close(db); @@ -489,6 +497,8 @@ class TYTD.Downloader { } return { authorName, + description, + tags, items = res }; } @@ -508,6 +518,12 @@ class TYTD.Downloader { this.Mutex.Unlock(); if(TypeOf(res) != "List") throw res; + + each(var item in res) + { + item.description ??= ""; + item.tags = Json.Decode(item.tags ?? "[]"); + } return res; } @@ -811,11 +827,20 @@ class TYTD.Downloader { if(TypeOf(res) == "List" && res.Count > 0) return res[0].bell; return null; } + + public GetSubscriptions() + { + this.Mutex.Lock(); + var db = this.OpenDB(); + var res = Sqlite.Exec(db,"SELECT subscriptions.channelId, subscriptions.bell, channels.title FROM subscriptions INNER JOIN channels ON subscriptions.channelId = channels.channelId;"); + Sqlite.Close(db); + this.Mutex.Unlock(); + + return res; + } public GetSubscriptionUrls() { - var cid = TYTD.GetChannelId(url); - if(cid == null) return []; //Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS subscriptions (id INTEGER PRIMARY KEY AUTOINCREMENT, channelId TEXT UNIQUE ON CONFLICT REPLACE, bell TEXT);"); this.Mutex.Lock(); var db = this.OpenDB(); @@ -951,6 +976,11 @@ class TYTD.Downloader { { var id = TYTD.GetChannelId(id); if(id == null) return FS.ReadAllBytes(this.Storage,/"Streams"/"nullthumb.jpg"); + + const videoThumb = /"ChannelThumbnails"/id.Substring(0,4)/id.Substring(4) + ".jpg"; + + if(Storage.FileExists(videoThumb)) return FS.ReadAllBytes(Storage, videoThumb); + this.Mutex.Lock(); var db = this.OpenDB(); var _res = Sqlite.Exec(db,$"SELECT * FROM videos WHERE channelId = {Sqlite.Escape(id)} LIMIT 1;"); @@ -1075,6 +1105,7 @@ class TYTD.Downloader { } public Start() { + this.Storage.CreateDirectory("ChannelThumbnails"); this.Storage.CreateDirectory(/"Streams"); this.Storage.CreateDirectory(/"Plugins"); if(!this.Storage.FileExists(/"Streams"/"nullthumb.jpg")) @@ -1514,8 +1545,85 @@ class TYTD.Downloader { var d = $"INSERT INTO videos (videoId,title,lengthSeconds,keywords,channelId,shortDescription,viewCount,author,addDate,tytdTag) VALUES ({Sqlite.Escape(info.videoId)},{Sqlite.Escape(info.title)},{info.lengthSeconds},{keywordsStr},{Sqlite.Escape(info.channelId)},{Sqlite.Escape(info.shortDescription)},{info.viewCount},{Sqlite.Escape(info.author)},{DateTime.NowEpoch},{Sqlite.Escape(this.TYTDTag)});"; Sqlite.Exec(db, d); - Sqlite.Exec(db, $"INSERT INTO channels (channelId,title) VALUES ({Sqlite.Escape(info.channelId)},{Sqlite.Escape(info.author)});"); Sqlite.Close(db); + + this.Mutex.Unlock(); + + DownloadChannelMetadataIfNotExists(info.channelId); + } + + private DownloadChannelMetadataIfNotExists(id) + { + this.Mutex.Lock(); + var db = this.OpenDB(); + const results=Sqlite.Exec(db, $"SELECT * FROM channels WHERE channelId = {Sqlite.Escape(id)};"); + Sqlite.Close(db); + + this.Mutex.Unlock(); + + if(TypeIsList(results) && results.Length > 0) return; + + try { + const result = {Id = id ,Tags = [], Title = "", Thumbnail = "", Description=""}; + const str= Net.Http.DownloadToString($"https://www.youtube.com/channel/{id}"); + + const html = Net.Http.ParseHtml(str); + + const headers = html.FindNodes("meta"); + each(var item in headers) + { + const content = item.Attributes["content"] ?? null; + const property = item.Attributes["property"] ?? null; + if(TypeIsString(content) && TypeIsString(property)) + { + switch(property) + { + case "og:image": + result.Thumbnail = content; + break; + case "og:title": + result.Title = content; + break; + case "og:description": + result.Description = content; + break; + case "og:video:tag": + result.Tags.Add(content); + break; + } + } + } + PutChannelMetadataIfNotExists(result); + }catch(ex) { + try{ + this.LOG($"Failed to get info on getting metadata for channel {ex}"); + + }catch(ex2){} + + } + } + + private PutChannelMetadataIfNotExists(metadata) + { + const videoThumb = /"ChannelThumbnails"/metadata.Id.Substring(0,4)/metadata.Id.Substring(4) + ".jpg"; + if(!Storage.FileExists(videoThumb)) + { + Storage.CreateDirectory(videoThumb.GetParent()); + const resp = Net.Http.MakeRequest(metadata.Thumbnail,{FollowRedirects=true}); + if(resp.StatusCode >= 200 && resp.StatusCode <= 299) + { + const strm=this.Storage.OpenFile(videoThumb,"wb"); + resp.CopyToStream(strm); + strm.Close(); + } + } + + this.Mutex.Lock(); + var db = this.OpenDB(); + //CREATE TABLE IF NOT EXISTS channels (id INTEGER PRIMARY KEY AUTOINCREMENT, channelId TEXT UNIQUE, title TEXT, description TEXT, tags TEXT); + Sqlite.Exec(db, $"INSERT INTO channels (channelId,title,description, tags) VALUES ({Sqlite.Escape(metadata.Id)}, {Sqlite.Escape(metadata.Title)}, {Sqlite.Escape(metadata.Description)}, {Sqlite.Escape(Json.Encode(metadata.Tags))});"); + Sqlite.Close(db); + this.Mutex.Unlock(); } @@ -1525,7 +1633,7 @@ class TYTD.Downloader { var db = this.OpenDB(); Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS videos (id INTEGER PRIMARY KEY AUTOINCREMENT, videoId TEXT UNIQUE, title TEXT, lengthSeconds INTEGER, keywords TEXT, channelId TEXT, shortDescription TEXT, viewCount INTEGER, author TEXT, addDate INTEGER, tytdTag TEXT);"); Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS playlists (id INTEGER PRIMARY KEY AUTOINCREMENT, playlistId TEXT UNIQUE,channelId TEXT,channelTitle TEXT, title TEXT);"); - Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS channels (id INTEGER PRIMARY KEY AUTOINCREMENT, channelId TEXT UNIQUE, title TEXT);"); + Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS channels (id INTEGER PRIMARY KEY AUTOINCREMENT, channelId TEXT UNIQUE, title TEXT, description TEXT, tags TEXT);"); Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS playlist_entries (id INTEGER PRIMARY KEY AUTOINCREMENT, playlistId INTEGER, videoId TEXT);"); Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS personal_lists (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, description TEXT);"); Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS personal_list_entries (id INTEGER PRIMARY KEY AUTOINCREMENT, listName TEXT, videoId TEXT);"); @@ -1578,34 +1686,7 @@ class TYTD.Downloader { } } - private DownloadChannelThumbInternal(channelId, thumbnail_url) - { - var id = TYTD.GetChannelId(channelId); - var path = /"ChannelThumbnails"/id.Substring(2,4)/id.Substring(6); - this.Storage.CreateDirectory(path.GetParent()); - if(!(this.Storage.FileExists(path + ".webp") || this.Storage.FileExists(path+".jpg"))) - { - if(thumbnail_url.StartsWith("//")) thumbnail_url = $"https:{thumbnail_url}"; - var dl = Net.Http.MakeRequest(thumbnail_url); - - if(dl.StatusCode >= 200 && dl.StatusCode <= 299) - { - var ct=dl.ResponseHeaders.TryGetFirst("Content-Type"); - if(TypeOf(ct) == "String") - { - var s = ct.Split("; ",true,2); - var ext = ".jpg"; - if(s[0] == "image/webp") - ext = ".webp"; - - var dest = this.Storage.OpenFile(path+ext,"wb"); - dl.CopyToStream(dest); - dest.Close(); - } - } - } - - } + private DiscoverInternal(q,continuation,params) { if(continuation == undefined) var continuation = null; diff --git a/changeme.md b/changelog.md similarity index 62% rename from changeme.md rename to changelog.md index 3a12799..72cbb12 100644 --- a/changeme.md +++ b/changelog.md @@ -1,5 +1,7 @@ # Changelog +## 1.0.2 +Target crosslang v0.0.11, add channel tags, descriptions and thumbnail downloading ## 1.0.1 Target crosslang v0.0.10, add /api/v1/progress_ex.json diff --git a/nfpm.yaml b/nfpm.yaml index 417bc07..01681f9 100644 --- a/nfpm.yaml +++ b/nfpm.yaml @@ -9,7 +9,7 @@ name: "tytd2025" arch: "any" platform: "linux" -version: "1.0.1" +version: "1.0.2" section: "default" priority: "extra"