Improve NoteService.CreateNote
This commit is contained in:
parent
ffe8408738
commit
0dc1533b75
7 changed files with 144 additions and 6 deletions
|
@ -1,7 +1,17 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Extensions;
|
||||
|
||||
public static class StringExtensions {
|
||||
public static string Truncate(this string target, int maxLength) {
|
||||
return target[..Math.Min(target.Length, maxLength)];
|
||||
}
|
||||
|
||||
public static string ToPunycode(this string target) {
|
||||
return new IdnMapping().GetAscii(target);
|
||||
}
|
||||
|
||||
public static string FromPunycode(this string target) {
|
||||
return new IdnMapping().GetUnicode(target);
|
||||
}
|
||||
}
|
|
@ -14,8 +14,8 @@ public class ASNote : ASObject {
|
|||
public string? Content { get; set; }
|
||||
|
||||
[J("https://www.w3.org/ns/activitystreams#url")]
|
||||
[JC(typeof(LDIdObjectConverter))]
|
||||
public LDIdObject? Url { get; set; }
|
||||
[JC(typeof(ASLinkConverter))]
|
||||
public ASLink? Url { get; set; }
|
||||
|
||||
[J("https://www.w3.org/ns/activitystreams#sensitive")]
|
||||
[JC(typeof(VC))]
|
||||
|
|
98
Iceshrimp.Backend/Core/Helpers/MfmHelpers.cs
Normal file
98
Iceshrimp.Backend/Core/Helpers/MfmHelpers.cs
Normal file
|
@ -0,0 +1,98 @@
|
|||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using AngleSharp.Dom;
|
||||
using AngleSharp.Html.Parser;
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Helpers;
|
||||
|
||||
public static class MfmHelpers {
|
||||
public static async Task<string> FromHtml(string? html) {
|
||||
if (html == null) return "";
|
||||
|
||||
// Ensure compatibility with AP servers that send both <br> as well as newlines
|
||||
var regex = new Regex(@"<br\s?\/?>\r?\n", RegexOptions.IgnoreCase);
|
||||
html = regex.Replace(html, "\n");
|
||||
|
||||
var dom = await new HtmlParser().ParseDocumentAsync(html);
|
||||
if (dom.Body == null) return "";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
dom.Body.ChildNodes.Select(ParseNode).ToList().ForEach(s => sb.Append(s));
|
||||
return sb.ToString().Trim();
|
||||
}
|
||||
|
||||
private static string? ParseNode(INode node) {
|
||||
if (node.NodeType is NodeType.Text)
|
||||
return node.TextContent;
|
||||
if (node.NodeType is NodeType.Comment or NodeType.Document)
|
||||
return null;
|
||||
|
||||
switch (node.NodeName) {
|
||||
case "BR": {
|
||||
return "\n";
|
||||
}
|
||||
case "A": {
|
||||
//TODO: implement parsing of links & mentions (automatically correct split domain mentions for the latter)
|
||||
return null;
|
||||
}
|
||||
case "H1": {
|
||||
return $"【{ParseChildren(node)}】\n";
|
||||
}
|
||||
case "B":
|
||||
case "STRONG": {
|
||||
return $"**{ParseChildren(node)}**";
|
||||
}
|
||||
case "SMALL": {
|
||||
return $"<small>{ParseChildren(node)}</small>";
|
||||
}
|
||||
case "S":
|
||||
case "DEL": {
|
||||
return $"~~{ParseChildren(node)}~~";
|
||||
}
|
||||
case "I":
|
||||
case "EM": {
|
||||
return $"<i>{ParseChildren(node)}</i>";
|
||||
}
|
||||
case "PRE": {
|
||||
return node.ChildNodes is [{ NodeName: "CODE" }]
|
||||
? $"\n```\n{string.Join(null, node.ChildNodes[0].TextContent)}\n```\n"
|
||||
: ParseChildren(node);
|
||||
}
|
||||
case "CODE": {
|
||||
return $"`{ParseChildren(node)}`";
|
||||
}
|
||||
case "BLOCKQUOTE": {
|
||||
return node.TextContent.Length > 0
|
||||
? $"\n> {string.Join("\n> ", node.TextContent.Split("\n"))}"
|
||||
: null;
|
||||
}
|
||||
|
||||
case "P":
|
||||
case "H2":
|
||||
case "H3":
|
||||
case "H4":
|
||||
case "H5":
|
||||
case "H6": {
|
||||
return $"\n\n{ParseChildren(node)}";
|
||||
}
|
||||
|
||||
case "DIV":
|
||||
case "HEADER":
|
||||
case "FOOTER":
|
||||
case "ARTICLE":
|
||||
case "LI":
|
||||
case "DT":
|
||||
case "DD": {
|
||||
return $"\n{ParseChildren(node)}";
|
||||
}
|
||||
|
||||
default: {
|
||||
return ParseChildren(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ParseChildren(INode node) {
|
||||
return string.Join(null, node.ChildNodes.Select(ParseNode));
|
||||
}
|
||||
}
|
|
@ -69,6 +69,14 @@ public class GracefulException(HttpStatusCode statusCode, string error, string m
|
|||
|
||||
public GracefulException(string message, string? details = null) :
|
||||
this(HttpStatusCode.InternalServerError, HttpStatusCode.InternalServerError.ToString(), message, details) { }
|
||||
|
||||
public static GracefulException UnprocessableEntity(string message, string? details = null) {
|
||||
return new GracefulException(HttpStatusCode.UnprocessableEntity, message, details);
|
||||
}
|
||||
|
||||
public static GracefulException Forbidden(string message, string? details = null) {
|
||||
return new GracefulException(HttpStatusCode.Forbidden, message, details);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ExceptionVerbosity {
|
||||
|
|
|
@ -3,6 +3,7 @@ using Iceshrimp.Backend.Core.Database.Tables;
|
|||
using Iceshrimp.Backend.Core.Federation.ActivityPub;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||
using Iceshrimp.Backend.Core.Helpers;
|
||||
using Iceshrimp.Backend.Core.Middleware;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Services;
|
||||
|
@ -19,16 +20,34 @@ public class NoteService(ILogger<NoteService> logger, DatabaseContext db, UserRe
|
|||
var user = await userResolver.Resolve(actor.Id);
|
||||
logger.LogDebug("Resolved user to {userId}", user.Id);
|
||||
|
||||
// Validate note
|
||||
if (note.AttributedTo is not { Count: 1 } || note.AttributedTo[0].Id != user.Uri)
|
||||
throw GracefulException.UnprocessableEntity("User.Uri doesn't match Note.AttributedTo");
|
||||
if (user.Uri == null)
|
||||
throw GracefulException.UnprocessableEntity("User.Uri is null");
|
||||
if (new Uri(note.Id).IdnHost != new Uri(user.Uri).IdnHost)
|
||||
throw GracefulException.UnprocessableEntity("User.Uri host doesn't match Note.Id host");
|
||||
if (!note.Id.StartsWith("https://"))
|
||||
throw GracefulException.UnprocessableEntity("Note.Id schema is invalid");
|
||||
if (note.Url?.Link != null && !note.Url.Link.StartsWith("https://"))
|
||||
throw GracefulException.UnprocessableEntity("Note.Url schema is invalid");
|
||||
if (note.PublishedAt is null or { Year: < 2007 } || note.PublishedAt > DateTime.Now + TimeSpan.FromDays(3))
|
||||
throw GracefulException.UnprocessableEntity("Note.PublishedAt is nonsensical");
|
||||
if (user.IsSuspended)
|
||||
throw GracefulException.Forbidden("User is suspended");
|
||||
|
||||
//TODO: validate AP object type
|
||||
//TODO: parse note visibility
|
||||
//TODO: resolve anything related to the note as well (reply thread, attachments, emoji, etc)
|
||||
|
||||
var dbNote = new Note {
|
||||
Id = IdHelpers.GenerateSlowflakeId(),
|
||||
Uri = note.Id,
|
||||
Url = note.Url?.Id, //FIXME: this doesn't seem to work yet
|
||||
Text = note.MkContent ?? note.Content, //TODO: html-to-mfm
|
||||
Url = note.Url?.Id, //FIXME: this doesn't seem to work yet
|
||||
Text = note.MkContent ?? await MfmHelpers.FromHtml(note.Content),
|
||||
UserId = user.Id,
|
||||
CreatedAt = note.PublishedAt?.ToUniversalTime() ??
|
||||
throw new Exception("Missing or invalid PublishedAt field"),
|
||||
throw GracefulException.UnprocessableEntity("Missing or invalid PublishedAt field"),
|
||||
UserHost = user.Host,
|
||||
Visibility = Note.NoteVisibility.Public //TODO: parse to & cc fields
|
||||
};
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityPub;
|
||||
using Iceshrimp.Backend.Core.Helpers;
|
||||
using Iceshrimp.Backend.Core.Middleware;
|
||||
|
@ -16,7 +18,7 @@ public class UserService(ILogger<UserService> logger, DatabaseContext db, APFetc
|
|||
var split = acct[5..].Split('@');
|
||||
if (split.Length != 2) throw new GracefulException(HttpStatusCode.BadRequest, "Invalid query");
|
||||
|
||||
return (split[0], split[1]);
|
||||
return (split[0], split[1].ToPunycode());
|
||||
}
|
||||
|
||||
public Task<User?> GetUserFromQuery(string query) {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="1.1.0" />
|
||||
<PackageReference Include="Asp.Versioning.Http" Version="8.0.0"/>
|
||||
<PackageReference Include="cuid.net" Version="5.0.2"/>
|
||||
<PackageReference Include="dotNetRdf.Core" Version="3.2.1-dev"/>
|
||||
|
|
Loading…
Add table
Reference in a new issue