basic mastodon interop

This commit is contained in:
rimu 2023-12-27 14:38:41 +13:00
parent 44b06cdad0
commit 6ac8aca227
4 changed files with 40 additions and 21 deletions

View file

@ -12,7 +12,7 @@ from app.models import User, Community, CommunityJoinRequest, CommunityMember, C
from app.activitypub.util import public_key, users_total, active_half_year, active_month, local_posts, local_comments, \
post_to_activity, find_actor_or_create, default_context, instance_blocked, find_reply_parent, find_liked_object, \
lemmy_site_data, instance_weight, is_activitypub_request, downvote_post_reply, downvote_post, upvote_post_reply, \
upvote_post, activity_already_ingested, make_image_sizes, delete_post_or_comment
upvote_post, activity_already_ingested, make_image_sizes, delete_post_or_comment, community_members
from app.utils import gibberish, get_setting, is_image_url, allowlist_html, html_to_markdown, render_template, \
domain_from_url, markdown_to_html, community_membership, ap_datetime
import werkzeug.exceptions
@ -347,6 +347,11 @@ def process_inbox_request(request_json, activitypublog_id):
community_ap_id = request_json['object']['cc'][0]
elif 'cc' in request_json['object'] and request_json['object']['cc']:
community_ap_id = request_json['object']['cc'][0]
if community_ap_id.endswith('/followers'): # mastodon
if 'inReplyTo' in request_json['object']:
post_being_replied_to = Post.query.filter_by(ap_id=request_json['object']['inReplyTo']).first()
if post_being_replied_to:
community_ap_id = post_being_replied_to.community.ap_profile_id
community = find_actor_or_create(community_ap_id)
user = find_actor_or_create(user_ap_id)
if (user and not user.is_local()) and community:
@ -959,29 +964,35 @@ def community_moderators(actor):
return jsonify(community_data)
@bp.route('/inspect')
def inspect():
return Response(b'<br><br>'.join(INBOX), status=200)
@bp.route('/u/<actor>/inbox', methods=['GET', 'POST'])
def user_inbox(actor):
resp = jsonify('ok')
resp.content_type = 'application/activity+json'
return resp
@bp.route('/users/<actor>/inbox', methods=['GET', 'POST'])
def inbox(actor):
""" To post to this inbox, you could use curl:
$ curl -d '{"key" : "value"}' -H "Content-Type: application/json" -X POST http://localhost:5001/users/test/inbox
Or, with an actual Mastodon follow request:
$ curl -d '{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":{"@id":"as:movedTo","@type":"@id"},"Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji","focalPoint":{"@container":"@list","@id":"toot:focalPoint"},"featured":{"@id":"toot:featured","@type":"@id"},"schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value"}],"id":"https://post.lurk.org/02d04ed5-dda6-48f3-a551-2e9c554de745","type":"Follow","actor":"https://post.lurk.org/users/manetta","object":"https://ap.virtualprivateserver.space/users/test","signature":{"type":"RsaSignature2017","creator":"https://post.lurk.org/users/manetta#main-key","created":"2018-11-28T16:15:35Z","signatureValue":"XUdBg+Zj9pkdOXlAYHhOtZlmU1Jdt63zwh2cXoJ8E8C1C+KvgGilkyfPTud9VNymVwdUQRl+YEW9KAZiiGaHb9H+tdVUr9BEkuR5E/tGehbMZr1sakC+qPehe4s3bRKEpJjTTJnTiSHaW7V6Qvr1u6+MVts6oj32az/ixuB/CfodSr3K/K+jZmmOl6SIUqX7Xg7xGwOxIsYaR7g9wbcJ4qyzKcTPZonPMsONq9/RSm3SeQBo7WO1FKlQiFxVP/y5eFaFP8GYDLZyK7Nj5kDL5TannfEpuF8f3oyTBErQhcFQYKcBZNbuaqX/WiIaGjtHIL2ctJe0Psb5Nfshx4MXmQ=="}}' -H "Content-Type: application/json" -X POST http://localhost:5001/users/test/inbox
"""
@bp.route('/c/<actor>/inbox', methods=['GET', 'POST'])
def community_inbox(actor):
return shared_inbox()
if request.method == 'GET':
return '''This has been a <em>{}</em> request. <br>
It came with the following header: <br><br><em>{}</em><br><br>
You have searched for the actor <em>{}</em>. <br>
This is <em>{}</em>'s shared inbox: <br><br><em>{}</em>'''.format(request.method, request.headers, actor,
current_app.config['SERVER_NAME'], str(INBOX))
if request.method == 'POST':
INBOX.append(request.data)
return Response(status=200)
@bp.route('/c/<actor>/followers', methods=['GET'])
def community_followers(actor):
actor = actor.strip()
community = Community.query.filter_by(name=actor, banned=False, ap_id=None).first()
if community is not None:
result = {
"@context": default_context(),
"id": f'https://{current_app.config["SERVER_NAME"]}/c/actor/followers',
"type": "Collection",
"totalItems": community_members(community.id),
"items": []
}
resp = jsonify(result)
resp.content_type = 'application/activity+json'
return resp
else:
abort(404)
@bp.route('/comment/<int:comment_id>', methods=['GET'])

View file

@ -34,6 +34,13 @@ def public_key():
return PUBLICKEY
def community_members(community_id):
sql = 'SELECT COUNT(id) as c FROM "user" as u '
sql += 'INNER JOIN community_member cm on u.id = cm.user_id '
sql += 'WHERE u.banned is false AND u.deleted is false AND cm.is_banned is false and cm.community_id = :community_id'
return db.session.execute(text(sql), {'community_id': community_id}).scalar()
def users_total():
return db.session.execute(text(
'SELECT COUNT(id) as c FROM "user" WHERE ap_id is null AND verified is true AND banned is false AND deleted is false')).scalar()

View file

@ -38,7 +38,7 @@ class CreatePostForm(FlaskForm):
communities = SelectField(_l('Community'), validators=[DataRequired()], coerce=int)
type = HiddenField() # https://getbootstrap.com/docs/4.6/components/navs/#tabs
discussion_title = StringField(_l('Title'), validators={Optional(), Length(min=3, max=255)})
discussion_body = TextAreaField(_l('Body'), render_kw={'placeholder': 'Text (optional)'})
discussion_body = TextAreaField(_l('Body'), validators={Optional(), Length(min=3, max=5000)}, render_kw={'placeholder': 'Text (optional)'})
link_title = StringField(_l('Title'), validators={Optional(), Length(min=3, max=255)})
link_url = StringField(_l('URL'), render_kw={'placeholder': 'https://...'})
image_title = StringField(_l('Title'), validators={Optional(), Length(min=3, max=255)})

View file

@ -38,6 +38,7 @@ def add_local():
rules=form.rules.data, nsfw=form.nsfw.data, private_key=private_key,
public_key=public_key,
ap_profile_id='https://' + current_app.config['SERVER_NAME'] + '/c/' + form.url.data,
ap_followers_url='https://' + current_app.config['SERVER_NAME'] + '/c/' + form.url.data + '/followers',
subscriptions_count=1, instance_id=1, low_quality='memes' in form.url.data)
icon_file = request.files['icon_file']
if icon_file and icon_file.filename != '':