mirror of
https://onedev.site.tesses.net/tytd2025
synced 2026-02-08 17:45:45 +00:00
Add some api docs
This commit is contained in:
@@ -14,5 +14,5 @@
|
|||||||
"project_dependencies": [
|
"project_dependencies": [
|
||||||
"..\/Tesses.YouTubeDownloader"
|
"..\/Tesses.YouTubeDownloader"
|
||||||
],
|
],
|
||||||
"version": "1.0.0.0-prod"
|
"version": "1.0.0.0-dev"
|
||||||
}
|
}
|
||||||
47
Tesses.YouTubeDownloader.Server/res/apiv1_routes.json
Normal file
47
Tesses.YouTubeDownloader.Server/res/apiv1_routes.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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>
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ func Components.InstalledPlugin(item)
|
|||||||
<div class="min">
|
<div class="min">
|
||||||
<if(item.pluginObject.Server != undefined && item.pluginObject.Server != null)>
|
<if(item.pluginObject.Server != undefined && item.pluginObject.Server != null)>
|
||||||
<true>
|
<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>
|
</true>
|
||||||
<false>
|
<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>
|
<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>
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ func Components.Shell(title, html, page, $mypage)
|
|||||||
icon = "settings",
|
icon = "settings",
|
||||||
href= (/"settings").MakeRelative(mypage).ToString(),
|
href= (/"settings").MakeRelative(mypage).ToString(),
|
||||||
classStr=""
|
classStr=""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = "Api",
|
||||||
|
icon = "api",
|
||||||
|
href = (/"api").MakeRelative(mypage).ToString(),
|
||||||
|
classStr=""
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
pages[page].classStr = "active";
|
pages[page].classStr = "active";
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ class TYTDApp {
|
|||||||
ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD));
|
ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD));
|
||||||
return true;
|
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")
|
else if(ctx.Path == "/api/v1/download")
|
||||||
{
|
{
|
||||||
var v = ctx.QueryParams.TryGetFirst("v");
|
var v = ctx.QueryParams.TryGetFirst("v");
|
||||||
@@ -44,12 +54,29 @@ class TYTDApp {
|
|||||||
if(path != null)
|
if(path != null)
|
||||||
{
|
{
|
||||||
var info = this.TYTD.GetVideo(v);
|
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");
|
var strm = this.TYTD.Storage.OpenFile(path,"rb");
|
||||||
ctx.WithMimeType(Net.Http.MimeType(path.ToString())).WithContentDisposition(filename,inline).SendStream(strm);
|
ctx.WithMimeType(Net.Http.MimeType(path.ToString())).WithContentDisposition(filename,inline).SendStream(strm);
|
||||||
strm.Close();
|
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")
|
else if(ctx.Path == "/api/v1/playlist.json")
|
||||||
{
|
{
|
||||||
var id = ctx.QueryParams.TryGetFirst("id");
|
var id = ctx.QueryParams.TryGetFirst("id");
|
||||||
@@ -102,6 +129,105 @@ class TYTDApp {
|
|||||||
ctx.WithMimeType("application/json").SendJson(result);
|
ctx.WithMimeType("application/json").SendJson(result);
|
||||||
return true;
|
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")
|
else if(ctx.Path == "/subscribe")
|
||||||
{
|
{
|
||||||
var id = ctx.QueryParams.TryGetFirst("id");
|
var id = ctx.QueryParams.TryGetFirst("id");
|
||||||
@@ -125,6 +251,23 @@ class TYTDApp {
|
|||||||
ctx.WithMimeType("text/html").SendText(Components.Subscribe(this.TYTD,id));
|
ctx.WithMimeType("text/html").SendText(Components.Subscribe(this.TYTD,id));
|
||||||
return true;
|
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")
|
else if(ctx.Path == "/downloads")
|
||||||
{
|
{
|
||||||
ctx.WithMimeType("text/html").SendText(Pages.Downloads(this.TYTD,ctx));
|
ctx.WithMimeType("text/html").SendText(Pages.Downloads(this.TYTD,ctx));
|
||||||
|
|||||||
12
Tesses.YouTubeDownloader.Server/src/pages/api.tcross
Normal file
12
Tesses.YouTubeDownloader.Server/src/pages/api.tcross
Normal 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);
|
||||||
|
}
|
||||||
27
Tesses.YouTubeDownloader.Server/src/pages/api/api-v1.tcross
Normal file
27
Tesses.YouTubeDownloader.Server/src/pages/api/api-v1.tcross
Normal 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());
|
||||||
@@ -20,6 +20,9 @@ func Pages.List(tytd,ctx)
|
|||||||
<h1>{name}</h1>
|
<h1>{name}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<raw(Components.PersonalListDescription(tytd,name,false))>
|
||||||
|
|
||||||
|
|
||||||
<each(var item : res)>
|
<each(var item : res)>
|
||||||
<raw(Components.DownloadedVideo(item))>
|
<raw(Components.DownloadedVideo(item))>
|
||||||
</each>
|
</each>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func Pages.PlaylistInfo(tytd,ctx)
|
|||||||
<h1>{res.title}</h1>
|
<h1>{res.title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="max">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<each(var item : res.items)>
|
<each(var item : res.items)>
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ func Pages.Settings(tytd,ctx)
|
|||||||
<footer>
|
<footer>
|
||||||
<button>Save</button>
|
<button>Save</button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<a class="button responsive" href="./api/v1/database.db"><i>download</i> Download Database</a>
|
||||||
</form>;
|
</form>;
|
||||||
|
|
||||||
return Components.Shell("Settings",html ,3);
|
return Components.Shell("Settings",html ,3);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func Pages.VideoInfo(tytd,ctx)
|
|||||||
</div>
|
</div>
|
||||||
<div class="min">
|
<div class="min">
|
||||||
<h4>{vi.title}</h4>
|
<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>
|
<p>{vi.shortDescription}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"name": "Tesses.YouTubeDownloader",
|
"name": "Tesses.YouTubeDownloader",
|
||||||
"version": "1.0.0.0-prod",
|
"version": "1.0.0.0-dev",
|
||||||
"icon": "icon.png"
|
"icon": "icon.png"
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,38 @@
|
|||||||
class TYTD.Downloader {
|
class TYTD.Downloader {
|
||||||
|
/^
|
||||||
|
The storage vfs that TYTD accesses
|
||||||
|
^/
|
||||||
public Storage;
|
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;
|
public DatabaseDirectory;
|
||||||
|
/^
|
||||||
|
Package manager object
|
||||||
|
^/
|
||||||
public PackageManager = new Tesses.CrossLang.PackageManager();
|
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)
|
public Downloader(vfs,dbDir)
|
||||||
{
|
{
|
||||||
this.Storage = vfs;
|
this.Storage = vfs;
|
||||||
this.DatabaseDirectory = dbDir;
|
this.DatabaseDirectory = dbDir;
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Download a video
|
||||||
|
id: video id or url
|
||||||
|
res: see Resolution
|
||||||
|
^/
|
||||||
public DownloadVideo(id,$res)
|
public DownloadVideo(id,$res)
|
||||||
{
|
{
|
||||||
|
this.LOG($"Adding video: {TYTD.GetVideoId(id)}, original val: {id}");
|
||||||
switch(res)
|
switch(res)
|
||||||
{
|
{
|
||||||
case Resolution.NoDownload:
|
case Resolution.NoDownload:
|
||||||
@@ -48,7 +69,11 @@ class TYTD.Downloader {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Download a playlist
|
||||||
|
id: playlist id or url
|
||||||
|
res: see Resolution
|
||||||
|
^/
|
||||||
public DownloadPlaylist(id,$res)
|
public DownloadPlaylist(id,$res)
|
||||||
{
|
{
|
||||||
var pid = TYTD.GetPlaylistId(id);
|
var pid = TYTD.GetPlaylistId(id);
|
||||||
@@ -56,6 +81,7 @@ class TYTD.Downloader {
|
|||||||
|
|
||||||
if(pid != null)
|
if(pid != null)
|
||||||
{
|
{
|
||||||
|
this.LOG($"Adding playlist: https://www.youtube.com/playlist?list={pid}");
|
||||||
this.PlaylistQueue.Push(()=>{
|
this.PlaylistQueue.Push(()=>{
|
||||||
each(var item : this.QueryPlaylistItems(pid,true))
|
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)
|
public DownloadChannel(id,$res)
|
||||||
{
|
{
|
||||||
var cid = TYTD.GetChannelId(id);
|
var cid = TYTD.GetChannelId(id);
|
||||||
|
|
||||||
if(cid != null)
|
if(cid != null)
|
||||||
{
|
{
|
||||||
|
this.LOG($"Adding channel: https://www.youtube.com/channel/{cid}");
|
||||||
this.PlaylistQueue.Push(()=>{
|
this.PlaylistQueue.Push(()=>{
|
||||||
each(var item : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
|
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)
|
public GetPluginThumbnail(name)
|
||||||
{
|
{
|
||||||
each(var item : this.Plugins)
|
each(var item : this.Plugins)
|
||||||
@@ -98,7 +131,11 @@ class TYTD.Downloader {
|
|||||||
return embed("package_icon.png");
|
return embed("package_icon.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/^
|
||||||
|
Download video, playlist or channel
|
||||||
|
url: id or url
|
||||||
|
res: see Resolution
|
||||||
|
^/
|
||||||
public DownloadItem(url, $res)
|
public DownloadItem(url, $res)
|
||||||
{
|
{
|
||||||
var vid = TYTD.GetVideoId(url);
|
var vid = TYTD.GetVideoId(url);
|
||||||
@@ -106,7 +143,7 @@ class TYTD.Downloader {
|
|||||||
|
|
||||||
var cid = TYTD.GetChannelId(url);
|
var cid = TYTD.GetChannelId(url);
|
||||||
|
|
||||||
if(vid != null && url.Length == 11)
|
if(vid != null)
|
||||||
{
|
{
|
||||||
this.DownloadVideo(vid, res);
|
this.DownloadVideo(vid, res);
|
||||||
}
|
}
|
||||||
@@ -114,25 +151,32 @@ class TYTD.Downloader {
|
|||||||
{
|
{
|
||||||
this.DownloadPlaylist(pid,res);
|
this.DownloadPlaylist(pid,res);
|
||||||
}
|
}
|
||||||
else if(vid != null)
|
|
||||||
{
|
|
||||||
this.DownloadVideo(vid, res);
|
|
||||||
}
|
|
||||||
else if(cid != null)
|
else if(cid != null)
|
||||||
{
|
{
|
||||||
this.DownloadChannel(cid,res);
|
this.DownloadChannel(cid,res);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Redirect url to info page
|
||||||
|
^/
|
||||||
public PageRedirect(url)
|
public PageRedirect(url)
|
||||||
{
|
{
|
||||||
var vid = TYTD.GetVideoId(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)}";
|
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 "./";
|
return "./";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,12 +192,18 @@ class TYTD.Downloader {
|
|||||||
return $"{views/1000000000000}T views";
|
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)
|
public GetVideos(query, offset, count)
|
||||||
{
|
{
|
||||||
this.Muxex.Lock();
|
this.Muxex.Lock();
|
||||||
var db = this.OpenDB();
|
var db = this.OpenDB();
|
||||||
|
|
||||||
var q = Sqlite.Escape($"%{query}%");
|
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};");
|
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 = [];
|
else item.keywords = [];
|
||||||
|
|
||||||
item.addDate = ParseLong(item.addDate);
|
item.addDate = ParseLong(item.addDate);
|
||||||
|
item.addDateStr = new DateTime(item.addDate).ToString();
|
||||||
item.lengthSeconds = ParseLong(item.lengthSeconds);
|
item.lengthSeconds = ParseLong(item.lengthSeconds);
|
||||||
item.viewCount = ParseLong(item.viewCount);
|
item.viewCount = ParseLong(item.viewCount);
|
||||||
item.viewCountStr = this.Views2Str(item.viewCount);
|
item.viewCountStr = this.Views2Str(item.viewCount);
|
||||||
@@ -176,7 +227,11 @@ class TYTD.Downloader {
|
|||||||
}
|
}
|
||||||
return res;
|
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)
|
public GetPlaylists(query, offset, count)
|
||||||
{
|
{
|
||||||
this.Muxex.Lock();
|
this.Muxex.Lock();
|
||||||
@@ -191,6 +246,11 @@ class TYTD.Downloader {
|
|||||||
|
|
||||||
return res;
|
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)
|
public GetPlaylistContents(id, offset, count)
|
||||||
{
|
{
|
||||||
id = TYTD.GetPlaylistId(id);
|
id = TYTD.GetPlaylistId(id);
|
||||||
@@ -239,6 +299,8 @@ class TYTD.Downloader {
|
|||||||
else
|
else
|
||||||
item.keywords = [];
|
item.keywords = [];
|
||||||
item.addDate = ParseLong(item.addDate);
|
item.addDate = ParseLong(item.addDate);
|
||||||
|
|
||||||
|
item.addDateStr = new DateTime(item.addDate).ToString();
|
||||||
item.lengthSeconds = ParseLong(item.lengthSeconds);
|
item.lengthSeconds = ParseLong(item.lengthSeconds);
|
||||||
item.viewCount = ParseLong(item.viewCount);
|
item.viewCount = ParseLong(item.viewCount);
|
||||||
item.viewCountStr = this.Views2Str(item.viewCount);
|
item.viewCountStr = this.Views2Str(item.viewCount);
|
||||||
@@ -250,6 +312,11 @@ class TYTD.Downloader {
|
|||||||
items = res2
|
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)
|
public GetChannelContents(id, offset, count)
|
||||||
{
|
{
|
||||||
id = TYTD.GetChannelId(id);
|
id = TYTD.GetChannelId(id);
|
||||||
@@ -283,6 +350,8 @@ class TYTD.Downloader {
|
|||||||
else
|
else
|
||||||
item.keywords = [];
|
item.keywords = [];
|
||||||
item.addDate = ParseLong(item.addDate);
|
item.addDate = ParseLong(item.addDate);
|
||||||
|
|
||||||
|
item.addDateStr = new DateTime(item.addDate).ToString();
|
||||||
item.lengthSeconds = ParseLong(item.lengthSeconds);
|
item.lengthSeconds = ParseLong(item.lengthSeconds);
|
||||||
item.viewCount = ParseLong(item.viewCount);
|
item.viewCount = ParseLong(item.viewCount);
|
||||||
item.viewCountStr = this.Views2Str(item.viewCount);
|
item.viewCountStr = this.Views2Str(item.viewCount);
|
||||||
@@ -292,7 +361,11 @@ class TYTD.Downloader {
|
|||||||
items = res
|
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)
|
public GetChannels(query, offset, count)
|
||||||
{
|
{
|
||||||
this.Muxex.Lock();
|
this.Muxex.Lock();
|
||||||
@@ -307,7 +380,10 @@ class TYTD.Downloader {
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Get the video path
|
||||||
|
res: see Resolution
|
||||||
|
^/
|
||||||
public GetVideoPath(v,res)
|
public GetVideoPath(v,res)
|
||||||
{
|
{
|
||||||
var id = TYTD.GetVideoId(v);
|
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)
|
public GetVideo(vid)
|
||||||
{
|
{
|
||||||
var id = TYTD.GetVideoId(vid);
|
var id = TYTD.GetVideoId(vid);
|
||||||
|
|
||||||
if(id == null) return null;
|
if(id == null) return null;
|
||||||
|
Console.WriteLine(id);
|
||||||
this.Muxex.Lock();
|
this.Muxex.Lock();
|
||||||
var db = this.OpenDB();
|
var db = this.OpenDB();
|
||||||
var res = Sqlite.Exec(db, $"SELECT * FROM videos WHERE videoId = {Sqlite.Escape(id)};");
|
var res = Sqlite.Exec(db, $"SELECT * FROM videos WHERE videoId = {Sqlite.Escape(id)};");
|
||||||
|
|
||||||
var out = null;
|
var out = null;
|
||||||
|
Console.WriteLine(res);
|
||||||
if(TypeOf(res) == "List" && res.Length == 1) out = res[0];
|
if(TypeOf(res) == "List" && res.Length == 1) out = res[0];
|
||||||
|
|
||||||
|
|
||||||
@@ -365,6 +447,8 @@ class TYTD.Downloader {
|
|||||||
else
|
else
|
||||||
out.keywords = [];
|
out.keywords = [];
|
||||||
out.addDate = ParseLong(out.addDate);
|
out.addDate = ParseLong(out.addDate);
|
||||||
|
|
||||||
|
out.addDateStr = new DateTime(out.addDate).ToString();
|
||||||
out.lengthSeconds = ParseLong(out.lengthSeconds);
|
out.lengthSeconds = ParseLong(out.lengthSeconds);
|
||||||
out.viewCount = ParseLong(out.viewCount);
|
out.viewCount = ParseLong(out.viewCount);
|
||||||
out.viewCountStr = this.Views2Str(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)
|
public GetPlaylist(id)
|
||||||
{
|
{
|
||||||
var id = TYTD.GetPlaylistId(vid);
|
var id = TYTD.GetPlaylistId(vid);
|
||||||
@@ -392,6 +480,10 @@ class TYTD.Downloader {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/^
|
||||||
|
Get channel info
|
||||||
|
id can be a channel url from youtube or just the id
|
||||||
|
^/
|
||||||
public GetChannel(id)
|
public GetChannel(id)
|
||||||
{
|
{
|
||||||
var id = TYTD.GetChannelId(vid);
|
var id = TYTD.GetChannelId(vid);
|
||||||
@@ -432,6 +524,7 @@ class TYTD.Downloader {
|
|||||||
this.Mutex.Unlock();
|
this.Mutex.Unlock();
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
/^ Set the description of a personal list ^/
|
||||||
public SetPersonalListDescription(name,description)
|
public SetPersonalListDescription(name,description)
|
||||||
{
|
{
|
||||||
this.Muxex.Lock();
|
this.Muxex.Lock();
|
||||||
@@ -441,6 +534,7 @@ class TYTD.Downloader {
|
|||||||
Sqlite.Close(db);
|
Sqlite.Close(db);
|
||||||
this.Mutex.Unlock();
|
this.Mutex.Unlock();
|
||||||
}
|
}
|
||||||
|
/^ Get the description of a personal list ^/
|
||||||
public GetPersonalListDescription(name)
|
public GetPersonalListDescription(name)
|
||||||
{
|
{
|
||||||
this.Muxex.Lock();
|
this.Muxex.Lock();
|
||||||
@@ -452,10 +546,13 @@ class TYTD.Downloader {
|
|||||||
|
|
||||||
if(TypeOf(res) == "List" && res.Length > 0)
|
if(TypeOf(res) == "List" && res.Length > 0)
|
||||||
{
|
{
|
||||||
res[0].description;
|
var d = res[0].description;
|
||||||
|
if(TypeOf(d)=="String")
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
/^ ^/
|
||||||
public GetPersonalListContents(name, offset, count)
|
public GetPersonalListContents(name, offset, count)
|
||||||
{
|
{
|
||||||
this.Muxex.Lock();
|
this.Muxex.Lock();
|
||||||
@@ -472,6 +569,8 @@ class TYTD.Downloader {
|
|||||||
item.keywords = Json.Decode(item.keywords);
|
item.keywords = Json.Decode(item.keywords);
|
||||||
else item.keywords = [];
|
else item.keywords = [];
|
||||||
item.addDate = ParseLong(item.addDate);
|
item.addDate = ParseLong(item.addDate);
|
||||||
|
|
||||||
|
item.addDateStr = new DateTime(item.addDate).ToString();
|
||||||
item.lengthSeconds = ParseLong(item.lengthSeconds);
|
item.lengthSeconds = ParseLong(item.lengthSeconds);
|
||||||
item.viewCount = ParseLong(item.viewCount);
|
item.viewCount = ParseLong(item.viewCount);
|
||||||
item.viewCountStr = this.Views2Str(item.viewCount);
|
item.viewCountStr = this.Views2Str(item.viewCount);
|
||||||
@@ -505,11 +604,19 @@ class TYTD.Downloader {
|
|||||||
this.Mutex.Lock();
|
this.Mutex.Lock();
|
||||||
var db = this.OpenDB();
|
var db = this.OpenDB();
|
||||||
//personal_lists (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, description TEXT)
|
//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);
|
Sqlite.Close(db);
|
||||||
this.Mutex.Unlock();
|
this.Mutex.Unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SetSubscriptionBell(url, bell)
|
public SetSubscriptionBell(url, bell)
|
||||||
{
|
{
|
||||||
var cid = TYTD.GetChannelId(url);
|
var cid = TYTD.GetChannelId(url);
|
||||||
@@ -587,7 +694,9 @@ class TYTD.Downloader {
|
|||||||
public Plugins = [];
|
public Plugins = [];
|
||||||
|
|
||||||
|
|
||||||
|
/^
|
||||||
|
The mutex (for database)
|
||||||
|
^/
|
||||||
public Mutex = new Mutex();
|
public Mutex = new Mutex();
|
||||||
|
|
||||||
public Running=true;
|
public Running=true;
|
||||||
@@ -595,6 +704,11 @@ class TYTD.Downloader {
|
|||||||
private DownloaderThreadHandle;
|
private DownloaderThreadHandle;
|
||||||
|
|
||||||
private Queue = new TYTD.Queue();
|
private Queue = new TYTD.Queue();
|
||||||
|
/^
|
||||||
|
Get Video Queue count
|
||||||
|
^/
|
||||||
|
|
||||||
|
public getVideoQueueCount() this.Queue.Count;
|
||||||
|
|
||||||
private PlaylistThreadHandle;
|
private PlaylistThreadHandle;
|
||||||
|
|
||||||
@@ -660,7 +774,7 @@ class TYTD.Downloader {
|
|||||||
try {info = Json.Decode(_exec.Info);} catch(ex) {}
|
try {info = Json.Decode(_exec.Info);} catch(ex) {}
|
||||||
_pkg.info = info;
|
_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 reso = _exec.Resources;
|
||||||
var ico = _exec.Icon;
|
var ico = _exec.Icon;
|
||||||
_pkg.pluginIcon = ico >= 0 && i < reso.Count ? reso[ico] : embed("package_icon.png");
|
_pkg.pluginIcon = ico >= 0 && i < reso.Count ? reso[ico] : embed("package_icon.png");
|
||||||
@@ -773,33 +887,35 @@ class TYTD.Downloader {
|
|||||||
private lastSubPollTime = 0;
|
private lastSubPollTime = 0;
|
||||||
private PlaylistThread()
|
private PlaylistThread()
|
||||||
{
|
{
|
||||||
|
|
||||||
while(this.Running)
|
while(this.Running)
|
||||||
{
|
{
|
||||||
var res = this.PlaylistQueue.Pop();
|
try {
|
||||||
|
var res = this.PlaylistQueue.Pop();
|
||||||
|
|
||||||
if(TypeOf(res) != "Null")
|
if(TypeOf(res) != "Null")
|
||||||
{
|
{
|
||||||
res();
|
res();
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentTime = DateTime.NowEpoch;
|
var currentTime = DateTime.NowEpoch;
|
||||||
var bt = this.Config.BellTimer;
|
var bt = this.Config.BellTimer;
|
||||||
|
|
||||||
|
|
||||||
if((currentTime-this.lastSubPollTime) > bt)
|
if((currentTime-this.lastSubPollTime) > bt)
|
||||||
{
|
{
|
||||||
this.lastSubPollTime = currentTime;
|
this.lastSubPollTime = currentTime;
|
||||||
|
|
||||||
|
|
||||||
this.Mutex.Lock();
|
this.Mutex.Lock();
|
||||||
var db = this.OpenDB();
|
var db = this.OpenDB();
|
||||||
var res = Sqlite.Exec(db, "SELECT * FROM subscriptions;");
|
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);");
|
//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();
|
this.Mutex.Unlock();
|
||||||
/*
|
/*
|
||||||
/^ Disabled bell ^/
|
/^ Disabled bell ^/
|
||||||
static getDisabled() "Disabled";
|
static getDisabled() "Disabled";
|
||||||
/^ Download (Low quality) ^/
|
/^ Download (Low quality) ^/
|
||||||
static getDownloadLow() "DownloadLow";
|
static getDownloadLow() "DownloadLow";
|
||||||
/^ Download (High quality) ^/
|
/^ Download (High quality) ^/
|
||||||
@@ -813,98 +929,123 @@ class TYTD.Downloader {
|
|||||||
static getBell() "Bell";
|
static getBell() "Bell";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
each(var sub : res)
|
each(var sub : res)
|
||||||
{
|
{
|
||||||
|
|
||||||
var downloadRes = Resolution.NoDownload;
|
var downloadRes = Resolution.NoDownload;
|
||||||
var notify = false;
|
var notify = false;
|
||||||
switch(sub.bell)
|
switch(sub.bell)
|
||||||
{
|
{
|
||||||
case SubscriptionBell.Bell:
|
case SubscriptionBell.Bell:
|
||||||
notify=true;
|
notify=true;
|
||||||
break;
|
break;
|
||||||
case SubscriptionBell.BellLow:
|
case SubscriptionBell.BellLow:
|
||||||
notify=true;
|
notify=true;
|
||||||
case SubscriptionBell.DownloadLow:
|
case SubscriptionBell.DownloadLow:
|
||||||
downloadRes = Resolution.LowVideo;
|
downloadRes = Resolution.LowVideo;
|
||||||
break;
|
break;
|
||||||
case SubscriptionBell.BellHigh:
|
case SubscriptionBell.BellHigh:
|
||||||
notify = true;
|
notify = true;
|
||||||
break;
|
break;
|
||||||
case SubscriptionBell.DownloadHigh:
|
case SubscriptionBell.DownloadHigh:
|
||||||
downloadRes = Resolution.MKV;
|
downloadRes = Resolution.MKV;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(!notify && downloadRes == Resolution.NoDownload) continue;
|
if(!notify && downloadRes == Resolution.NoDownload) continue;
|
||||||
var cid = sub.channelId;
|
var cid = sub.channelId;
|
||||||
|
|
||||||
var newVideos = [];
|
var newVideos = [];
|
||||||
|
|
||||||
each(var batch : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
|
each(var batch : this.QueryPlaylistItems($"UU{cid.Substring(2)}",false))
|
||||||
{
|
|
||||||
each(var videoId : batch)
|
|
||||||
{
|
{
|
||||||
|
each(var videoId : batch)
|
||||||
|
{
|
||||||
|
|
||||||
if(this.GetVideo(videoId) == null)
|
if(this.GetVideo(videoId) == null)
|
||||||
newVideos.Add(videoId);
|
newVideos.Add(videoId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(notify)
|
if(notify)
|
||||||
{
|
|
||||||
each(var id : newVideos)
|
|
||||||
{
|
{
|
||||||
this.PutVideoInfoIfNotExists(id);
|
each(var id : newVideos)
|
||||||
var res = this.GetVideo(id);
|
{
|
||||||
if(res != null)
|
this.PutVideoInfoIfNotExists(id);
|
||||||
this.Bell.Invoke(this,{
|
var res = this.GetVideo(id);
|
||||||
Video = res
|
if(res != null)
|
||||||
});
|
this.Bell.Invoke(this,{
|
||||||
|
Video = res
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if(downloadRes != Resolution.NoDownload)
|
||||||
if(downloadRes != Resolution.NoDownload)
|
|
||||||
{
|
|
||||||
each(var id : newVideos)
|
|
||||||
{
|
{
|
||||||
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()
|
private DownloadThread()
|
||||||
{
|
{
|
||||||
while(this.Running)
|
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.TYTD = this;
|
||||||
res.Progress = (progress)=>{
|
res.Progress = (progress)=>{
|
||||||
this.CurrentVideoProgress = progress;
|
this.CurrentVideoProgress = progress;
|
||||||
this.VideoProgress.Invoke(this, {
|
this.VideoProgress.Invoke(this, {
|
||||||
Video = res.Video,
|
Video = res.Video,
|
||||||
progress
|
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,{
|
this.VideoEnded.Invoke(this,{
|
||||||
Video = res.Video
|
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");
|
return FS.ReadAllBytes(this.Storage,/"Streams"/"nullthumb.jpg");
|
||||||
}
|
}
|
||||||
private OpenDB()
|
/^
|
||||||
|
Open the database
|
||||||
|
^/
|
||||||
|
public OpenDB()
|
||||||
{
|
{
|
||||||
var dbFile = this.DatabaseDirectory / "tytd.db";
|
var dbFile = this.DatabaseDirectory / "tytd.db";
|
||||||
return Sqlite.Open(dbFile);
|
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)
|
public PackageState(name, version)
|
||||||
{
|
{
|
||||||
each(var item : this.Plugins)
|
each(var item : this.Plugins)
|
||||||
@@ -994,6 +1142,10 @@ class TYTD.Downloader {
|
|||||||
this.PackageManager.DownloadPlugin(dir,name,version);
|
this.PackageManager.DownloadPlugin(dir,name,version);
|
||||||
this.LoadPlugins();
|
this.LoadPlugins();
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Install plugin
|
||||||
|
version must be a Version not a String
|
||||||
|
^/
|
||||||
public PackageInstall(name, version)
|
public PackageInstall(name, version)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -1013,7 +1165,9 @@ class TYTD.Downloader {
|
|||||||
|
|
||||||
this.PackageDownload(name, version);
|
this.PackageDownload(name, version);
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Uninstall plugin
|
||||||
|
^/
|
||||||
public PackageUninstall(name)
|
public PackageUninstall(name)
|
||||||
{
|
{
|
||||||
each(var item : this.Plugins)
|
each(var item : this.Plugins)
|
||||||
@@ -1029,11 +1183,18 @@ class TYTD.Downloader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Get the TYTD Tag
|
||||||
|
^/
|
||||||
public getTYTDTag()
|
public getTYTDTag()
|
||||||
{
|
{
|
||||||
return this.Config.TYTDTag ?? "UnknownPC";
|
return this.Config.TYTDTag ?? "UnknownPC";
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Download video info if it does not exist
|
||||||
|
pass in a video id or url
|
||||||
|
|
||||||
|
^/
|
||||||
public PutVideoInfoIfNotExists(vid)
|
public PutVideoInfoIfNotExists(vid)
|
||||||
{
|
{
|
||||||
var id = TYTD.GetVideoId(vid);
|
var id = TYTD.GetVideoId(vid);
|
||||||
@@ -1048,13 +1209,22 @@ class TYTD.Downloader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Put video info from info into database
|
||||||
|
^/
|
||||||
public PutVideoInfo(info)
|
public PutVideoInfo(info)
|
||||||
{
|
{
|
||||||
this.Muxex.Lock();
|
this.Muxex.Lock();
|
||||||
var db = this.OpenDB();
|
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.Exec(db, $"INSERT INTO channels (channelId,title) VALUES ({Sqlite.Escape(info.channelId)},{Sqlite.Escape(info.author)});");
|
||||||
Sqlite.Close(db);
|
Sqlite.Close(db);
|
||||||
this.Mutex.Unlock();
|
this.Mutex.Unlock();
|
||||||
@@ -1108,6 +1278,7 @@ class TYTD.Downloader {
|
|||||||
return config[0].value;
|
return config[0].value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DownloadChannelThumbInternal(channelId, thumbnail_url)
|
private DownloadChannelThumbInternal(channelId, thumbnail_url)
|
||||||
{
|
{
|
||||||
var id = TYTD.GetChannelId(channelId);
|
var id = TYTD.GetChannelId(channelId);
|
||||||
@@ -1286,7 +1457,9 @@ class TYTD.Downloader {
|
|||||||
}
|
}
|
||||||
return {items};
|
return {items};
|
||||||
}
|
}
|
||||||
|
/^
|
||||||
|
Make a video manifest request
|
||||||
|
^/
|
||||||
public ManifestRequest(vid)
|
public ManifestRequest(vid)
|
||||||
{
|
{
|
||||||
var id = TYTD.GetVideoId(vid);
|
var id = TYTD.GetVideoId(vid);
|
||||||
@@ -1509,7 +1682,6 @@ class TYTD.Downloader {
|
|||||||
|
|
||||||
private rlm=new Muxex();
|
private rlm=new Muxex();
|
||||||
|
|
||||||
|
|
||||||
private RateLimit()
|
private RateLimit()
|
||||||
{
|
{
|
||||||
this.rlm.Lock();
|
this.rlm.Lock();
|
||||||
@@ -1530,4 +1702,19 @@ class TYTD.Downloader {
|
|||||||
this.lastRequest = curRequest;
|
this.lastRequest = curRequest;
|
||||||
this.rlm.Unlock();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class TYTD.AOVideoDownload : IVideoDownload {
|
|||||||
|
|
||||||
var req = this.tytd.ManifestRequest(id).playerResponse;
|
var req = this.tytd.ManifestRequest(id).playerResponse;
|
||||||
this.info.Title = req.videoDetails.title;
|
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.Channel = req.videoDetails.author;
|
||||||
this.info.ChannelId = req.videoDetails.channelId;
|
this.info.ChannelId = req.videoDetails.channelId;
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class TYTD.NoConvertVideoDownload : IVideoDownload {
|
|||||||
|
|
||||||
|
|
||||||
this.info.Title = req.videoDetails.title;
|
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.Channel = req.videoDetails.author;
|
||||||
this.info.ChannelId = req.videoDetails.channelId;
|
this.info.ChannelId = req.videoDetails.channelId;
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class TYTD.SDVideoDownload : IVideoDownload {
|
|||||||
|
|
||||||
var req = this.tytd.ManifestRequest(id).playerResponse;
|
var req = this.tytd.ManifestRequest(id).playerResponse;
|
||||||
this.info.Title = req.videoDetails.title;
|
this.info.Title = req.videoDetails.title;
|
||||||
|
tytd.LOG($"Downloading: {this.info.Title} with id: {id} LowVideo");
|
||||||
this.info.Channel = req.videoDetails.author;
|
this.info.Channel = req.videoDetails.author;
|
||||||
this.info.ChannelId = req.videoDetails.channelId;
|
this.info.ChannelId = req.videoDetails.channelId;
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class TYTD.VOVideoDownload : IVideoDownload {
|
|||||||
this.info.Title = req.videoDetails.title;
|
this.info.Title = req.videoDetails.title;
|
||||||
this.info.Channel = req.videoDetails.author;
|
this.info.Channel = req.videoDetails.author;
|
||||||
this.info.ChannelId = req.videoDetails.channelId;
|
this.info.ChannelId = req.videoDetails.channelId;
|
||||||
|
tytd.LOG($"Downloading: {this.info.Title} with id: {id} Highest Video");
|
||||||
this.tytd.PutVideoInfo(req.videoDetails);
|
this.tytd.PutVideoInfo(req.videoDetails);
|
||||||
|
|
||||||
var width = 0;
|
var width = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user