Accept Quote Post Requests

This commit is contained in:
Tim Zöller 2026-04-27 22:16:27 +02:00
parent 102d515b42
commit d79678aae3
2 changed files with 83 additions and 0 deletions

View file

@ -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<String, Object> 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. * Send an activity to a remote inbox.
* *

View file

@ -238,6 +238,19 @@ public class InboxProcessor {
return; 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"); String inReplyTo = (String) noteObject.get("inReplyTo");
if (inReplyTo == null) { 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<String, Object> 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). * Process a comment (Note with inReplyTo).
*/ */