[backend/masto-api] Detect quotes on note create
This commit is contained in:
parent
9cd99eb244
commit
7a7b3f81b3
5 changed files with 46 additions and 14 deletions
|
@ -30,8 +30,12 @@ public class NoteRenderer(
|
||||||
? await RenderAsync(note.Renote, user, accounts, mentions, attachments, likeCounts, likedNotes, --recurse)
|
? await RenderAsync(note.Renote, user, accounts, mentions, attachments, likeCounts, likedNotes, --recurse)
|
||||||
: null;
|
: null;
|
||||||
var text = note.Text;
|
var text = note.Text;
|
||||||
if (quote != null && text != null && !text.EndsWith(quote.Url) && !text.EndsWith(quote.Uri))
|
if (note is { Renote: not null, IsQuote: true } && text != null)
|
||||||
text += $"\n\nRE: {quote.Url}"; //TODO: render as inline quote
|
{
|
||||||
|
var quoteUri = note.Renote?.Url ?? note.Renote?.Uri ?? note.Renote?.GetPublicUriOrNull(config.Value);
|
||||||
|
if (quoteUri != null)
|
||||||
|
text += $"\n\nRE: {quoteUri}"; //TODO: render as inline quote
|
||||||
|
}
|
||||||
|
|
||||||
var likeCount = likeCounts?.GetValueOrDefault(note.Id, 0) ?? await db.NoteLikes.CountAsync(p => p.Note == note);
|
var likeCount = likeCounts?.GetValueOrDefault(note.Id, 0) ?? await db.NoteLikes.CountAsync(p => p.Note == note);
|
||||||
var liked = likedNotes?.Contains(note.Id) ?? await db.NoteLikes.AnyAsync(p => p.Note == note && p.User == user);
|
var liked = likedNotes?.Contains(note.Id) ?? await db.NoteLikes.AnyAsync(p => p.Note == note && p.User == user);
|
||||||
|
|
|
@ -3,6 +3,7 @@ using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
||||||
using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
|
using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
|
||||||
using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
|
using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
|
||||||
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
||||||
|
using Iceshrimp.Backend.Core.Configuration;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Extensions;
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
|
@ -12,6 +13,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.RateLimiting;
|
using Microsoft.AspNetCore.RateLimiting;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Controllers.Mastodon;
|
namespace Iceshrimp.Backend.Controllers.Mastodon;
|
||||||
|
|
||||||
|
@ -25,7 +27,8 @@ public class StatusController(
|
||||||
DatabaseContext db,
|
DatabaseContext db,
|
||||||
NoteRenderer noteRenderer,
|
NoteRenderer noteRenderer,
|
||||||
NoteService noteSvc,
|
NoteService noteSvc,
|
||||||
IDistributedCache cache
|
IDistributedCache cache,
|
||||||
|
IOptions<Config.InstanceSection> config
|
||||||
) : ControllerBase
|
) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
|
@ -67,6 +70,7 @@ public class StatusController(
|
||||||
.RenderAllForMastodonAsync(noteRenderer, user);
|
.RenderAllForMastodonAsync(noteRenderer, user);
|
||||||
|
|
||||||
var descendants = await db.NoteDescendants(id, maxDepth, maxDescendants)
|
var descendants = await db.NoteDescendants(id, maxDepth, maxDescendants)
|
||||||
|
.Where(p => !p.IsQuote || p.RenoteId != id)
|
||||||
.IncludeCommonProperties()
|
.IncludeCommonProperties()
|
||||||
.EnsureVisibleFor(user)
|
.EnsureVisibleFor(user)
|
||||||
.PrecomputeVisibilities(user)
|
.PrecomputeVisibilities(user)
|
||||||
|
@ -166,8 +170,19 @@ public class StatusController(
|
||||||
? await db.DriveFiles.Where(p => request.MediaIds.Contains(p.Id)).ToListAsync()
|
? await db.DriveFiles.Where(p => request.MediaIds.Contains(p.Id)).ToListAsync()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
var note = await noteSvc.CreateNoteAsync(user, visibility, request.Text, request.Cw, reply,
|
var lastToken = request.Text?.Split(' ').LastOrDefault();
|
||||||
attachments: attachments);
|
var quoteUri = lastToken?.StartsWith("https://") ?? false ? lastToken : null;
|
||||||
|
var quote = lastToken?.StartsWith($"https://{config.Value.WebDomain}/notes/") ?? false
|
||||||
|
? await db.Notes.IncludeCommonProperties()
|
||||||
|
.FirstOrDefaultAsync(p => p.Id ==
|
||||||
|
lastToken.Substring($"https://{config.Value.WebDomain}/notes/".Length))
|
||||||
|
: await db.Notes.IncludeCommonProperties()
|
||||||
|
.FirstOrDefaultAsync(p => p.Uri == quoteUri || p.Url == quoteUri);
|
||||||
|
|
||||||
|
if (quote != null && quoteUri != null && request.Text != null)
|
||||||
|
request.Text = request.Text[..(request.Text.Length - quoteUri.Length - 1)];
|
||||||
|
|
||||||
|
var note = await noteSvc.CreateNoteAsync(user, visibility, request.Text, request.Cw, reply, quote, attachments);
|
||||||
|
|
||||||
if (idempotencyKey != null)
|
if (idempotencyKey != null)
|
||||||
await cache.SetAsync($"idempotency:{idempotencyKey}", note.Id, TimeSpan.FromHours(24));
|
await cache.SetAsync($"idempotency:{idempotencyKey}", note.Id, TimeSpan.FromHours(24));
|
||||||
|
|
|
@ -238,8 +238,11 @@ public class Note : IEntity
|
||||||
[InverseProperty(nameof(InverseReply))]
|
[InverseProperty(nameof(InverseReply))]
|
||||||
public virtual Note? Reply { get; set; }
|
public virtual Note? Reply { get; set; }
|
||||||
|
|
||||||
[NotMapped] [Projectable] public bool IsPureRenote => RenoteId != null && !IsQuote;
|
[NotMapped] [Projectable] public bool IsPureRenote => (RenoteId != null || Renote != null) && !IsQuote;
|
||||||
[NotMapped] [Projectable] public bool IsQuote => RenoteId != null && (Text != null || HasPoll || FileIds.Count > 0);
|
|
||||||
|
[NotMapped]
|
||||||
|
[Projectable]
|
||||||
|
public bool IsQuote => (RenoteId != null || Renote != null) && (Text != null || HasPoll || FileIds.Count > 0);
|
||||||
|
|
||||||
[ForeignKey("UserId")]
|
[ForeignKey("UserId")]
|
||||||
[InverseProperty(nameof(Tables.User.Notes))]
|
[InverseProperty(nameof(Tables.User.Notes))]
|
||||||
|
@ -285,6 +288,10 @@ public class Note : IEntity
|
||||||
? $"https://{config.WebDomain}/notes/{Id}"
|
? $"https://{config.WebDomain}/notes/{Id}"
|
||||||
: throw new Exception("Cannot access PublicUri for remote note");
|
: throw new Exception("Cannot access PublicUri for remote note");
|
||||||
|
|
||||||
|
public string? GetPublicUriOrNull(Config.InstanceSection config) => UserHost == null
|
||||||
|
? $"https://{config.WebDomain}/notes/{Id}"
|
||||||
|
: null;
|
||||||
|
|
||||||
public class MentionedUser
|
public class MentionedUser
|
||||||
{
|
{
|
||||||
[J("uri")] public required string Uri { get; set; }
|
[J("uri")] public required string Uri { get; set; }
|
||||||
|
|
|
@ -80,6 +80,9 @@ public class NoteRenderer(IOptions<Config.InstanceSection> config, MfmConverter
|
||||||
.ToListAsync()
|
.ToListAsync()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
var quoteUri = note.IsQuote ? note.Renote?.Uri ?? note.Renote?.GetPublicUriOrNull(config.Value) : null;
|
||||||
|
var text = quoteUri != null ? note.Text + $"\n\nRE: {quoteUri}" : note.Text;
|
||||||
|
|
||||||
return new ASNote
|
return new ASNote
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
|
@ -93,13 +96,15 @@ public class NoteRenderer(IOptions<Config.InstanceSection> config, MfmConverter
|
||||||
To = to,
|
To = to,
|
||||||
Tags = tags,
|
Tags = tags,
|
||||||
Attachments = attachments,
|
Attachments = attachments,
|
||||||
Content = note.Text != null
|
Content = text != null ? await mfmConverter.ToHtmlAsync(text, mentions, note.UserHost) : null,
|
||||||
? await mfmConverter.ToHtmlAsync(note.Text, mentions, note.UserHost)
|
|
||||||
: null,
|
|
||||||
Summary = note.Cw,
|
Summary = note.Cw,
|
||||||
Source = note.Text != null
|
Source =
|
||||||
? new ASNoteSource { Content = note.Text, MediaType = "text/x.misskeymarkdown" }
|
text != null
|
||||||
: null
|
? new ASNoteSource { Content = text, MediaType = "text/x.misskeymarkdown" }
|
||||||
|
: null,
|
||||||
|
MkQuote = quoteUri,
|
||||||
|
QuoteUri = quoteUri,
|
||||||
|
QuoteUrl = quoteUri
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -184,6 +184,7 @@ public class NotificationService(
|
||||||
public async Task GenerateRenoteNotification(Note note)
|
public async Task GenerateRenoteNotification(Note note)
|
||||||
{
|
{
|
||||||
if (note.Renote is not { UserHost: null }) return;
|
if (note.Renote is not { UserHost: null }) return;
|
||||||
|
if (note.RenoteUserId == note.UserId) return;
|
||||||
if (!note.VisibilityIsPublicOrHome &&
|
if (!note.VisibilityIsPublicOrHome &&
|
||||||
!await db.Notes.AnyAsync(p => p.Id == note.Id && p.IsVisibleFor(note.Renote.User)))
|
!await db.Notes.AnyAsync(p => p.Id == note.Id && p.IsVisibleFor(note.Renote.User)))
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Add table
Reference in a new issue