From 04a4abe9d532e11fc484b08be05ff514d2ec2498 Mon Sep 17 00:00:00 2001 From: rimu <3310831+rimu@users.noreply.github.com> Date: Sat, 27 Jan 2024 12:22:35 +1300 Subject: [PATCH] local only communities - do not federate --- app/activitypub/routes.py | 68 +++-- app/activitypub/util.py | 8 + app/community/routes.py | 149 ++++----- app/post/routes.py | 627 +++++++++++++++++++------------------- 4 files changed, 436 insertions(+), 416 deletions(-) diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index 40c9ca60..0690f215 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -589,39 +589,43 @@ def process_inbox_request(request_json, activitypublog_id, ip_address): follow_id = request_json['id'] user = find_actor_or_create(user_ap_id) community = find_actor_or_create(community_ap_id) - if user is not None and community is not None: - # check if user is banned from this community - banned = CommunityBan.query.filter_by(user_id=user.id, community_id=community.id).first() - if banned is None: - user.last_seen = utcnow() - if community_membership(user, community) != SUBSCRIPTION_MEMBER: - member = CommunityMember(user_id=user.id, community_id=community.id) - db.session.add(member) - db.session.commit() - cache.delete_memoized(community_membership, user, community) - # send accept message to acknowledge the follow - accept = { - "@context": default_context(), - "actor": community.ap_profile_id, - "to": [ - user.ap_profile_id - ], - "object": { - "actor": user.ap_profile_id, - "to": None, - "object": community.ap_profile_id, - "type": "Follow", - "id": follow_id - }, - "type": "Accept", - "id": f"https://{current_app.config['SERVER_NAME']}/activities/accept/" + gibberish(32) - } - if post_request(user.ap_inbox_url, accept, community.private_key, f"https://{current_app.config['SERVER_NAME']}/c/{community.name}#main-key"): - activity_log.result = 'success' + if community and community.local_only: + # todo: send Deny activity + activity_log.exception_message = 'Local only cannot be followed by remote users' + else: + if user is not None and community is not None: + # check if user is banned from this community + banned = CommunityBan.query.filter_by(user_id=user.id, community_id=community.id).first() + if banned is None: + user.last_seen = utcnow() + if community_membership(user, community) != SUBSCRIPTION_MEMBER: + member = CommunityMember(user_id=user.id, community_id=community.id) + db.session.add(member) + db.session.commit() + cache.delete_memoized(community_membership, user, community) + # send accept message to acknowledge the follow + accept = { + "@context": default_context(), + "actor": community.ap_profile_id, + "to": [ + user.ap_profile_id + ], + "object": { + "actor": user.ap_profile_id, + "to": None, + "object": community.ap_profile_id, + "type": "Follow", + "id": follow_id + }, + "type": "Accept", + "id": f"https://{current_app.config['SERVER_NAME']}/activities/accept/" + gibberish(32) + } + if post_request(user.ap_inbox_url, accept, community.private_key, f"https://{current_app.config['SERVER_NAME']}/c/{community.name}#main-key"): + activity_log.result = 'success' + else: + activity_log.exception_message = 'Error sending Accept' else: - activity_log.exception_message = 'Error sending Accept' - else: - activity_log.exception_message = 'user is banned from this community' + activity_log.exception_message = 'user is banned from this community' # Accept: remote server is accepting our previous follow request elif request_json['type'] == 'Accept': if request_json['object']['type'] == 'Follow': diff --git a/app/activitypub/util.py b/app/activitypub/util.py index 36ee3006..e7797144 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -917,6 +917,10 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id def create_post_reply(activity_log: ActivityPubLog, community: Community, in_reply_to, request_json: dict, user: User, announce_id=None) -> Union[Post, None]: + if community.local_only: + activity_log.exception_message = 'Community is local only, reply discarded' + activity_log.result = 'ignored' + return None post_id, parent_comment_id, root_id = find_reply_parent(in_reply_to) if post_id or parent_comment_id or root_id: # set depth to +1 of the parent depth @@ -1006,6 +1010,10 @@ def create_post_reply(activity_log: ActivityPubLog, community: Community, in_rep def create_post(activity_log: ActivityPubLog, community: Community, request_json: dict, user: User, announce_id=None) -> Union[Post, None]: + if community.local_only: + activity_log.exception_message = 'Community is local only, post discarded' + activity_log.result = 'ignored' + return None post = Post(user_id=user.id, community_id=community.id, title=html.unescape(request_json['object']['name']), comments_enabled=request_json['object']['commentsEnabled'], diff --git a/app/community/routes.py b/app/community/routes.py index a6ee643a..11db4f75 100644 --- a/app/community/routes.py +++ b/app/community/routes.py @@ -376,87 +376,88 @@ def add_post(actor): notify_about_post(post) - page = { - 'type': 'Page', - 'id': post.ap_id, - 'attributedTo': current_user.ap_profile_id, - 'to': [ - community.ap_profile_id, - 'https://www.w3.org/ns/activitystreams#Public' - ], - 'name': post.title, - 'cc': [], - 'content': post.body_html if post.body_html else '', - 'mediaType': 'text/html', - 'source': { - 'content': post.body if post.body else '', - 'mediaType': 'text/markdown' - }, - 'attachment': [], - 'commentsEnabled': post.comments_enabled, - 'sensitive': post.nsfw, - 'nsfl': post.nsfl, - 'published': ap_datetime(utcnow()), - 'audience': community.ap_profile_id - } - create = { - "id": f"https://{current_app.config['SERVER_NAME']}/activities/create/{gibberish(15)}", - "actor": current_user.ap_profile_id, - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - community.ap_profile_id - ], - "type": "Create", - "audience": community.ap_profile_id, - "object": page, - '@context': default_context() - } - if post.type == POST_TYPE_LINK: - page['attachment'] = [{'href': post.url, 'type': 'Link'}] - elif post.image_id: - if post.image.file_path: - image_url = post.image.file_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/") - elif post.image.thumbnail_path: - image_url = post.image.thumbnail_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/") - else: - image_url = post.image.source_url - # NB image is a dict while attachment is a list of dicts (usually just one dict in the list) - page['image'] = {'type': 'Image', 'url': image_url} - if post.type == POST_TYPE_IMAGE: - page['attachment'] = [{'type': 'Link', 'href': post.image.source_url}] # source_url is always a https link, no need for .replace() as done above - 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, - current_user.ap_profile_id + '#main-key') - if success: - flash(_('Your post to %(name)s has been made.', name=community.title)) - else: - flash('There was a problem making your post to ' + community.title) - else: # local community - send (announce) post out to followers - announce = { - "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", - "type": 'Announce', + if not community.local_only: + page = { + 'type': 'Page', + 'id': post.ap_id, + 'attributedTo': current_user.ap_profile_id, + 'to': [ + community.ap_profile_id, + 'https://www.w3.org/ns/activitystreams#Public' + ], + 'name': post.title, + 'cc': [], + 'content': post.body_html if post.body_html else '', + 'mediaType': 'text/html', + 'source': { + 'content': post.body if post.body else '', + 'mediaType': 'text/markdown' + }, + 'attachment': [], + 'commentsEnabled': post.comments_enabled, + 'sensitive': post.nsfw, + 'nsfl': post.nsfl, + 'published': ap_datetime(utcnow()), + 'audience': community.ap_profile_id + } + create = { + "id": f"https://{current_app.config['SERVER_NAME']}/activities/create/{gibberish(15)}", + "actor": current_user.ap_profile_id, "to": [ "https://www.w3.org/ns/activitystreams#Public" ], - "actor": community.ap_profile_id, "cc": [ - community.ap_followers_url + community.ap_profile_id ], - '@context': default_context(), - 'object': create + "type": "Create", + "audience": community.ap_profile_id, + "object": page, + '@context': default_context() } + if post.type == POST_TYPE_LINK: + page['attachment'] = [{'href': post.url, 'type': 'Link'}] + elif post.image_id: + if post.image.file_path: + image_url = post.image.file_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/") + elif post.image.thumbnail_path: + image_url = post.image.thumbnail_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/") + else: + image_url = post.image.source_url + # NB image is a dict while attachment is a list of dicts (usually just one dict in the list) + page['image'] = {'type': 'Image', 'url': image_url} + if post.type == POST_TYPE_IMAGE: + page['attachment'] = [{'type': 'Link', 'href': post.image.source_url}] # source_url is always a https link, no need for .replace() as done above + 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, + current_user.ap_profile_id + '#main-key') + if success: + flash(_('Your post to %(name)s has been made.', name=community.title)) + else: + flash('There was a problem making your post to ' + community.title) + else: # local community - send (announce) post out to followers + announce = { + "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", + "type": 'Announce', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "actor": community.ap_profile_id, + "cc": [ + community.ap_followers_url + ], + '@context': default_context(), + 'object': create + } - sent_to = 0 - for instance in community.following_instances(): - if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): - send_to_remote_instance(instance.id, community.id, announce) - sent_to += 1 - if sent_to: - flash(_('Your post to %(name)s has been made.', name=community.title)) - else: - flash(_('Your post to %(name)s has been made.', name=community.title)) + sent_to = 0 + for instance in community.following_instances(): + if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): + send_to_remote_instance(instance.id, community.id, announce) + sent_to += 1 + if sent_to: + flash(_('Your post to %(name)s has been made.', name=community.title)) + else: + flash(_('Your post to %(name)s has been made.', name=community.title)) return redirect(f"/c/{community.link()}") else: diff --git a/app/post/routes.py b/app/post/routes.py index 8c584952..3f6fa099 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -232,36 +232,37 @@ def post_vote(post_id: int, vote_direction): post.author.reputation += effect db.session.add(vote) - action_type = 'Like' if vote_direction == 'upvote' else 'Dislike' - action_json = { - 'actor': current_user.profile_id(), - 'object': post.profile_id(), - 'type': action_type, - 'id': f"https://{current_app.config['SERVER_NAME']}/activities/{action_type.lower()}/{gibberish(15)}", - 'audience': post.community.profile_id() - } - if post.community.is_local(): - announce = { - "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", - "type": 'Announce', - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "actor": post.community.ap_profile_id, - "cc": [ - post.community.ap_followers_url - ], - '@context': default_context(), - 'object': action_json + if not post.community.local_only: + action_type = 'Like' if vote_direction == 'upvote' else 'Dislike' + action_json = { + 'actor': current_user.profile_id(), + 'object': post.profile_id(), + 'type': action_type, + 'id': f"https://{current_app.config['SERVER_NAME']}/activities/{action_type.lower()}/{gibberish(15)}", + 'audience': post.community.profile_id() } - for instance in post.community.following_instances(): - if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): - send_to_remote_instance(instance.id, post.community.id, announce) - else: - success = post_request(post.community.ap_inbox_url, action_json, current_user.private_key, - current_user.ap_profile_id + '#main-key') - if not success: - flash('Failed to send vote', 'warning') + if post.community.is_local(): + announce = { + "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", + "type": 'Announce', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "actor": post.community.ap_profile_id, + "cc": [ + post.community.ap_followers_url + ], + '@context': default_context(), + 'object': action_json + } + for instance in post.community.following_instances(): + if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): + send_to_remote_instance(instance.id, post.community.id, announce) + else: + success = post_request(post.community.ap_inbox_url, action_json, current_user.private_key, + current_user.ap_profile_id + '#main-key') + if not success: + flash('Failed to send vote', 'warning') current_user.last_seen = utcnow() current_user.ip_address = ip_address() @@ -325,18 +326,19 @@ def comment_vote(comment_id, vote_direction): ... # todo: federate vote else: - action_type = 'Like' if vote_direction == 'upvote' else 'Dislike' - action_json = { - 'actor': current_user.profile_id(), - 'object': comment.profile_id(), - 'type': action_type, - 'id': f"https://{current_app.config['SERVER_NAME']}/activities/{action_type.lower()}/{gibberish(15)}", - 'audience': comment.community.profile_id() - } - success = post_request(comment.community.ap_inbox_url, action_json, current_user.private_key, - current_user.ap_profile_id + '#main-key') - if not success: - flash('Failed to send vote', 'error') + if not comment.community.local_only: + action_type = 'Like' if vote_direction == 'upvote' else 'Dislike' + action_json = { + 'actor': current_user.profile_id(), + 'object': comment.profile_id(), + 'type': action_type, + 'id': f"https://{current_app.config['SERVER_NAME']}/activities/{action_type.lower()}/{gibberish(15)}", + 'audience': comment.community.profile_id() + } + success = post_request(comment.community.ap_inbox_url, action_json, current_user.private_key, + current_user.ap_profile_id + '#main-key') + if not success: + flash('Failed to send vote', 'error') current_user.last_seen = utcnow() current_user.ip_address = ip_address() @@ -438,80 +440,81 @@ def add_reply(post_id: int, comment_id: int): post.flush_cache() # federation - reply_json = { - 'type': 'Note', - 'id': reply.profile_id(), - 'attributedTo': current_user.profile_id(), - 'to': [ - 'https://www.w3.org/ns/activitystreams#Public', - in_reply_to.author.profile_id() - ], - 'cc': [ - post.community.profile_id(), - current_user.followers_url() - ], - 'content': reply.body_html, - 'inReplyTo': in_reply_to.profile_id(), - 'url': reply.profile_id(), - 'mediaType': 'text/html', - 'source': { - 'content': reply.body, - 'mediaType': 'text/markdown' - }, - 'published': ap_datetime(utcnow()), - 'distinguished': False, - 'audience': post.community.profile_id(), - 'contentMap': { - 'en': reply.body_html - } - } - create_json = { - '@context': default_context(), - 'type': 'Create', - 'actor': current_user.profile_id(), - 'audience': post.community.profile_id(), - 'to': [ - 'https://www.w3.org/ns/activitystreams#Public', - in_reply_to.author.profile_id() - ], - 'cc': [ - post.community.profile_id(), - current_user.followers_url() - ], - 'object': reply_json, - 'id': f"https://{current_app.config['SERVER_NAME']}/activities/create/{gibberish(15)}" - } - if in_reply_to.notify_author and in_reply_to.author.ap_id is not None: - reply_json['tag'] = [ - { - 'href': in_reply_to.author.ap_profile_id, - 'name': '@' + in_reply_to.author.ap_id, - 'type': 'Mention' + if not post.community.local_only: + reply_json = { + 'type': 'Note', + 'id': reply.profile_id(), + 'attributedTo': current_user.profile_id(), + 'to': [ + 'https://www.w3.org/ns/activitystreams#Public', + in_reply_to.author.profile_id() + ], + 'cc': [ + post.community.profile_id(), + current_user.followers_url() + ], + 'content': reply.body_html, + 'inReplyTo': in_reply_to.profile_id(), + 'url': reply.profile_id(), + 'mediaType': 'text/html', + 'source': { + 'content': reply.body, + 'mediaType': 'text/markdown' + }, + 'published': ap_datetime(utcnow()), + 'distinguished': False, + 'audience': post.community.profile_id(), + 'contentMap': { + 'en': reply.body_html } - ] - 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, create_json, current_user.private_key, - current_user.ap_profile_id + '#main-key') - if not success: - flash('Failed to send reply', 'error') - else: # local community - send it to followers on remote instances - announce = { - "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", - "type": 'Announce', - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "actor": post.community.ap_profile_id, - "cc": [ - post.community.ap_followers_url - ], - '@context': default_context(), - 'object': create_json } + create_json = { + '@context': default_context(), + 'type': 'Create', + 'actor': current_user.profile_id(), + 'audience': post.community.profile_id(), + 'to': [ + 'https://www.w3.org/ns/activitystreams#Public', + in_reply_to.author.profile_id() + ], + 'cc': [ + post.community.profile_id(), + current_user.followers_url() + ], + 'object': reply_json, + 'id': f"https://{current_app.config['SERVER_NAME']}/activities/create/{gibberish(15)}" + } + if in_reply_to.notify_author and in_reply_to.author.ap_id is not None: + reply_json['tag'] = [ + { + 'href': in_reply_to.author.ap_profile_id, + 'name': '@' + in_reply_to.author.ap_id, + 'type': 'Mention' + } + ] + 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, create_json, current_user.private_key, + current_user.ap_profile_id + '#main-key') + if not success: + flash('Failed to send reply', 'error') + else: # local community - send it to followers on remote instances + announce = { + "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", + "type": 'Announce', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "actor": post.community.ap_profile_id, + "cc": [ + post.community.ap_followers_url + ], + '@context': default_context(), + 'object': create_json + } - for instance in post.community.following_instances(): - if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): - send_to_remote_instance(instance.id, post.community.id, announce) + for instance in post.community.following_instances(): + if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): + send_to_remote_instance(instance.id, post.community.id, announce) if reply.depth <= constants.THREAD_CUTOFF_DEPTH: return redirect(url_for('activitypub.post_ap', post_id=post_id, _anchor=f'comment_{reply.id}')) @@ -566,79 +569,80 @@ def post_edit(post_id: int): flash(_('Your changes have been saved.'), 'success') # federate edit - page_json = { - 'type': 'Page', - 'id': post.ap_id, - 'attributedTo': current_user.ap_profile_id, - 'to': [ - post.community.ap_profile_id, - 'https://www.w3.org/ns/activitystreams#Public' - ], - 'name': post.title, - 'cc': [], - 'content': post.body_html if post.body_html else '', - 'mediaType': 'text/html', - 'source': { - 'content': post.body if post.body else '', - 'mediaType': 'text/markdown' - }, - 'attachment': [], - 'commentsEnabled': post.comments_enabled, - 'sensitive': post.nsfw, - 'nsfl': post.nsfl, - 'published': ap_datetime(post.posted_at), - 'updated': ap_datetime(post.edited_at), - 'audience': post.community.ap_profile_id - } - update_json = { - 'id': f"https://{current_app.config['SERVER_NAME']}/activities/update/{gibberish(15)}", - 'type': 'Update', - 'actor': current_user.profile_id(), - 'audience': post.community.profile_id(), - 'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'], - 'published': ap_datetime(utcnow()), - 'cc': [ - current_user.followers_url() - ], - 'object': page_json, - } - if post.type == POST_TYPE_LINK: - page_json['attachment'] = [{'href': post.url, 'type': 'Link'}] - elif post.image_id: - if post.image.file_path: - image_url = post.image.file_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/") - elif post.image.thumbnail_path: - image_url = post.image.thumbnail_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/") - else: - image_url = post.image.source_url - # NB image is a dict while attachment is a list of dicts (usually just one dict in the list) - page_json['image'] = {'type': 'Image', 'url': image_url} - if post.type == POST_TYPE_IMAGE: - page_json['attachment'] = [{'type': 'Link', 'href': post.image.source_url}] # source_url is always a https link, no need for .replace() as done above - - 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, - current_user.ap_profile_id + '#main-key') - if not success: - flash('Failed to send edit to remote server', 'error') - else: # local community - send it to followers on remote instances - announce = { - "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", - "type": 'Announce', - "to": [ - "https://www.w3.org/ns/activitystreams#Public" + if not post.community.local_only: + page_json = { + 'type': 'Page', + 'id': post.ap_id, + 'attributedTo': current_user.ap_profile_id, + 'to': [ + post.community.ap_profile_id, + 'https://www.w3.org/ns/activitystreams#Public' ], - "actor": post.community.ap_profile_id, - "cc": [ - post.community.ap_followers_url - ], - '@context': default_context(), - 'object': update_json + 'name': post.title, + 'cc': [], + 'content': post.body_html if post.body_html else '', + 'mediaType': 'text/html', + 'source': { + 'content': post.body if post.body else '', + 'mediaType': 'text/markdown' + }, + 'attachment': [], + 'commentsEnabled': post.comments_enabled, + 'sensitive': post.nsfw, + 'nsfl': post.nsfl, + 'published': ap_datetime(post.posted_at), + 'updated': ap_datetime(post.edited_at), + 'audience': post.community.ap_profile_id } + update_json = { + 'id': f"https://{current_app.config['SERVER_NAME']}/activities/update/{gibberish(15)}", + 'type': 'Update', + 'actor': current_user.profile_id(), + 'audience': post.community.profile_id(), + 'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'], + 'published': ap_datetime(utcnow()), + 'cc': [ + current_user.followers_url() + ], + 'object': page_json, + } + if post.type == POST_TYPE_LINK: + page_json['attachment'] = [{'href': post.url, 'type': 'Link'}] + elif post.image_id: + if post.image.file_path: + image_url = post.image.file_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/") + elif post.image.thumbnail_path: + image_url = post.image.thumbnail_path.replace('app/static/', f"https://{current_app.config['SERVER_NAME']}/static/") + else: + image_url = post.image.source_url + # NB image is a dict while attachment is a list of dicts (usually just one dict in the list) + page_json['image'] = {'type': 'Image', 'url': image_url} + if post.type == POST_TYPE_IMAGE: + page_json['attachment'] = [{'type': 'Link', 'href': post.image.source_url}] # source_url is always a https link, no need for .replace() as done above - for instance in post.community.following_instances(): - if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): - send_to_remote_instance(instance.id, post.community.id, announce) + 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, + current_user.ap_profile_id + '#main-key') + if not success: + flash('Failed to send edit to remote server', 'error') + else: # local community - send it to followers on remote instances + announce = { + "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", + "type": 'Announce', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "actor": post.community.ap_profile_id, + "cc": [ + post.community.ap_followers_url + ], + '@context': default_context(), + 'object': update_json + } + + for instance in post.community.following_instances(): + if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): + send_to_remote_instance(instance.id, post.community.id, announce) return redirect(url_for('activitypub.post_ap', post_id=post.id)) else: @@ -680,43 +684,44 @@ def post_delete(post_id: int): db.session.commit() flash(_('Post deleted.')) - delete_json = { - 'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}", - 'type': 'Delete', - 'actor': current_user.profile_id(), - 'audience': post.community.profile_id(), - 'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'], - 'published': ap_datetime(utcnow()), - 'cc': [ - current_user.followers_url() - ], - 'object': post.ap_id, - } - - 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, delete_json, current_user.private_key, - current_user.ap_profile_id + '#main-key') - if not success: - flash('Failed to send delete to remote server', 'error') - else: # local community - send it to followers on remote instances - announce = { - "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", - "type": 'Announce', - "to": [ - "https://www.w3.org/ns/activitystreams#Public" + if not community.local_only: + delete_json = { + 'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}", + 'type': 'Delete', + 'actor': current_user.profile_id(), + 'audience': post.community.profile_id(), + 'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'], + 'published': ap_datetime(utcnow()), + 'cc': [ + current_user.followers_url() ], - "actor": post.community.ap_profile_id, - "cc": [ - post.community.ap_followers_url - ], - '@context': default_context(), - 'object': delete_json + 'object': post.ap_id, } - for instance in post.community.following_instances(): - if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned( - instance.domain): - send_to_remote_instance(instance.id, post.community.id, announce) + 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, delete_json, current_user.private_key, + current_user.ap_profile_id + '#main-key') + if not success: + flash('Failed to send delete to remote server', 'error') + else: # local community - send it to followers on remote instances + announce = { + "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", + "type": 'Announce', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "actor": post.community.ap_profile_id, + "cc": [ + post.community.ap_followers_url + ], + '@context': default_context(), + 'object': delete_json + } + + for instance in post.community.following_instances(): + if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned( + instance.domain): + send_to_remote_instance(instance.id, post.community.id, announce) return redirect(url_for('activitypub.community_profile', actor=community.ap_id if community.ap_id is not None else community.name)) @@ -919,71 +924,72 @@ def post_reply_edit(post_id: int, comment_id: int): else: in_reply_to = post # federate edit - reply_json = { - 'type': 'Note', - 'id': post_reply.profile_id(), - 'attributedTo': current_user.profile_id(), - 'to': [ - 'https://www.w3.org/ns/activitystreams#Public', - in_reply_to.author.profile_id() - ], - 'cc': [ - post.community.profile_id(), - current_user.followers_url() - ], - 'content': post_reply.body_html, - 'inReplyTo': in_reply_to.profile_id(), - 'url': post_reply.profile_id(), - 'mediaType': 'text/html', - 'source': { - 'content': post_reply.body, - 'mediaType': 'text/markdown' - }, - 'published': ap_datetime(post_reply.posted_at), - 'updated': ap_datetime(post_reply.edited_at), - 'distinguished': False, - 'audience': post.community.profile_id(), - 'contentMap': { - 'en': post_reply.body_html + if not post.community.local_only: + reply_json = { + 'type': 'Note', + 'id': post_reply.profile_id(), + 'attributedTo': current_user.profile_id(), + 'to': [ + 'https://www.w3.org/ns/activitystreams#Public', + in_reply_to.author.profile_id() + ], + 'cc': [ + post.community.profile_id(), + current_user.followers_url() + ], + 'content': post_reply.body_html, + 'inReplyTo': in_reply_to.profile_id(), + 'url': post_reply.profile_id(), + 'mediaType': 'text/html', + 'source': { + 'content': post_reply.body, + 'mediaType': 'text/markdown' + }, + 'published': ap_datetime(post_reply.posted_at), + 'updated': ap_datetime(post_reply.edited_at), + 'distinguished': False, + 'audience': post.community.profile_id(), + 'contentMap': { + 'en': post_reply.body_html + } } - } - update_json = { - 'id': f"https://{current_app.config['SERVER_NAME']}/activities/update/{gibberish(15)}", - 'type': 'Update', - 'actor': current_user.profile_id(), - 'audience': post.community.profile_id(), - 'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'], - 'published': ap_datetime(utcnow()), - 'cc': [ - current_user.followers_url() - ], - 'object': reply_json, - } - - 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, - current_user.ap_profile_id + '#main-key') - if not success: - flash('Failed to send edit to remote server', 'error') - else: # local community - send it to followers on remote instances - announce = { - "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", - "type": 'Announce', - "to": [ - "https://www.w3.org/ns/activitystreams#Public" + update_json = { + 'id': f"https://{current_app.config['SERVER_NAME']}/activities/update/{gibberish(15)}", + 'type': 'Update', + 'actor': current_user.profile_id(), + 'audience': post.community.profile_id(), + 'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'], + 'published': ap_datetime(utcnow()), + 'cc': [ + current_user.followers_url() ], - "actor": post.community.ap_profile_id, - "cc": [ - post.community.ap_followers_url - ], - '@context': default_context(), - 'object': update_json + 'object': reply_json, } - for instance in post.community.following_instances(): - if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned( - instance.domain): - send_to_remote_instance(instance.id, post.community.id, announce) + 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, + current_user.ap_profile_id + '#main-key') + if not success: + flash('Failed to send edit to remote server', 'error') + else: # local community - send it to followers on remote instances + announce = { + "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", + "type": 'Announce', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "actor": post.community.ap_profile_id, + "cc": [ + post.community.ap_followers_url + ], + '@context': default_context(), + 'object': update_json + } + + for instance in post.community.following_instances(): + if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned( + instance.domain): + send_to_remote_instance(instance.id, post.community.id, announce) return redirect(url_for('activitypub.post_ap', post_id=post.id)) else: form.body.data = post_reply.body @@ -1014,42 +1020,43 @@ def post_reply_delete(post_id: int, comment_id: int): post.flush_cache() flash(_('Comment deleted.')) # federate delete - delete_json = { - 'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}", - 'type': 'Delete', - 'actor': current_user.profile_id(), - 'audience': post.community.profile_id(), - 'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'], - 'published': ap_datetime(utcnow()), - 'cc': [ - current_user.followers_url() - ], - 'object': post_reply.ap_id, - } - - 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, delete_json, current_user.private_key, - current_user.ap_profile_id + '#main-key') - if not success: - flash('Failed to send delete to remote server', 'error') - else: # local community - send it to followers on remote instances - announce = { - "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", - "type": 'Announce', - "to": [ - "https://www.w3.org/ns/activitystreams#Public" + if not post.community.local_only: + delete_json = { + 'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}", + 'type': 'Delete', + 'actor': current_user.profile_id(), + 'audience': post.community.profile_id(), + 'to': [post.community.profile_id(), 'https://www.w3.org/ns/activitystreams#Public'], + 'published': ap_datetime(utcnow()), + 'cc': [ + current_user.followers_url() ], - "actor": post.community.ap_profile_id, - "cc": [ - post.community.ap_followers_url - ], - '@context': default_context(), - 'object': delete_json + 'object': post_reply.ap_id, } - for instance in post.community.following_instances(): - if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): - send_to_remote_instance(instance.id, post.community.id, announce) + 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, delete_json, current_user.private_key, + current_user.ap_profile_id + '#main-key') + if not success: + flash('Failed to send delete to remote server', 'error') + else: # local community - send it to followers on remote instances + announce = { + "id": f"https://{current_app.config['SERVER_NAME']}/activities/announce/{gibberish(15)}", + "type": 'Announce', + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "actor": post.community.ap_profile_id, + "cc": [ + post.community.ap_followers_url + ], + '@context': default_context(), + 'object': delete_json + } + + for instance in post.community.following_instances(): + if instance.inbox and not current_user.has_blocked_instance(instance.id) and not instance_banned(instance.domain): + send_to_remote_instance(instance.id, post.community.id, announce) return redirect(url_for('activitypub.post_ap', post_id=post.id))