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

@@ -140,28 +140,6 @@ namespace PrivaPub.Api.Mastodon.Controllers
public class StubsController : MastodonController
{
[HttpGet("/api/v1/suggestions"), Scope("read")]
public IActionResult SuggestionsV1() => Json(Array.Empty<object>());
[HttpGet("/api/v2/suggestions"), Scope("read")]
public IActionResult SuggestionsV2() => Json(Array.Empty<object>());
[HttpGet("/api/v1/endorsements"), Scope("read:accounts")]
public IActionResult Endorsements() => Json(Array.Empty<object>());
[HttpGet("/api/v1/featured_tags"), Scope("read:accounts")]
public IActionResult FeaturedTags() => Json(Array.Empty<object>());
[HttpGet("/api/v1/preferences"), Scope("read:accounts")]
public IActionResult Preferences() => Json(new Dictionary<string, object>
{
["posting:default:visibility"] = Me.Settings.DefaultVisibility ?? "public",
["posting:default:sensitive"] = Me.Settings.DefaultSensitive,
["posting:default:language"] = Me.Settings.DefaultLanguage,
["reading:expand:media"] = "default",
["reading:expand:spoilers"] = false
});
[HttpGet("/api/v1/push/subscription"), Scope("push")]
public IActionResult PushSubscription() => Error(StatusCodes.Status404NotFound, "Record not found");
@@ -179,5 +157,21 @@ namespace PrivaPub.Api.Mastodon.Controllers
[HttpGet("/api/v1/instance/translation_languages"), Microsoft.AspNetCore.Authorization.AllowAnonymous]
public IActionResult TranslationLanguages() => Json(new { });
// translation is cut (owner decision 2026-10-17): no language offers any (translation_languages), and a client that
// asks anyway is told so, as Mastodon tells it when it can't translate
[HttpPost("/api/v1/statuses/{id}/translate"), Scope("read:statuses")]
public IActionResult Translate(string id) => Error(StatusCodes.Status422UnprocessableEntity, "Translation is not available on this server");
// Mastodon 4.3's yearly reports (Wrapstodon): none are made here, so there are never any to show
[HttpGet("/api/v1/annual_reports"), Scope("read:accounts")]
public IActionResult AnnualReports() => Json(new { annual_reports = Array.Empty<object>(), accounts = Array.Empty<object>(), statuses = Array.Empty<object>() });
// the Mastodon admin API is cut: moderation stays on /clientapi (ModeratorController, AdminController), so every
// admin route answers as Mastodon answers an account that may not use it, whoever asks
[HttpGet("/api/v1/admin/{**rest}"), HttpPost("/api/v1/admin/{**rest}"), HttpPut("/api/v1/admin/{**rest}"), HttpPatch("/api/v1/admin/{**rest}"),
HttpDelete("/api/v1/admin/{**rest}"), HttpGet("/api/v2/admin/{**rest}"), HttpPost("/api/v2/admin/{**rest}"),
ApiExplorerSettings(IgnoreApi = true), Microsoft.AspNetCore.Authorization.AllowAnonymous]
public IActionResult Admin(string rest) => Error(StatusCodes.Status403Forbidden, "This action is not allowed");
}
}