292 lines
10 KiB
C#
292 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
|
|||
|
{
|
|||
|
static readonly ILiteDatabase liteDatabase=new LiteDatabase("data/database.db");
|
|||
|
static 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();
|
|||
|
static readonly YoutubeClient client=new YoutubeClient(http);
|
|||
|
|
|||
|
static string Downloader {get;set;}="https://tytd.site.tesses.net/";
|
|||
|
|
|||
|
static bool AllowAddingTo {get;set;}=true;
|
|||
|
|
|||
|
static bool AllowDownloadingFrom {get;set;}=true;
|
|||
|
static bool AllowDownloadingInfoFrom {get;set;}=false;
|
|||
|
static void Main(string[] args)
|
|||
|
{
|
|||
|
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);
|
|||
|
}
|
|||
|
|
|||
|
static 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}"));
|
|||
|
}
|
|||
|
static 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);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
static 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));
|
|||
|
|
|||
|
}
|
|||
|
}
|
|||
|
static 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 static 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 static 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;}="";
|
|||
|
|
|||
|
}
|