149 lines
5.2 KiB
C#
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;
|
|
}
|
|
} |