Bring TYTD Back
This commit is contained in:
commit
3d1a64770a
|
@ -0,0 +1,3 @@
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
Creds.priv.cs
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="SSH.NET" Version="2024.1.0" />
|
||||||
|
<PackageReference Include="Tesses.WebServer" Version="1.0.4.3" />
|
||||||
|
<PackageReference Include="Tesses.WebServer.EasyServer" Version="1.0.1" />
|
||||||
|
<PackageReference Include="YouTubeExplode" Version="6.4.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,9 @@
|
||||||
|
public static partial class Creds
|
||||||
|
{
|
||||||
|
public static string IP => _ip;
|
||||||
|
public static string Username => _username;
|
||||||
|
|
||||||
|
public static string Password => _password;
|
||||||
|
|
||||||
|
public static string DirectoryOnServer => _dir;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
ADD . /src
|
||||||
|
WORKDIR /src
|
||||||
|
RUN dotnet publish -c Release -o /out
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/runtime:8.0
|
||||||
|
EXPOSE 3252
|
||||||
|
COPY --from=build /out /out
|
||||||
|
COPY --from=build /src/wwwroot /out/wwwroot
|
||||||
|
|
||||||
|
WORKDIR /out
|
||||||
|
CMD ["/out/BringTYTDBack"]
|
||||||
|
RUN chmod +x /out/BringTYTDBack
|
|
@ -0,0 +1,166 @@
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Renci.SshNet;
|
||||||
|
using YoutubeExplode;
|
||||||
|
using YoutubeExplode.Videos;
|
||||||
|
using Tesses.WebServer;
|
||||||
|
using static Creds;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using YoutubeExplode.Playlists;
|
||||||
|
using YoutubeExplode.Channels;
|
||||||
|
|
||||||
|
YoutubeClient ytc=new YoutubeClient();
|
||||||
|
ConcurrentStack<VideoId> videoIds=new ConcurrentStack<VideoId>();
|
||||||
|
bool isRunning=true;
|
||||||
|
|
||||||
|
async Task AddVideo(VideoId id)
|
||||||
|
{
|
||||||
|
SshClient client = new SshClient(IP,Username,Password);
|
||||||
|
await client.ConnectAsync(default);
|
||||||
|
using(var res=client.CreateCommand($"echo {DirectoryOnServer}/*-{id.Value}.mp4"))
|
||||||
|
{
|
||||||
|
await res.ExecuteAsync();
|
||||||
|
|
||||||
|
if(res.Result == $"{DirectoryOnServer}/*-{id.Value}.mp4\n")
|
||||||
|
{
|
||||||
|
using(var res2=client.CreateCommand($"cd {DirectoryOnServer} && tytd {id.Value}"))
|
||||||
|
{
|
||||||
|
await res2.ExecuteAsync();
|
||||||
|
Console.WriteLine($"Downloaded: https://www.youtube.com/watch?v={id.Value}");
|
||||||
|
_=res2.Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Video already exists: https://www.youtube.com/watch?v={id.Value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task AddItem(string url)
|
||||||
|
{
|
||||||
|
VideoId? vId = VideoId.TryParse(url);
|
||||||
|
PlaylistId? pId = PlaylistId.TryParse(url);
|
||||||
|
ChannelId? cId = ChannelId.TryParse(url);
|
||||||
|
UserName? username = UserName.TryParse(url);
|
||||||
|
ChannelSlug? slug = ChannelSlug.TryParse(url);
|
||||||
|
ChannelHandle? handle = ChannelHandle.TryParse(url);
|
||||||
|
|
||||||
|
if(url.Length == 11 && vId.HasValue)
|
||||||
|
{
|
||||||
|
videoIds.Push(vId.Value);
|
||||||
|
}
|
||||||
|
else if(pId.HasValue)
|
||||||
|
{
|
||||||
|
await foreach(var v in ytc.Playlists.GetVideosAsync(pId.Value))
|
||||||
|
{
|
||||||
|
videoIds.Push(v.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(vId.HasValue)
|
||||||
|
{
|
||||||
|
videoIds.Push(vId.Value);
|
||||||
|
}
|
||||||
|
else if(cId.HasValue)
|
||||||
|
{
|
||||||
|
await foreach(var c in ytc.Channels.GetUploadsAsync(cId.Value))
|
||||||
|
{
|
||||||
|
videoIds.Push(c.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(username.HasValue)
|
||||||
|
{
|
||||||
|
cId = (await ytc.Channels.GetByUserAsync(username.Value)).Id;
|
||||||
|
await foreach(var c in ytc.Channels.GetUploadsAsync(cId.Value))
|
||||||
|
{
|
||||||
|
videoIds.Push(c.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(slug.HasValue)
|
||||||
|
{
|
||||||
|
cId = (await ytc.Channels.GetBySlugAsync(slug.Value)).Id;
|
||||||
|
await foreach(var c in ytc.Channels.GetUploadsAsync(cId.Value))
|
||||||
|
{
|
||||||
|
videoIds.Push(c.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(handle.HasValue)
|
||||||
|
{
|
||||||
|
cId = (await ytc.Channels.GetByHandleAsync(handle.Value)).Id;
|
||||||
|
await foreach(var c in ytc.Channels.GetUploadsAsync(cId.Value))
|
||||||
|
{
|
||||||
|
videoIds.Push(c.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Factory.StartNew(async()=>{
|
||||||
|
while(isRunning)
|
||||||
|
{
|
||||||
|
if(videoIds.TryPop(out var res))
|
||||||
|
{
|
||||||
|
await AddVideo(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},TaskCreationOptions.LongRunning).Wait(0);
|
||||||
|
|
||||||
|
MountableServer msvr=new MountableServer(new StaticServer("wwwroot"));
|
||||||
|
|
||||||
|
msvr.Mount("/api/AddItemRes/1/",new TYTDServer(AddItem));
|
||||||
|
msvr.Mount("/api/AddItem/",new TYTDServer(AddItem));
|
||||||
|
msvr.Mount("/api/AddVideoRes/1/",new TYTDServer(videoIds));
|
||||||
|
msvr.Mount("/api/AddVideo/",new TYTDServer(videoIds));
|
||||||
|
RouteServer routeServer=new RouteServer(msvr);
|
||||||
|
routeServer.Add("/itemsInQueue",async(ctx)=>{
|
||||||
|
await ctx.SendJsonAsync(videoIds.Count);
|
||||||
|
});
|
||||||
|
routeServer.Add("/add",async(ctx)=>{
|
||||||
|
if(ctx.QueryParams.TryGetFirst("v",out var v))
|
||||||
|
{
|
||||||
|
await AddItem(v);
|
||||||
|
}
|
||||||
|
await ctx.SendRedirectAsync("/");
|
||||||
|
});
|
||||||
|
routeServer.StartServer(3252);
|
||||||
|
|
||||||
|
public class TYTDServer : Server
|
||||||
|
{
|
||||||
|
bool onlyVideos;
|
||||||
|
ConcurrentStack<VideoId>? stk;
|
||||||
|
|
||||||
|
Func<string,Task>? cb;
|
||||||
|
public TYTDServer(ConcurrentStack<VideoId> stk)
|
||||||
|
{
|
||||||
|
onlyVideos=true;
|
||||||
|
this.stk = stk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TYTDServer(Func<string,Task> cb)
|
||||||
|
{
|
||||||
|
onlyVideos=false;
|
||||||
|
this.cb = cb;
|
||||||
|
}
|
||||||
|
public override async Task GetAsync(ServerContext ctx)
|
||||||
|
{
|
||||||
|
if(onlyVideos)
|
||||||
|
{
|
||||||
|
string url=ctx.UrlPath.Substring(1);
|
||||||
|
stk?.Push(url);
|
||||||
|
await ctx.SendTextAsync("<script>window.history.back();</script>");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(cb == null) {
|
||||||
|
await ctx.SendTextAsync("<script>window.history.back();</script>");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
string url=ctx.UrlPath.Substring(1);
|
||||||
|
await cb(url);
|
||||||
|
await ctx.SendTextAsync("<script>window.history.back();</script>");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
Bring TYTD Back
|
||||||
|
===============
|
||||||
|
|
||||||
|
I wanted to bring [TYTD](https://gitea.site.tesses.net/tesses50/tytd) back so I did somewhat
|
||||||
|
|
||||||
|
This project requires [tytd-cli](https://gitea.site.tesses.net/tesses50/tytd-cli-cpp) to be installed on a ssh (password protected server, could use private key if you modify Program.cs async Task AddVideo), the tytd-cli must be installed on PATH
|
||||||
|
|
||||||
|
To Configure you must add a file called Creds.priv.cs with the following content
|
||||||
|
|
||||||
|
```cs
|
||||||
|
public static partial class Creds
|
||||||
|
{
|
||||||
|
static string _ip = "YOUR_SSH_SERVER";
|
||||||
|
static string _username = "YOUR_SSH_USERNAME";
|
||||||
|
static string _password = "YOUR_SSH_PASSWORD";
|
||||||
|
static string _dir = "YOUR_SSH_PATH";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# WITHOUT DOCKER
|
||||||
|
then compile the project using
|
||||||
|
```bash
|
||||||
|
dotnet publish -c Release -o out --self-contained -r linux-x64 -p:PublishSingleFile=true -p:PublishReadyToRun=true
|
||||||
|
```
|
||||||
|
then copy wwwroot to out
|
||||||
|
|
||||||
|
# WITH DOCKER
|
||||||
|
```bash
|
||||||
|
docker build -t bring-tytd-back:latest .
|
||||||
|
docker run -p 3252:3252 -d bring-tytd-back:latest
|
||||||
|
```
|
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 7.2 KiB |
|
@ -0,0 +1,47 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>TYTD</title>
|
||||||
|
<link rel="stylesheet" href="./beer.min.css">
|
||||||
|
<link rel="stylesheet" href="./theme.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<article class="medium middle-align center-align">
|
||||||
|
<div>
|
||||||
|
<i class="extra">download</i>
|
||||||
|
<h5>Add a video, playlist or channel to my TYTD</h5>
|
||||||
|
|
||||||
|
<div class="space"></div>
|
||||||
|
<form action="./add">
|
||||||
|
|
||||||
|
<nav class="no-space">
|
||||||
|
|
||||||
|
<div class="max field border left-round">
|
||||||
|
<input type="url" name="v">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="large right-round">Add</button>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
There are <span id="noItems">0</span> item(s) in the queue
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<script src="./beer.min.js"></script>
|
||||||
|
|
||||||
|
<script defer>
|
||||||
|
const d = document.getElementById('noItems');
|
||||||
|
setInterval(()=>{
|
||||||
|
fetch('./itemsInQueue').then(e=>e.text()).then(e=>{
|
||||||
|
d.innerText = e;
|
||||||
|
});
|
||||||
|
},3000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "Tesses YouTube Downloader",
|
||||||
|
"short_name": "Tesses YouTube Downloader",
|
||||||
|
"start_url": "./",
|
||||||
|
"scope": ".",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "./android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "./android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ff0000",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
:root,html,
|
||||||
|
body {
|
||||||
|
--primary:#ffb4a8;
|
||||||
|
--on-primary:#690000;
|
||||||
|
--primary-container:#930100;
|
||||||
|
--on-primary-container:#ffdad4;
|
||||||
|
--secondary:#e7bdb6;
|
||||||
|
--on-secondary:#442925;
|
||||||
|
--secondary-container:#5d3f3b;
|
||||||
|
--on-secondary-container:#ffdad4;
|
||||||
|
--tertiary:#dec48c;
|
||||||
|
--on-tertiary:#3e2e04;
|
||||||
|
--tertiary-container:#564419;
|
||||||
|
--on-tertiary-container:#fbdfa6;
|
||||||
|
--error:#ffb4ab;
|
||||||
|
--on-error:#690005;
|
||||||
|
--error-container:#93000a;
|
||||||
|
--on-error-container:#ffb4ab;
|
||||||
|
--background:#201a19;
|
||||||
|
--on-background:#ede0dd;
|
||||||
|
--surface:#181211;
|
||||||
|
--on-surface:#ede0dd;
|
||||||
|
--surface-variant:#534341;
|
||||||
|
--on-surface-variant:#d8c2be;
|
||||||
|
--outline:#a08c89;
|
||||||
|
--outline-variant:#534341;
|
||||||
|
--shadow:#000000;
|
||||||
|
--scrim:#000000;
|
||||||
|
--inverse-surface:#ede0dd;
|
||||||
|
--inverse-on-surface:#362f2e;
|
||||||
|
--inverse-primary:#c00100;
|
||||||
|
--surface-dim:#181211;
|
||||||
|
--surface-bright:#3f3736;
|
||||||
|
--surface-container-lowest:#120d0c;
|
||||||
|
--surface-container-low:#201a19;
|
||||||
|
--surface-container:#251e1d;
|
||||||
|
--surface-container-high:#2f2827;
|
||||||
|
--surface-container-highest:#3b3332;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root,html,
|
||||||
|
body {
|
||||||
|
--primary:#c00100;
|
||||||
|
--on-primary:#ffffff;
|
||||||
|
--primary-container:#ffdad4;
|
||||||
|
--on-primary-container:#410000;
|
||||||
|
--secondary:#775651;
|
||||||
|
--on-secondary:#ffffff;
|
||||||
|
--secondary-container:#ffdad4;
|
||||||
|
--on-secondary-container:#2c1512;
|
||||||
|
--tertiary:#705c2e;
|
||||||
|
--on-tertiary:#ffffff;
|
||||||
|
--tertiary-container:#fbdfa6;
|
||||||
|
--on-tertiary-container:#251a00;
|
||||||
|
--error:#ba1a1a;
|
||||||
|
--on-error:#ffffff;
|
||||||
|
--error-container:#ffdad6;
|
||||||
|
--on-error-container:#410002;
|
||||||
|
--background:#fffbff;
|
||||||
|
--on-background:#201a19;
|
||||||
|
--surface:#fff8f6;
|
||||||
|
--on-surface:#201a19;
|
||||||
|
--surface-variant:#f5ddda;
|
||||||
|
--on-surface-variant:#534341;
|
||||||
|
--outline:#857370;
|
||||||
|
--outline-variant:#d8c2be;
|
||||||
|
--shadow:#000000;
|
||||||
|
--scrim:#000000;
|
||||||
|
--inverse-surface:#362f2e;
|
||||||
|
--inverse-on-surface:#fbeeec;
|
||||||
|
--inverse-primary:#ffb4a8;
|
||||||
|
--surface-dim:#e4d7d5;
|
||||||
|
--surface-bright:#fff8f6;
|
||||||
|
--surface-container-lowest:#ffffff;
|
||||||
|
--surface-container-low:#fef1ee;
|
||||||
|
--surface-container:#f8ebe9;
|
||||||
|
--surface-container-high:#f3e5e3;
|
||||||
|
--surface-container-highest:#ede0dd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizonal
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.vertical
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.filler
|
||||||
|
{
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
Loading…
Reference in New Issue