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": [
|
||||
"..\/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">
|
||||
<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>
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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));
|
||||
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
<raw(Components.PersonalListDescription(tytd,name,false))>
|
||||
|
||||
|
||||
<each(var item : res)>
|
||||
<raw(Components.DownloadedVideo(item))>
|
||||
</each>
|
||||
|
||||
@@ -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)>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user