Add pwa support
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"icon": "icon.png",
|
||||
"icon": "tytd-128.png",
|
||||
"info": {
|
||||
"description": "Download YouTube Videos (using CrossLang, via web interface, great for homelabs)",
|
||||
"maintainer": "Mike Nolan",
|
||||
@@ -14,5 +14,6 @@
|
||||
"project_dependencies": [
|
||||
"..\/Tesses.YouTubeDownloader"
|
||||
],
|
||||
"version": "1.0.0.0-dev"
|
||||
"version": "1.0.0.1-prod",
|
||||
"compTime": "secure"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<div>
|
||||
<h1>Cannot connect to the TYTD2025 server.</h1>
|
||||
Refresh page to add to offline queue
|
||||
</div>
|
||||
106
Tesses.YouTubeDownloader.Server/res/offline.html
Normal file
@@ -0,0 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TYTD2025 is Offline</title>
|
||||
<link rel="stylesheet" href="/beer.min.css">
|
||||
<link rel="stylesheet" href="/theme.css">
|
||||
<script src="/offline.js" defer></script>
|
||||
<script type="module" src="/beer.min.js" defer></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="row">
|
||||
<div class="max"></div>
|
||||
<div class="min">
|
||||
<img src="tytd-128.png" alt="tytd-logo">
|
||||
</div>
|
||||
<div class="max"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="max"></div>
|
||||
<div class="min">
|
||||
<h3>Cannot connect to the TYTD2025 server.</h3>
|
||||
</div>
|
||||
<div class="max"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="max"></div>
|
||||
<div class="min">
|
||||
<form>
|
||||
|
||||
<div class="field label border small fill">
|
||||
<input id="url" type="text">
|
||||
<label>Url or id</label>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="max">
|
||||
<div class="field label suffix border fill">
|
||||
<select id="res">
|
||||
|
||||
|
||||
<option value="NoDownload">Don't Download</option>
|
||||
|
||||
|
||||
|
||||
<option value="LowVideo" selected="">Low (muxed by YouTube)</option>
|
||||
|
||||
|
||||
|
||||
<option value="VideoOnly">Highest video (no audio)</option>
|
||||
|
||||
|
||||
|
||||
<option value="AudioOnly">Highest audio (no video)</option>
|
||||
|
||||
|
||||
|
||||
<option value="MP3">Convert to MP3</option>
|
||||
|
||||
|
||||
|
||||
<option value="FLAC">Convert to FLAC</option>
|
||||
|
||||
|
||||
|
||||
<option value="MP4">Convert to MP4</option>
|
||||
|
||||
|
||||
|
||||
<option value="MKV">Mux to MKV (no transcoding)</option>
|
||||
|
||||
|
||||
|
||||
<option value="DontConvert">Don't convert or Mux</option>
|
||||
|
||||
|
||||
</select>
|
||||
<label>Resolution</label>
|
||||
<i>arrow_drop_down</i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button id="btn">
|
||||
<i>add</i>
|
||||
Add when online
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="max"></div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
btn.onclick = (evt)=>{
|
||||
evt.preventDefault();
|
||||
addOffline(url.value, res.value);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
54
Tesses.YouTubeDownloader.Server/res/offline.js
Normal file
@@ -0,0 +1,54 @@
|
||||
function addOffline(url,res)
|
||||
{
|
||||
var videos = JSON.parse(localStorage.getItem("videos") ?? "[]");
|
||||
videos.push({
|
||||
url: url,
|
||||
res: res
|
||||
});
|
||||
localStorage.setItem('videos',JSON.stringify(videos));
|
||||
}
|
||||
|
||||
async function syncOffline()
|
||||
{
|
||||
const json = localStorage.getItem("videos") ?? "[]";
|
||||
if(json !== "[]")
|
||||
{
|
||||
const resp = await fetch('/api/v1/add',{
|
||||
body: json,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
method: "POST"
|
||||
});
|
||||
if(resp.ok)
|
||||
{
|
||||
localStorage.removeItem('videos');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(navigator.online)
|
||||
{
|
||||
syncOffline();
|
||||
}
|
||||
|
||||
window.addEventListener('online',()=>{
|
||||
syncOffline();
|
||||
});
|
||||
|
||||
async function getPersonalTempLink()
|
||||
{
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const ent = searchParams.get("name");
|
||||
if(ent)
|
||||
{
|
||||
const resp=await fetch(`./api/v1/personal_tmp_link?name=${encodeURIComponent(ent)}`);
|
||||
if(resp.ok)
|
||||
{
|
||||
const text = await resp.text();
|
||||
the_playlist_url.innerText = text;
|
||||
the_playlist_url.href = text;
|
||||
ui("#dialog");
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Tesses.YouTubeDownloader.Server/res/service_worker.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const assets = ["<@ASSETS@>"];
|
||||
|
||||
const staticCacheName = "tytd-static-<@BUILD_TIME@>";
|
||||
|
||||
self.addEventListener('install',evt => {
|
||||
evt.waitUntil(
|
||||
caches.open(staticCacheName).then(cache =>{
|
||||
cache.addAll(assets);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('activate',(evt)=>{
|
||||
evt.waitUntil(
|
||||
caches.keys().then(keys => {
|
||||
return Promise.all(keys.filter(key => key !== staticCacheName).map(key => caches.delete(key)));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', evt => {
|
||||
const uri = new URL(evt.request.url);
|
||||
|
||||
evt.respondWith(
|
||||
|
||||
caches.match(evt.request).then(cacheRes=>{
|
||||
return cacheRes || fetch(evt.request);
|
||||
}).catch(()=>{
|
||||
if(uri.pathname === '/queue-size')
|
||||
{
|
||||
return new Response('<span hx-trigger="every 5000ms" hx-target="this" hx-push-url="false" hx-indicator="none" hx-get="./queue-size" hx-swap="outerHTML" class="badge">?</span>',{
|
||||
headers: { 'Content-Type': 'text/html' }
|
||||
});
|
||||
}
|
||||
if(uri.pathname === "/progress")
|
||||
{
|
||||
return caches.match("/offline-progress.html");
|
||||
}
|
||||
return caches.match('/offline.html');
|
||||
})
|
||||
);
|
||||
});
|
||||
73
Tesses.YouTubeDownloader.Server/res/site.webmanifest
Normal file
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"short_name": "TYTD2025",
|
||||
"name": "Tesses YouTubeDownloader 2025",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#ffb2be",
|
||||
"background_color": "#241e1f",
|
||||
"description": "A web based YouTube archiver to preserve videos that might be removed",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/tytd.svg",
|
||||
"sizes": "192x192 256x256 384x384 512x512",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/tytd-128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/tytd-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/tytd-256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/tytd-384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/tytd-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/tytd-1024.png",
|
||||
"sizes": "1024x1024",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "Open Downloads",
|
||||
"short_name": "Downloads",
|
||||
"description": "Open Downloads page",
|
||||
"url": "/downloads"
|
||||
},
|
||||
{
|
||||
"name": "Open Installed Plugins",
|
||||
"short_name": "Installed plugins",
|
||||
"description": "Open the installed plugins page",
|
||||
"url": "/plugins"
|
||||
},
|
||||
{
|
||||
"name": "Open Download Plugins",
|
||||
"short_name": "Download plugins",
|
||||
"description": "Open the download plugins page",
|
||||
"url": "/plugins-download"
|
||||
},
|
||||
{
|
||||
"name": "Open Settings",
|
||||
"short_name": "Settings",
|
||||
"description": "Open settings page",
|
||||
"url": "/settings"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Tesses.YouTubeDownloader.Server/res/tytd-1024.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
Tesses.YouTubeDownloader.Server/res/tytd-192.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
Tesses.YouTubeDownloader.Server/res/tytd-256.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
Tesses.YouTubeDownloader.Server/res/tytd-384.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
Tesses.YouTubeDownloader.Server/res/tytd-512.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
@@ -20,11 +20,19 @@ func Components.PersonalListDescription(tytd,name,editing)
|
||||
</form>
|
||||
</true>
|
||||
<false>
|
||||
<dialog id="dialog">
|
||||
<h5>The url</h5>
|
||||
<div><a id="the_playlist_url" href=""></a></div>
|
||||
<nav class="right-align no-space">
|
||||
<button data-ui="#dialog" class="transparent link">OK</button>
|
||||
</nav>
|
||||
</dialog>
|
||||
<div class="row" id="description">
|
||||
<div class="max">
|
||||
<plink(description)>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button onclick="getPersonalTempLink()"><i>link</i></button>
|
||||
<button hx-get={$"./edit-personal-description?name={Net.Http.UrlEncode(name)}"} hx-target="#description" hx-swap="outerHTML"><i>edit</i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var progress=0;
|
||||
|
||||
func Components.Progress(tytd)
|
||||
{
|
||||
var vid = tytd.CurrentVideo;
|
||||
@@ -8,6 +8,5 @@ func Components.Progress(tytd)
|
||||
<h4><a hx-target="body" hx-push-url="true" hx-get={$"./channel?id={Net.Http.UrlEncode(vid.ChannelId)}"} href={$"./channel?id={Net.Http.UrlEncode(vid.ChannelId)}"}>{vid.Channel}</a></h4>
|
||||
<progress class="wavy light-green-text" value={tytd.CurrentVideoProgress * 100.0} max="100" min="0"></progress>
|
||||
</div>;
|
||||
progress++;
|
||||
return html;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
func Components.QueueSZ(tytd)
|
||||
{
|
||||
var vid = tytd.CurrentVideo;
|
||||
tytd.Mutex.Lock();
|
||||
|
||||
|
||||
var html = <span hx-trigger="every 5000ms" hx-target="this" hx-push-url="false" hx-indicator="none" hx-get="./queue-size" hx-swap="outerHTML" class="badge">{tytd.VideoQueueCount}</span>;
|
||||
|
||||
tytd.Mutex.Unlock();
|
||||
return html;
|
||||
}
|
||||
@@ -1,11 +1,21 @@
|
||||
func Components.Shell(title, html, page, $mypage)
|
||||
{
|
||||
|
||||
if(TypeOf(mypage) != "Path")
|
||||
mypage = /;
|
||||
|
||||
var index = (/).MakeRelative(mypage).ToString();
|
||||
if(index == "") index = "./";
|
||||
|
||||
const service_worker_script_path = (/"service_worker.js").MakeRelative(mypage).ToString();
|
||||
const service_worker_script = $"<script>
|
||||
if(\"serviceWorker\" in navigator) {123.ToChar()}
|
||||
navigator.serviceWorker.register({Json.Encode(service_worker_script_path)})
|
||||
.then(()=>console.log('Service worker registered'))
|
||||
.catch(()=>console.log('service work not registered'));
|
||||
{125.ToChar()}
|
||||
</script>";
|
||||
|
||||
var pages = [
|
||||
{
|
||||
text="Home",
|
||||
@@ -47,7 +57,11 @@ func Components.Shell(title, html, page, $mypage)
|
||||
<title>TYTD - {title}</title>
|
||||
<link rel="stylesheet" href={(/"beer.min.css").MakeRelative(mypage).ToString()}>
|
||||
<link rel="stylesheet" href={(/"theme.css").MakeRelative(mypage).ToString()}>
|
||||
<link rel="manifest" href={(/"site.webmanifest").MakeRelative(mypage).ToString()}>
|
||||
|
||||
<script src={(/"htmx.min.js").MakeRelative(mypage).ToString()} defer></script>
|
||||
|
||||
<script src={(/"offline.js").MakeRelative(mypage).ToString()} defer></script>
|
||||
<script type="module" src={(/"beer.min.js").MakeRelative(mypage).ToString()} defer></script>
|
||||
</head>
|
||||
<body hx-indicator="#loading-indicator">
|
||||
@@ -56,7 +70,15 @@ func Components.Shell(title, html, page, $mypage)
|
||||
<each(var page : pages)>
|
||||
<a hx-get={page.href} hx-target="body" hx-push-url="true" class={page.classStr}>
|
||||
<i>{page.icon}</i>
|
||||
<if(page.text == "Home")>
|
||||
<true>
|
||||
<span hx-trigger="every 1000ms" hx-target="this" hx-push-url="false" hx-indicator="none" hx-get="./queue-size" hx-swap="outerHTML" class="badge">
|
||||
?
|
||||
</span>
|
||||
</true>
|
||||
</if>
|
||||
<div>{page.text}</div>
|
||||
|
||||
</a>
|
||||
</each>
|
||||
</nav>
|
||||
@@ -69,7 +91,7 @@ func Components.Shell(title, html, page, $mypage)
|
||||
<div class="htmx-indicator shape loading-indicator extra" id="loading-indicator">
|
||||
<img class="responsive" src="./tytd.svg">
|
||||
</div>
|
||||
|
||||
<raw(service_worker_script)>
|
||||
</body>
|
||||
</html>;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
const BUILD_TIME = comptime DateTime.NowEpoch;
|
||||
|
||||
var TYTDResources = [
|
||||
{path="/beer.min.css",value=embed("beer.min.css")},
|
||||
{path="/beer.min.js",value=embed("beer.min.js")},
|
||||
@@ -8,12 +10,31 @@ var TYTDResources = [
|
||||
{path="/htmx.min.js",value=embed("htmx.min.js")},
|
||||
{path="/favicon.ico",value=embed("favicon.ico")},
|
||||
{path="/tytd.svg",value=embed("tytd.svg")},
|
||||
{path="/tytd-128.png",value=embed("tytd-128.png")},
|
||||
{path="/tytd-192.png",value=embed("tytd-192.png")},
|
||||
{path="/tytd-256.png",value=embed("tytd-256.png")},
|
||||
{path="/tytd-384.png",value=embed("tytd-384.png")},
|
||||
{path="/tytd-512.png",value=embed("tytd-512.png")},
|
||||
{path="/tytd-1024.png",value=embed("tytd-1024.png")},
|
||||
{path="/loading-indicator.svg",value=embed("loading-indicator.svg")},
|
||||
{path="/theme.css",value=embed("theme.css")},
|
||||
{path="/video.min.js",value=embed("video.min.js")},
|
||||
{path="/video-js.css",value=embed("video-js.css")},
|
||||
{path="/wavy.svg",value=embed("wavy.svg")}
|
||||
{path="/wavy.svg",value=embed("wavy.svg")},
|
||||
{path="/site.webmanifest",value=embed("site.webmanifest")},
|
||||
{path="/offline-progress.html",value=embed("offline-progress.html")},
|
||||
{path="/offline.html",value=embed("offline.html")},
|
||||
{path="/offline.js", value=embed("offline.js")},
|
||||
];
|
||||
|
||||
const fileNames = [];
|
||||
each(var item : TYTDResources)
|
||||
{
|
||||
fileNames.Add(item.path);
|
||||
}
|
||||
|
||||
const service_worker_str = embed("service_worker.js").ToString().Replace("[\"<@ASSETS@>\"]",Json.Encode(fileNames)).Replace("<@BUILD_TIME@>",BUILD_TIME.ToString());
|
||||
|
||||
var times=1;
|
||||
|
||||
class TYTDApp {
|
||||
@@ -29,21 +50,66 @@ class TYTDApp {
|
||||
|
||||
public TYTDApp()
|
||||
{
|
||||
|
||||
Console.WriteLine($"Built at {new DateTime(BUILD_TIME).ToString()}");
|
||||
var tytdfs = new SubdirFilesystem(FS.Local, GetTYTDDir());
|
||||
this.TYTD = new TYTD.Downloader(tytdfs,FS.MakeFull(GetTYTDDir()));
|
||||
this.TYTD.Start();
|
||||
|
||||
}
|
||||
|
||||
public Handle(ctx)
|
||||
{
|
||||
if(ctx.Path == "/service_worker.js" || fileNames.Contains(ctx.Path))
|
||||
{
|
||||
const inm=ctx.RequestHeaders.TryGetFirst("If-None-Match");
|
||||
if(TypeIsString(inm))
|
||||
{
|
||||
const strs = inm.Split(", ");
|
||||
each(var item : strs)
|
||||
{
|
||||
if(item == $"W/\"{BUILD_TIME}\"" || item == $"\"{BUILD_TIME}\"")
|
||||
{
|
||||
ctx.StatusCode = 304;
|
||||
ctx.WriteHeaders();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.WithHeader("ETag",$"W/\"{BUILD_TIME}\"");
|
||||
}
|
||||
if(ctx.Path == "/service_worker.js")
|
||||
{
|
||||
ctx.WithMimeType(Net.Http.MimeType("service_worker.js")).SendText(service_worker_str);
|
||||
return true;
|
||||
}
|
||||
if(ctx.Path == "/api/v1/auth")
|
||||
{
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
const req=ctx.ReadJson();
|
||||
const result = this.TYTD.Auth(req.username, req.password);
|
||||
if(result)
|
||||
{
|
||||
ctx.SendJson({
|
||||
success = true,
|
||||
flags = result.flags
|
||||
});
|
||||
}
|
||||
else {
|
||||
ctx.SendJson({
|
||||
success=false
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
if(ctx.Path == "/api/v1/login")
|
||||
{
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
const req=ctx.ReadJson();
|
||||
const result = this.TYTD.Login(req.username, req.password);
|
||||
const result = this.TYTD.Login(req.username, req.password, false);
|
||||
if(result)
|
||||
{
|
||||
ctx.SendJson({
|
||||
@@ -70,14 +136,17 @@ class TYTDApp {
|
||||
const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/";
|
||||
const username = ctx.QueryParams.TryGetFirst("username") ?? "";
|
||||
const password = ctx.QueryParams.TryGetFirst("password") ?? "";
|
||||
|
||||
|
||||
|
||||
if(ctx.Method == "POST")
|
||||
{
|
||||
const result = this.TYTD.Login(username, password);
|
||||
const result = this.TYTD.Login(username, password,true);
|
||||
if(result)
|
||||
{
|
||||
ctx.StatusCode = 303;
|
||||
ctx.WithHeader("Set-Cookie",$"Session={result}; SameSite=Strict").SendRedirect("/");
|
||||
var date = new DateTime(DateTime.NowEpoch + UserFlags.Expires);
|
||||
ctx.WithHeader("Set-Cookie",$"Session={result}; SameSite=Lax; Expires={date.ToHttpDate()}; HttpOnly").SendRedirect(redirect);
|
||||
return true;
|
||||
}
|
||||
else incorrect=true;
|
||||
@@ -97,6 +166,25 @@ class TYTDApp {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(ctx.Path == "/progress")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(
|
||||
<div>
|
||||
<h1>You have been logged out</h1>
|
||||
<a href="./login">Login</a>
|
||||
</div>
|
||||
);
|
||||
return true;
|
||||
}
|
||||
if(ctx.Path == "/sso")
|
||||
{
|
||||
const app = ctx.QueryParams.TryGetFirst("app") ?? "";
|
||||
const token = ctx.QueryParams.TryGetFirst("token") ?? "";
|
||||
const path = $"/sso?app={Net.Http.UrlEncode(app)}&token={Net.Http.UrlEncode(token)}";
|
||||
ctx.StatusCode = 307;
|
||||
ctx.SendRedirect($"/login?redirect={Net.Http.UrlEncode(path)}");
|
||||
return true;
|
||||
}
|
||||
ctx.StatusCode = 307;
|
||||
ctx.SendRedirect($"/login?redirect={Net.Http.UrlEncode(ctx.Path)}");
|
||||
return true;
|
||||
@@ -208,7 +296,6 @@ class TYTDApp {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(ctx.Path == "/welcome")
|
||||
{
|
||||
const redirect = ctx.QueryParams.TryGetFirst("redirect") ?? "/settings";
|
||||
@@ -245,11 +332,49 @@ class TYTDApp {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(ctx.Path == "/")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/sso")
|
||||
{
|
||||
const app = ctx.QueryParams.TryGetFirst("app");
|
||||
const token = ctx.QueryParams.TryGetFirst("token");
|
||||
if(TypeIsString(app) && TypeIsString(token))
|
||||
{
|
||||
|
||||
const sso = this.TYTD.GetSSO(app);
|
||||
if(TypeIsDictionary(sso))
|
||||
{
|
||||
const user = this.TYTD.WhoAmI(ctx);
|
||||
if(TypeIsDictionary(user) && user.flags != 0)
|
||||
{
|
||||
const postJson = {
|
||||
username = user.username,
|
||||
flags = user.flags,
|
||||
token = token,
|
||||
sso_app_key = sso.sso_app_key
|
||||
};
|
||||
|
||||
const resp = Net.Http.MakeRequest(sso.service_auth_post, {
|
||||
Method = "POST",
|
||||
Body = Net.Http.TextHttpRequestBody(postJson.ToString(),"application/json")
|
||||
});
|
||||
|
||||
if(resp.StatusCode >= 200 && resp.StatusCode <= 299)
|
||||
{
|
||||
ctx.StatusCode = 307;
|
||||
ctx.SendRedirect($"{sso.service_auth_redirect}{Net.Http.UrlEncode(token)}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.StatusCode = 401;
|
||||
return false;
|
||||
}
|
||||
else if(ctx.Path == "/passwd")
|
||||
{
|
||||
var incorrect=false;
|
||||
@@ -265,6 +390,7 @@ class TYTDApp {
|
||||
|
||||
ctx.WithMimeType("text/html").SendText(Pages.ChangePassword(redirect, incorrect));
|
||||
}
|
||||
|
||||
else if(ctx.Path == "/newuser")
|
||||
{
|
||||
var error = null;
|
||||
@@ -340,6 +466,36 @@ class TYTDApp {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/register_sso")
|
||||
{
|
||||
if(ctx.Method!="POST")
|
||||
{
|
||||
ctx.WithMimeType("application/json").SendJson({
|
||||
success=false,
|
||||
reason = "Method must be post",
|
||||
type = "method"
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if(!UserFlags.IsAdmin(this.TYTD.IsLoggedIn(ctx)))
|
||||
{
|
||||
ctx.WithMimeType("application/json").SendJson({
|
||||
success=false,
|
||||
reason = "You are either not logged in or not admin",
|
||||
type ="auth"
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
const json = ctx.ReadJson();
|
||||
|
||||
const resp = this.TYTD.RegisterSSO(json);
|
||||
|
||||
ctx.WithMimeType("application/json").SendJson(
|
||||
resp
|
||||
);
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/download")
|
||||
{
|
||||
var v = ctx.QueryParams.TryGetFirst("v");
|
||||
@@ -371,6 +527,20 @@ class TYTDApp {
|
||||
ctx.WithMimeType("application/json").SendJson(jo);
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/add")
|
||||
{
|
||||
if(ctx.Method=="POST")
|
||||
{
|
||||
const json = ctx.ReadJson();
|
||||
each(var item : json)
|
||||
{
|
||||
this.TYTD.DownloadItem(item.url,item.res);
|
||||
}
|
||||
ctx.StatusCode=204;
|
||||
ctx.WriteHeaders();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/video.json")
|
||||
{
|
||||
var id = ctx.QueryParams.TryGetFirst("v");
|
||||
@@ -439,6 +609,20 @@ class TYTDApp {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/personal_tmp_link")
|
||||
{
|
||||
const name = ctx.QueryParams.TryGetFirst("name");
|
||||
if(TypeIsString(name))
|
||||
{
|
||||
const ents = this.TYTD.GetPersonalListTempUrl(name);
|
||||
if(TypeIsString(ents))
|
||||
{
|
||||
ctx.WithMimeType("text/plain").SendText(ents);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/api/v1/personal")
|
||||
{
|
||||
/*
|
||||
@@ -714,6 +898,36 @@ class TYTDApp {
|
||||
ctx.WithMimeType("text/html").SendText(Pages.VideoInfo(this.TYTD,ctx));
|
||||
return true;
|
||||
}
|
||||
|
||||
else if(ctx.Path == "/watch_videos")
|
||||
{
|
||||
const video_ids = ctx.QueryParams.TryGetFirst("video_ids");
|
||||
if(TypeIsString(video_ids))
|
||||
{
|
||||
if(ctx.Method=="GET")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.YouTubeAnonyPlaylist(video_ids));
|
||||
return true;
|
||||
}
|
||||
if(ctx.Method=="POST")
|
||||
{
|
||||
const name = ctx.QueryParams.TryGetFirst("name");
|
||||
if(TypeIsString(name))
|
||||
{
|
||||
const nameParts=video_ids.Split(",");
|
||||
Console.WriteLine(nameParts);
|
||||
each(var item : nameParts)
|
||||
{
|
||||
|
||||
this.TYTD.AddToPersonalList(name,item);
|
||||
}
|
||||
Console.WriteLine(name);
|
||||
ctx.SendRedirect($"/list?name={Net.Http.UrlEncode(name)}",303);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(ctx.Path == "/playlist")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.PlaylistInfo(this.TYTD,ctx));
|
||||
@@ -802,6 +1016,12 @@ class TYTDApp {
|
||||
ctx.WithMimeType("text/html").SendText(Components.Progress(this.TYTD));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/queue-size")
|
||||
{
|
||||
|
||||
ctx.WithMimeType("text/html").SendText(Components.QueueSZ(this.TYTD));
|
||||
return true;
|
||||
}
|
||||
else if(ctx.Path == "/plugins")
|
||||
{
|
||||
ctx.WithMimeType("text/html").SendText(Pages.Plugins(this.TYTD,ctx));
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
func Pages.YouTubeAnonyPlaylist(video_ids)
|
||||
{
|
||||
const html = <null>
|
||||
<div class="row">
|
||||
<div class="max"></div>
|
||||
<div class="min">
|
||||
<form method="POST" action="./watch_videos">
|
||||
<input type="hidden" name="video_ids" value={video_ids}>
|
||||
<div class="row">
|
||||
<div class="max">
|
||||
<div class="field label border">
|
||||
<input type="text" name="name">
|
||||
<label>Playlist Name</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min">
|
||||
<button>Add</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="max"></div>
|
||||
</div>
|
||||
</null>;
|
||||
return Components.Shell("Create playlist",html ,1);
|
||||
}
|
||||