A comment in a community was announced only as Announce{Create}, which
Akkoma, Mastodon and Misskey drop: their members never saw it (the village
found Akkoma's missing). A comment is now also announced as itself, as a
new post already was; Lemmy answers that form 400, harmlessly. Nothing is
decided by the follower's software.
The town's checker expects a followers-only quote to stay with the
followers, and G-0003 covers only Lemmy-hosted communities (their relayed
moderation), since relayed likes count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
81 lines
2.9 KiB
C#
81 lines
2.9 KiB
C#
using MongoDB.Entities;
|
|
|
|
using PrivaPub.Federation.Actors;
|
|
using PrivaPub.Federation.Rendering;
|
|
using PrivaPub.Models.Federation;
|
|
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace PrivaPub.Federation.Outbox
|
|
{
|
|
public interface IGroupDistributor
|
|
{
|
|
Task Announce(LocalActor group, JsonObject activity, string objectUri, bool isNewPost, CancellationToken token);
|
|
}
|
|
|
|
public class GroupDistributor : IGroupDistributor
|
|
{
|
|
// so the announce's id resolves (PeasantsController.Grunt); a repeat of the same announce keeps the first
|
|
static async Task Keep(LocalActor group, JsonObject announce, string objectUri, CancellationToken token)
|
|
{
|
|
var body = (JsonObject)announce.DeepClone();
|
|
body.Remove("@context");
|
|
try
|
|
{
|
|
await DB.Default.SaveAsync(new GroupAnnouncement
|
|
{
|
|
GroupId = group.Id,
|
|
ActivityURI = body["id"]!.GetValue<string>(),
|
|
ObjectURI = objectUri,
|
|
Body = body.ToJsonString()
|
|
}, token);
|
|
}
|
|
catch (MongoDB.Driver.MongoWriteException ex) when (ex.WriteError?.Category == MongoDB.Driver.ServerErrorCategory.DuplicateKey)
|
|
{
|
|
}
|
|
}
|
|
|
|
readonly IDeliveryService _delivery;
|
|
|
|
public GroupDistributor(IDeliveryService delivery)
|
|
{
|
|
_delivery = delivery;
|
|
}
|
|
|
|
public async Task Announce(LocalActor group, JsonObject activity, string objectUri, bool isNewPost, CancellationToken token)
|
|
{
|
|
if (group is not { IsCircle: false, Kind: Models.Federation.LocalActorKind.Group })
|
|
return;
|
|
var embedded = (JsonObject)activity.DeepClone();
|
|
embedded.Remove("@context");
|
|
embedded["audience"] ??= group.Uri;
|
|
var key = Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(activity["id"]?.GetValue<string>() ?? embedded.ToJsonString())))[..24];
|
|
var announce = new JsonObject
|
|
{
|
|
["@context"] = ActivityPubRenderer.Context(),
|
|
["id"] = group.ActivityUri($"announce-{key}"),
|
|
["type"] = "Announce",
|
|
["actor"] = group.Uri,
|
|
["to"] = new JsonArray(ActivityPubRenderer.Public),
|
|
["cc"] = new JsonArray(group.Followers),
|
|
["audience"] = group.Uri,
|
|
["object"] = embedded
|
|
};
|
|
// Every follower, the author's own server included: Lemmy 1.0 keeps its user's post pending until the community
|
|
// announces it back, and clears that before it answers the echo 400 ("Object is not remote").
|
|
await _delivery.EnqueueToFollowers(group, announce, token);
|
|
await Keep(group, announce, objectUri, token);
|
|
// a new post or comment is also boosted as itself: microblogs (Mastodon, Akkoma, Misskey) drop an Announce of an
|
|
// activity, so without it their members never see what is said in the community; Lemmy answers it 400, harmlessly
|
|
if (isNewPost && !string.IsNullOrEmpty(objectUri))
|
|
{
|
|
var boost = ActivityPubRenderer.Announce(group, objectUri, $"announce-object-{key}");
|
|
await _delivery.EnqueueToFollowers(group, boost, token);
|
|
await Keep(group, boost, objectUri, token);
|
|
}
|
|
}
|
|
}
|
|
}
|