diff --git a/Tesses.YouTubeDownloader.Server/cross.json b/Tesses.YouTubeDownloader.Server/cross.json
index f024001..d710983 100644
--- a/Tesses.YouTubeDownloader.Server/cross.json
+++ b/Tesses.YouTubeDownloader.Server/cross.json
@@ -14,5 +14,5 @@
"project_dependencies": [
"..\/Tesses.YouTubeDownloader"
],
- "version": "1.0.0.0-prod"
+ "version": "1.0.0.0-dev"
}
\ No newline at end of file
diff --git a/Tesses.YouTubeDownloader.Server/res/apiv1_routes.json b/Tesses.YouTubeDownloader.Server/res/apiv1_routes.json
new file mode 100644
index 0000000..b147ce0
--- /dev/null
+++ b/Tesses.YouTubeDownloader.Server/res/apiv1_routes.json
@@ -0,0 +1,47 @@
+[
+ {
+ "route": "/api/v1/video.json",
+ "method": "GET",
+ "queryParams": [
+ {
+ "name": "v",
+ "type": "string",
+ "description": "The 11 character videoid"
+ }
+ ],
+ "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",
+ "method": "GET",
+ "queryParams": [
+ {
+ "name": "q",
+ "type": "string?",
+ "description": "The search parameter"
+ },
+ {
+ "name": "type",
+ "type": "string?",
+ "description": "Whether you are searching for videos, playlists, channels or personal (defaults to videos)"
+ },
+ {
+ "name": "page",
+ "type": "long?",
+ "description": "The page to retreve, starts at 1 and defaults to 1"
+ },
+ {
+ "name": "count",
+ "type": "long?",
+ "description": "The entries to return, defaults to 20"
+ }
+ ],
+ "description": "Query or browse the downloads"
+ },
+ {
+ "route": "/api/v1/database.db",
+ "method": "GET",
+ "queryParams": [],
+ "description": "Download the database"
+ }
+]
\ No newline at end of file
diff --git a/Tesses.YouTubeDownloader.Server/src/components/personallistdescription.tcross b/Tesses.YouTubeDownloader.Server/src/components/personallistdescription.tcross
new file mode 100644
index 0000000..224f4eb
--- /dev/null
+++ b/Tesses.YouTubeDownloader.Server/src/components/personallistdescription.tcross
@@ -0,0 +1,44 @@
+func Components.PersonalListDescription(tytd,name,editing)
+{
+ var description = tytd.GetPersonalListDescription(name);
+ var first=true;
+ var description_with_br = "";
+ each(var txt : description.Split("\n"))
+ {
+ if(!first)
+ {
+ description_with_br += ;
+ }
+ description_with_br += Net.Http.HtmlEncode(txt);
+ first=false;
+ }
+
+
+
+
+
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/Tesses.YouTubeDownloader.Server/src/components/plugin.tcross b/Tesses.YouTubeDownloader.Server/src/components/plugin.tcross
index dda889c..77360e7 100644
--- a/Tesses.YouTubeDownloader.Server/src/components/plugin.tcross
+++ b/Tesses.YouTubeDownloader.Server/src/components/plugin.tcross
@@ -11,7 +11,7 @@ func Components.InstalledPlugin(item)
- {TypeOf(item.info.short_name_pretty) == "String" ? item.info.short_name_pretty : (TypeOf(item.info.short_name) == "String" ? item.info.short_name : item.name)}
+ {TypeOf(item.info.short_name_pretty) == "String" ? item.info.short_name_pretty : (TypeOf(item.info.short_name) == "String" ? item.info.short_name : item.name)}
{TypeOf(item.info.short_name_pretty) == "String" ? item.info.short_name_pretty : (TypeOf(item.info.short_name) == "String" ? item.info.short_name : item.name)}
diff --git a/Tesses.YouTubeDownloader.Server/src/components/shell.tcross b/Tesses.YouTubeDownloader.Server/src/components/shell.tcross
index 73a40ad..288adde 100644
--- a/Tesses.YouTubeDownloader.Server/src/components/shell.tcross
+++ b/Tesses.YouTubeDownloader.Server/src/components/shell.tcross
@@ -30,6 +30,12 @@ func Components.Shell(title, html, page, $mypage)
icon = "settings",
href= (/"settings").MakeRelative(mypage).ToString(),
classStr=""
+ },
+ {
+ text = "Api",
+ icon = "api",
+ href = (/"api").MakeRelative(mypage).ToString(),
+ classStr=""
}
];
pages[page].classStr = "active";
diff --git a/Tesses.YouTubeDownloader.Server/src/main.tcross b/Tesses.YouTubeDownloader.Server/src/main.tcross
index 02e4868..c7e3b34 100644
--- a/Tesses.YouTubeDownloader.Server/src/main.tcross
+++ b/Tesses.YouTubeDownloader.Server/src/main.tcross
@@ -31,6 +31,16 @@ class TYTDApp {
ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD));
return true;
}
+ else if(ctx.Path == "/api")
+ {
+ ctx.WithMimeType("text/html").SendText(Pages.Api());
+ return true;
+ }
+ else if(ctx.Path == "/api-v1")
+ {
+ ctx.WithMimeType("text/html").SendText(Pages.ApiV1());
+ return true;
+ }
else if(ctx.Path == "/api/v1/download")
{
var v = ctx.QueryParams.TryGetFirst("v");
@@ -44,12 +54,29 @@ class TYTDApp {
if(path != null)
{
var info = this.TYTD.GetVideo(v);
- var filename = $"{info.title}-{info.videoId}.{path.GetExtension()}";
+ var filename = $"{info.title}-{info.videoId}-{res}{path.GetExtension()}";
var strm = this.TYTD.Storage.OpenFile(path,"rb");
ctx.WithMimeType(Net.Http.MimeType(path.ToString())).WithContentDisposition(filename,inline).SendStream(strm);
strm.Close();
}
}
+ else if(ctx.Path == "/api/v1/progress.json")
+ {
+ var jo = {
+ CurrentVideoProgress = this.TYTD.CurrentVideoProgress,
+ CurrentVideo = this.TYTD.CurrentVideo
+
+ };
+ ctx.WithMimeType("application/json").SendJson(jo);
+ return true;
+ }
+ else if(ctx.Path == "/api/v1/video.json")
+ {
+ var id = ctx.QueryParams.TryGetFirst("v");
+ if(TypeOf(id)!="String") id = "";
+ ctx.WithMimeType("application/json").SendJson(this.TYTD.GetVideo(id));
+ return true;
+ }
else if(ctx.Path == "/api/v1/playlist.json")
{
var id = ctx.QueryParams.TryGetFirst("id");
@@ -102,6 +129,105 @@ class TYTDApp {
ctx.WithMimeType("application/json").SendJson(result);
return true;
}
+ else if(ctx.Path == "/api/v1/database.db")
+ {
+ this.TYTD.SendDatabase(ctx);
+ return true;
+ }
+ else if(ctx.Path == "/api/v1/personal")
+ {
+ /*
+ GET /api/v1/personal
+ GET /api/v1/personal?name=NAME
+ DELETE /api/v1/personal
+ name=NAME
+ id=THEID //If this does not exist delete the entire list
+ POST /api/v1/personal
+ id=THEID or description=
+ name=
+ */
+
+ if(ctx.Method == "GET")
+ {
+ var name = ctx.QueryParams.TryGetFirst("name");
+ if(TypeOf(name) == "String")
+ {
+ var page = ctx.QueryParams.TryGetFirstInt("page") ?? 1;
+ var count = ctx.QueryParams.TryGetFirstInt("count") ?? 20;
+
+ var json = {
+ description = this.TYTD.GetPersonalListDescription(name),
+ items = this.TYTD.GetPersonalListContents(name,page-1,count)
+ };
+ ctx.WithMimeType("application/json").SendJson(json);
+ return true;
+ }
+ else {
+ var json = {
+ items = this.TYTD.GetPersonalLists()
+ };
+ ctx.WithMimeType("application/json").SendJson(json);
+ return true;
+ }
+ }
+ else if(ctx.Method == "POST")
+ {
+ //{name="",id="",description=""}
+
+ var j = ctx.ReadJson();
+ if(TypeOf(j.name) == "String")
+ {
+ var idHas = TypeOf(j.id) == "String" && j.id != "";
+ var descHas = TypeOf(j.description) == "String" && j.description != "";
+ if(idHas && descHas)
+ {
+ ctx.WithMimeType("application/json").SendJson({success=false,reason="You provided both a description (to set the list description) and id (to add a item) (you can only provide one)"});
+ }
+ else if(idHas)
+ {
+ this.TYTD.AddToPersonalList(j.name, j.id);
+ ctx.WithMimeType("application/json").SendJson({success=true});
+ }
+ else if(descHas)
+ {
+ this.TYTD.SetPersonalListDescription(j.name,j.description);
+ ctx.WithMimeType("application/json").SendJson({success=true});
+ }
+ else {
+ ctx.WithMimeType("application/json").SendJson({success=false,reason="You must provide either a description (to set the list description) or id (to add a item)"});
+ }
+ }
+ else {
+ ctx.WithMimeType("application/json").SendJson({success=false, reason="Need a name"});
+ }
+
+ return true;
+ }
+ else if(ctx.Method == "DELETE")
+ {
+ var j = ctx.ReadJson();
+ /*
+ public RemoveFromPersonalList(name,id)
+ public RemovePersonalList(name)
+ */
+ if(TypeOf(j.name) == "String")
+ {
+ if(TypeOf(j.id) == "String" && j.id != "")
+ {
+ this.TYTD.RemoveFromPersonalList(j.name,j.id);
+ }
+ else {
+ this.TYTD.RemovePersonalList(j.name);
+ }
+ ctx.WithMimeType("application/json").SendJson({success=true});
+ }
+ else {
+ ctx.WithMimeType("application/json").SendJson({success=false, reason="Need a name"});
+ }
+ return true;
+ }
+
+ }
else if(ctx.Path == "/subscribe")
{
var id = ctx.QueryParams.TryGetFirst("id");
@@ -125,6 +251,23 @@ class TYTDApp {
ctx.WithMimeType("text/html").SendText(Components.Subscribe(this.TYTD,id));
return true;
}
+ else if(ctx.Path == "/edit-personal-description")
+ {
+ var name = ctx.QueryParams.TryGetFirst("name");
+ if(ctx.Method == "GET")
+ {
+ ctx.WithMimeType("text/html").SendText(Components.PersonalListDescription(this.TYTD,name,true));
+ return true;
+ }
+ else if(ctx.Method == "POST")
+ {
+ var description =ctx.QueryParams.TryGetFirst("description");
+ this.TYTD.SetPersonalListDescription(name,description);
+ ctx.WithMimeType("text/html").SendText(Components.PersonalListDescription(this.TYTD,name,false));
+
+ return true;
+ }
+ }
else if(ctx.Path == "/downloads")
{
ctx.WithMimeType("text/html").SendText(Pages.Downloads(this.TYTD,ctx));
diff --git a/Tesses.YouTubeDownloader.Server/src/pages/api.tcross b/Tesses.YouTubeDownloader.Server/src/pages/api.tcross
new file mode 100644
index 0000000..8cdbe40
--- /dev/null
+++ b/Tesses.YouTubeDownloader.Server/src/pages/api.tcross
@@ -0,0 +1,12 @@
+func Pages.Api()
+{
+ var html =
+ Developers
+
+ ;
+ return Components.Shell("Api",html,4);
+}
\ 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
new file mode 100644
index 0000000..80b147a
--- /dev/null
+++ b/Tesses.YouTubeDownloader.Server/src/pages/api/api-v1.tcross
@@ -0,0 +1,27 @@
+func Pages.ApiV1()
+{
+ var html =
+ Api V1
+
+
+
+
+ {r.method} {r.route}
+
+ Query Parameters
+
+ {qp.name} ({qp.type}): {qp.description}
+
+
+ Description
+
+
+
+
+
+
+ ;
+ return Components.Shell("Api V1",html,4);
+}
+
+var apiv1_routes = Json.Decode(embed("apiv1_routes.json").ToString());
diff --git a/Tesses.YouTubeDownloader.Server/src/pages/list.tcross b/Tesses.YouTubeDownloader.Server/src/pages/list.tcross
index a7cd1b2..bb2659b 100644
--- a/Tesses.YouTubeDownloader.Server/src/pages/list.tcross
+++ b/Tesses.YouTubeDownloader.Server/src/pages/list.tcross
@@ -20,6 +20,9 @@ func Pages.List(tytd,ctx)
{name}
+
+
+
diff --git a/Tesses.YouTubeDownloader.Server/src/pages/playlist/info.tcross b/Tesses.YouTubeDownloader.Server/src/pages/playlist/info.tcross
index 773783c..8aec628 100644
--- a/Tesses.YouTubeDownloader.Server/src/pages/playlist/info.tcross
+++ b/Tesses.YouTubeDownloader.Server/src/pages/playlist/info.tcross
@@ -19,7 +19,7 @@ func Pages.PlaylistInfo(tytd,ctx)
{res.title}
diff --git a/Tesses.YouTubeDownloader.Server/src/pages/settings.tcross b/Tesses.YouTubeDownloader.Server/src/pages/settings.tcross
index ab0be33..5d70167 100644
--- a/Tesses.YouTubeDownloader.Server/src/pages/settings.tcross
+++ b/Tesses.YouTubeDownloader.Server/src/pages/settings.tcross
@@ -60,6 +60,8 @@ func Pages.Settings(tytd,ctx)
+
+ download Download Database
;
return Components.Shell("Settings",html ,3);
diff --git a/Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross b/Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross
index d367cc4..525bc01 100644
--- a/Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross
+++ b/Tesses.YouTubeDownloader.Server/src/pages/video/info.tcross
@@ -19,7 +19,7 @@ func Pages.VideoInfo(tytd,ctx)
diff --git a/Tesses.YouTubeDownloader/cross.json b/Tesses.YouTubeDownloader/cross.json
index 4b6a5ed..737909a 100644
--- a/Tesses.YouTubeDownloader/cross.json
+++ b/Tesses.YouTubeDownloader/cross.json
@@ -14,6 +14,6 @@
}
],
"name": "Tesses.YouTubeDownloader",
- "version": "1.0.0.0-prod",
+ "version": "1.0.0.0-dev",
"icon": "icon.png"
}
\ No newline at end of file
diff --git a/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross b/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross
index 712e7ca..850ed06 100644
--- a/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross
+++ b/Tesses.YouTubeDownloader/src/YouTubeDownloader.tcross
@@ -1,17 +1,38 @@
class TYTD.Downloader {
+ /^
+ The storage vfs that TYTD accesses
+ ^/
public Storage;
+ /^
+ The directory of where the database is (should be the same physical folder as the Storage vfs if you want ffmpeg)
+ ^/
public DatabaseDirectory;
+ /^
+ Package manager object
+ ^/
public PackageManager = new Tesses.CrossLang.PackageManager();
-
- public Servers = Net.Http.MountableServer({Handle=(ctx)=>false});
+ /^
+ All the plugin webpages
+ ^/
+ public Servers = Net.Http.MountableServer({Handle=(ctx)=>false});
+ /^
+ vfs: The storage vfs that TYTD accesses
+ dbDir: The directory of where the database is (should be the same physical folder as the Storage vfs if you want ffmpeg)
+ ^/
public Downloader(vfs,dbDir)
{
this.Storage = vfs;
this.DatabaseDirectory = dbDir;
}
+ /^
+ Download a video
+ id: video id or url
+ res: see Resolution
+ ^/
public DownloadVideo(id,$res)
{
+ this.LOG($"Adding video: {TYTD.GetVideoId(id)}, original val: {id}");
switch(res)
{
case Resolution.NoDownload:
@@ -48,7 +69,11 @@ class TYTD.Downloader {
break;
}
}
-
+ /^
+ Download a playlist
+ id: playlist id or url
+ res: see Resolution
+ ^/
public DownloadPlaylist(id,$res)
{
var pid = TYTD.GetPlaylistId(id);
@@ -56,6 +81,7 @@ class TYTD.Downloader {
if(pid != null)
{
+ this.LOG($"Adding playlist: https://www.youtube.com/playlist?list={pid}");
this.PlaylistQueue.Push(()=>{
each(var item : this.QueryPlaylistItems(pid,true))
{
@@ -68,13 +94,18 @@ class TYTD.Downloader {
});
}
}
-
+ /^
+ Download a channel
+ id: channel id or url
+ res: see Resolution
+ ^/
public DownloadChannel(id,$res)
{
var cid = TYTD.GetChannelId(id);
if(cid != null)
{
+ this.LOG($"Adding channel: https://www.youtube.com/channel/{cid}");
this.PlaylistQueue.Push(()=>{
each(var item : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
{
@@ -86,7 +117,9 @@ class TYTD.Downloader {
});
}
}
-
+ /^
+ Get the thumbnail of a plugin
+ ^/
public GetPluginThumbnail(name)
{
each(var item : this.Plugins)
@@ -98,7 +131,11 @@ class TYTD.Downloader {
return embed("package_icon.png");
}
-
+ /^
+ Download video, playlist or channel
+ url: id or url
+ res: see Resolution
+ ^/
public DownloadItem(url, $res)
{
var vid = TYTD.GetVideoId(url);
@@ -106,7 +143,7 @@ class TYTD.Downloader {
var cid = TYTD.GetChannelId(url);
- if(vid != null && url.Length == 11)
+ if(vid != null)
{
this.DownloadVideo(vid, res);
}
@@ -114,25 +151,32 @@ class TYTD.Downloader {
{
this.DownloadPlaylist(pid,res);
}
- else if(vid != null)
- {
- this.DownloadVideo(vid, res);
- }
else if(cid != null)
{
this.DownloadChannel(cid,res);
}
}
-
+ /^
+ Redirect url to info page
+ ^/
public PageRedirect(url)
{
var vid = TYTD.GetVideoId(url);
+ var pid = TYTD.GetPlaylistId(url);
- if(vid != null && url.Length == 11)
+ if(vid != null)
{
return $"./video?v={Net.Http.UrlEncode(vid)}";
}
+ else if(pid != null)
+ {
+ return $"./playlist?id={Net.Http.UrlEncode(pid)}";
+ }
+ else if(cid != null)
+ {
+ return $"./playlist?id={Net.Http.UrlEncode(cid)}";
+ }
return "./";
}
@@ -148,12 +192,18 @@ class TYTD.Downloader {
return $"{views/1000000000000}T views";
}
-
+ /^
+ Get videos
+ set offset to the page (starting at 0)
+ set count to how many items per page
+ ^/
public GetVideos(query, offset, count)
{
this.Muxex.Lock();
var db = this.OpenDB();
+
var q = Sqlite.Escape($"%{query}%");
+
var res = Sqlite.Exec(db, $"SELECT * FROM videos v WHERE (v.title LIKE {q} OR v.shortDescription LIKE {q}) LIMIT {count} OFFSET {offset*count};");
@@ -169,6 +219,7 @@ class TYTD.Downloader {
else item.keywords = [];
item.addDate = ParseLong(item.addDate);
+ item.addDateStr = new DateTime(item.addDate).ToString();
item.lengthSeconds = ParseLong(item.lengthSeconds);
item.viewCount = ParseLong(item.viewCount);
item.viewCountStr = this.Views2Str(item.viewCount);
@@ -176,7 +227,11 @@ class TYTD.Downloader {
}
return res;
}
-
+ /^
+ Get playlists
+ set offset to the page (starting at 0)
+ set count to how many items per page
+ ^/
public GetPlaylists(query, offset, count)
{
this.Muxex.Lock();
@@ -191,6 +246,11 @@ class TYTD.Downloader {
return res;
}
+ /^
+ Get playlist contents
+ set offset to the page (starting at 0)
+ set count to how many items per page
+ ^/
public GetPlaylistContents(id, offset, count)
{
id = TYTD.GetPlaylistId(id);
@@ -239,6 +299,8 @@ class TYTD.Downloader {
else
item.keywords = [];
item.addDate = ParseLong(item.addDate);
+
+ item.addDateStr = new DateTime(item.addDate).ToString();
item.lengthSeconds = ParseLong(item.lengthSeconds);
item.viewCount = ParseLong(item.viewCount);
item.viewCountStr = this.Views2Str(item.viewCount);
@@ -250,6 +312,11 @@ class TYTD.Downloader {
items = res2
};
}
+ /^
+ Get channel contents
+ set offset to the page (starting at 0)
+ set count to how many items per page
+ ^/
public GetChannelContents(id, offset, count)
{
id = TYTD.GetChannelId(id);
@@ -283,6 +350,8 @@ class TYTD.Downloader {
else
item.keywords = [];
item.addDate = ParseLong(item.addDate);
+
+ item.addDateStr = new DateTime(item.addDate).ToString();
item.lengthSeconds = ParseLong(item.lengthSeconds);
item.viewCount = ParseLong(item.viewCount);
item.viewCountStr = this.Views2Str(item.viewCount);
@@ -292,7 +361,11 @@ class TYTD.Downloader {
items = res
};
}
-
+ /^
+ Get channels
+ set offset to the page (starting at 0)
+ set count to how many items per page
+ ^/
public GetChannels(query, offset, count)
{
this.Muxex.Lock();
@@ -307,7 +380,10 @@ class TYTD.Downloader {
return res;
}
-
+ /^
+ Get the video path
+ res: see Resolution
+ ^/
public GetVideoPath(v,res)
{
var id = TYTD.GetVideoId(v);
@@ -343,16 +419,22 @@ class TYTD.Downloader {
}
-
+ /^
+ Get video info
+ vid can be a video url from youtube or just the id
+ ^/
public GetVideo(vid)
{
var id = TYTD.GetVideoId(vid);
+
if(id == null) return null;
+ Console.WriteLine(id);
this.Muxex.Lock();
var db = this.OpenDB();
var res = Sqlite.Exec(db, $"SELECT * FROM videos WHERE videoId = {Sqlite.Escape(id)};");
var out = null;
+ Console.WriteLine(res);
if(TypeOf(res) == "List" && res.Length == 1) out = res[0];
@@ -365,6 +447,8 @@ class TYTD.Downloader {
else
out.keywords = [];
out.addDate = ParseLong(out.addDate);
+
+ out.addDateStr = new DateTime(out.addDate).ToString();
out.lengthSeconds = ParseLong(out.lengthSeconds);
out.viewCount = ParseLong(out.viewCount);
out.viewCountStr = this.Views2Str(out.viewCount);
@@ -374,6 +458,10 @@ class TYTD.Downloader {
+ /^
+ Get playlist info
+ id can be a playlist url from youtube or just the id
+ ^/
public GetPlaylist(id)
{
var id = TYTD.GetPlaylistId(vid);
@@ -392,6 +480,10 @@ class TYTD.Downloader {
return out;
}
+ /^
+ Get channel info
+ id can be a channel url from youtube or just the id
+ ^/
public GetChannel(id)
{
var id = TYTD.GetChannelId(vid);
@@ -432,6 +524,7 @@ class TYTD.Downloader {
this.Mutex.Unlock();
return items;
}
+ /^ Set the description of a personal list ^/
public SetPersonalListDescription(name,description)
{
this.Muxex.Lock();
@@ -441,6 +534,7 @@ class TYTD.Downloader {
Sqlite.Close(db);
this.Mutex.Unlock();
}
+ /^ Get the description of a personal list ^/
public GetPersonalListDescription(name)
{
this.Muxex.Lock();
@@ -452,10 +546,13 @@ class TYTD.Downloader {
if(TypeOf(res) == "List" && res.Length > 0)
{
- res[0].description;
+ var d = res[0].description;
+ if(TypeOf(d)=="String")
+ return d;
}
return "";
}
+ /^ ^/
public GetPersonalListContents(name, offset, count)
{
this.Muxex.Lock();
@@ -472,6 +569,8 @@ class TYTD.Downloader {
item.keywords = Json.Decode(item.keywords);
else item.keywords = [];
item.addDate = ParseLong(item.addDate);
+
+ item.addDateStr = new DateTime(item.addDate).ToString();
item.lengthSeconds = ParseLong(item.lengthSeconds);
item.viewCount = ParseLong(item.viewCount);
item.viewCountStr = this.Views2Str(item.viewCount);
@@ -505,11 +604,19 @@ class TYTD.Downloader {
this.Mutex.Lock();
var db = this.OpenDB();
//personal_lists (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, description TEXT)
- Sqlite.Exec(db, $"DELETE FROM personal_list_entries e WHERE (e.listName = {Sqlite.Escape(name)} AND e.videoId = {Sqlite.Escape(id)});");
+ Sqlite.Exec(db, $"DELETE FROM personal_list_entries WHERE (listName = {Sqlite.Escape(name)} AND videoId = {Sqlite.Escape(id)});");
+ Sqlite.Close(db);
+ this.Mutex.Unlock();
+ }
+ public RemovePersonalList(name)
+ {
+ this.Mutex.Lock();
+ var db = this.OpenDB();
+ //personal_lists (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, description TEXT)
+ Sqlite.Exec(db, $"DELETE FROM personal_list_entries WHERE listName = {Sqlite.Escape(name)}; DELETE FROM personal_lists WHERE name = {Sqlite.Escape(name)};");
Sqlite.Close(db);
this.Mutex.Unlock();
}
-
public SetSubscriptionBell(url, bell)
{
var cid = TYTD.GetChannelId(url);
@@ -587,7 +694,9 @@ class TYTD.Downloader {
public Plugins = [];
-
+ /^
+ The mutex (for database)
+ ^/
public Mutex = new Mutex();
public Running=true;
@@ -595,6 +704,11 @@ class TYTD.Downloader {
private DownloaderThreadHandle;
private Queue = new TYTD.Queue();
+ /^
+ Get Video Queue count
+ ^/
+
+ public getVideoQueueCount() this.Queue.Count;
private PlaylistThreadHandle;
@@ -660,7 +774,7 @@ class TYTD.Downloader {
try {info = Json.Decode(_exec.Info);} catch(ex) {}
_pkg.info = info;
- _pkg.pluginName = TypeOf(info.short_name) ? info.short_name : name;
+ _pkg.pluginName = TypeOf(info.short_name) == "String" ? info.short_name : name;
var reso = _exec.Resources;
var ico = _exec.Icon;
_pkg.pluginIcon = ico >= 0 && i < reso.Count ? reso[ico] : embed("package_icon.png");
@@ -773,33 +887,35 @@ class TYTD.Downloader {
private lastSubPollTime = 0;
private PlaylistThread()
{
+
while(this.Running)
{
- var res = this.PlaylistQueue.Pop();
+ try {
+ var res = this.PlaylistQueue.Pop();
- if(TypeOf(res) != "Null")
- {
- res();
- }
+ if(TypeOf(res) != "Null")
+ {
+ res();
+ }
- var currentTime = DateTime.NowEpoch;
- var bt = this.Config.BellTimer;
+ var currentTime = DateTime.NowEpoch;
+ var bt = this.Config.BellTimer;
- if((currentTime-this.lastSubPollTime) > bt)
- {
- this.lastSubPollTime = currentTime;
+ if((currentTime-this.lastSubPollTime) > bt)
+ {
+ this.lastSubPollTime = currentTime;
- this.Mutex.Lock();
- var db = this.OpenDB();
- var res = Sqlite.Exec(db, "SELECT * FROM subscriptions;");
- //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();
+ var res = Sqlite.Exec(db, "SELECT * FROM subscriptions;");
+ //Sqlite.Exec(db,"CREATE TABLE IF NOT EXISTS subscriptions (id INTEGER PRIMARY KEY AUTOINCREMENT, channelId TEXT UNIQUE ON CONFLICT REPLACE, bell TEXT);");
- this.Mutex.Unlock();
- /*
- /^ Disabled bell ^/
- static getDisabled() "Disabled";
+ this.Mutex.Unlock();
+ /*
+ /^ Disabled bell ^/
+ static getDisabled() "Disabled";
/^ Download (Low quality) ^/
static getDownloadLow() "DownloadLow";
/^ Download (High quality) ^/
@@ -813,98 +929,123 @@ class TYTD.Downloader {
static getBell() "Bell";
*/
- each(var sub : res)
- {
+ each(var sub : res)
+ {
- var downloadRes = Resolution.NoDownload;
- var notify = false;
- switch(sub.bell)
- {
- case SubscriptionBell.Bell:
- notify=true;
- break;
- case SubscriptionBell.BellLow:
- notify=true;
- case SubscriptionBell.DownloadLow:
- downloadRes = Resolution.LowVideo;
- break;
- case SubscriptionBell.BellHigh:
- notify = true;
- break;
- case SubscriptionBell.DownloadHigh:
- downloadRes = Resolution.MKV;
- break;
- }
- if(!notify && downloadRes == Resolution.NoDownload) continue;
- var cid = sub.channelId;
+ var downloadRes = Resolution.NoDownload;
+ var notify = false;
+ switch(sub.bell)
+ {
+ case SubscriptionBell.Bell:
+ notify=true;
+ break;
+ case SubscriptionBell.BellLow:
+ notify=true;
+ case SubscriptionBell.DownloadLow:
+ downloadRes = Resolution.LowVideo;
+ break;
+ case SubscriptionBell.BellHigh:
+ notify = true;
+ break;
+ case SubscriptionBell.DownloadHigh:
+ downloadRes = Resolution.MKV;
+ break;
+ }
+ if(!notify && downloadRes == Resolution.NoDownload) continue;
+ var cid = sub.channelId;
- var newVideos = [];
+ var newVideos = [];
- each(var batch : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
- {
- each(var videoId : batch)
+ each(var batch : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
{
+ each(var videoId : batch)
+ {
- if(this.GetVideo(videoId) == null)
- newVideos.Add(videoId);
+ if(this.GetVideo(videoId) == null)
+ newVideos.Add(videoId);
+ }
}
- }
- if(notify)
- {
- each(var id : newVideos)
+ if(notify)
{
- this.PutVideoInfoIfNotExists(id);
- var res = this.GetVideo(id);
- if(res != null)
- this.Bell.Invoke(this,{
- Video = res
- });
-
+ each(var id : newVideos)
+ {
+ this.PutVideoInfoIfNotExists(id);
+ var res = this.GetVideo(id);
+ if(res != null)
+ this.Bell.Invoke(this,{
+ Video = res
+ });
+ }
}
- }
- if(downloadRes != Resolution.NoDownload)
- {
- each(var id : newVideos)
+ if(downloadRes != Resolution.NoDownload)
{
- this.DownloadVideo(id,downloadRes);
+ each(var id : newVideos)
+ {
+ this.DownloadVideo(id,downloadRes);
+ }
}
- }
+ }
}
- }
+
+ }catch(ex) {
+ try{
+ this.LOG($"Exception caught on playlist thread: {e}");
+ }catch(ex2){}
}
}
+ }
+ private LOGDATEFMT = "%Y%m%d_%H%M%S";
+ private LOGDATE = DateTime.Now;
+ /^
+ Log stuff
+ ^/
+ public LOG(text)
+ {
+ this.Mutex.Lock();
+ this.Storage.CreateDirectory(/"Logs");
+ var strm = this.Storage.OpenFile(/"Logs"/$"{this.LOGDATE.ToString(this.LOGDATEFMT)}.log","a");
+ strm.WriteText($"[{DateTime.Now.ToString()}] {text}\n");
+ strm.Close();
+ this.Mutex.Unlock();
+ }
private DownloadThread()
{
while(this.Running)
{
- var res = this.Queue.Pop();
+ try {
+ var res = this.Queue.Pop();
- if(TypeOf(res) != "Null")
- {
+ if(TypeOf(res) != "Null")
+ {
- res.TYTD = this;
- res.Progress = (progress)=>{
- this.CurrentVideoProgress = progress;
- this.VideoProgress.Invoke(this, {
- Video = res.Video,
- progress
+ res.TYTD = this;
+ res.Progress = (progress)=>{
+ this.CurrentVideoProgress = progress;
+ this.VideoProgress.Invoke(this, {
+ Video = res.Video,
+ progress
+ });
+ };
+
+ this.CurrentVideo = res.Video;
+
+ this.VideoStarted.Invoke(this,{
+ Video = res.Video
});
- };
-
- this.CurrentVideo = res.Video;
-
- this.VideoStarted.Invoke(this,{
- Video = res.Video
- });
- res.Start();
+ res.Start();
- this.VideoEnded.Invoke(this,{
- Video = res.Video
- });
+ this.VideoEnded.Invoke(this,{
+ Video = res.Video
+ });
+ }
+ } catch(ex) {
+ try{
+ this.LOG($"Exception caught on download thread: {e}");
+ }catch(ex2){}
}
}
}
@@ -971,12 +1112,19 @@ class TYTD.Downloader {
}
return FS.ReadAllBytes(this.Storage,/"Streams"/"nullthumb.jpg");
}
- private OpenDB()
+ /^
+ Open the database
+ ^/
+ public OpenDB()
{
var dbFile = this.DatabaseDirectory / "tytd.db";
return Sqlite.Open(dbFile);
}
-
+ /^
+ Get whether package is installed
+ version must be the current version as a Version not string
+ returns 0 if not, 1 if installed or 2 if can update
+ ^/
public PackageState(name, version)
{
each(var item : this.Plugins)
@@ -994,6 +1142,10 @@ class TYTD.Downloader {
this.PackageManager.DownloadPlugin(dir,name,version);
this.LoadPlugins();
}
+ /^
+ Install plugin
+ version must be a Version not a String
+ ^/
public PackageInstall(name, version)
{
@@ -1013,7 +1165,9 @@ class TYTD.Downloader {
this.PackageDownload(name, version);
}
-
+ /^
+ Uninstall plugin
+ ^/
public PackageUninstall(name)
{
each(var item : this.Plugins)
@@ -1029,11 +1183,18 @@ class TYTD.Downloader {
}
}
}
-
+ /^
+ Get the TYTD Tag
+ ^/
public getTYTDTag()
{
return this.Config.TYTDTag ?? "UnknownPC";
}
+ /^
+ Download video info if it does not exist
+ pass in a video id or url
+
+ ^/
public PutVideoInfoIfNotExists(vid)
{
var id = TYTD.GetVideoId(vid);
@@ -1048,13 +1209,22 @@ class TYTD.Downloader {
}
}
-
+ /^
+ Put video info from info into database
+ ^/
public PutVideoInfo(info)
{
this.Muxex.Lock();
var db = this.OpenDB();
- 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},{Sqlite.Escape(info.keywords.ToString())},{Sqlite.Escape(info.channelId)},{Sqlite.Escape(info.shortDescription)},{info.viewCount},{Sqlite.Escape(info.author)},{DateTime.NowEpoch},{Sqlite.Escape(this.TYTDTag)});";
- Sqlite.Exec(db, d);
+
+ var keywords = info.keywords;
+ if(TypeOf(keywords) != "List") keywords = [];
+
+ var keywordsStr = Sqlite.Escape(Json.Encode(keywords));
+
+ 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)});";
+
+ Console.WriteLine(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();
@@ -1108,6 +1278,7 @@ class TYTD.Downloader {
return config[0].value;
}
}
+
private DownloadChannelThumbInternal(channelId, thumbnail_url)
{
var id = TYTD.GetChannelId(channelId);
@@ -1286,7 +1457,9 @@ class TYTD.Downloader {
}
return {items};
}
-
+ /^
+ Make a video manifest request
+ ^/
public ManifestRequest(vid)
{
var id = TYTD.GetVideoId(vid);
@@ -1509,7 +1682,6 @@ class TYTD.Downloader {
private rlm=new Muxex();
-
private RateLimit()
{
this.rlm.Lock();
@@ -1530,4 +1702,19 @@ class TYTD.Downloader {
this.lastRequest = curRequest;
this.rlm.Unlock();
}
-}
\ No newline at end of file
+ /^
+ Send the database as http response
+ ^/
+ public SendDatabase(ctx)
+ {
+ this.Mutex.Lock();
+ try {
+ var strm = FS.Local.OpenFile(this.DatabaseDirectory/"tytd.db","rb");
+ ctx.SendStream(strm);
+ strm.Close();
+ }catch(ex) {
+ Console.WriteLine($"ERROR: {ex}");
+ }
+ this.Mutex.Unlock();
+ }
+}
diff --git a/Tesses.YouTubeDownloader/src/videodownload/audioonlydownload.tcross b/Tesses.YouTubeDownloader/src/videodownload/audioonlydownload.tcross
index f8c210c..6664536 100644
--- a/Tesses.YouTubeDownloader/src/videodownload/audioonlydownload.tcross
+++ b/Tesses.YouTubeDownloader/src/videodownload/audioonlydownload.tcross
@@ -27,6 +27,7 @@ class TYTD.AOVideoDownload : IVideoDownload {
var req = this.tytd.ManifestRequest(id).playerResponse;
this.info.Title = req.videoDetails.title;
+ tytd.LOG($"Downloading: {this.info.Title} with id: {id} Highest Audio");
this.info.Channel = req.videoDetails.author;
this.info.ChannelId = req.videoDetails.channelId;
diff --git a/Tesses.YouTubeDownloader/src/videodownload/noconvertdownload.tcross b/Tesses.YouTubeDownloader/src/videodownload/noconvertdownload.tcross
index 7427311..8e73760 100644
--- a/Tesses.YouTubeDownloader/src/videodownload/noconvertdownload.tcross
+++ b/Tesses.YouTubeDownloader/src/videodownload/noconvertdownload.tcross
@@ -35,6 +35,7 @@ class TYTD.NoConvertVideoDownload : IVideoDownload {
this.info.Title = req.videoDetails.title;
+ tytd.LOG($"Downloading: {this.info.Title} with id: {id} Highest Video/Audio");
this.info.Channel = req.videoDetails.author;
this.info.ChannelId = req.videoDetails.channelId;
diff --git a/Tesses.YouTubeDownloader/src/videodownload/sdvideodownload.tcross b/Tesses.YouTubeDownloader/src/videodownload/sdvideodownload.tcross
index dd90dca..df92191 100644
--- a/Tesses.YouTubeDownloader/src/videodownload/sdvideodownload.tcross
+++ b/Tesses.YouTubeDownloader/src/videodownload/sdvideodownload.tcross
@@ -27,6 +27,7 @@ class TYTD.SDVideoDownload : IVideoDownload {
var req = this.tytd.ManifestRequest(id).playerResponse;
this.info.Title = req.videoDetails.title;
+ tytd.LOG($"Downloading: {this.info.Title} with id: {id} LowVideo");
this.info.Channel = req.videoDetails.author;
this.info.ChannelId = req.videoDetails.channelId;
diff --git a/Tesses.YouTubeDownloader/src/videodownload/videoonlydownload.tcross b/Tesses.YouTubeDownloader/src/videodownload/videoonlydownload.tcross
index 4e85768..32b948a 100644
--- a/Tesses.YouTubeDownloader/src/videodownload/videoonlydownload.tcross
+++ b/Tesses.YouTubeDownloader/src/videodownload/videoonlydownload.tcross
@@ -29,7 +29,7 @@ class TYTD.VOVideoDownload : IVideoDownload {
this.info.Title = req.videoDetails.title;
this.info.Channel = req.videoDetails.author;
this.info.ChannelId = req.videoDetails.channelId;
-
+ tytd.LOG($"Downloading: {this.info.Title} with id: {id} Highest Video");
this.tytd.PutVideoInfo(req.videoDetails);
var width = 0;