tytd-throwback/Program.cs

295 lines
10 KiB
C#

using System.Text;
using System.Web;
using Tesses.WebServer;
using YoutubeExplode;
using YoutubeExplode.Search;
using YoutubeExplode.Videos;
using LiteDB;
using Newtonsoft.Json;
using System.Security.Cryptography;
using YoutubeExplode.Videos.Streams;
using System.Net.Mime;
namespace TYTDThrowback;
class Program
{
ILiteDatabase liteDatabase=new LiteDatabase("data/database.db");
ILiteCollection<VideoInfo> Videos => liteDatabase.GetCollection<VideoInfo>("videos");
public class VideoInfo {
public VideoInfo()
{
}
public VideoInfo(SavedVideo tytd)
{
VideoId = tytd.Id;
VideoTitle = tytd.Title;
ChannelId = tytd.AuthorId;
ChannelTitle=tytd.AuthorTitle;
Description = tytd.Description;
Likes = tytd.Likes;
Views = tytd.Views;
}
public VideoInfo(Video video)
{
VideoId = video.Id;
VideoTitle = video.Title;
ChannelId = video.Author.ChannelId;
ChannelTitle = video.Author.ChannelTitle;
Views = video.Engagement.ViewCount;
Likes = video.Engagement.LikeCount;
Duration = video.Duration ?? TimeSpan.Zero;
UploadDate = video.UploadDate.DateTime;
Description = video.Description;
}
public long Id {get;set;}
public string VideoId {get;set;}="";
public string VideoTitle {get;set;}="";
public string ChannelId {get;set;}="";
public string ChannelTitle {get;set;}="";
public long Views {get;set;}=0;
public long Likes {get;set;}=0;
public string Description {get;set;}="";
public TimeSpan Duration {get;set;}=TimeSpan.Zero;
public DateTime UploadDate {get;set;}=DateTime.Now;
public DateTime Expires {get;set;}=DateTime.Now;
public string Url {get;set;}="";
public long Bitrate {get;set;}=0;
public long Size {get;set;}=0;
public string Container {get;set;}="";
public void SetIStream(IStreamInfo info)
{
Bitrate=info.Bitrate.BitsPerSecond;
Url = info.Url;
Size = info.Size.Bytes;
Container = info.Container.Name;
}
public IStreamInfo GetIStream()
{
return new SStreamInfo(Url,Bitrate,Size,Container);
}
}
static readonly HttpClient http = new HttpClient();
YoutubeClient client=new YoutubeClient(http);
string Downloader {get;set;}="https://tytd.site.tesses.net/";
bool AllowAddingTo {get;set;}=true;
bool AllowDownloadingFrom {get;set;}=true;
bool AllowDownloadingInfoFrom {get;set;}=false;
static void Main(string[] args)
{
new Program().Run();
}
void Run()
{
if(File.Exists("data/tytd.json"))
{
var res=JsonConvert.DeserializeObject<dynamic?>(File.ReadAllText("data/tytd.json"));
AllowDownloadingFrom = res?.AllowDownloadingFrom ?? true;
AllowAddingTo = res?.AllowAddingTo ?? true;
AllowDownloadingInfoFrom = res?.AllowDownloadingInfoFrom ?? false;
Downloader = res?.Downloader ?? "https://tytd.site.tesses.net/";
}
RouteServer routeServer = new RouteServer(new StaticServer("data/www"));
routeServer.Add("/search",SearchAsync);
routeServer.Add("/info", InfoAsync);
routeServer.Add("/download",DownloadAsync);
routeServer.StartServer(9900);
}
async Task StreamAsync(ServerContext ctx,bool play, VideoInfo video)
{
ContentDisposition disposition=new ContentDisposition();
disposition.FileName = $"{video.VideoTitle}-{video.VideoId}.{video.Container}";
disposition.Inline = play;
//disposition.
ctx.ResponseHeaders.Add("Content-Disposition",disposition.ToString());
await ctx.SendStreamAsync(await client.Videos.Streams.GetAsync(video.GetIStream()),HeyRed.Mime.MimeTypesMap.GetMimeType($"file.{video.Container}"));
}
async Task DownloadAsync(ServerContext ctx)
{
VideoId? vi=null;
if(ctx.QueryParams.TryGetFirst("v", out var v) && (vi = VideoId.TryParse(v) ).HasValue)
{
bool play = ctx.QueryParams.ContainsKey("play");
var info=await EnsureVideoAsync(vi.Value);
DateTime now = DateTime.Now;
if(now<info.Expires)
{
await StreamAsync(ctx,play,info);
}
else
{
info.Expires = now.AddHours(6);
info.SetIStream((await client.Videos.Streams.GetManifestAsync(vi.Value)).GetMuxedStreams().GetWithHighestVideoQuality());
Videos.Update(info);
await StreamAsync(ctx,play,info);
}
}
}
async Task InfoAsync(ServerContext ctx)
{
VideoId? vi=null;
if(ctx.QueryParams.TryGetFirst("v", out var v) && (vi = VideoId.TryParse(v) ).HasValue)
{
var video=await EnsureVideoAsync(vi.Value);
string desc = string.IsNullOrWhiteSpace(video.Description) ? "" : HttpUtility.HtmlEncode(video.Description).Replace("\n","<br>");
string dfm = "./not_allowed_df.html";
string atm = "./not_allowed_at.html";
if(!string.IsNullOrWhiteSpace(Downloader) && AllowDownloadingFrom)
{
dfm = $"{Downloader.TrimEnd('/')}/api/Storage/Video/{video.VideoId}";
}
if(!string.IsNullOrWhiteSpace(Downloader) && AllowAddingTo)
{
atm = $"{Downloader.TrimEnd('/')}/api/AddVideo/{video.VideoId}";
}
await ctx.SendTextAsync((await File.ReadAllTextAsync("data/www/info.html")).Replace("$TITLE---429492492942",HttpUtility.HtmlEncode(video.VideoTitle)).Replace("$AUTHOR---429492492942",HttpUtility.HtmlEncode(video.ChannelTitle)).Replace("$UPLOAD---429492492942",$"{video.UploadDate.Month}/{video.UploadDate.Day}/{video.UploadDate.Year}").Replace("$LIKES---429492492942",video.Likes.ToString()).Replace("$VIEWS---429492492942",video.Views.ToString()).Replace("$DURATION---429492492942",video.Duration.ToString()).Replace("$WOY---429492492942",$"https://www.youtube.com/watch?v={video.VideoId}").Replace("$WFY---429492492942",$"./download?v={video.VideoId}&play").Replace("$DFY---429492492942",$"./download?v={video.VideoId}").Replace("$DFM---429492492942",dfm).Replace("$ATM---429492492942",atm).Replace("$DESC---429492492942",desc));
}
}
async Task SearchAsync(ServerContext ctx)
{
if(!ctx.QueryParams.TryGetFirst("q",out var query))
query="Trending";
StringBuilder b = new StringBuilder();
void AddLi(VideoInfo info)
{
b.Append($"<li><a href=\"./info?v={info.VideoId}\" class=\"tytd-li-span\">{HttpUtility.HtmlEncode(info.VideoTitle)}</a><br><span class=\"tytd-li-span\">{HttpUtility.HtmlEncode(info.ChannelTitle)}</span><span class=\"tytd-li-span-2\">{info.UploadDate.Month}/{info.UploadDate.Day}/{info.UploadDate.Year}</span><br><span class=\"tytd-li-span\">V: {info.Views} L: {info.Likes}</span><span class=\"tytd-li-span-2\">{info.Duration.ToString()}</span></li>");
}
await foreach(var item in client.Search.GetResultBatchesAsync(query,YoutubeExplode.Search.SearchFilter.Video))
{
foreach(var v in item.Items)
{
var v2 = v as VideoSearchResult;
if(v2 != null)
AddLi(await EnsureVideoAsync(v2.Id));
}
break;
}
await ctx.SendTextAsync((await File.ReadAllTextAsync("data/www/search.html")).Replace("$QUERY---429492492942",HttpUtility.HtmlAttributeEncode(query)).Replace("$LI---429492492942",b.ToString()));
}
private async Task<VideoInfo?> TYTDVideoInfo(VideoId id)
{
if(!AllowDownloadingInfoFrom) return null;
if(string.IsNullOrWhiteSpace(Downloader))
return null;
try{
string url = Downloader.TrimEnd('/');
if((await http.GetStringAsync($"{url}/api/Storage/FileExists/Info/{id.Value}.json")) == "false") return null;
var res=JsonConvert.DeserializeObject<SavedVideo>(await http.GetStringAsync($"{url}/api/Storage/File/Info/{id.Value}.json"));
if(res != null)
{
return new VideoInfo(res);
}
return null;
}
catch(Exception ex)
{
_=ex;
return null;
}
}
private async Task<VideoInfo> EnsureVideoAsync(VideoId v)
{
var vid = Videos.FindOne(e=>e.VideoId == v.Value);
if(vid == null)
{
var res=await TYTDVideoInfo(v.Value);
if(res != null)
{
Videos.Insert(res);
return res;
}
try{
var _res=await client.Videos.GetAsync(v.Value);
res=new VideoInfo(_res);
Videos.Insert(res);
return res;
}
catch(Exception ex)
{
_=ex;
}
vid=new VideoInfo();
}
return vid;
}
}
internal class SStreamInfo : IStreamInfo
{
private string url;
private long bitrate;
private long size;
private string container;
public SStreamInfo(string url, long bitrate, long size, string container)
{
this.url = url;
this.bitrate = bitrate;
this.size = size;
this.container = container;
}
public string Url => url;
public Container Container => new Container(container);
public FileSize Size => new FileSize(size);
public Bitrate Bitrate => new Bitrate(bitrate);
}
public class SavedVideo
{
public string Id {get;set;}="";
public string Title {get;set;}="";
public string AuthorId {get;set;}="";
public string AuthorTitle {get;set;}="";
public long Likes {get;set;}=0;
public long Views {get;set;}=0;
public TimeSpan Duration {get;set;}=TimeSpan.Zero;
public DateTime UploadDate {get;set;}=DateTime.Now;
public string Description {get;set;}="";
}