Files
2026-04-28 03:21:01 -05:00

149 lines
5.2 KiB
C#

using Tesses.WebServer;
using Scriban;
using System.Web;
using YoutubeExplode.Search;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using Newtonsoft.Json;
namespace Tesses.YouTubeDownloader2025Companion;
public class SearchResultHandler : IEventable {
private class SearchResultObj(IAsyncEnumerator<YoutubeExplode.Common.Batch<ISearchResult>> e,bool isDialog) {
public long expires = Program.NowEpoch + (3600*6);
public IAsyncEnumerator<YoutubeExplode.Common.Batch<ISearchResult>> enumerator = e;
public bool isDialog = isDialog;
}
private ConcurrentDictionary<string,SearchResultObj> ents=new ConcurrentDictionary<string, SearchResultObj>();
public SearchResultHandler()
{
}
private class SearchResult(string tytdUrl, string title, string id)
{
public string Url => HttpUtility.HtmlAttributeEncode($"{tytdUrl.TrimEnd('/')}/watch?v={id}");
public string Imgurl => HttpUtility.HtmlAttributeEncode($"/api/v1/video-thumbnail?v={id}");
public string Title => HttpUtility.HtmlEncode(title);
public string Dialogok => HttpUtility.HtmlAttributeEncode($"searchDialogSubmit('{HttpUtility.JavaScriptStringEncode(id)}')");
public string Addbtnjson => HttpUtility.HtmlAttributeEncode(JsonConvert.SerializeObject(new{type="add",value=id}));
}
public async Task<string> SearchAsync(string query,bool isDialog)
{
var obj = Program.YouTubeClient.Search.GetResultBatchesAsync(query, YoutubeExplode.Search.SearchFilter.Video);
SearchResultObj ro = new SearchResultObj(obj.GetAsyncEnumerator(),isDialog);
string id = RandomNumberGenerator.GetHexString(32);
while(!ents.TryAdd(id,ro))
{
id = RandomNumberGenerator.GetHexString(32);
}
return await MoreAsync(id,ro);
}
private static ReloadingScript results_component = new ReloadingScript("wwwroot/components/search-results.sbnhtml");
private static ReloadingScript results_dialog_component = new ReloadingScript("wwwroot/components/search-results-dialog.sbnhtml");
private async Task<string> MoreAsync(string id,SearchResultObj o)
{
if(o.expires <= Program.NowEpoch)
{
return """<div class="row"><div class="max"></div><div class="min"><h1>The search has expired</h1></div><div class="max"></div></div>""";
}
if(await o.enumerator.MoveNextAsync())
{
var tytd = Program.Config ?? new AuthConfig();
List<SearchResult> results = new List<SearchResult>();
foreach(var ent in o.enumerator.Current.Items)
if(ent is VideoSearchResult item)
results.Add(new SearchResult(tytd.TYTDUrl, item.Title, item.Id));
return o.isDialog ? results_dialog_component.Render(new{results, moreurl = HttpUtility.HtmlAttributeEncode($"/search_more?id={id}")}) : results_component.Render(new {
results, moreurl = HttpUtility.HtmlAttributeEncode($"/search_more?id={id}")
});
}
return """<div class="row"><div class="max"></div><div class="min"><h1>There are no more results</h1></div><div class="max"></div></div>""";
}
public async Task<string> MoreAsync(string id)
{
if(ents.TryGetValue(id, out var v))
{
return await MoreAsync(id,v);
}
return """<div class="row"><div class="max"></div><div class="min"><h1>There are no more results</h1></div><div class="max"></div></div>""";
}
public async Task EventAsync()
{
try {
List<string> expired = new List<string>();
var time = Program.NowEpoch;
foreach(var item in ents)
{
if(item.Value.expires <= time)
expired.Add(item.Key);
}
foreach(var expire in expired)
ents.Remove(expire,out var e);
}catch{
}
await Task.CompletedTask;
}
}
public static partial class Components {
public static async Task<string> SearchMoreAsync(ServerContext ctx)
{
if(ctx.QueryParams.TryGetFirst("id",out var id))
{
return await Program.SearchResultHandler.MoreAsync(id);
}
return """<div class="row"><div class="max"></div><div class="min"><h1>No continuation id provided</h1></div><div class="max"></div></div>""";
}
}
public static partial class Pages {
private static ReloadingScript search_page = new ReloadingScript("wwwroot/pages/search.sbnhtml");
public static async Task<string> SearchAsync(ServerContext ctx)
{
string q="";
string body = "";
if(ctx.QueryParams.TryGetFirst("q",out q))
{
body = await Program.SearchResultHandler.SearchAsync(q,false);
}
return Components.Shell($"Search {q}",search_page.Render(new {body,q = HttpUtility.HtmlAttributeEncode(q)}),Program.DefaultLinks(1));
}
public static async Task<string> SearchDialogAsync(ServerContext ctx)
{
string q="";
string body = "";
if(ctx.QueryParams.TryGetFirst("q",out q))
{
body = await Program.SearchResultHandler.SearchAsync(q,true);
}
return body;
}
}