R8 the Mastodon API made real: featured tags, endorsements, notes, notify and languages, suggestions, the direct stream

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
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-18 08:38:11 +02:00
1 parent d08fbabaa5
commit 87529bca49
42 files changed
+1505 -77

No files matched your search

@@ -244,13 +244,21 @@ namespace PrivaPub.Federation.Controllers
return Activity(ActivityPubRenderer.OrderedCollection(local.Featured, notes.Count, notes));
}
// the hashtags a persona features (R8), as Hashtag objects in the order it featured them, as Mastodon's featured tags
// collection lists them (Mastodon reads Collection and OrderedCollection alike); a group features none
[HttpGet, Route("{actor}/tattoos")]
public async Task<IActionResult> FeaturedTags(string actor, CancellationToken token)
{
var local = await _localActors.FindByUserName(actor, token);
return local is not { IsFederated: true }
? NotFound()
: Activity(ActivityPubRenderer.OrderedCollection(local.FeaturedTags, 0, Enumerable.Empty<JsonNode>()));
if (local is not { IsFederated: true })
return NotFound();
var tags = local.Kind == LocalActorKind.Person
? await DB.Default.Find<FeaturedTag>().Match(t => t.AvatarId == local.Id).Sort(t => t.ID, Order.Ascending).ExecuteAsync(token)
: new List<FeaturedTag>();
var collection = ActivityPubRenderer.OrderedCollection(local.FeaturedTags, tags.Count,
tags.Select(t => (JsonNode)Domain.Social.FeaturedTags.Hashtag(local.BaseAddress, t)));
collection["@context"] = ActivityPubRenderer.Context();//(Hashtag is Mastodon's term, defined there)
return Activity(collection);
}
[HttpGet, Route("{actor}/scribbles/{postId}")]