[backend/masto-client] Implement /reblog and /unreblog endpoints
This commit is contained in:
parent
a8b02aa6f8
commit
5d7035e63c
4 changed files with 107 additions and 7 deletions
|
@ -5,6 +5,7 @@ using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
|
|||
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
|
||||
using Iceshrimp.Backend.Core.Configuration;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Iceshrimp.Backend.Core.Database.Tables;
|
||||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Middleware;
|
||||
using Iceshrimp.Backend.Core.Services;
|
||||
|
@ -119,6 +120,52 @@ public class StatusController(
|
|||
return await GetNote(id);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/reblog")]
|
||||
[Authorize("write:favourites")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||
public async Task<IActionResult> Renote(string id)
|
||||
{
|
||||
var user = HttpContext.GetUserOrFail();
|
||||
var note = await db.Notes.Where(p => p.Id == id)
|
||||
.IncludeCommonProperties()
|
||||
.EnsureVisibleFor(user)
|
||||
.FirstOrDefaultAsync() ??
|
||||
throw GracefulException.RecordNotFound();
|
||||
|
||||
await noteSvc.CreateNoteAsync(user, Note.NoteVisibility.Followers, renote: note);
|
||||
return await GetNote(id);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/unreblog")]
|
||||
[Authorize("write:favourites")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||
public async Task<IActionResult> UndoRenote(string id)
|
||||
{
|
||||
var user = HttpContext.GetUserOrFail();
|
||||
if (!await db.Notes.Where(p => p.Id == id).EnsureVisibleFor(user).AnyAsync())
|
||||
throw GracefulException.RecordNotFound();
|
||||
|
||||
var renotes = await db.Notes.Where(p => p.RenoteId == id && p.IsPureRenote && p.User == user)
|
||||
.IncludeCommonProperties()
|
||||
.ToListAsync();
|
||||
|
||||
if (renotes.Count > 0)
|
||||
{
|
||||
renotes[0].Renote!.RenoteCount--;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
foreach (var renote in renotes) await noteSvc.DeleteNoteAsync(renote);
|
||||
|
||||
return await GetNote(id);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize("write:statuses")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
||||
|
|
|
@ -122,4 +122,36 @@ public class ActivityRenderer(
|
|||
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter", Justification = "This only makes sense for users")]
|
||||
private string RenderFollowId(User follower, User followee) =>
|
||||
$"https://{config.Value.WebDomain}/follows/{follower.Id}/{followee.Id}";
|
||||
|
||||
public static ASAnnounce RenderAnnounce(
|
||||
ASNote note, ASActor actor, List<ASObjectBase> to, List<ASObjectBase> cc, string uri
|
||||
) => new()
|
||||
{
|
||||
Id = uri,
|
||||
Actor = actor.Compact(),
|
||||
Object = note,
|
||||
To = to,
|
||||
Cc = cc
|
||||
};
|
||||
|
||||
public static ASAnnounce RenderAnnounce(
|
||||
ASNote note, string renoteUri, ASActor actor, Note.NoteVisibility visibility, string followersUri
|
||||
)
|
||||
{
|
||||
List<ASObjectBase> to = visibility switch
|
||||
{
|
||||
Note.NoteVisibility.Public => [new ASLink($"{Constants.ActivityStreamsNs}#Public")],
|
||||
Note.NoteVisibility.Followers => [new ASLink(followersUri)],
|
||||
Note.NoteVisibility.Specified => throw new Exception("Announce cannot be specified"),
|
||||
_ => []
|
||||
};
|
||||
|
||||
List<ASObjectBase> cc = visibility switch
|
||||
{
|
||||
Note.NoteVisibility.Home => [new ASLink($"{Constants.ActivityStreamsNs}#Public")],
|
||||
_ => []
|
||||
};
|
||||
|
||||
return RenderAnnounce(note, actor, to, cc, renoteUri);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ using Iceshrimp.Backend.Core.Database.Tables;
|
|||
using Iceshrimp.Backend.Core.Extensions;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
||||
using Iceshrimp.Backend.Core.Middleware;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
|
@ -29,6 +30,9 @@ public class NoteRenderer(IOptions<Config.InstanceSection> config, MfmConverter
|
|||
? new ASObjectBase(note.Reply.Uri ?? note.Reply.GetPublicUri(config.Value))
|
||||
: null;
|
||||
|
||||
if (note.IsPureRenote)
|
||||
throw GracefulException.BadRequest("Refusing to render pure renote as ASNote");
|
||||
|
||||
mentions ??= await db.Users
|
||||
.Where(p => note.Mentions.Contains(p.Id))
|
||||
.IncludeCommonProperties()
|
||||
|
|
|
@ -104,12 +104,20 @@ public class NoteService(
|
|||
|
||||
if (user.Host != null) return note;
|
||||
|
||||
var actor = userRenderer.RenderLite(user);
|
||||
var obj = await noteRenderer.RenderAsync(note, mentions);
|
||||
var activity = ActivityPub.ActivityRenderer.RenderCreate(obj, actor);
|
||||
var actor = userRenderer.RenderLite(user);
|
||||
ASActivity activity = note is { IsPureRenote: true, Renote: not null }
|
||||
? ActivityPub.ActivityRenderer.RenderAnnounce(noteRenderer.RenderLite(note.Renote),
|
||||
note.GetPublicUri(config.Value), actor, note.Visibility,
|
||||
user.GetPublicUri(config.Value) + "/followers")
|
||||
: ActivityPub.ActivityRenderer.RenderCreate(await noteRenderer.RenderAsync(note, mentions), actor);
|
||||
|
||||
List<string> additionalUserIds =
|
||||
note is { IsPureRenote: true, Renote: not null, Visibility: < Note.NoteVisibility.Followers }
|
||||
? [note.Renote.User.Id]
|
||||
: [];
|
||||
|
||||
var recipients = await db.Users
|
||||
.Where(p => mentionedUserIds.Contains(p.Id))
|
||||
.Where(p => mentionedUserIds.Concat(additionalUserIds).Contains(p.Id))
|
||||
.Select(p => new User { Host = p.Host, Inbox = p.Inbox })
|
||||
.ToListAsync();
|
||||
|
||||
|
@ -221,8 +229,16 @@ public class NoteService(
|
|||
.Select(p => new User { Host = p.Host, Inbox = p.Inbox })
|
||||
.ToListAsync();
|
||||
|
||||
var actor = userRenderer.RenderLite(note.User);
|
||||
var activity = activityRenderer.RenderDelete(actor, new ASTombstone { Id = note.GetPublicUri(config.Value) });
|
||||
var actor = userRenderer.RenderLite(note.User);
|
||||
ASActivity activity = note.IsPureRenote
|
||||
? activityRenderer.RenderUndo(actor,
|
||||
ActivityPub.ActivityRenderer
|
||||
.RenderAnnounce(noteRenderer.RenderLite(note.Renote ?? throw new Exception("Refusing to undo renote without renote")),
|
||||
note.GetPublicUri(config.Value), actor,
|
||||
note.Visibility,
|
||||
note.User.GetPublicUri(config.Value) +
|
||||
"/followers"))
|
||||
: activityRenderer.RenderDelete(actor, new ASTombstone { Id = note.GetPublicUri(config.Value) });
|
||||
|
||||
if (note.Visibility == Note.NoteVisibility.Specified)
|
||||
await deliverSvc.DeliverToAsync(activity, note.User, recipients.ToArray());
|
||||
|
@ -250,6 +266,7 @@ public class NoteService(
|
|||
logger.LogDebug("Deleting note '{id}' owned by {userId}", note.Id, actor.Id);
|
||||
|
||||
actor.NotesCount--;
|
||||
if (dbNote.IsPureRenote) dbNote.RenoteCount--;
|
||||
db.Remove(dbNote);
|
||||
eventSvc.RaiseNoteDeleted(this, dbNote);
|
||||
await db.SaveChangesAsync();
|
||||
|
|
Loading…
Add table
Reference in a new issue