From d79678aae35a871de22318b8d6a46ed9b2a871a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Z=C3=B6ller?= Date: Mon, 27 Apr 2026 22:16:27 +0200 Subject: [PATCH] Accept Quote Post Requests --- .../fitpub/service/FederationService.java | 31 +++++++++++ .../fitpub/service/InboxProcessor.java | 52 +++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/main/java/net/javahippie/fitpub/service/FederationService.java b/src/main/java/net/javahippie/fitpub/service/FederationService.java index 4da6a52..7479fae 100644 --- a/src/main/java/net/javahippie/fitpub/service/FederationService.java +++ b/src/main/java/net/javahippie/fitpub/service/FederationService.java @@ -252,6 +252,37 @@ public class FederationService { } } + /** + * Send an Accept for a quote interaction (FEP-5e53). + * This tells the quoting server that the quote has been approved. + * + * @param createActivityId the ID of the incoming Create activity that contains the quote + * @param remoteActorUri the actor URI of the user who quoted the post + * @param localUser the local user who owns the quoted post + */ + @Async("taskExecutor") + public void sendAcceptQuote(String createActivityId, String remoteActorUri, User localUser) { + try { + RemoteActor remoteActor = fetchRemoteActor(remoteActorUri); + + String acceptId = baseUrl + "/activities/" + UUID.randomUUID(); + String actorUri = baseUrl + "/users/" + localUser.getUsername(); + + Map acceptActivity = new HashMap<>(); + acceptActivity.put("@context", "https://www.w3.org/ns/activitystreams"); + acceptActivity.put("type", "Accept"); + acceptActivity.put("id", acceptId); + acceptActivity.put("actor", actorUri); + acceptActivity.put("object", createActivityId); + + sendActivity(remoteActor.getInboxUrl(), acceptActivity, localUser); + log.info("Sent Accept (quote approval) to: {} for Create {}", remoteActor.getActorUri(), createActivityId); + + } catch (Exception e) { + log.error("Failed to send Accept for quote: {}", createActivityId, e); + } + } + /** * Send an activity to a remote inbox. * diff --git a/src/main/java/net/javahippie/fitpub/service/InboxProcessor.java b/src/main/java/net/javahippie/fitpub/service/InboxProcessor.java index 95e3262..a5afcfb 100644 --- a/src/main/java/net/javahippie/fitpub/service/InboxProcessor.java +++ b/src/main/java/net/javahippie/fitpub/service/InboxProcessor.java @@ -238,6 +238,19 @@ public class InboxProcessor { return; } + // Check if this Note quotes a local activity (FEP-5e53). + // Mastodon and other implementations use various field names for the quote reference. + String quoteUri = firstNonNull( + (String) noteObject.get("quoteUri"), + (String) noteObject.get("quote"), + (String) noteObject.get("quoteUrl"), + (String) noteObject.get("_misskey_quote") + ); + + if (quoteUri != null) { + handleQuoteApproval(username, activity, actor, quoteUri); + } + String inReplyTo = (String) noteObject.get("inReplyTo"); if (inReplyTo == null) { @@ -252,6 +265,45 @@ public class InboxProcessor { } } + /** + * If the quoted URI points to a local activity, send an Accept back to + * the quoting actor so that Mastodon (and other FEP-5e53 implementations) + * marks the quote as approved. + */ + private void handleQuoteApproval(String username, Map createActivity, String actor, String quoteUri) { + try { + UUID activityId = extractActivityIdFromUri(quoteUri); + if (activityId == null) { + log.debug("Quote URI {} does not reference a local activity, skipping approval", quoteUri); + return; + } + + Activity localActivity = activityRepository.findById(activityId).orElse(null); + if (localActivity == null) { + log.warn("Quoted activity not found: {}", activityId); + return; + } + + User localUser = userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalArgumentException("User not found: " + username)); + + String createActivityId = (String) createActivity.get("id"); + log.info("Approving quote from {} for activity {} (Create id: {})", actor, activityId, createActivityId); + + federationService.sendAcceptQuote(createActivityId, actor, localUser); + + } catch (Exception e) { + log.error("Error handling quote approval for {}", quoteUri, e); + } + } + + private static String firstNonNull(String... values) { + for (String v : values) { + if (v != null) return v; + } + return null; + } + /** * Process a comment (Note with inReplyTo). */