Add some api docs

This commit is contained in:
2025-11-20 18:20:07 -06:00
parent c71c73bb77
commit 9da78e364c
18 changed files with 599 additions and 125 deletions

View File

@@ -14,5 +14,5 @@
"project_dependencies": [
"..\/Tesses.YouTubeDownloader"
],
"version": "1.0.0.0-prod"
"version": "1.0.0.0-dev"
}

View File

@@ -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"
}
]

View File

@@ -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 += <br>;
}
description_with_br += Net.Http.HtmlEncode(txt);
first=false;
}
<return>
<if(editing)>
<true>
<form hx-post={$"./edit-personal-description?name={Net.Http.UrlEncode(name)}"} hx-swap="outerHTML">
<div class="row">
<div class="max">
<div class="field textarea label border">
<textarea name="description">{description}</textarea>
<label>Description</label>
</div>
</div>
<div class="min">
<button><i>save</i></button>
</div>
</div>
</form>
</true>
<false>
<div class="row" id="description">
<div class="max">
<p><raw(description_with_br)></p>
</div>
<div class="min">
<button hx-get={$"./edit-personal-description?name={Net.Http.UrlEncode(name)}"} hx-target="#description" hx-swap="outerHTML"><i>edit</i></button>
</div>
</div>
</false>
</if>
</return>
}

View File

@@ -11,7 +11,7 @@ func Components.InstalledPlugin(item)
<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>
<a class="underline" 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>

View File

@@ -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";

View File

@@ -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));

View File

@@ -0,0 +1,12 @@
func Pages.Api()
{
var html = <null>
<h1>Developers</h1>
<ul>
<a class="underline" href="./api-v1">
API v1
</a>
</ul>
</null>;
return Components.Shell("Api",html,4);
}

View File

@@ -0,0 +1,27 @@
func Pages.ApiV1()
{
var html = <null>
<h1>Api V1</h1>
<each(var r : apiv1_routes)>
<fieldset>
<legend>{r.method} {r.route}</legend>
<h6>Query Parameters</h6>
<each(var qp : r.queryParams)>
<div>{qp.name} ({qp.type}): {qp.description}</div>
</each>
<h6>Description</h6>
<plink(r.description)>
</fieldset>
</each>
</null>;
return Components.Shell("Api V1",html,4);
}
var apiv1_routes = Json.Decode(embed("apiv1_routes.json").ToString());

View File

@@ -20,6 +20,9 @@ func Pages.List(tytd,ctx)
<h1>{name}</h1>
</div>
</div>
<raw(Components.PersonalListDescription(tytd,name,false))>
<each(var item : res)>
<raw(Components.DownloadedVideo(item))>
</each>

View File

@@ -19,7 +19,7 @@ func Pages.PlaylistInfo(tytd,ctx)
<h1>{res.title}</h1>
</div>
<div class="max">
<a href={$"./channel?id={Net.Http.UrlEncode(res.channelId)}"}>{res.channelTitle}</a>
<a class="underline" href={$"./channel?id={Net.Http.UrlEncode(res.channelId)}"}>{res.channelTitle}</a>
</div>
</div>
<each(var item : res.items)>

View File

@@ -60,6 +60,8 @@ func Pages.Settings(tytd,ctx)
<footer>
<button>Save</button>
</footer>
<a class="button responsive" href="./api/v1/database.db"><i>download</i> Download Database</a>
</form>;
return Components.Shell("Settings",html ,3);

View File

@@ -19,7 +19,7 @@ func Pages.VideoInfo(tytd,ctx)
</div>
<div class="min">
<h4>{vi.title}</h4>
<a href={$"./channel?id={Net.Http.UrlEncode(vi.channelId)}"}>{vi.author}</a>
<a class="underline" href={$"./channel?id={Net.Http.UrlEncode(vi.channelId)}"}>{vi.author}</a>
<p>{vi.shortDescription}</p>
</div>

View File

@@ -14,6 +14,6 @@
}
],
"name": "Tesses.YouTubeDownloader",
"version": "1.0.0.0-prod",
"version": "1.0.0.0-dev",
"icon": "icon.png"
}

View File

@@ -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();
}
}
/^
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();
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;