First commit
This commit is contained in:
+149
@@ -0,0 +1,149 @@
|
||||
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());
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user