Featured tags per persona (/api/v1/featured_tags, suggestions, tags/:name/feature, accounts' featured tags, max 10),
listed at /tattoos as Hashtags and sent as Add/Remove{Hashtag} to followers on the featured collection, as Mastodon
sends them; remote accounts' tags received and read from featuredTags. Endorsements (pin/endorse) and private notes
(PersonaNote) kept per persona and never federated; the relationship's notifying, languages, endorsed and note, with
follow's notify (status notifications) and languages (home filter). Reading preferences set on /clientapi. Suggestions
v1/v2 with dismissals and staff picks, never the persona or a sibling. conversation events on the direct stream.
translate, annual reports and the admin API answer. New collections backed up and removed with their persona.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
61 lines
2.7 KiB
C#
61 lines
2.7 KiB
C#
using PrivaPub.Federation.Actors;
|
|
using PrivaPub.Models.User;
|
|
|
|
using System.Text.Json.Nodes;
|
|
|
|
namespace PrivaPub.Federation.Inbox.Handlers
|
|
{
|
|
// An account pins one of its posts (Add to its `featured` collection) or unpins it (Remove), as Mastodon and Akkoma
|
|
// send it; inside a community's announce, the community features a post made in it. A Hashtag added to or removed from
|
|
// its `featured` collection (as Mastodon sends it) or its `featuredTags` is a featured tag (R8). On its wall
|
|
// (Smithereen's, FEP-400e) it tells of a post someone wrote there, or takes it off. Any other target (a community's
|
|
// moderators) is not ours to keep.
|
|
public class FeaturedHandler : IActivityHandler
|
|
{
|
|
readonly IFeaturedPosts _featured;
|
|
readonly IWalls _walls;
|
|
readonly IRemoteActorService _remoteActors;
|
|
readonly Infrastructure.Jobs.IJobQueue _queue;
|
|
readonly Domain.Social.IFeaturedTags _tags;
|
|
readonly bool _add;
|
|
|
|
public FeaturedHandler(IFeaturedPosts featured, bool add, IWalls walls = default, IRemoteActorService remoteActors = default,
|
|
Infrastructure.Jobs.IJobQueue queue = default, Domain.Social.IFeaturedTags tags = default)
|
|
{
|
|
_tags = tags;
|
|
_featured = featured;
|
|
_walls = walls;
|
|
_remoteActors = remoteActors;
|
|
_queue = queue;
|
|
_add = add;
|
|
}
|
|
|
|
public string Type => _add ? "Add" : "Remove";
|
|
|
|
public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token)
|
|
{
|
|
if (_add && _queue != default && await ConversationContainers.Unwrap(activity, actor, _queue, token))
|
|
return;
|
|
var target = Objects.ActivityJson.Id(activity["target"]);
|
|
// a collection of its own we do not know of: its document read again, at most hourly, in case it was kept before
|
|
// we read that collection (a wall)
|
|
if (_remoteActors != default && target != default && target != actor.FeaturedURL && target != actor.WallURL && target != actor.FeaturedTagsURL
|
|
&& Objects.Origin.Same(target, actor.ActorURI) && actor.UpdatedAt < DateTime.UtcNow.AddHours(-1))
|
|
actor = await _remoteActors.GetActor(actor.ActorURI, refresh: true, token) ?? actor;
|
|
// a featured hashtag: an object of its own, with no id (its href is no post), so before anything reads it as one
|
|
if (Objects.ActivityJson.Value(activity["object"], "type") == "Hashtag")
|
|
{
|
|
if (_tags != default && target != default && (target == actor.FeaturedURL || target == actor.FeaturedTagsURL))
|
|
await _tags.Receive(activity, actor, _add, token);
|
|
else
|
|
Arrival.Drop("unknown-target");
|
|
return;
|
|
}
|
|
if (_walls != default && !string.IsNullOrEmpty(actor.WallURL) && target == actor.WallURL)
|
|
await _walls.Receive(activity, actor, _add, token);
|
|
else
|
|
await _featured.Receive(activity, actor, _add, token);
|
|
}
|
|
}
|
|
}
|