P6: custom emoji, fuller remote profiles, and polls both ways
Build / Build (push) Successful in 39s
Deploy / privapub.thepra.dev (push) Successful in 57s

- Custom emoji (Emoji tags) on posts, display names, bios and profile fields, at most 64 per object, proxied, in
  Status.emojis and Account.emojis.
- Remote profiles keep their header, profile fields, locked flag, published date, movedTo, indexable, memorial and
  image descriptions (a locked GoToSocial account no longer shows as open).
- Polls: incoming Questions (Mastodon, Misskey, Pleroma, GoToSocial shapes) with counts, voters, end and closed;
  our own polls from the Mastodon API go out as Questions; votes in are counted once per voter and never become
  replies; personas vote on other servers' polls with one Note per choice; counts refresh with an Update at most every
  three minutes; a poll closes on time and tells its voters and its author. GET /api/v1/polls/:id and POST
  /api/v1/polls/:id/votes.
- An Update without a newer `updated` only refreshes poll, video, audio and event details and leaves no revision.

Checked live against GoToSocial: each side's poll reaches the other as a poll and each side's vote is counted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-01 18:35:20 +02:00
1 parent 81de470f64
commit 7be6749a23
33 files changed
+937 -28

No files matched your search

+3 -1
View File
@@ -23,7 +23,9 @@ namespace PrivaPub.Models.Jobs
Deliver,
ProcessInbox,
FetchAncestors,
DescribeInstance
DescribeInstance,
PollRefresh,
PollClose
}
public enum JobState
+3
View File
@@ -25,6 +25,7 @@ namespace PrivaPub.Models.Post
public List<PostMention> Mentions { get; set; } = new();
public List<string> Tags { get; set; } = new();
public List<PostMedia> Media { get; set; } = new();
public List<CustomEmoji> Emojis { get; set; } = new();
[BsonIgnoreIfNull]
public PostSource Source { get; set; }//the remote author's own text (Markdown, MFM, BBCode)
[BsonIgnoreIfNull]
@@ -37,6 +38,8 @@ namespace PrivaPub.Models.Post
public AudioDetails Audio { get; set; }
[BsonIgnoreIfNull]
public EventDetails Event { get; set; }
[BsonIgnoreIfNull]
public PostPoll Poll { get; set; }
public List<float> Location { get; set; } = new();
public float RangeKm { get; set; } = 5.0f;
[BsonIgnoreIfNull]
+23
View File
@@ -1,5 +1,28 @@
namespace PrivaPub.Models.Post
{
public class CustomEmoji
{
public string Shortcode { get; set; }//without the colons
public string URL { get; set; }
public DateTime? Updated { get; set; }
}
public class PostPoll
{
public List<PollOption> Options { get; set; } = new();
public bool Multiple { get; set; }
public DateTime? ExpiresAt { get; set; }
public DateTime? ClosedAt { get; set; }
public int? VotersCount { get; set; }//null when the origin does not say (Misskey)
public bool HideTotals { get; set; }
}
public class PollOption
{
public string Title { get; set; }
public int Votes { get; set; }
}
public class PostSource
{
public string Content { get; set; }
+2 -1
View File
@@ -21,6 +21,7 @@ namespace PrivaPub.Models.Social
FollowRequest,
Favourite,
Reblog,
Update
Update,
Poll
}
}
+15
View File
@@ -0,0 +1,15 @@
using MongoDB.Entities;
namespace PrivaPub.Models.Social
{
public class PollVote : Entity
{
public string PostId { get; set; }
public string VoterAccountId { get; set; }//Avatar.ID for a local voter, ForeignAvatar.ID for a remote one
public string ActorURI { get; set; }
public bool IsLocalVoter { get; set; }
public int Choice { get; set; }
public string ActivityURI { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
}
+7
View File
@@ -59,6 +59,13 @@ namespace PrivaPub.Models.User
public string PersonalNote { get; set; }
public string ModerationNote { get; set; }
public bool IsDiscoverable { get; set; } = true;
public bool IsIndexable { get; set; }
public bool IsLocked { get; set; }//manuallyApprovesFollowers
public bool IsMemorial { get; set; }
public List<PrivaPub.Models.Post.CustomEmoji> Emojis { get; set; } = new();
public string PictureDescription { get; set; }
public string HeaderDescription { get; set; }
public DateTime? Published { get; set; }//the actor's own published
public string ActorURI { get; set; }//id
public string PublicKeyId { get; set; }