poll - federation #181

This commit is contained in:
rimu 2024-05-18 21:06:57 +12:00
parent 79798b28e3
commit 0d9d4f12cb
4 changed files with 111 additions and 8 deletions

View file

@ -549,7 +549,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address):
user.last_seen = community.last_active = site.last_active = utcnow() user.last_seen = community.last_active = site.last_active = utcnow()
object_type = request_json['object']['type'] object_type = request_json['object']['type']
new_content_types = ['Page', 'Article', 'Link', 'Note'] new_content_types = ['Page', 'Article', 'Link', 'Note', 'Question']
if object_type in new_content_types: # create a new post if object_type in new_content_types: # create a new post
in_reply_to = request_json['object']['inReplyTo'] if 'inReplyTo' in request_json['object'] else None in_reply_to = request_json['object']['inReplyTo'] if 'inReplyTo' in request_json['object'] else None
if not in_reply_to: if not in_reply_to:

View file

@ -13,7 +13,7 @@ from sqlalchemy import text, func
from app import db, cache, constants, celery from app import db, cache, constants, celery
from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, \ from app.models import User, Post, Community, BannedInstances, File, PostReply, AllowedInstances, Instance, utcnow, \
PostVote, PostReplyVote, ActivityPubLog, Notification, Site, CommunityMember, InstanceRole, Report, Conversation, \ PostVote, PostReplyVote, ActivityPubLog, Notification, Site, CommunityMember, InstanceRole, Report, Conversation, \
Language, Tag Language, Tag, Poll, PollChoice
from app.activitypub.signature import signed_get_request from app.activitypub.signature import signed_get_request
import time import time
import base64 import base64
@ -179,7 +179,25 @@ def post_to_activity(post: Post, community: Community):
if post.image_id is not None: if post.image_id is not None:
activity_data["object"]["object"]["image"] = {"url": post.image.view_url(), "type": "Image"} activity_data["object"]["object"]["image"] = {"url": post.image.view_url(), "type": "Image"}
if post.image.alt_text: if post.image.alt_text:
activity_data["object"]["object"]["image"]['altText'] = post.image.alt_text activity_data["object"]["object"]["image"]['name'] = post.image.alt_text
if post.type == POST_TYPE_POLL:
poll = Poll.query.filter_by(post_id=post.id).first()
activity_data["object"]["object"]['type'] = 'Question'
mode = 'oneOf' if poll.mode == 'single' else 'anyOf'
choices = []
for choice in PollChoice.query.filter_by(post_id=post.id).order_by(PollChoice.sort_order).all():
choices.append({
"type": "Note",
"name": choice.choice_text,
"replies": {
"type": "Collection",
"totalItems": choice.num_votes
}
})
activity_data["object"]["object"][mode] = choices
activity_data["object"]["object"]['endTime'] = ap_datetime(poll.end_poll)
activity_data["object"]["object"]['votersCount'] = poll.total_votes()
return activity_data return activity_data
@ -1554,6 +1572,21 @@ def create_post(activity_log: ActivityPubLog, community: Community, request_json
activity_log.result = 'success' activity_log.result = 'success'
db.session.commit() db.session.commit()
# Polls need to be processed quite late because they need a post_id to refer to
if request_json['object']['type'] == 'Question':
post.type = POST_TYPE_POLL
mode = 'single'
if 'anyOf' in request_json['object']:
mode = 'multiple'
poll = Poll(post_id=post.id, end_poll=request_json['object']['endTime'], mode=mode, local_only=False)
db.session.add(poll)
i = 1
for choice_ap in request_json['object']['oneOf' if mode == 'single' else 'anyOf']:
new_choice = PollChoice(post_id=post.id, choice_text=choice_ap['name'], sort_order=i)
db.session.add(new_choice)
i += 1
db.session.commit()
if post.image_id: if post.image_id:
make_image_sizes(post.image_id, 150, 512, 'posts') # the 512 sized image is for masonry view make_image_sizes(post.image_id, 150, 512, 'posts') # the 512 sized image is for masonry view

View file

@ -21,11 +21,11 @@ from app.community.util import search_for_community, actor_to_community, \
delete_post_from_community, delete_post_reply_from_community, community_in_list delete_post_from_community, delete_post_reply_from_community, community_in_list
from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \ from app.constants import SUBSCRIPTION_MEMBER, SUBSCRIPTION_OWNER, POST_TYPE_LINK, POST_TYPE_ARTICLE, POST_TYPE_IMAGE, \
SUBSCRIPTION_PENDING, SUBSCRIPTION_MODERATOR, REPORT_STATE_NEW, REPORT_STATE_ESCALATED, REPORT_STATE_RESOLVED, \ SUBSCRIPTION_PENDING, SUBSCRIPTION_MODERATOR, REPORT_STATE_NEW, REPORT_STATE_ESCALATED, REPORT_STATE_RESOLVED, \
REPORT_STATE_DISCARDED, POST_TYPE_VIDEO, NOTIF_COMMUNITY REPORT_STATE_DISCARDED, POST_TYPE_VIDEO, NOTIF_COMMUNITY, POST_TYPE_POLL
from app.inoculation import inoculation from app.inoculation import inoculation
from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \ from app.models import User, Community, CommunityMember, CommunityJoinRequest, CommunityBan, Post, \
File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation, PostReply, \ File, PostVote, utcnow, Report, Notification, InstanceBlock, ActivityPubLog, Topic, Conversation, PostReply, \
NotificationSubscription, UserFollower, Instance, Language NotificationSubscription, UserFollower, Instance, Language, Poll, PollChoice
from app.community import bp from app.community import bp
from app.user.utils import search_for_user from app.user.utils import search_for_user
from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \ from app.utils import get_setting, render_template, allowlist_html, markdown_to_html, validation_required, \
@ -805,6 +805,7 @@ def add_poll_post(actor):
abort(401) abort(401)
post = Post(user_id=current_user.id, community_id=form.communities.data, instance_id=1) post = Post(user_id=current_user.id, community_id=form.communities.data, instance_id=1)
save_post(form, post, 'poll') save_post(form, post, 'poll')
poll = Poll.query.filter_by(post_id=post.id).first()
community.post_count += 1 community.post_count += 1
community.last_active = g.site.last_active = utcnow() community.last_active = g.site.last_active = utcnow()
db.session.commit() db.session.commit()
@ -815,9 +816,9 @@ def add_poll_post(actor):
notify_about_post(post) notify_about_post(post)
if not community.local_only: if not community.local_only and not poll.local_only:
federate_post(community, post) federate_post(community, post)
federate_post_to_user_followers(post) federate_post_to_user_followers(post)
return redirect(f"/post/{post.id}") return redirect(f"/post/{post.id}")
else: else:
@ -897,6 +898,23 @@ def federate_post(community, post):
if post.type == POST_TYPE_IMAGE: if post.type == POST_TYPE_IMAGE:
page['attachment'] = [{'type': 'Link', page['attachment'] = [{'type': 'Link',
'href': post.image.source_url}] # source_url is always a https link, no need for .replace() as done above 'href': post.image.source_url}] # source_url is always a https link, no need for .replace() as done above
if post.type == POST_TYPE_POLL:
poll = Poll.query.filter_by(post_id=post.id).first()
page['type'] = 'Question'
page['endTime'] = ap_datetime(poll.end_poll)
page['votersCount'] = 0
choices = []
for choice in PollChoice.query.filter_by(post_id=post.id).all():
choices.append({
"type": "Note",
"name": choice.choice_text,
"replies": {
"type": "Collection",
"totalItems": 0
}
})
page['oneOf' if poll.mode == 'single' else 'anyOf'] = choices
if not community.is_local(): # this is a remote community - send the post to the instance that hosts it if not community.is_local(): # this is a remote community - send the post to the instance that hosts it
success = post_request(community.ap_inbox_url, create, current_user.private_key, success = post_request(community.ap_inbox_url, create, current_user.private_key,
current_user.ap_profile_id + '#main-key') current_user.ap_profile_id + '#main-key')
@ -989,8 +1007,25 @@ def federate_post_to_user_followers(post):
if post.body_html: if post.body_html:
note['content'] = note['content'] + '<p>' + post.body_html + '</p>' note['content'] = note['content'] + '<p>' + post.body_html + '</p>'
if post.type == POST_TYPE_POLL:
poll = Poll.query.filter_by(post_id=post.id).first()
note['type'] = 'Question'
note['endTime'] = ap_datetime(poll.end_poll)
note['votersCount'] = 0
choices = []
for choice in PollChoice.query.filter_by(post_id=post.id).all():
choices.append({
"type": "Note",
"name": choice.choice_text,
"replies": {
"type": "Collection",
"totalItems": 0
}
})
note['oneOf' if poll.mode == 'single' else 'anyOf'] = choices
instances = Instance.query.join(User, User.instance_id == Instance.id).join(UserFollower, UserFollower.remote_user_id == User.id) instances = Instance.query.join(User, User.instance_id == Instance.id).join(UserFollower, UserFollower.remote_user_id == User.id)
instances = instances.filter(UserFollower.local_user_id == post.user_id) instances = instances.filter(UserFollower.local_user_id == post.user_id).filter(Instance.gone_forever == False)
for i in instances: for i in instances:
post_request(i.inbox, create, current_user.private_key, current_user.ap_profile_id + '#main-key') post_request(i.inbox, create, current_user.private_key, current_user.ap_profile_id + '#main-key')

View file

@ -1233,6 +1233,8 @@ def post_edit_poll_post(post_id: int):
) )
else: else:
abort(401) abort(401)
def federate_post_update(post): def federate_post_update(post):
page_json = { page_json = {
'type': 'Page', 'type': 'Page',
@ -1288,6 +1290,23 @@ def federate_post_update(post):
if post.type == POST_TYPE_IMAGE: if post.type == POST_TYPE_IMAGE:
page_json['attachment'] = [{'type': 'Link', page_json['attachment'] = [{'type': 'Link',
'href': post.image.source_url}] # source_url is always a https link, no need for .replace() as done above 'href': post.image.source_url}] # source_url is always a https link, no need for .replace() as done above
if post.type == POST_TYPE_POLL:
poll = Poll.query.filter_by(post_id=post.id).first()
page_json['type'] = 'Question'
page_json['endTime'] = ap_datetime(poll.end_poll)
page_json['votersCount'] = 0
choices = []
for choice in PollChoice.query.filter_by(post_id=post.id).all():
choices.append({
"type": "Note",
"name": choice.choice_text,
"replies": {
"type": "Collection",
"totalItems": 0
}
})
page_json['oneOf' if poll.mode == 'single' else 'anyOf'] = choices
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
success = post_request(post.community.ap_inbox_url, update_json, current_user.private_key, success = post_request(post.community.ap_inbox_url, update_json, current_user.private_key,
current_user.ap_profile_id + '#main-key') current_user.ap_profile_id + '#main-key')
@ -1369,6 +1388,22 @@ def federate_post_edit_to_user_followers(post):
note['attachment'] = [{'type': 'Document', 'url': post.image.source_url, 'name': post.image.alt_text}] note['attachment'] = [{'type': 'Document', 'url': post.image.source_url, 'name': post.image.alt_text}]
else: else:
note['attachment'] = [{'type': 'Document', 'url': post.image.source_url}] note['attachment'] = [{'type': 'Document', 'url': post.image.source_url}]
elif post.type == POST_TYPE_POLL:
poll = Poll.query.filter_by(post_id=post.id).first()
note['type'] = 'Question'
note['endTime'] = ap_datetime(poll.end_poll)
note['votersCount'] = 0
choices = []
for choice in PollChoice.query.filter_by(post_id=post.id).all():
choices.append({
"type": "Note",
"name": choice.choice_text,
"replies": {
"type": "Collection",
"totalItems": 0
}
})
note['oneOf' if poll.mode == 'single' else 'anyOf'] = choices
if post.body_html: if post.body_html:
note['content'] = note['content'] + '<p>' + post.body_html + '</p>' note['content'] = note['content'] + '<p>' + post.body_html + '</p>'