using System.Net; using System.Text.RegularExpressions; using decePubClient.Models.Mastodon; using Microsoft.AspNetCore.Components; namespace decePubClient.Helpers { /// /// A post's or an account's custom emoji (`:shortcode:` among its emojis) shown as their pictures. The shortcode /// stays as the picture's alt text, so it is what a screen reader reads and what is copied. Only shortcodes the /// server listed for that post or account are replaced, and only with an http(s) picture. /// public static partial class CustomEmojis { [GeneratedRegex(@":([A-Za-z0-9_+-]{1,64}):")] private static partial Regex Shortcode(); [GeneratedRegex(@"(<[^>]*>)")] private static partial Regex Tag(); /// Plain text (a name, a content warning): escaped, then its shortcodes as pictures. public static MarkupString Text(string text, IReadOnlyCollection emojis) => new(Replace(WebUtility.HtmlEncode(text ?? string.Empty), emojis)); /// HTML the server sanitized: the shortcodes in its text, never inside a tag. public static string Html(string html, IReadOnlyCollection emojis) => emojis.Count == 0 || string.IsNullOrEmpty(html) ? html : string.Concat(Tag().Split(html).Select(part => part.StartsWith('<') ? part : Replace(part, emojis))); static string Replace(string text, IReadOnlyCollection emojis) { if (emojis.Count == 0 || !text.Contains(':')) return text; var known = new Dictionary(StringComparer.Ordinal); foreach (var emoji in emojis) if (!string.IsNullOrEmpty(emoji.Shortcode) && Uri.TryCreate(emoji.Url, UriKind.Absolute, out var url) && url.Scheme is "https" or "http") known.TryAdd(emoji.Shortcode, url.AbsoluteUri); return Shortcode().Replace(text, match => known.TryGetValue(match.Groups[1].Value, out var url) ? $"\"{match.Value}\"" : match.Value); } } }