P6: custom emoji, fuller remote profiles, and polls both ways
- 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:
1 parent
81de470f64
commit
7be6749a23
33 files changed
+937
-28
No files matched your search
@@ -4,6 +4,7 @@ using MongoDB.Entities;
|
||||
using PrivaPub.Domain.Timelines;
|
||||
using PrivaPub.Federation.Actors;
|
||||
using PrivaPub.Federation.Moderation;
|
||||
using PrivaPub.Domain.Statuses;
|
||||
using PrivaPub.Federation.Objects;
|
||||
using PrivaPub.Federation.Outbox;
|
||||
using PrivaPub.Infrastructure.Ids;
|
||||
@@ -35,11 +36,13 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
readonly IRemotePosts _remotePosts;
|
||||
readonly IGroupDistributor _groups;
|
||||
readonly IObjectRecords _records;
|
||||
readonly IPollService _polls;
|
||||
|
||||
public CreateHandler(DbEntities dbEntities, ILocalActorService localActors, IRemoteActorService remoteActors, IDeliveryService delivery,
|
||||
IDomainBlocks domainBlocks, IFanout fanout, IRemotePosts remotePosts, IGroupDistributor groups, IObjectRecords records)
|
||||
IDomainBlocks domainBlocks, IFanout fanout, IRemotePosts remotePosts, IGroupDistributor groups, IObjectRecords records, IPollService polls)
|
||||
{
|
||||
_records = records;
|
||||
_polls = polls;
|
||||
_groups = groups;
|
||||
_fanout = fanout;
|
||||
_remotePosts = remotePosts;
|
||||
@@ -64,6 +67,15 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
var note = NoteParser.Parse(node);
|
||||
if (note == default || note.AttributedTo != author.ActorURI)
|
||||
return;
|
||||
if (note.Title != default && note.InReplyTo != default && ObjectShapes.Text(note.ContentHtml, 1) == default)
|
||||
{
|
||||
var question = await _dbEntities.Posts.Match(p => p.ObjectURI == note.InReplyTo && p.Poll != null).ExecuteFirstAsync(token);
|
||||
if (question != default)
|
||||
{
|
||||
await _polls.Receive(question, author, note.Title, Id(activity), token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await _dbEntities.Posts.Match(p => p.ObjectURI == note.Id).ExecuteAnyAsync(token)
|
||||
|| await DB.Default.Find<DeletedObject>().Match(d => d.ObjectURI == note.Id).ExecuteAnyAsync(token))
|
||||
return;
|
||||
|
||||
@@ -55,6 +55,17 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
if (post == default || post.DeletedAt.HasValue)
|
||||
return;
|
||||
|
||||
post.Poll = note.Poll ?? post.Poll;
|
||||
post.Video = note.Video ?? post.Video;
|
||||
post.Audio = note.Audio ?? post.Audio;
|
||||
post.Event = note.Event ?? post.Event;
|
||||
if (!IsEdit(note, post))
|
||||
{
|
||||
await DB.Default.SaveAsync(post, token);
|
||||
await _records.Revise(note, Id(activity), token);
|
||||
return;
|
||||
}
|
||||
|
||||
post.Revisions.Add(new PostRevision
|
||||
{
|
||||
Title = post.Title,
|
||||
@@ -77,11 +88,9 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
post.SpoilerText = note.SpoilerText;
|
||||
post.Excerpt = note.Excerpt;
|
||||
post.Source = note.Source;
|
||||
post.Emojis = note.Emojis.ToList();
|
||||
post.CoverURL = note.CoverURL;
|
||||
post.Link = note.Link;
|
||||
post.Video = note.Video;
|
||||
post.Audio = note.Audio;
|
||||
post.Event = note.Event;
|
||||
post.HasContentWarning = note.Sensitive;
|
||||
post.Text = note.ContentHtml;
|
||||
post.ContentHtml = note.ContentHtml;
|
||||
@@ -96,5 +105,8 @@ namespace PrivaPub.Federation.Inbox.Handlers
|
||||
if (!string.IsNullOrEmpty(post.GroupId) && await _localActors.FindById(Models.Federation.LocalActorKind.Group, post.GroupId, token) is { IsCircle: false } community)
|
||||
await _groups.Announce(community, activity.AsObject(), post.ObjectURI, isNewPost: false, token);
|
||||
}
|
||||
|
||||
static bool IsEdit(NoteDocument note, PostEntity post) =>
|
||||
note.Updated is { } updated && updated > (post.EditedAt ?? post.CreationDate);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,8 @@ namespace PrivaPub.Federation.Inbox
|
||||
SpoilerText = note.SpoilerText,
|
||||
Excerpt = note.Excerpt,
|
||||
Source = note.Source,
|
||||
Poll = note.Poll,
|
||||
Emojis = note.Emojis.ToList(),
|
||||
CoverURL = note.CoverURL,
|
||||
Link = note.Link,
|
||||
Video = note.Video,
|
||||
|
||||
Reference in new issue
Block a user