diff --git a/app/activitypub/routes.py b/app/activitypub/routes.py index bbed2c98..e31a6ce6 100644 --- a/app/activitypub/routes.py +++ b/app/activitypub/routes.py @@ -835,8 +835,7 @@ def process_inbox_request(request_json, activitypublog_id, ip_address): for ocp in old_cross_posts: if ocp.cross_posts is not None and post.id in ocp.cross_posts: ocp.cross_posts.remove(post.id) - delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id) - activity_log.result = 'success' + delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id, activity_log.id) elif request_json['object']['type'] == 'Page': # Sent for Mastodon's benefit activity_log.result = 'ignored' activity_log.exception_message = 'Intended for Mastodon' diff --git a/app/activitypub/util.py b/app/activitypub/util.py index 4def65ef..0f41bfda 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -220,6 +220,13 @@ def comment_model_to_json(reply: PostReply) -> dict: } if reply.edited_at: reply_data['updated'] = ap_datetime(reply.edited_at) + if reply.deleted: + if reply.deleted_by == reply.user_id: + reply_data['content'] = '
Deleted by author
' + reply_data['source']['content'] = 'Deleted by author' + else: + reply_data['content'] = 'Deleted by moderator
' + reply_data['source']['content'] = 'Deleted by moderator' return reply_data @@ -1295,18 +1302,24 @@ def is_activitypub_request(): return 'application/ld+json' in request.headers.get('Accept', '') or 'application/activity+json' in request.headers.get('Accept', '') -def delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id): +def delete_post_or_comment(user_ap_id, community_ap_id, to_be_deleted_ap_id, aplog_id): if current_app.debug: - delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id) + delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id, aplog_id) else: - delete_post_or_comment_task.delay(user_ap_id, community_ap_id, to_be_deleted_ap_id) + delete_post_or_comment_task.delay(user_ap_id, community_ap_id, to_be_deleted_ap_id, aplog_id) @celery.task -def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id): +def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id, aplog_id): deletor = find_actor_or_create(user_ap_id) community = find_actor_or_create(community_ap_id, community_only=True) to_delete = find_liked_object(to_be_deleted_ap_id) + if to_delete.deleted: + aplog = ActivityPubLog.query.get(aplog_id) + if aplog: + aplog.result = 'ignored' + aplog.exception_message = 'Activity about local content which is already present' + return if deletor and community and to_delete: if deletor.is_admin() or community.is_moderator(deletor) or community.is_instance_admin(deletor) or to_delete.author.id == deletor.id: @@ -1323,12 +1336,8 @@ def delete_post_or_comment_task(user_ap_id, community_ap_id, to_be_deleted_ap_id elif isinstance(to_delete, PostReply): if not to_delete.author.bot: to_delete.post.reply_count -= 1 - if to_delete.has_replies(): - to_delete.body = 'Deleted by author' if to_delete.author.id == deletor.id else 'Deleted by moderator' - to_delete.body_html = markdown_to_html(to_delete.body) - else: - to_delete.delete_dependencies() - to_delete.deleted = True + to_delete.deleted = True + to_delete.deleted_by = deletor.id to_delete.author.post_reply_count -= 1 to_delete.deleted_by = deletor.id db.session.commit() @@ -1398,12 +1407,8 @@ def remove_data_from_banned_user_task(deletor_ap_id, user_ap_id, target): for post_reply in post_replies: if not user.bot: post_reply.post.reply_count -= 1 - if post_reply.has_replies(): - post_reply.body = 'Banned' - post_reply.body_html = markdown_to_html(post_reply.body) - else: - post_reply.delete_dependencies() - post_reply.deleted = True + post_reply.deleted = True + post_reply.deleted_by = deletor.id db.session.commit() for post in posts: diff --git a/app/cli.py b/app/cli.py index a42783b9..4b22c811 100644 --- a/app/cli.py +++ b/app/cli.py @@ -195,7 +195,8 @@ def register(app): for post_reply in PostReply.query.filter(PostReply.deleted == True, PostReply.posted_at < utcnow() - timedelta(days=7)).all(): post_reply.delete_dependencies() - db.session.delete(post_reply) + if not post_reply.has_replies(): + db.session.delete(post_reply) db.session.commit() for post in Post.query.filter(Post.deleted == True, diff --git a/app/community/util.py b/app/community/util.py index b865d48c..7d5eba0f 100644 --- a/app/community/util.py +++ b/app/community/util.py @@ -566,12 +566,8 @@ def delete_post_reply_from_community_task(post_reply_id): post = post_reply.post community = post.community if post_reply.user_id == current_user.id or community.is_moderator(): - if post_reply.has_replies(): - post_reply.body = 'Deleted by author' if post_reply.author.id == current_user.id else 'Deleted by moderator' - post_reply.body_html = markdown_to_html(post_reply.body) - else: - post_reply.delete_dependencies() - post_reply.deleted = True + post_reply.deleted = True + post_reply.deleted_by = current_user.id db.session.commit() # federate delete diff --git a/app/models.py b/app/models.py index 38f67f19..2779d2f0 100644 --- a/app/models.py +++ b/app/models.py @@ -1513,9 +1513,18 @@ class PostReply(db.Model): return parent.author.public_url() def delete_dependencies(self): + """ + The first loop doesn't seem to ever be invoked with the current behaviour. + For replies with their own replies: functions which deal with removal don't set reply.deleted and don't call this, and + because reply.deleted isn't set, the cli task 7 days later doesn't call this either. + + The plan is to set reply.deleted whether there's child replies or not (as happens with the API call), so I've commented + it out so the current behaviour isn't changed. + for child_reply in self.child_replies(): child_reply.delete_dependencies() db.session.delete(child_reply) + """ db.session.query(PostReplyBookmark).filter(PostReplyBookmark.post_reply_id == self.id).delete() db.session.query(Report).filter(Report.suspect_post_reply_id == self.id).delete() diff --git a/app/post/routes.py b/app/post/routes.py index 47f5fc77..6770d1fc 100644 --- a/app/post/routes.py +++ b/app/post/routes.py @@ -681,9 +681,13 @@ def post_options(post_id: int): def post_reply_options(post_id: int, comment_id: int): post = Post.query.get_or_404(post_id) post_reply = PostReply.query.get_or_404(comment_id) - if current_user.is_anonymous or not current_user.is_admin(): - if post.deleted or post_reply.deleted: + if post.deleted or post_reply.deleted: + if current_user.is_anonymous: abort(404) + if (not post.community.is_moderator() and + not current_user.is_admin() and + (post_reply.deleted_by is not None and post_reply.deleted_by != current_user.id)): + abort(401) existing_bookmark = [] if current_user.is_authenticated: @@ -1597,17 +1601,12 @@ def post_reply_delete(post_id: int, comment_id: int): post_reply = PostReply.query.get_or_404(comment_id) community = post.community if post_reply.user_id == current_user.id or community.is_moderator() or current_user.is_admin(): - if post_reply.has_replies(): - post_reply.body = 'Deleted by author' if post_reply.author.id == current_user.id else 'Deleted by moderator' - post_reply.body_html = markdown_to_html(post_reply.body) - else: - post_reply.delete_dependencies() - post_reply.deleted = True + post_reply.deleted = True + post_reply.deleted_by = current_user.id g.site.last_active = community.last_active = utcnow() if not post_reply.author.bot: post.reply_count -= 1 post_reply.author.post_reply_count -= 1 - post_reply.deleted_by = current_user.id db.session.commit() flash(_('Comment deleted.')) # federate delete @@ -1627,11 +1626,12 @@ def post_reply_delete(post_id: int, comment_id: int): if post_reply.user_id != current_user.id: delete_json['summary'] = 'Deleted by mod' - 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.public_url() + '#main-key') - if success is False or isinstance(success, str): - flash('Failed to send delete to remote server', 'error') + if not post.community.is_local(): + if post_reply.user_id == current_user.id or post.community.is_moderator(current_user): + success = post_request(post.community.ap_inbox_url, delete_json, current_user.private_key, + current_user.public_url() + '#main-key') + if success is False or isinstance(success, str): + 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)}", @@ -1658,6 +1658,107 @@ def post_reply_delete(post_id: int, comment_id: int): return redirect(url_for('activitypub.post_ap', post_id=post.id)) +@bp.route('/post/Deleted by moderator
+ {% else -%} +Deleted by author
+ {% endif -%} + {% else -%} + {{ comment['comment'].body_html | community_links | safe }} + {% endif -%}
Deleted by moderator
+ {% else -%} +Deleted by author
+ {% endif -%} + {% else -%} + {{ comment['comment'].body_html | community_links | safe }} + {% endif -%}