The composer uploads each file as soon as it is attached (streamed from the browser file, under its own ten-minute
timeout), shows it uploading, processing (a 202 followed until ready), ready or failed with Retry, and cancels one
removed; publishing sends the ids, so a failed publish or a retried upload sends nothing twice. Size limits and types
come from /api/v2/instance, a file without a browser type gets one from its extension, and each refusal names the file
and why. Previews are 320 px thumbnails, not whole files in memory. A picture without a description is asked about once
("Publish anyway"; a setting turns it off), descriptions are sent with PUT, and an edit keeps the post's media, language
and sensitive flag.
Profile pictures are checked against the server's types and can be removed. A card shows "Media unavailable" with the
original's link when a picture fails, the blurhash behind a sensitive placeholder, and plays a GIF only on tap under
reduced motion. A failed export says so instead of downloading part of a file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
293 lines
9.8 KiB
C#
293 lines
9.8 KiB
C#
using System.Text.Json.Nodes;
|
|
|
|
namespace decePubClient.Models.Mastodon;
|
|
|
|
/// <summary>/api/privapub/v1/instances: what PrivaPub stored about a server, and where it is.</summary>
|
|
public class InstanceDescription
|
|
{
|
|
public string Host { get; set; }
|
|
public string Software { get; set; }
|
|
public string Version { get; set; }
|
|
public string NodeName { get; set; }
|
|
public List<string> Protocols { get; set; } = [];
|
|
public bool? OpenRegistrations { get; set; }
|
|
public DateTime? DescribedAt { get; set; }
|
|
public string DescriptionError { get; set; }
|
|
public JsonNode NodeInfo { get; set; }
|
|
public InstanceDelivery Delivery { get; set; }
|
|
public InstanceLocation Geo { get; set; }
|
|
}
|
|
|
|
public class InstanceDelivery
|
|
{
|
|
public int ConsecutiveFailures { get; set; }
|
|
public DateTime? UnavailableUntil { get; set; }
|
|
public DateTime? LastSuccessAt { get; set; }
|
|
public DateTime? LastFailureAt { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The public projection of a server's place. Precision: city (coordinates to 0.1°, city and network), country (located
|
|
/// only to a country) or cdn (the CDN's name, its domain, how PrivaPub found it, and where the server was before it).
|
|
/// </summary>
|
|
public class InstanceLocation
|
|
{
|
|
public string Precision { get; set; }
|
|
public string Country { get; set; }
|
|
public string City { get; set; }
|
|
public double? Latitude { get; set; }
|
|
public double? Longitude { get; set; }
|
|
public string Network { get; set; }
|
|
public string Cdn { get; set; }
|
|
public string CdnDomain { get; set; }
|
|
/// <summary>ranges (the CDN's published addresses), headers (its fingerprint in the server's answers) or asn.</summary>
|
|
public string CdnSource { get; set; }
|
|
public InstancePlace BeforeCdn { get; set; }
|
|
public string Source { get; set; }
|
|
public DateTime? LocatedAt { get; set; }
|
|
}
|
|
|
|
/// <summary>Where a server was in a week (an ISO week, "2026-W40").</summary>
|
|
public class InstancePlace
|
|
{
|
|
public string Week { get; set; }
|
|
public string Country { get; set; }
|
|
public string City { get; set; }
|
|
public double? Latitude { get; set; }
|
|
public double? Longitude { get; set; }
|
|
}
|
|
|
|
/// <summary>/api/privapub/v1/instances/{host}/history: one weekly snapshot of a server.</summary>
|
|
public class InstanceWeek
|
|
{
|
|
public string Week { get; set; }
|
|
public DateTime? TakenAt { get; set; }
|
|
public bool Reachable { get; set; }
|
|
public string Software { get; set; }
|
|
public string Version { get; set; }
|
|
public long? UsersTotal { get; set; }
|
|
public long? UsersActiveMonth { get; set; }
|
|
public long? LocalPosts { get; set; }
|
|
public bool? OpenRegistrations { get; set; }
|
|
public InstanceLocation Geo { get; set; }
|
|
}
|
|
|
|
/// <summary>/api/privapub/v1/cdns: a CDN as a group of servers, named by its own domain (Key).</summary>
|
|
public class CdnSummary
|
|
{
|
|
public string Key { get; set; }
|
|
public string Name { get; set; }
|
|
public string Domain { get; set; }
|
|
public int Servers { get; set; }
|
|
public int Ranges { get; set; }
|
|
public DateTime? RangesUpdatedAt { get; set; }
|
|
public List<string> DetectedBy { get; set; } = [];
|
|
}
|
|
|
|
/// <summary>/api/privapub/v1/cdns/{key}: the servers behind a CDN and, newest first, week by week who joined and left.</summary>
|
|
public class CdnDetail : CdnSummary
|
|
{
|
|
public List<string> Hosts { get; set; } = [];
|
|
public bool HostsTruncated { get; set; }
|
|
public List<CdnWeek> Weeks { get; set; } = [];
|
|
}
|
|
|
|
public class CdnWeek
|
|
{
|
|
public string Week { get; set; }
|
|
public int Servers { get; set; }
|
|
public List<string> Joined { get; set; } = [];
|
|
public List<string> Left { get; set; } = [];
|
|
}
|
|
|
|
/// <summary>/api/privapub/v1/statuses/{id}/provenance: how a remote post reached PrivaPub. Path is local for a local post.</summary>
|
|
public class Provenance
|
|
{
|
|
public string ObjectUri { get; set; }
|
|
public string ObjectType { get; set; }
|
|
public string Url { get; set; }
|
|
/// <summary>delivered, fetched, unrecorded or local.</summary>
|
|
public string Path { get; set; }
|
|
public bool Refetched { get; set; }
|
|
public ProvenanceActivity Activity { get; set; }
|
|
public string Inbox { get; set; }
|
|
public string FetchedBy { get; set; }
|
|
public ProvenanceSignature Signature { get; set; }
|
|
public DateTime? ReceivedAt { get; set; }
|
|
public DateTime? Published { get; set; }
|
|
public DateTime? Updated { get; set; }
|
|
public List<string> Extensions { get; set; } = [];
|
|
public List<string> ContextNamespaces { get; set; } = [];
|
|
public JsonNode Raw { get; set; }
|
|
public int RawBytes { get; set; }
|
|
public string RawHash { get; set; }
|
|
public bool RawTruncated { get; set; }
|
|
public List<ProvenanceRevision> Revisions { get; set; } = [];
|
|
public InstanceDescription Instance { get; set; }
|
|
}
|
|
|
|
public class ProvenanceActivity
|
|
{
|
|
public string Id { get; set; }
|
|
public string Type { get; set; }
|
|
public string Actor { get; set; }
|
|
}
|
|
|
|
public class ProvenanceSignature
|
|
{
|
|
public string Scheme { get; set; }
|
|
public string KeyId { get; set; }
|
|
public string Algorithm { get; set; }
|
|
public List<string> Headers { get; set; } = [];
|
|
}
|
|
|
|
public class ProvenanceRevision
|
|
{
|
|
public string ActivityId { get; set; }
|
|
public DateTime? Updated { get; set; }
|
|
public DateTime? ReceivedAt { get; set; }
|
|
public string RawHash { get; set; }
|
|
}
|
|
|
|
/// <summary>The answer of /oauth/token.</summary>
|
|
public class TokenResponse
|
|
{
|
|
public string AccessToken { get; set; }
|
|
public string TokenType { get; set; }
|
|
public string IssuedTokenType { get; set; }
|
|
public string Scope { get; set; }
|
|
public long CreatedAt { get; set; }
|
|
}
|
|
|
|
/// <summary>An error of the Mastodon API or of /oauth: {"error": ..., "error_description": ...}.</summary>
|
|
public class MastodonError
|
|
{
|
|
public string Error { get; set; }
|
|
public string ErrorDescription { get; set; }
|
|
}
|
|
|
|
/// <summary>POST /api/v1/statuses. The text is plain: the Mastodon API does not take Markdown.</summary>
|
|
public class PostStatusForm
|
|
{
|
|
public string Status { get; set; }
|
|
public List<string> MediaIds { get; set; } = [];
|
|
public string InReplyToId { get; set; }
|
|
public bool Sensitive { get; set; }
|
|
public string SpoilerText { get; set; }
|
|
public string Visibility { get; set; }
|
|
public string Language { get; set; }
|
|
/// <summary>When the post is to be published (ISO 8601, at least five minutes ahead); null posts it now.</summary>
|
|
public string ScheduledAt { get; set; }
|
|
public PollForm Poll { get; set; }
|
|
public string QuotedStatusId { get; set; }
|
|
}
|
|
|
|
public class PollForm
|
|
{
|
|
public List<string> Options { get; set; } = [];
|
|
public int ExpiresIn { get; set; }
|
|
public bool Multiple { get; set; }
|
|
}
|
|
|
|
/// <summary>What an edit sends, the whole post as Mastodon wants it: the words, their warning and sensitive flag, the
|
|
/// post's language, the media it keeps (media_ids) and the descriptions changed (media_attributes).</summary>
|
|
public class StatusEditForm
|
|
{
|
|
public string Status { get; set; }
|
|
public string SpoilerText { get; set; }
|
|
public bool Sensitive { get; set; }
|
|
public string Language { get; set; }
|
|
public List<string> MediaIds { get; set; } = [];
|
|
public List<MediaAttributeForm> MediaAttributes { get; set; } = [];
|
|
}
|
|
|
|
/// <summary>A posted attachment's new description, sent with the edit.</summary>
|
|
public class MediaAttributeForm
|
|
{
|
|
public string Id { get; set; }
|
|
public string Description { get; set; }
|
|
}
|
|
|
|
/// <summary>/api/v1/statuses/:id/source: the author's own text of a post, to edit it.</summary>
|
|
public class StatusSource
|
|
{
|
|
public string Id { get; set; }
|
|
public string Text { get; set; }
|
|
public string SpoilerText { get; set; }
|
|
}
|
|
|
|
/// <summary>/api/v2/notifications/policy: what notifications from people the persona does not follow, people who do not
|
|
/// follow it, new accounts, unasked private mentions and limited accounts become: accept, filter or drop.</summary>
|
|
public class NotificationPolicy
|
|
{
|
|
public string ForNotFollowing { get; set; }
|
|
public string ForNotFollowers { get; set; }
|
|
public string ForNewAccounts { get; set; }
|
|
public string ForPrivateMentions { get; set; }
|
|
public string ForLimitedAccounts { get; set; }
|
|
public NotificationPolicySummary Summary { get; set; }
|
|
}
|
|
|
|
public class NotificationPolicySummary
|
|
{
|
|
public int PendingRequestsCount { get; set; }
|
|
public int PendingNotificationsCount { get; set; }
|
|
}
|
|
|
|
/// <summary>/api/v1/notifications/requests: the notifications the policy filtered from one account, waiting to be let in
|
|
/// (and that account from then on) or dismissed.</summary>
|
|
public class NotificationRequest
|
|
{
|
|
public string Id { get; set; }
|
|
public DateTime CreatedAt { get; set; }
|
|
public DateTime UpdatedAt { get; set; }
|
|
/// <summary>A number, as a string (Mastodon's).</summary>
|
|
public string NotificationsCount { get; set; }
|
|
public Account Account { get; set; }
|
|
public Status LastStatus { get; set; }
|
|
}
|
|
|
|
/// <summary>/api/v1/reports: an account, and some of its posts, reported to the persona's server's moderators; forwarded,
|
|
/// a remote account's server gets it too, from the server's own actor.</summary>
|
|
public class ReportRequest
|
|
{
|
|
public string AccountId { get; set; }
|
|
public List<string> StatusIds { get; set; } = [];
|
|
public string Comment { get; set; }
|
|
/// <summary>spam, legal or other.</summary>
|
|
public string Category { get; set; }
|
|
public bool Forward { get; set; }
|
|
}
|
|
|
|
/// <summary>/api/v1/statuses/:id/history: one version of a post, the oldest first and the one showing last.</summary>
|
|
public class StatusEdit
|
|
{
|
|
public string Content { get; set; }
|
|
public string SpoilerText { get; set; }
|
|
public bool Sensitive { get; set; }
|
|
public DateTime CreatedAt { get; set; }
|
|
public List<CustomEmoji> Emojis { get; set; } = [];
|
|
}
|
|
|
|
/// <summary>A post waiting for its time (/api/v1/scheduled_statuses).</summary>
|
|
public class ScheduledStatus
|
|
{
|
|
public string Id { get; set; }
|
|
public DateTime ScheduledAt { get; set; }
|
|
public ScheduledParams Params { get; set; } = new();
|
|
public List<MediaAttachment> MediaAttachments { get; set; } = [];
|
|
}
|
|
|
|
public class ScheduledParams
|
|
{
|
|
public string Text { get; set; }
|
|
public string SpoilerText { get; set; }
|
|
public string Visibility { get; set; }
|
|
public string InReplyToId { get; set; }
|
|
}
|
|
|
|
public class ScheduleForm
|
|
{
|
|
public string ScheduledAt { get; set; }
|
|
}
|