diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs
index 70533f5b..22c66f3e 100644
--- a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs
+++ b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/NoteRenderer.cs
@@ -81,7 +81,7 @@ public class NoteRenderer(
? await GetAttachmentsAsync([note])
: [..data.Attachments.Where(p => note.FileIds.Contains(p.Id))];
- if (user == null && security.Value.PublicPreview == Enums.PublicPreview.RestrictedNoMedia) //TODO
+ if (user == null && security.Value.PublicPreview == Enums.PublicPreview.RestrictedNoMedia)
attachments = [];
var reactions = data?.Reactions == null
@@ -103,9 +103,7 @@ public class NoteRenderer(
note.Renote == null && ((note.RenoteId != null && recurse > 0) || note.RenoteUri != null);
var sensitive = note.Cw != null || attachments.Any(p => p.Sensitive);
-
- // TODO: canDisplayInlineMedia oauth_token flag that marks all media as "other" so they end up as links
- // (and doesn't remove the attachments)
+
var inlineMedia = attachments.Select(p => new MfmInlineMedia(p.Type switch
{
AttachmentType.Audio => MfmInlineMedia.MediaType.Audio,
@@ -113,9 +111,10 @@ public class NoteRenderer(
AttachmentType.Image or AttachmentType.Gif => MfmInlineMedia.MediaType.Image,
_ => MfmInlineMedia.MediaType.Other
}, p.RemoteUrl ?? p.Url, p.Description)).ToList();
-
+
string? content = null;
if (data?.Source != true)
+ {
if (text != null || quoteUri != null || quoteInaccessible || replyInaccessible)
{
(content, inlineMedia) = await mfmConverter.ToHtmlAsync(text ?? "", mentionedUsers, note.UserHost, quoteUri,
@@ -127,7 +126,8 @@ public class NoteRenderer(
{
content = "";
}
-
+ }
+
var account = data?.Accounts?.FirstOrDefault(p => p.Id == note.UserId) ??
await userRenderer.RenderAsync(note.User, user);
@@ -215,9 +215,7 @@ public class NoteRenderer(
foreach (var edit in edits)
{
var files = attachments.Where(p => edit.FileIds.Contains(p.Id)).ToList();
-
- // TODO: canDisplayInlineMedia oauth_token flag that marks all media as "other" so they end up as links
- // (and doesn't remove the attachments)
+
var inlineMedia = files.Select(p => new MfmInlineMedia(p.Type switch
{
AttachmentType.Audio => MfmInlineMedia.MediaType.Audio,
@@ -228,7 +226,7 @@ public class NoteRenderer(
(var content, inlineMedia) = await mfmConverter.ToHtmlAsync(edit.Text ?? "", mentionedUsers, note.UserHost, media: inlineMedia);
files.RemoveAll(attachment => inlineMedia.Any(inline => inline.Src == (attachment.RemoteUrl ?? attachment.Url)));
-
+
var entry = new StatusEdit
{
Account = account,
diff --git a/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta4/20241128193322_IndexDriveFileUrl.cs b/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta4/20241128193322_IndexDriveFileUrl.cs
index 761fd656..7541d878 100644
--- a/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta4/20241128193322_IndexDriveFileUrl.cs
+++ b/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta4/20241128193322_IndexDriveFileUrl.cs
@@ -13,13 +13,13 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
///
protected override void Up(MigrationBuilder migrationBuilder)
{
- migrationBuilder.Sql("""CREATE INDEX IX_drive_file_accessUrl ON drive_file (COALESCE("webpublicUrl", url));""");
+ migrationBuilder.Sql("""CREATE INDEX "IX_drive_file_accessUrl" ON "drive_file" (COALESCE("webpublicUrl", url));""");
}
///
protected override void Down(MigrationBuilder migrationBuilder)
{
- migrationBuilder.Sql("""DROP INDEX IX_drive_file_accessUrl;""");
+ migrationBuilder.Sql("""DROP INDEX "IX_drive_file_accessUrl";""");
}
}
}
diff --git a/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta5/20241203204110_AddInlineHtmlFeatureFlag.cs b/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta5/20241203204110_AddInlineHtmlFeatureFlag.cs
index 80e8973c..4726a242 100644
--- a/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta5/20241203204110_AddInlineHtmlFeatureFlag.cs
+++ b/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta5/20241203204110_AddInlineHtmlFeatureFlag.cs
@@ -21,7 +21,7 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
defaultValue: false);
// automatically enable inline media for pleroma clients, as pleroma itself does support it
- migrationBuilder.Sql("""UPDATE oauth_token SET "supportsInlineMedia"=TRUE WHERE "isPleroma"=TRUE;""");
+ migrationBuilder.Sql("""UPDATE "oauth_token" SET "supportsInlineMedia"=TRUE WHERE "isPleroma"=TRUE;""");
}
///
diff --git a/Iceshrimp.Backend/Core/Federation/ActivityPub/NoteRenderer.cs b/Iceshrimp.Backend/Core/Federation/ActivityPub/NoteRenderer.cs
index 91f23fa3..5df80bdb 100644
--- a/Iceshrimp.Backend/Core/Federation/ActivityPub/NoteRenderer.cs
+++ b/Iceshrimp.Backend/Core/Federation/ActivityPub/NoteRenderer.cs
@@ -25,7 +25,7 @@ public class NoteRenderer(
{
return new ASNote(false) { Id = note.Uri ?? note.GetPublicUri(config.Value) };
}
-
+
public async Task RenderAsync(Note note, List? mentions = null)
{
if (note.IsPureRenote)
@@ -125,7 +125,7 @@ public class NoteRenderer(
})
.Cast()
.ToList();
-
+
var inlineMedia = driveFiles?.Select(p => new MfmInlineMedia(MfmInlineMedia.GetType(p.Type), p.AccessUrl, p.Comment))
.ToList();
diff --git a/Iceshrimp.Backend/Core/Helpers/LibMfm/Conversion/MfmConverter.cs b/Iceshrimp.Backend/Core/Helpers/LibMfm/Conversion/MfmConverter.cs
index 5860da82..3e3736b4 100644
--- a/Iceshrimp.Backend/Core/Helpers/LibMfm/Conversion/MfmConverter.cs
+++ b/Iceshrimp.Backend/Core/Helpers/LibMfm/Conversion/MfmConverter.cs
@@ -14,7 +14,7 @@ using HtmlParser = AngleSharp.Html.Parser.HtmlParser;
namespace Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
-public record MfmInlineMedia(MfmInlineMedia.MediaType Type, string Src, string? Alt)
+public readonly record struct MfmInlineMedia(MfmInlineMedia.MediaType Type, string Src, string? Alt)
{
public enum MediaType
{
@@ -23,7 +23,7 @@ public record MfmInlineMedia(MfmInlineMedia.MediaType Type, string Src, string?
Video,
Audio
}
-
+
public static MediaType GetType(string mime)
{
if (mime.StartsWith("image/")) return MediaType.Image;
@@ -34,6 +34,12 @@ public record MfmInlineMedia(MfmInlineMedia.MediaType Type, string Src, string?
}
}
+/// Resulting data after HTML to MFM conversion
+public readonly record struct HtmlMfmData(string Mfm, List InlineMedia);
+
+/// Resulting data after MFM to HTML conversion
+public readonly record struct MfmHtmlData(string Html, List InlineMedia);
+
public class MfmConverter(
IOptions config
) : ISingletonService
@@ -41,10 +47,10 @@ public class MfmConverter(
public AsyncLocal SupportsHtmlFormatting { get; } = new();
public AsyncLocal SupportsInlineMedia { get; } = new();
- public static async Task<(string Mfm, List InlineMedia)> FromHtmlAsync(string? html, List? mentions = null)
+ public static async Task FromHtmlAsync(string? html, List? mentions = null)
{
var media = new List();
- if (html == null) return ("", media);
+ if (html == null) return new HtmlMfmData("", media);
// Ensure compatibility with AP servers that send both
as well as newlines
var regex = new Regex(@"
\r?\n", RegexOptions.IgnoreCase);
@@ -54,12 +60,12 @@ public class MfmConverter(
html = html.Replace("\u00A0", " ");
var dom = await new HtmlParser().ParseDocumentAsync(html);
- if (dom.Body == null) return ("", media);
+ if (dom.Body == null) return new HtmlMfmData("", media);
var sb = new StringBuilder();
var parser = new MfmHtmlParser(mentions ?? [], media);
dom.Body.ChildNodes.Select(parser.ParseNode).ToList().ForEach(s => sb.Append(s));
- return (sb.ToString().Trim(), media);
+ return new HtmlMfmData(sb.ToString().Trim(), media);
}
public static async Task> ExtractMentionsFromHtmlAsync(string? html)
@@ -80,7 +86,7 @@ public class MfmConverter(
return parser.Mentions;
}
- public async Task<(string Html, List InlineMedia)> ToHtmlAsync(
+ public async Task ToHtmlAsync(
IEnumerable nodes, List mentions, string? host, string? quoteUri = null,
bool quoteInaccessible = false, bool replyInaccessible = false, string rootElement = "p",
List? emoji = null, List? media = null
@@ -109,7 +115,7 @@ public class MfmConverter(
}
var usedMedia = new List();
- foreach (var node in nodeList) element.AppendNodes(FromMfmNode(document, node, mentions, host, ref usedMedia, emoji, media));
+ foreach (var node in nodeList) element.AppendNodes(FromMfmNode(document, node, mentions, host, usedMedia, emoji, media));
if (quoteUri != null)
{
@@ -149,10 +155,10 @@ public class MfmConverter(
await using var sw = new StringWriter();
await element.ToHtmlAsync(sw);
- return (sw.ToString(), usedMedia);
+ return new MfmHtmlData(sw.ToString(), usedMedia);
}
- public async Task<(string Html, List InlineMedia)> ToHtmlAsync(
+ public async Task ToHtmlAsync(
string mfm, List mentions, string? host, string? quoteUri = null,
bool quoteInaccessible = false, bool replyInaccessible = false, string rootElement = "p",
List? emoji = null, List? media = null
@@ -164,19 +170,19 @@ public class MfmConverter(
}
private INode FromMfmNode(
- IDocument document, IMfmNode node, List mentions, string? host, ref List usedMedia,
+ IDocument document, IMfmNode node, List mentions, string? host, List usedMedia,
List? emoji = null, List? media = null
)
{
switch (node)
{
- case MfmFnNode { Name: "media" } fn when media != null:
+ case MfmFnNode { Name: "media" } fn when media is { Count: > 0 }:
{
var urlNode = fn.Children.FirstOrDefault();
if (urlNode is MfmUrlNode url)
{
- var current = media.FirstOrDefault(m => m.Src == url.Url);
- if (current != null)
+ MfmInlineMedia? maybeCurrent = media.FirstOrDefault(m => m.Src == url.Url);
+ if (maybeCurrent is { } current)
{
usedMedia.Add(current);
@@ -187,16 +193,16 @@ public class MfmConverter(
if (current.Type == MfmInlineMedia.MediaType.Other)
el.SetAttribute("download", "true");
-
+
var icon = current.Type switch
{
MfmInlineMedia.MediaType.Image => "\ud83d\uddbc\ufe0f", // framed picture emoji
MfmInlineMedia.MediaType.Video => "\ud83c\udfac", // clapperboard emoji
MfmInlineMedia.MediaType.Audio => "\ud83c\udfb5", // music note emoji
- _ => "\ud83d\udcbe", // floppy disk emoji
+ _ => "\ud83d\udcbe", // floppy disk emoji
};
- el.TextContent = $"[{icon} {current.Alt ?? current.Src}]";
+ el.TextContent = $"[{icon} {current.Alt ?? current.Src}]";
return el;
}
else
@@ -220,7 +226,7 @@ public class MfmConverter(
{
var el = CreateInlineFormattingElement(document, "i");
AddHtmlMarkup(document, el, "*");
- AppendChildren(el, document, node, mentions, host, ref usedMedia);
+ AppendChildren(el, document, node, mentions, host, usedMedia);
AddHtmlMarkup(document, el, "*");
return el;
}
@@ -229,21 +235,21 @@ public class MfmConverter(
{
var el = CreateInlineFormattingElement(document, "b");
AddHtmlMarkup(document, el, "**");
- AppendChildren(el, document, node, mentions, host, ref usedMedia);
+ AppendChildren(el, document, node, mentions, host, usedMedia);
AddHtmlMarkup(document, el, "**");
return el;
}
case MfmSmallNode:
{
var el = document.CreateElement("small");
- AppendChildren(el, document, node, mentions, host, ref usedMedia);
+ AppendChildren(el, document, node, mentions, host, usedMedia);
return el;
}
case MfmStrikeNode:
{
var el = CreateInlineFormattingElement(document, "del");
AddHtmlMarkup(document, el, "~~");
- AppendChildren(el, document, node, mentions, host, ref usedMedia);
+ AppendChildren(el, document, node, mentions, host, usedMedia);
AddHtmlMarkup(document, el, "~~");
return el;
}
@@ -252,7 +258,7 @@ public class MfmConverter(
{
var el = CreateInlineFormattingElement(document, "i");
AddHtmlMarkup(document, el, "*");
- AppendChildren(el, document, node, mentions, host, ref usedMedia);
+ AppendChildren(el, document, node, mentions, host, usedMedia);
AddHtmlMarkup(document, el, "*");
return el;
}
@@ -267,7 +273,7 @@ public class MfmConverter(
case MfmCenterNode:
{
var el = document.CreateElement("div");
- AppendChildren(el, document, node, mentions, host, ref usedMedia);
+ AppendChildren(el, document, node, mentions, host, usedMedia);
return el;
}
case MfmEmojiCodeNode emojiCodeNode:
@@ -357,7 +363,7 @@ public class MfmConverter(
{
var el = CreateInlineFormattingElement(document, "blockquote");
AddHtmlMarkup(document, el, "> ");
- AppendChildren(el, document, node, mentions, host, ref usedMedia);
+ AppendChildren(el, document, node, mentions, host, usedMedia);
el.AppendChild(document.CreateElement("br"));
return el;
}
@@ -391,7 +397,7 @@ public class MfmConverter(
case MfmPlainNode:
{
var el = document.CreateElement("span");
- AppendChildren(el, document, node, mentions, host, ref usedMedia);
+ AppendChildren(el, document, node, mentions, host, usedMedia);
return el;
}
default:
@@ -403,11 +409,11 @@ public class MfmConverter(
private void AppendChildren(
INode element, IDocument document, IMfmNode parent,
- List mentions, string? host, ref List usedMedia,
+ List mentions, string? host, List usedMedia,
List? emoji = null, List? media = null
)
{
- foreach (var node in parent.Children) element.AppendNodes(FromMfmNode(document, node, mentions, host, ref usedMedia, emoji, media));
+ foreach (var node in parent.Children) element.AppendNodes(FromMfmNode(document, node, mentions, host, usedMedia, emoji, media));
}
private IElement CreateInlineFormattingElement(IDocument document, string name)
diff --git a/Iceshrimp.Backend/Core/Helpers/LibMfm/Parsing/HtmlParser.cs b/Iceshrimp.Backend/Core/Helpers/LibMfm/Parsing/HtmlParser.cs
index 6dbb9ccb..b702454c 100644
--- a/Iceshrimp.Backend/Core/Helpers/LibMfm/Parsing/HtmlParser.cs
+++ b/Iceshrimp.Backend/Core/Helpers/LibMfm/Parsing/HtmlParser.cs
@@ -79,7 +79,7 @@ internal class HtmlParser(IEnumerable mentions, ICollection<
? $"\n> {string.Join("\n> ", node.TextContent.Split("\n"))}"
: null;
}
-
+
case "VIDEO":
case "AUDIO":
case "IMG":
@@ -87,9 +87,9 @@ internal class HtmlParser(IEnumerable mentions, ICollection<
if (node is not HtmlElement el) return node.TextContent;
var src = el.GetAttribute("src");
- if (!Uri.IsWellFormedUriString(src, UriKind.Absolute))
+ if (src == null || !Uri.TryCreate(src, UriKind.Absolute, out var uri) && uri is { Scheme: "http" or "https" })
return node.TextContent;
-
+
var alt = el.GetAttribute("alt") ?? el.GetAttribute("title");
var type = node.NodeName switch
@@ -99,10 +99,10 @@ internal class HtmlParser(IEnumerable mentions, ICollection<
"IMG" => MfmInlineMedia.MediaType.Image,
_ => MfmInlineMedia.MediaType.Other,
};
-
+
media.Add(new MfmInlineMedia(type, src, alt));
- return $"$[media {src} ]";
+ return $"$[media {src}]";
}
case "P":
diff --git a/Iceshrimp.Backend/Core/Services/NoteService.cs b/Iceshrimp.Backend/Core/Services/NoteService.cs
index d3ad5b1a..6ff49ebd 100644
--- a/Iceshrimp.Backend/Core/Services/NoteService.cs
+++ b/Iceshrimp.Backend/Core/Services/NoteService.cs
@@ -494,18 +494,15 @@ public class NoteService(
if (node is MfmFnNode { Name: "media" } fn)
{
var urlNode = fn.Children.FirstOrDefault();
- if (urlNode is MfmUrlNode url)
- {
- urls.Add(url.Url);
- }
+ if (urlNode is MfmUrlNode url) urls.Add(url.Url);
}
-
+
urls.AddRange(GetInlineMediaUrls(node.Children));
}
return urls;
}
-
+
public async Task UpdateNoteAsync(NoteUpdateData data)
{
@@ -628,7 +625,7 @@ public class NoteService(
}
var attachments = data.Attachments?.ToList() ?? [];
-
+
var inlineMediaUrls = nodes != null ? GetInlineMediaUrls(nodes) : [];
var newMediaUrls = data.Attachments?.Select(p => p.Url) ?? [];
var missingUrls = inlineMediaUrls.Except(newMediaUrls).ToArray();
@@ -1075,9 +1072,7 @@ public class NoteService(
List? htmlInlineMedia = null;
if (text == null)
- {
(text, htmlInlineMedia) = await MfmConverter.FromHtmlAsync(note.Content, mentionData.Mentions);
- }
var cw = note.Summary;