mirror of
https://codeberg.org/rimu/pyfedi
synced 2025-01-23 19:36:56 -08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
2d6d9b960c
12 changed files with 192 additions and 50 deletions
|
@ -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'
|
||||
|
|
|
@ -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'] = '<p>Deleted by author</p>'
|
||||
reply_data['source']['content'] = 'Deleted by author'
|
||||
else:
|
||||
reply_data['content'] = '<p>Deleted by moderator</p>'
|
||||
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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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/<int:post_id>/comment/<int:comment_id>/restore', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def post_reply_restore(post_id: int, comment_id: int):
|
||||
post = Post.query.get_or_404(post_id)
|
||||
post_reply = PostReply.query.get_or_404(comment_id)
|
||||
community = post.community
|
||||
if post_reply.user_id == current_user.id or post.community.is_moderator() or current_user.is_admin():
|
||||
if post_reply.deleted_by == post_reply.user_id:
|
||||
was_mod_deletion = False
|
||||
else:
|
||||
was_mod_deletion = True
|
||||
post_reply.deleted = False
|
||||
post_reply.deleted_by = None
|
||||
if not post_reply.author.bot:
|
||||
post.reply_count += 1
|
||||
post_reply.author.post_reply_count += 1
|
||||
db.session.commit()
|
||||
flash(_('Comment restored.'))
|
||||
|
||||
# Federate un-delete
|
||||
if not post.community.local_only:
|
||||
delete_json = {
|
||||
"actor": current_user.public_url(),
|
||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object": {
|
||||
'id': f"https://{current_app.config['SERVER_NAME']}/activities/delete/{gibberish(15)}",
|
||||
'type': 'Delete',
|
||||
'actor': current_user.public_url(),
|
||||
'audience': post.community.public_url(),
|
||||
'to': [post.community.public_url(), 'https://www.w3.org/ns/activitystreams#Public'],
|
||||
'published': ap_datetime(utcnow()),
|
||||
'cc': [
|
||||
current_user.followers_url()
|
||||
],
|
||||
'object': post_reply.ap_id,
|
||||
'uri': post_reply.ap_id,
|
||||
},
|
||||
"cc": [post.community.public_url()],
|
||||
"audience": post.community.public_url(),
|
||||
"type": "Undo",
|
||||
"id": f"https://{current_app.config['SERVER_NAME']}/activities/undo/{gibberish(15)}"
|
||||
}
|
||||
if was_mod_deletion:
|
||||
delete_json['object']['summary'] = "Deleted by mod"
|
||||
|
||||
if not post.community.is_local(): # this is a remote community, send it to the instance that hosts it
|
||||
if not was_mod_deletion or (was_mod_deletion and 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)}",
|
||||
"type": 'Announce',
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"actor": post.community.public_url(),
|
||||
"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)
|
||||
|
||||
if post_reply.user_id != current_user.id:
|
||||
add_to_modlog('restore_post_reply', community_id=post.community.id, link_text=f'comment on {shorten_string(post.title)}',
|
||||
link=f'post/{post.id}#comment_{post_reply.id}')
|
||||
|
||||
return redirect(url_for('activitypub.post_ap', post_id=post.id))
|
||||
|
||||
|
||||
@bp.route('/post/<int:post_id>/comment/<int:comment_id>/purge', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def post_reply_purge(post_id: int, comment_id: int):
|
||||
post = Post.query.get_or_404(post_id)
|
||||
post_reply = PostReply.query.get_or_404(comment_id)
|
||||
if not post_reply.deleted:
|
||||
abort(404)
|
||||
if post_reply.user_id == current_user.id and (post_reply.deleted_by is None or post_reply.deleted_by != post_reply.user_id):
|
||||
abort(401)
|
||||
if post_reply.user_id == current_user.id or post.community.is_moderator() or current_user.is_admin():
|
||||
if not post_reply.has_replies():
|
||||
post_reply.delete_dependencies()
|
||||
db.session.delete(post_reply)
|
||||
db.session.commit()
|
||||
flash(_('Comment purged.'))
|
||||
else:
|
||||
flash(_('Comments that have been replied to cannot be purged.'))
|
||||
else:
|
||||
abort(401)
|
||||
|
||||
return redirect(url_for('activitypub.post_ap', post_id=post.id))
|
||||
|
||||
|
||||
@bp.route('/post/<int:post_id>/notification', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def post_notification(post_id: int):
|
||||
|
|
|
@ -11,7 +11,7 @@ from app.utils import blocked_instances, blocked_users
|
|||
|
||||
# replies to a post, in a tree, sorted by a variety of methods
|
||||
def post_replies(post_id: int, sort_by: str, show_first: int = 0) -> List[PostReply]:
|
||||
comments = PostReply.query.filter_by(post_id=post_id).filter(PostReply.deleted == False)
|
||||
comments = PostReply.query.filter_by(post_id=post_id)
|
||||
if current_user.is_authenticated:
|
||||
instance_ids = blocked_instances(current_user.id)
|
||||
if instance_ids:
|
||||
|
@ -52,7 +52,7 @@ def get_comment_branch(post_id: int, comment_id: int, sort_by: str) -> List[Post
|
|||
if parent_comment is None:
|
||||
return []
|
||||
|
||||
comments = PostReply.query.filter(PostReply.post_id == post_id, PostReply.deleted == False)
|
||||
comments = PostReply.query.filter(PostReply.post_id == post_id)
|
||||
if current_user.is_authenticated:
|
||||
instance_ids = blocked_instances(current_user.id)
|
||||
if instance_ids:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
no_collapse: Don't collapse for admin and moderator views
|
||||
#}
|
||||
{% if current_user.is_authenticated -%}
|
||||
{% set collapsed = (post_reply.score <= current_user.reply_collapse_threshold)
|
||||
{% set collapsed = ((post_reply.score <= current_user.reply_collapse_threshold) or post_reply.deleted)
|
||||
and not no_collapse -%}
|
||||
{% else -%}
|
||||
{% set collapsed = (post_reply.score <= -10) and not no_collapse -%}
|
||||
|
@ -36,6 +36,11 @@
|
|||
<span class="red fe fe-report" title="{{ _('Reported. Check comment for issues.') }}"></span>
|
||||
{% endif -%}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
{% if post_reply.deleted -%}
|
||||
<span class="red fe fe-delete" title="{{ _('Comment deleted') }}"></span>
|
||||
{% endif -%}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<a class="unhide" href="#"><span class="fe fe-expand"></span></a>
|
||||
</div>
|
||||
|
|
|
@ -28,7 +28,15 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
<div class="comment_body hidable {% if comment['comment'].reports and current_user.is_authenticated and post.community.is_moderator(current_user) %}reported{% endif %}">
|
||||
{{ comment['comment'].body_html | safe }}
|
||||
{% if comment['comment'].deleted -%}
|
||||
{% if comment['comment'].deleted_by is none or comment['comment'].deleted_by != comment['comment'].user_id -%}
|
||||
<p>Deleted by moderator</p>
|
||||
{% else -%}
|
||||
<p>Deleted by author</p>
|
||||
{% endif -%}
|
||||
{% else -%}
|
||||
{{ comment['comment'].body_html | community_links | safe }}
|
||||
{% endif -%}
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment_actions hidable">
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
aria-level="{{ comment['comment'].depth + 1 }}" role="treeitem" aria-expanded="true" tabindex="0">
|
||||
<div class="limit_height">{% if not comment['comment'].author.indexable -%}<!--googleoff: all-->{% endif -%}
|
||||
<div class="comment_author">
|
||||
{% with collapsed = comment['comment'].score < reply_collapse_threshold -%}
|
||||
{% with collapsed = (comment['comment'].score < reply_collapse_threshold or comment['comment'].deleted) -%}
|
||||
{{ render_username(comment['comment'].author) }}
|
||||
{% endwith -%}
|
||||
{% if comment['comment'].author.id == post.author.id -%}<span title="Submitter of original post" aria-label="{{ _('Post creator') }}" class="small">[OP] </span>{% endif -%}
|
||||
|
@ -91,7 +91,15 @@
|
|||
{% endif -%}
|
||||
</div>
|
||||
<div class="comment_body hidable {% if comment['comment'].reports and current_user.is_authenticated and post.community.is_moderator(current_user) -%}reported{% endif -%}">
|
||||
{{ comment['comment'].body_html | community_links | safe }}
|
||||
{% if comment['comment'].deleted -%}
|
||||
{% if comment['comment'].deleted_by is none or comment['comment'].deleted_by != comment['comment'].user_id -%}
|
||||
<p>Deleted by moderator</p>
|
||||
{% else -%}
|
||||
<p>Deleted by author</p>
|
||||
{% endif -%}
|
||||
{% else -%}
|
||||
{{ comment['comment'].body_html | community_links | safe }}
|
||||
{% endif -%}
|
||||
</div>{% if not comment['comment'].author.indexable -%}<!--googleon: all-->{% endif -%}
|
||||
</div>
|
||||
<div class="comment_actions hidable">
|
||||
|
@ -104,7 +112,7 @@
|
|||
{% endwith -%}
|
||||
</div>
|
||||
<div class="hide_button">
|
||||
{% if comment['comment'].score <= reply_collapse_threshold -%}
|
||||
{% if comment['comment'].score <= reply_collapse_threshold or comment['comment'].deleted -%}
|
||||
<a href='#'><span class="fe fe-expand"></span></a>
|
||||
{% else -%}
|
||||
<a href='#'><span class="fe fe-collapse"></span></a>
|
||||
|
@ -132,7 +140,7 @@
|
|||
{% endif -%}
|
||||
{% endif -%}
|
||||
</div>
|
||||
{% if comment['comment'].score <= reply_collapse_threshold -%}
|
||||
{% if comment['comment'].score <= reply_collapse_threshold or comment['comment'].deleted -%}
|
||||
<script nonce="{{ session['nonce'] }}" type="text/javascript">
|
||||
toBeHidden.push({{ comment['comment'].id }});
|
||||
</script>
|
||||
|
|
|
@ -19,8 +19,14 @@
|
|||
{% endif -%}
|
||||
{% if post_reply.user_id == current_user.id or post.community.is_moderator() or post.community.is_owner() or current_user.is_admin() -%}
|
||||
{% if post_reply.deleted -%}
|
||||
<li><a href="{{ url_for('post.post_reply_restore', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete"></span>
|
||||
<li><a href="{{ url_for('post.post_reply_restore', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-arrow-up"></span>
|
||||
{{ _('Restore') }}</a></li>
|
||||
{% if not post_reply.has_replies() -%}
|
||||
{% if post.community.is_moderator() or current_user.is_admin() or (post_reply.user_id == current_user.id and post_reply.deleted_by == post_reply.user_id) -%}
|
||||
<li><a href="{{ url_for('post.post_reply_purge', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete red"></span>
|
||||
{{ _('Purge') }}</a></li>
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
{% else -%}
|
||||
<li><a href="{{ url_for('post.post_reply_delete', post_id=post.id, comment_id=post_reply.id) }}" class="no-underline confirm_first" rel="nofollow"><span class="fe fe-delete"></span>
|
||||
{{ _('Delete') }}</a></li>
|
||||
|
|
|
@ -63,9 +63,13 @@ def show_profile(user):
|
|||
else:
|
||||
upvoted = []
|
||||
subscribed = Community.query.filter_by(banned=False).join(CommunityMember).filter(CommunityMember.user_id == user.id).all()
|
||||
if current_user.is_anonymous or user.id != current_user.id:
|
||||
if current_user.is_anonymous or (user.id != current_user.id and not current_user.is_admin()):
|
||||
moderates = moderates.filter(Community.private_mods == False)
|
||||
post_replies = PostReply.query.filter_by(user_id=user.id).filter(PostReply.deleted == False).order_by(desc(PostReply.posted_at)).paginate(page=replies_page, per_page=50, error_out=False)
|
||||
post_replies = PostReply.query.filter_by(user_id=user.id, deleted=False).order_by(desc(PostReply.posted_at)).paginate(page=replies_page, per_page=50, error_out=False)
|
||||
elif current_user.is_admin():
|
||||
post_replies = PostReply.query.filter_by(user_id=user.id).order_by(desc(PostReply.posted_at)).paginate(page=replies_page, per_page=50, error_out=False)
|
||||
elif current_user.id == user.id:
|
||||
post_replies = PostReply.query.filter_by(user_id=user.id).filter(or_(PostReply.deleted == False, PostReply.deleted_by == user.id)).order_by(desc(PostReply.posted_at)).paginate(page=replies_page, per_page=50, error_out=False)
|
||||
|
||||
# profile info
|
||||
canonical = user.ap_public_url if user.ap_public_url else None
|
||||
|
|
Loading…
Reference in a new issue