[backend/federation] Get favicon and icon for remote instances

This commit is contained in:
pancakes 2025-03-27 17:42:05 +10:00
parent 6975fa9495
commit cfd7d62777
No known key found for this signature in database

View file

@ -1,3 +1,4 @@
using AngleSharp.Html.Parser;
using AsyncKeyedLock; using AsyncKeyedLock;
using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Database.Tables;
@ -63,14 +64,15 @@ public class InstanceService(
{ {
instance.InfoUpdatedAt = DateTime.UtcNow; instance.InfoUpdatedAt = DateTime.UtcNow;
var nodeinfo = await GetNodeInfoAsync(webDomain); var nodeinfo = await GetNodeInfoAsync(webDomain);
var icons = await GetIconsAsync(webDomain);
if (nodeinfo != null) if (nodeinfo != null)
{ {
instance.Name = nodeinfo.Metadata?.NodeName; instance.Name = nodeinfo.Metadata?.NodeName;
instance.Description = nodeinfo.Metadata?.NodeDescription; instance.Description = nodeinfo.Metadata?.NodeDescription;
//instance.FaviconUrl = TODO, instance.FaviconUrl = icons.FaviconUrl;
//instance.FollowersCount = TODO, //instance.FollowersCount = TODO,
//instance.FollowingCount = TODO, //instance.FollowingCount = TODO,
//instance.IconUrl = TODO, instance.IconUrl = icons.IconUrl;
instance.MaintainerName = nodeinfo.Metadata?.Maintainer?.Name; instance.MaintainerName = nodeinfo.Metadata?.Maintainer?.Name;
instance.MaintainerEmail = nodeinfo.Metadata?.Maintainer?.Email; instance.MaintainerEmail = nodeinfo.Metadata?.Maintainer?.Email;
instance.OpenRegistrations = nodeinfo.OpenRegistrations; instance.OpenRegistrations = nodeinfo.OpenRegistrations;
@ -92,6 +94,65 @@ public class InstanceService(
return await GetUpdatedInstanceMetadataAsync(user.Host, new Uri(user.Uri).Host); return await GetUpdatedInstanceMetadataAsync(user.Host, new Uri(user.Uri).Host);
} }
private async Task<(string? FaviconUrl, string? IconUrl)> GetIconsAsync(string webDomain)
{
try
{
const int maxLength = 1_000_000;
var res = await httpClient.GetAsync($"https://{webDomain}", HttpCompletionOption.ResponseHeadersRead);
if (res is not
{
IsSuccessStatusCode: true,
Content.Headers: { ContentType.MediaType: "text/html", ContentLength: null or <= maxLength }
})
return (null, null);
var contentLength = res.Content.Headers.ContentLength;
var stream = await res.Content.ReadAsStreamAsync()
.ContinueWithResult(p => p.GetSafeStreamOrNullAsync(maxLength, contentLength));
if (stream == Stream.Null) return (null, null);
var document = await new HtmlParser().ParseDocumentAsync(stream);
var faviconUrl = document.Head?.Children.Last(e => e.NodeName.ToLower() == "link"
&& (e.GetAttribute("rel")
?.Contains("icon")
?? false))
.GetAttribute("href");
if (faviconUrl == null)
{
var favicon = await httpClient.GetAsync($"https://{webDomain}/favicon.ico");
if (favicon.IsSuccessStatusCode) faviconUrl = $"https://{webDomain}/favicon.ico";
}
var iconUrl = document.Head?.Children.Last(e => e.NodeName.ToLower() == "link"
&& (e.GetAttribute("rel")
?.Contains("apple-touch-icon-precomposed")
?? false)
|| (e.GetAttribute("rel")
?.Contains("apple-touch-icon")
?? false)
|| (e.GetAttribute("rel")
?.Contains("icon")
?? false))
.GetAttribute("href");
var baseUri = new Uri($"https://{webDomain}");
return (
faviconUrl != null ? new Uri(baseUri, faviconUrl).ToString() : null,
iconUrl != null ? new Uri(baseUri, iconUrl).ToString() : null
);
}
catch (Exception e)
{
return (null, null);
}
}
private async Task<NodeInfoResponse?> GetNodeInfoAsync(string webDomain) private async Task<NodeInfoResponse?> GetNodeInfoAsync(string webDomain)
{ {
try try