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

@@ -25,6 +25,7 @@ namespace PrivaPub.ClientModels.Admin
public DateTime? TermsEffectiveAt { get; set; }
public List<ViewServerRule> Rules { get; set; } = [];//deleted ones too (IsDeleted), as old reports name them
public List<ViewRelay> Relays { get; set; } = [];
public List<ViewStaffPick> StaffPicks { get; set; } = [];//the accounts suggested first to every persona (R8), their own routes
public ViewMediaLimits Media { get; set; } = new();
public string RegistrationMode { get; set; }//"Invitations" or "Open", from the configuration
public bool PublishActivity { get; set; } = false;
@@ -158,6 +159,26 @@ namespace PrivaPub.ClientModels.Admin
public DateTime? AnsweredAt { get; set; }
}
// an account the administrators suggest to every persona here, first (R8: Mastodon's staff picks, `featured` among a
// suggestion's sources): a persona of this server or an account elsewhere, by its Mastodon account id. Added and removed
// on their own routes (/clientapi/admin/staff-picks), never through the settings form, so an older page saving the form
// never drops them.
public class ViewStaffPick
{
public string AccountId { get; set; }
public bool IsLocal { get; set; } = false;
public string Handle { get; set; }//user@host
public string Name { get; set; }
public bool IsShown { get; set; } = true;//false: deleted, suspended or silenced since, so suggested to nobody
}
public class StaffPickForm
{
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)),
StringLength(64, ErrorMessageResourceName = "StringLength", ErrorMessageResourceType = typeof(ErrorsResource))]
public string AccountId { get; set; }
}
public class RelayForm
{
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)),
@@ -0,0 +1,29 @@
using PrivaPub.ClientModels.Resources;
using System.ComponentModel.DataAnnotations;
namespace PrivaPub.ClientModels.User.Avatar
{
// How a persona's clients show what others post (R8): Mastodon's reading:expand:media ("default" hides media marked
// sensitive, "show_all", "hide_all") and reading:expand:spoilers, which GET /api/v1/preferences answers. Mastodon's API
// sets neither, so they are set here: GET and POST /clientapi/avatar/private/preferences, for the login's own persona.
public class ViewPersonaPreferences
{
public string AvatarId { get; set; }
public string ExpandMedia { get; set; }
public bool ExpandSpoilers { get; set; } = false;
}
public class PersonaPreferencesForm
{
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)),
Display(Name = nameof(AvatarId), ResourceType = typeof(FieldsNameResource))]
public string AvatarId { get; set; }
[Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)),
StringLength(16, ErrorMessageResourceName = "StringLength", ErrorMessageResourceType = typeof(ErrorsResource))]
public string ExpandMedia { get; set; }//"default", "show_all" or "hide_all"
public bool ExpandSpoilers { get; set; } = false;
}
}