2023-11-21 23:05:07 +13:00
from datetime import datetime
2023-12-03 22:41:15 +13:00
from app import db , constants , cache
2023-08-05 21:24:10 +12:00
from app . activitypub import bp
2023-09-16 19:09:04 +12:00
from flask import request , Response , current_app , abort , jsonify , json
2023-08-29 22:01:06 +12:00
2023-09-08 20:04:01 +12:00
from app . activitypub . signature import HttpSignature
2023-08-29 22:01:06 +12:00
from app . community . routes import show_community
2023-12-09 22:14:16 +13:00
from app . post . routes import continue_discussion , show_post
2023-10-07 21:32:19 +13:00
from app . user . routes import show_profile
2023-12-03 22:41:15 +13:00
from app . constants import POST_TYPE_LINK , POST_TYPE_IMAGE , SUBSCRIPTION_MEMBER
2023-09-10 20:20:53 +12:00
from app . models import User , Community , CommunityJoinRequest , CommunityMember , CommunityBan , ActivityPubLog , Post , \
2023-11-23 15:10:44 +13:00
PostReply , Instance , PostVote , PostReplyVote , File , AllowedInstances , BannedInstances
2023-08-10 21:13:37 +12:00
from app . activitypub . util import public_key , users_total , active_half_year , active_month , local_posts , local_comments , \
2023-11-23 15:10:44 +13:00
post_to_activity , find_actor_or_create , default_context , instance_blocked , find_reply_parent , find_liked_object , \
2023-11-24 22:52:42 +13:00
lemmy_site_data , instance_weight
2023-09-16 19:09:04 +12:00
from app . utils import gibberish , get_setting , is_image_url , allowlist_html , html_to_markdown , render_template , \
2023-12-08 17:13:38 +13:00
domain_from_url , markdown_to_html , community_membership , ap_datetime
2023-10-10 22:25:37 +13:00
import werkzeug . exceptions
2023-08-05 21:24:10 +12:00
INBOX = [ ]
@bp.route ( ' /.well-known/webfinger ' )
def webfinger ( ) :
if request . args . get ( ' resource ' ) :
query = request . args . get ( ' resource ' ) # acct:alice@tada.club
if ' acct: ' in query :
actor = query . split ( ' : ' ) [ 1 ] . split ( ' @ ' ) [ 0 ] # alice
elif ' https: ' in query or ' http: ' in query :
actor = query . split ( ' / ' ) [ - 1 ]
else :
return ' Webfinger regex failed to match '
seperator = ' u '
type = ' Person '
user = User . query . filter_by ( user_name = actor . strip ( ) , deleted = False , banned = False , ap_id = None ) . first ( )
if user is None :
community = Community . query . filter_by ( name = actor . strip ( ) , ap_id = None ) . first ( )
if community is None :
return ' '
seperator = ' c '
type = ' Group '
webfinger_data = {
" subject " : f " acct: { actor } @ { current_app . config [ ' SERVER_NAME ' ] } " ,
" aliases " : [ f " https:// { current_app . config [ ' SERVER_NAME ' ] } / { seperator } / { actor } " ] ,
" links " : [
{
" rel " : " http://webfinger.net/rel/profile-page " ,
" type " : " text/html " ,
" href " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } / { seperator } / { actor } "
} ,
{
" rel " : " self " ,
" type " : " application/activity+json " ,
" href " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } / { seperator } / { actor } " ,
" properties " : {
" https://www.w3.org/ns/activitystreams#type " : type
}
}
]
}
resp = jsonify ( webfinger_data )
resp . headers . add_header ( ' Access-Control-Allow-Origin ' , ' * ' )
return resp
else :
abort ( 404 )
@bp.route ( ' /.well-known/nodeinfo ' )
def nodeinfo ( ) :
nodeinfo_data = { " links " : [ { " rel " : " http://nodeinfo.diaspora.software/ns/schema/2.0 " ,
" href " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /nodeinfo/2.0 " } ] }
return jsonify ( nodeinfo_data )
@bp.route ( ' /nodeinfo/2.0 ' )
2023-11-23 15:10:44 +13:00
@bp.route ( ' /nodeinfo/2.0.json ' )
2023-08-05 21:24:10 +12:00
def nodeinfo2 ( ) :
nodeinfo_data = {
" version " : " 2.0 " ,
" software " : {
2023-11-16 22:31:14 +13:00
" name " : " PieFed " ,
2023-08-05 21:24:10 +12:00
" version " : " 0.1 "
} ,
" protocols " : [
" activitypub "
] ,
" usage " : {
" users " : {
2023-08-10 21:13:37 +12:00
" total " : users_total ( ) ,
" activeHalfyear " : active_half_year ( ) ,
" activeMonth " : active_month ( )
2023-08-05 21:24:10 +12:00
} ,
2023-08-10 21:13:37 +12:00
" localPosts " : local_posts ( ) ,
" localComments " : local_comments ( )
2023-08-05 21:24:10 +12:00
} ,
" openRegistrations " : True
}
return jsonify ( nodeinfo_data )
2023-11-23 15:10:44 +13:00
@bp.route ( ' /api/v3/site ' )
def lemmy_site ( ) :
return jsonify ( lemmy_site_data ( ) )
@bp.route ( ' /api/v3/federated_instances ' )
def lemmy_federated_instances ( ) :
instances = Instance . query . all ( )
linked = [ ]
allowed = [ ]
blocked = [ ]
for instance in instances :
instance_data = { " id " : instance . id , " domain " : instance . domain , " published " : instance . created_at . isoformat ( ) , " updated " : instance . updated_at . isoformat ( ) }
if instance . software :
instance_data [ ' software ' ] = instance . software
if instance . version :
instance_data [ ' version ' ] = instance . version
linked . append ( instance_data )
for instance in AllowedInstances . query . all ( ) :
allowed . append ( { " id " : instance . id , " domain " : instance . domain , " published " : datetime . utcnow ( ) , " updated " : datetime . utcnow ( ) } )
for instance in BannedInstances . query . all ( ) :
blocked . append ( { " id " : instance . id , " domain " : instance . domain , " published " : datetime . utcnow ( ) , " updated " : datetime . utcnow ( ) } )
return jsonify ( {
" federated_instances " : {
" linked " : linked ,
" allowed " : allowed ,
" blocked " : blocked
}
} )
2023-12-09 22:14:16 +13:00
def is_activitypub_request ( ) :
return ' application/ld+json ' in request . headers . get ( ' Accept ' , ' ' ) or ' application/activity+json ' in request . headers . get ( ' Accept ' , ' ' )
2023-08-10 21:13:37 +12:00
@bp.route ( ' /u/<actor> ' , methods = [ ' GET ' ] )
def user_profile ( actor ) :
""" Requests to this endpoint can be for a JSON representation of the user, or a HTML rendering of their profile.
The two types of requests are differentiated by the header """
actor = actor . strip ( )
2023-11-24 20:22:58 +13:00
if ' @ ' in actor :
user = User . query . filter_by ( ap_id = actor , deleted = False , banned = False ) . first ( )
else :
user = User . query . filter_by ( user_name = actor , deleted = False , banned = False , ap_id = None ) . first ( )
2023-08-10 21:13:37 +12:00
if user is not None :
2023-12-09 22:14:16 +13:00
if is_activitypub_request ( ) :
2023-08-10 21:13:37 +12:00
server = current_app . config [ ' SERVER_NAME ' ]
2023-09-16 19:09:04 +12:00
actor_data = { " @context " : default_context ( ) ,
2023-08-10 21:13:37 +12:00
" type " : " Person " ,
" id " : f " https:// { server } /u/ { actor } " ,
" preferredUsername " : actor ,
" inbox " : f " https:// { server } /u/ { actor } /inbox " ,
" outbox " : f " https:// { server } /u/ { actor } /outbox " ,
" publicKey " : {
" id " : f " https:// { server } /u/ { actor } #main-key " ,
" owner " : f " https:// { server } /u/ { actor } " ,
2023-12-03 22:41:15 +13:00
" publicKeyPem " : user . public_key # .replace("\n", "\\n") #LOOKSWRONG
2023-08-10 21:13:37 +12:00
} ,
" endpoints " : {
" sharedInbox " : f " https:// { server } /inbox "
} ,
2023-12-08 17:13:38 +13:00
" published " : ap_datetime ( user . created ) ,
2023-08-10 21:13:37 +12:00
}
if user . avatar_id is not None :
actor_data [ " icon " ] = {
" type " : " Image " ,
2023-12-08 17:13:38 +13:00
" url " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } { user . avatar_image ( ) } "
}
if user . cover_id is not None :
actor_data [ " image " ] = {
" type " : " Image " ,
" url " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } { user . cover_image ( ) } "
2023-08-10 21:13:37 +12:00
}
2023-10-07 21:32:19 +13:00
if user . about :
actor_data [ ' source ' ] = {
" content " : user . about ,
" mediaType " : " text/markdown "
}
2023-10-10 22:25:37 +13:00
actor_data [ ' summary ' ] = markdown_to_html ( user . about )
2023-08-10 21:13:37 +12:00
resp = jsonify ( actor_data )
resp . content_type = ' application/activity+json '
return resp
else :
2023-10-07 21:32:19 +13:00
return show_profile ( user )
2023-10-21 15:49:01 +13:00
else :
abort ( 404 )
2023-08-10 21:13:37 +12:00
@bp.route ( ' /c/<actor> ' , methods = [ ' GET ' ] )
def community_profile ( actor ) :
""" Requests to this endpoint can be for a JSON representation of the community, or a HTML rendering of it.
The two types of requests are differentiated by the header """
actor = actor . strip ( )
2023-08-29 22:01:06 +12:00
if ' @ ' in actor :
# don't provide activitypub info for remote communities
2023-12-03 22:41:15 +13:00
if ' application/ld+json ' in request . headers . get ( ' Accept ' , ' ' ) or ' application/activity+json ' in request . headers . get ( ' Accept ' , ' ' ) :
2023-08-29 22:01:06 +12:00
abort ( 404 )
2023-11-16 22:31:14 +13:00
community : Community = Community . query . filter_by ( ap_id = actor , banned = False ) . first ( )
2023-08-29 22:01:06 +12:00
else :
2023-11-16 22:31:14 +13:00
community : Community = Community . query . filter_by ( name = actor , banned = False , ap_id = None ) . first ( )
2023-08-10 21:13:37 +12:00
if community is not None :
2023-12-09 22:14:16 +13:00
if is_activitypub_request ( ) :
2023-08-10 21:13:37 +12:00
server = current_app . config [ ' SERVER_NAME ' ]
2023-09-16 19:09:04 +12:00
actor_data = { " @context " : default_context ( ) ,
2023-08-10 21:13:37 +12:00
" type " : " Group " ,
" id " : f " https:// { server } /c/ { actor } " ,
2023-11-16 22:31:14 +13:00
" name " : community . title ,
" summary " : community . description ,
" sensitive " : True if community . nsfw or community . nsfl else False ,
2023-08-10 21:13:37 +12:00
" preferredUsername " : actor ,
" inbox " : f " https:// { server } /c/ { actor } /inbox " ,
" outbox " : f " https:// { server } /c/ { actor } /outbox " ,
" followers " : f " https:// { server } /c/ { actor } /followers " ,
" moderators " : f " https:// { server } /c/ { actor } /moderators " ,
" featured " : f " https:// { server } /c/ { actor } /featured " ,
" attributedTo " : f " https:// { server } /c/ { actor } /moderators " ,
2023-11-16 22:31:14 +13:00
" postingRestrictedToMods " : community . restricted_to_mods ,
2023-08-10 21:13:37 +12:00
" url " : f " https:// { server } /c/ { actor } " ,
" publicKey " : {
" id " : f " https:// { server } /c/ { actor } #main-key " ,
" owner " : f " https:// { server } /c/ { actor } " ,
2023-11-22 22:12:58 +13:00
" publicKeyPem " : community . public_key
2023-08-10 21:13:37 +12:00
} ,
" endpoints " : {
" sharedInbox " : f " https:// { server } /inbox "
} ,
2023-12-08 17:13:38 +13:00
" published " : ap_datetime ( community . created_at ) ,
" updated " : ap_datetime ( community . last_active ) ,
2023-08-10 21:13:37 +12:00
}
2023-11-16 22:31:14 +13:00
if community . icon_id is not None :
2023-08-10 21:13:37 +12:00
actor_data [ " icon " ] = {
" type " : " Image " ,
2023-12-08 17:13:38 +13:00
" url " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } { community . icon_image ( ) } "
}
if community . image_id is not None :
actor_data [ " image " ] = {
" type " : " Image " ,
" url " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } { community . header_image ( ) } "
2023-08-10 21:13:37 +12:00
}
resp = jsonify ( actor_data )
resp . content_type = ' application/activity+json '
return resp
2023-08-29 22:01:06 +12:00
else : # browser request - return html
return show_community ( community )
else :
abort ( 404 )
2023-08-10 21:13:37 +12:00
2023-09-08 20:04:01 +12:00
@bp.route ( ' /inbox ' , methods = [ ' GET ' , ' POST ' ] )
def shared_inbox ( ) :
if request . method == ' POST ' :
2023-09-09 20:46:40 +12:00
# save all incoming data to aid in debugging and development
activity_log = ActivityPubLog ( direction = ' in ' , activity_json = request . data , result = ' failure ' )
try :
request_json = request . get_json ( force = True )
except werkzeug . exceptions . BadRequest as e :
activity_log . exception_message = ' Unable to parse json body: ' + e . description
2023-09-16 19:09:04 +12:00
activity_log . result = ' failure '
2023-09-09 20:46:40 +12:00
db . session . add ( activity_log )
db . session . commit ( )
return
else :
if ' id ' in request_json :
activity_log . activity_id = request_json [ ' id ' ]
2023-09-16 19:09:04 +12:00
actor = find_actor_or_create ( request_json [ ' actor ' ] ) if ' actor ' in request_json else None
2023-09-08 20:04:01 +12:00
if actor is not None :
if HttpSignature . verify_request ( request , actor . public_key , skip_date = True ) :
if ' type ' in request_json :
2023-09-09 20:46:40 +12:00
activity_log . activity_type = request_json [ ' type ' ]
2023-09-16 19:09:04 +12:00
if not instance_blocked ( request_json [ ' id ' ] ) :
2023-11-23 22:36:12 +13:00
# Create is new content
2023-11-21 23:05:07 +13:00
if request_json [ ' type ' ] == ' Create ' :
2023-11-23 22:36:12 +13:00
activity_log . activity_type = ' Create '
2023-11-21 23:05:07 +13:00
user_ap_id = request_json [ ' object ' ] [ ' attributedTo ' ]
community_ap_id = request_json [ ' to ' ] [ 0 ]
2023-11-22 20:48:27 +13:00
if community_ap_id == ' https://www.w3.org/ns/activitystreams#Public ' : # kbin does this when posting a reply
2023-11-23 22:36:12 +13:00
if ' to ' in request_json [ ' object ' ] and request_json [ ' object ' ] [ ' to ' ] :
community_ap_id = request_json [ ' object ' ] [ ' to ' ] [ 0 ]
if community_ap_id == ' https://www.w3.org/ns/activitystreams#Public ' and ' cc ' in request_json [ ' object ' ] and request_json [ ' object ' ] [ ' cc ' ] :
community_ap_id = request_json [ ' object ' ] [ ' cc ' ] [ 0 ]
elif ' cc ' in request_json [ ' object ' ] and request_json [ ' object ' ] [ ' cc ' ] :
2023-11-22 20:48:27 +13:00
community_ap_id = request_json [ ' object ' ] [ ' cc ' ] [ 0 ]
2023-11-21 23:05:07 +13:00
community = find_actor_or_create ( community_ap_id )
user = find_actor_or_create ( user_ap_id )
if user and community :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-11-21 23:05:07 +13:00
object_type = request_json [ ' object ' ] [ ' type ' ]
new_content_types = [ ' Page ' , ' Article ' , ' Link ' , ' Note ' ]
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
if not in_reply_to :
post = Post ( user_id = user . id , community_id = community . id ,
title = request_json [ ' object ' ] [ ' name ' ] ,
comments_enabled = request_json [ ' object ' ] [
' commentsEnabled ' ] ,
sticky = request_json [ ' object ' ] [ ' stickied ' ] if ' stickied ' in
request_json [
' object ' ] else False ,
nsfw = request_json [ ' object ' ] [ ' sensitive ' ] ,
nsfl = request_json [ ' object ' ] [ ' nsfl ' ] if ' nsfl ' in request_json [
' object ' ] else False ,
ap_id = request_json [ ' object ' ] [ ' id ' ] ,
ap_create_id = request_json [ ' id ' ] ,
ap_announce_id = None ,
2023-11-24 22:52:42 +13:00
type = constants . POST_TYPE_ARTICLE ,
up_votes = 1 ,
score = instance_weight ( user . ap_domain )
2023-11-21 23:05:07 +13:00
)
2023-11-23 22:36:12 +13:00
if ' source ' in request_json [ ' object ' ] and \
request_json [ ' object ' ] [ ' source ' ] [
' mediaType ' ] == ' text/markdown ' :
post . body = request_json [ ' object ' ] [ ' source ' ] [ ' content ' ]
post . body_html = markdown_to_html ( post . body )
elif ' content ' in request_json [ ' object ' ] and request_json [ ' object ' ] [ ' content ' ] is not None :
2023-11-21 23:05:07 +13:00
post . body_html = allowlist_html ( request_json [ ' object ' ] [ ' content ' ] )
post . body = html_to_markdown ( post . body_html )
if ' attachment ' in request_json [ ' object ' ] and \
len ( request_json [ ' object ' ] [ ' attachment ' ] ) > 0 and \
' type ' in request_json [ ' object ' ] [ ' attachment ' ] [ 0 ] :
if request_json [ ' object ' ] [ ' attachment ' ] [ 0 ] [ ' type ' ] == ' Link ' :
post . url = request_json [ ' object ' ] [ ' attachment ' ] [ 0 ] [ ' href ' ]
if is_image_url ( post . url ) :
post . type = POST_TYPE_IMAGE
else :
post . type = POST_TYPE_LINK
domain = domain_from_url ( post . url )
if not domain . banned :
post . domain_id = domain . id
else :
post = None
activity_log . exception_message = domain . name + ' is blocked by admin '
activity_log . result = ' failure '
if ' image ' in request_json [ ' object ' ] :
image = File ( source_url = request_json [ ' object ' ] [ ' image ' ] [ ' url ' ] )
db . session . add ( image )
post . image = image
if post is not None :
db . session . add ( post )
community . post_count + = 1
2023-11-22 20:48:27 +13:00
community . last_active = datetime . utcnow ( )
2023-11-24 22:52:42 +13:00
activity_log . result = ' success '
2023-11-21 23:05:07 +13:00
db . session . commit ( )
2023-11-24 22:52:42 +13:00
vote = PostVote ( user_id = user . id , author_id = post . user_id ,
post_id = post . id ,
effect = instance_weight ( user . ap_domain ) )
db . session . add ( vote )
2023-11-21 23:05:07 +13:00
else :
post_id , parent_comment_id , root_id = find_reply_parent ( in_reply_to )
post_reply = PostReply ( user_id = user . id , community_id = community . id ,
post_id = post_id , parent_id = parent_comment_id ,
root_id = root_id ,
nsfw = community . nsfw ,
nsfl = community . nsfl ,
2023-11-24 22:52:42 +13:00
up_votes = 1 ,
score = instance_weight ( user . ap_domain ) ,
2023-11-21 23:05:07 +13:00
ap_id = request_json [ ' object ' ] [ ' id ' ] ,
ap_create_id = request_json [ ' id ' ] ,
ap_announce_id = None )
if ' source ' in request_json [ ' object ' ] and \
request_json [ ' object ' ] [ ' source ' ] [
' mediaType ' ] == ' text/markdown ' :
post_reply . body = request_json [ ' object ' ] [ ' source ' ] [ ' content ' ]
post_reply . body_html = markdown_to_html ( post_reply . body )
elif ' content ' in request_json [ ' object ' ] :
post_reply . body_html = allowlist_html (
request_json [ ' object ' ] [ ' content ' ] )
post_reply . body = html_to_markdown ( post_reply . body_html )
if post_reply is not None :
2023-11-22 20:48:27 +13:00
post = Post . query . get ( post_id )
2023-11-21 23:05:07 +13:00
db . session . add ( post_reply )
2023-11-22 20:48:27 +13:00
post . reply_count + = 1
2023-11-21 23:05:07 +13:00
community . post_reply_count + = 1
2023-12-09 22:14:16 +13:00
community . last_active = post . last_active = datetime . utcnow ( )
2023-11-24 22:52:42 +13:00
activity_log . result = ' success '
2023-11-21 23:05:07 +13:00
db . session . commit ( )
2023-11-24 22:52:42 +13:00
vote = PostReplyVote ( user_id = user . id , author_id = post_reply . user_id , post_reply_id = post_reply . id ,
effect = instance_weight ( user . ap_domain ) )
db . session . add ( vote )
2023-11-21 23:05:07 +13:00
else :
activity_log . exception_message = ' Unacceptable type (kbin): ' + object_type
2023-12-03 22:41:15 +13:00
# Announce is new content and votes, mastodon style (?)
2023-09-16 19:09:04 +12:00
if request_json [ ' type ' ] == ' Announce ' :
if request_json [ ' object ' ] [ ' type ' ] == ' Create ' :
activity_log . activity_type = request_json [ ' object ' ] [ ' type ' ]
user_ap_id = request_json [ ' object ' ] [ ' object ' ] [ ' attributedTo ' ]
community_ap_id = request_json [ ' object ' ] [ ' audience ' ]
community = find_actor_or_create ( community_ap_id )
2023-09-10 20:20:53 +12:00
user = find_actor_or_create ( user_ap_id )
2023-09-16 19:09:04 +12:00
if user and community :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-09-16 19:09:04 +12:00
object_type = request_json [ ' object ' ] [ ' object ' ] [ ' type ' ]
new_content_types = [ ' Page ' , ' Article ' , ' Link ' , ' Note ' ]
if object_type in new_content_types : # create a new post
in_reply_to = request_json [ ' object ' ] [ ' object ' ] [ ' inReplyTo ' ] if ' inReplyTo ' in \
request_json [ ' object ' ] [ ' object ' ] else None
if not in_reply_to :
post = Post ( user_id = user . id , community_id = community . id ,
title = request_json [ ' object ' ] [ ' object ' ] [ ' name ' ] ,
comments_enabled = request_json [ ' object ' ] [ ' object ' ] [ ' commentsEnabled ' ] ,
sticky = request_json [ ' object ' ] [ ' object ' ] [ ' stickied ' ] if ' stickied ' in request_json [ ' object ' ] [ ' object ' ] else False ,
nsfw = request_json [ ' object ' ] [ ' object ' ] [ ' sensitive ' ] ,
nsfl = request_json [ ' object ' ] [ ' object ' ] [ ' nsfl ' ] if ' nsfl ' in request_json [ ' object ' ] [ ' object ' ] else False ,
ap_id = request_json [ ' object ' ] [ ' object ' ] [ ' id ' ] ,
ap_create_id = request_json [ ' object ' ] [ ' id ' ] ,
ap_announce_id = request_json [ ' id ' ] ,
2023-11-21 23:05:07 +13:00
type = constants . POST_TYPE_ARTICLE
2023-09-16 19:09:04 +12:00
)
if ' source ' in request_json [ ' object ' ] [ ' object ' ] and \
request_json [ ' object ' ] [ ' object ' ] [ ' source ' ] [ ' mediaType ' ] == ' text/markdown ' :
post . body = request_json [ ' object ' ] [ ' object ' ] [ ' source ' ] [ ' content ' ]
2023-10-10 22:25:37 +13:00
post . body_html = markdown_to_html ( post . body )
2023-09-16 19:09:04 +12:00
elif ' content ' in request_json [ ' object ' ] [ ' object ' ] :
post . body_html = allowlist_html ( request_json [ ' object ' ] [ ' object ' ] [ ' content ' ] )
post . body = html_to_markdown ( post . body_html )
if ' attachment ' in request_json [ ' object ' ] [ ' object ' ] and \
len ( request_json [ ' object ' ] [ ' object ' ] [ ' attachment ' ] ) > 0 and \
' type ' in request_json [ ' object ' ] [ ' object ' ] [ ' attachment ' ] [ 0 ] :
if request_json [ ' object ' ] [ ' object ' ] [ ' attachment ' ] [ 0 ] [ ' type ' ] == ' Link ' :
post . url = request_json [ ' object ' ] [ ' object ' ] [ ' attachment ' ] [ 0 ] [ ' href ' ]
if is_image_url ( post . url ) :
post . type = POST_TYPE_IMAGE
else :
post . type = POST_TYPE_LINK
domain = domain_from_url ( post . url )
if not domain . banned :
post . domain_id = domain . id
else :
post = None
activity_log . exception_message = domain . name + ' is blocked by admin '
activity_log . result = ' failure '
if ' image ' in request_json [ ' object ' ] [ ' object ' ] :
image = File ( source_url = request_json [ ' object ' ] [ ' object ' ] [ ' image ' ] [ ' url ' ] )
db . session . add ( image )
post . image = image
if post is not None :
db . session . add ( post )
2023-10-02 22:16:44 +13:00
community . post_count + = 1
2023-09-16 19:09:04 +12:00
db . session . commit ( )
else :
post_id , parent_comment_id , root_id = find_reply_parent ( in_reply_to )
post_reply = PostReply ( user_id = user . id , community_id = community . id ,
post_id = post_id , parent_id = parent_comment_id ,
root_id = root_id ,
nsfw = community . nsfw ,
nsfl = community . nsfl ,
ap_id = request_json [ ' object ' ] [ ' object ' ] [ ' id ' ] ,
ap_create_id = request_json [ ' object ' ] [ ' id ' ] ,
ap_announce_id = request_json [ ' id ' ] )
if ' source ' in request_json [ ' object ' ] [ ' object ' ] and \
request_json [ ' object ' ] [ ' object ' ] [ ' source ' ] [
' mediaType ' ] == ' text/markdown ' :
post_reply . body = request_json [ ' object ' ] [ ' object ' ] [ ' source ' ] [ ' content ' ]
2023-10-10 22:25:37 +13:00
post_reply . body_html = markdown_to_html ( post_reply . body )
2023-09-16 19:09:04 +12:00
elif ' content ' in request_json [ ' object ' ] [ ' object ' ] :
post_reply . body_html = allowlist_html (
request_json [ ' object ' ] [ ' object ' ] [ ' content ' ] )
post_reply . body = html_to_markdown ( post_reply . body_html )
if post_reply is not None :
db . session . add ( post_reply )
2023-10-02 22:16:44 +13:00
community . post_reply_count + = 1
2023-09-16 19:09:04 +12:00
db . session . commit ( )
2023-09-10 20:20:53 +12:00
else :
2023-09-16 19:09:04 +12:00
activity_log . exception_message = ' Unacceptable type: ' + object_type
elif request_json [ ' object ' ] [ ' type ' ] == ' Like ' or request_json [ ' object ' ] [ ' type ' ] == ' Dislike ' :
activity_log . activity_type = request_json [ ' object ' ] [ ' type ' ]
vote_effect = 1.0 if request_json [ ' object ' ] [ ' type ' ] == ' Like ' else - 1.0
if vote_effect < 0 and get_setting ( ' allow_dislike ' , True ) is False :
activity_log . exception_message = ' Dislike ignored because of allow_dislike setting '
else :
user_ap_id = request_json [ ' object ' ] [ ' actor ' ]
liked_ap_id = request_json [ ' object ' ] [ ' object ' ]
user = find_actor_or_create ( user_ap_id )
2023-10-21 15:49:01 +13:00
if user :
2023-11-24 22:52:42 +13:00
vote_weight = instance_weight ( user . ap_domain )
2023-10-21 15:49:01 +13:00
liked = find_liked_object ( liked_ap_id )
# insert into voted table
2023-12-03 22:41:15 +13:00
if liked is None :
activity_log . exception_message = ' Liked object not found '
elif liked is not None and isinstance ( liked , Post ) :
2023-10-21 15:49:01 +13:00
existing_vote = PostVote . query . filter_by ( user_id = user . id , post_id = liked . id ) . first ( )
if existing_vote :
existing_vote . effect = vote_effect * vote_weight
else :
vote = PostVote ( user_id = user . id , author_id = liked . user_id , post_id = liked . id ,
effect = vote_effect * vote_weight )
db . session . add ( vote )
db . session . commit ( )
activity_log . result = ' success '
elif liked is not None and isinstance ( liked , PostReply ) :
existing_vote = PostVote . query . filter_by ( user_id = user . id , post_id = liked . id ) . first ( )
if existing_vote :
existing_vote . effect = vote_effect * vote_weight
else :
vote = PostReplyVote ( user_id = user . id , author_id = liked . user_id , post_reply_id = liked . id ,
effect = vote_effect * vote_weight )
db . session . add ( vote )
db . session . commit ( )
activity_log . result = ' success '
2023-09-16 19:09:04 +12:00
else :
2023-10-21 15:49:01 +13:00
activity_log . exception_message = ' Could not detect type of like '
if activity_log . result == ' success ' :
. . . # todo: recalculate 'hotness' of liked post/reply
# todo: if vote was on content in local community, federate the vote out to followers
2023-09-16 19:09:04 +12:00
2023-12-03 22:41:15 +13:00
# Follow: remote user wants to join/follow one of our communities
2023-09-16 19:09:04 +12:00
elif request_json [ ' type ' ] == ' Follow ' : # Follow is when someone wants to join a community
user_ap_id = request_json [ ' actor ' ]
community_ap_id = request_json [ ' object ' ]
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 :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-12-03 22:41:15 +13:00
if community_membership ( user , community ) != SUBSCRIPTION_MEMBER :
2023-09-16 19:09:04 +12:00
member = CommunityMember ( user_id = user . id , community_id = community . id )
db . session . add ( member )
db . session . commit ( )
2023-12-03 22:41:15 +13:00
cache . delete_memoized ( community_membership , user , community )
2023-09-16 19:09:04 +12:00
# 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 )
}
try :
HttpSignature . signed_request ( user . ap_inbox_url , accept , community . private_key ,
f " https:// { current_app . config [ ' SERVER_NAME ' ] } /c/ { community . name } #main-key " )
except Exception as e :
accept_log = ActivityPubLog ( direction = ' out ' , activity_json = json . dumps ( accept ) ,
result = ' failure ' , activity_id = accept [ ' id ' ] ,
exception_message = ' could not send Accept ' + str ( e ) )
db . session . add ( accept_log )
db . session . commit ( )
return
2023-09-10 20:20:53 +12:00
activity_log . result = ' success '
else :
2023-09-16 19:09:04 +12:00
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 ' :
community_ap_id = request_json [ ' actor ' ]
user_ap_id = request_json [ ' object ' ] [ ' actor ' ]
user = find_actor_or_create ( user_ap_id )
community = find_actor_or_create ( community_ap_id )
2023-10-21 15:49:01 +13:00
if user and community :
join_request = CommunityJoinRequest . query . filter_by ( user_id = user . id ,
community_id = community . id ) . first ( )
if join_request :
member = CommunityMember ( user_id = user . id , community_id = community . id )
db . session . add ( member )
community . subscriptions_count + = 1
db . session . commit ( )
activity_log . result = ' success '
2023-12-03 22:41:15 +13:00
cache . delete_memoized ( community_membership , user , community )
2023-11-16 22:31:14 +13:00
elif request_json [ ' type ' ] == ' Undo ' :
if request_json [ ' object ' ] [ ' type ' ] == ' Follow ' : # Unsubscribe from a community
2023-12-03 22:41:15 +13:00
community_ap_id = request_json [ ' object ' ] [ ' object ' ]
2023-11-16 22:31:14 +13:00
user_ap_id = request_json [ ' object ' ] [ ' actor ' ]
user = find_actor_or_create ( user_ap_id )
community = find_actor_or_create ( community_ap_id )
if user and community :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-11-16 22:31:14 +13:00
member = CommunityMember . query . filter_by ( user_id = user . id , community_id = community . id ) . first ( )
2023-11-17 22:02:44 +13:00
join_request = CommunityJoinRequest . query . filter_by ( user_id = user . id ,
community_id = community . id ) . first ( )
if member :
db . session . delete ( member )
if join_request :
db . session . delete ( join_request )
2023-11-16 22:31:14 +13:00
db . session . commit ( )
activity_log . result = ' success '
2023-11-24 22:28:31 +13:00
elif request_json [ ' object ' ] [ ' type ' ] == ' Like ' : # Undoing an upvote or downvote
2023-11-23 22:36:12 +13:00
activity_log . activity_type = request_json [ ' object ' ] [ ' type ' ]
user_ap_id = request_json [ ' actor ' ]
user = find_actor_or_create ( user_ap_id )
post = None
comment = None
target_ap_id = request_json [ ' object ' ] [ ' object ' ]
if ' /comment/ ' in target_ap_id :
comment = PostReply . query . filter_by ( ap_id = target_ap_id ) . first ( )
if ' /post/ ' in target_ap_id :
post = Post . query . filter_by ( ap_id = target_ap_id ) . first ( )
if user and post :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-11-23 22:36:12 +13:00
existing_vote = PostVote . query . filter_by ( user_id = user . id , post_id = post . id ) . first ( )
if existing_vote :
post . author . reputation - = existing_vote . effect
2023-11-24 22:28:31 +13:00
if existing_vote . effect < 0 : # Lemmy sends 'like' for upvote and 'dislike' for down votes. Cool! When it undoes an upvote it sends an 'Undo Like'. Fine. When it undoes a downvote it sends an 'Undo Like' - not 'Undo Dislike'?!
post . down_votes - = 1
else :
post . up_votes - = 1
post . score - = existing_vote . effect
2023-11-23 22:36:12 +13:00
db . session . delete ( existing_vote )
2023-11-24 22:28:31 +13:00
activity_log . result = ' success '
2023-11-23 22:36:12 +13:00
if user and comment :
existing_vote = PostReplyVote . query . filter_by ( user_id = user . id , post_reply_id = comment . id ) . first ( )
if existing_vote :
comment . author . reputation - = existing_vote . effect
2023-11-24 22:52:42 +13:00
if existing_vote . effect < 0 : # Lemmy sends 'like' for upvote and 'dislike' for down votes. Cool! When it undoes an upvote it sends an 'Undo Like'. Fine. When it undoes a downvote it sends an 'Undo Like' - not 'Undo Dislike'?!
comment . down_votes - = 1
else :
comment . up_votes - = 1
2023-11-24 22:28:31 +13:00
comment . score - = existing_vote . effect
2023-11-23 22:36:12 +13:00
db . session . delete ( existing_vote )
activity_log . result = ' success '
2023-11-24 22:28:31 +13:00
elif request_json [ ' object ' ] [ ' type ' ] == ' Dislike ' : # Undoing a downvote - probably unused
2023-11-23 22:36:12 +13:00
activity_log . activity_type = request_json [ ' object ' ] [ ' type ' ]
user_ap_id = request_json [ ' actor ' ]
user = find_actor_or_create ( user_ap_id )
post = None
comment = None
target_ap_id = request_json [ ' object ' ] [ ' object ' ]
if ' /comment/ ' in target_ap_id :
comment = PostReply . query . filter_by ( ap_id = target_ap_id ) . first ( )
if ' /post/ ' in target_ap_id :
post = Post . query . filter_by ( ap_id = target_ap_id ) . first ( )
if user and post :
existing_vote = PostVote . query . filter_by ( user_id = user . id , post_id = post . id ) . first ( )
if existing_vote :
2023-11-24 22:28:31 +13:00
post . author . reputation - = existing_vote . effect
post . down_votes - = 1
post . score - = existing_vote . effect
2023-11-23 22:36:12 +13:00
db . session . delete ( existing_vote )
2023-11-24 22:28:31 +13:00
activity_log . result = ' success '
2023-11-23 22:36:12 +13:00
if user and comment :
existing_vote = PostReplyVote . query . filter_by ( user_id = user . id ,
post_reply_id = comment . id ) . first ( )
if existing_vote :
2023-11-24 22:28:31 +13:00
comment . author . reputation - = existing_vote . effect
comment . down_votes - = 1
comment . score - = existing_vote . effect
2023-11-23 22:36:12 +13:00
db . session . delete ( existing_vote )
activity_log . result = ' success '
2023-11-21 23:05:07 +13:00
elif request_json [ ' type ' ] == ' Update ' :
if request_json [ ' object ' ] [ ' type ' ] == ' Page ' : # Editing a post
post = Post . query . filter_by ( ap_id = request_json [ ' object ' ] [ ' id ' ] ) . first ( )
if post :
if ' source ' in request_json [ ' object ' ] and \
request_json [ ' object ' ] [ ' source ' ] [ ' mediaType ' ] == ' text/markdown ' :
post . body = request_json [ ' object ' ] [ ' source ' ] [ ' content ' ]
post . body_html = markdown_to_html ( post . body )
elif ' content ' in request_json [ ' object ' ] :
post . body_html = allowlist_html ( request_json [ ' object ' ] [ ' content ' ] )
post . body = html_to_markdown ( post . body_html )
post . edited_at = datetime . utcnow ( )
db . session . commit ( )
activity_log . result = ' success '
2023-11-22 20:48:27 +13:00
elif request_json [ ' object ' ] [ ' type ' ] == ' Note ' : # Editing a reply
reply = PostReply . query . filter_by ( ap_id = request_json [ ' object ' ] [ ' id ' ] ) . first ( )
if reply :
if ' source ' in request_json [ ' object ' ] and \
request_json [ ' object ' ] [ ' source ' ] [ ' mediaType ' ] == ' text/markdown ' :
reply . body = request_json [ ' object ' ] [ ' source ' ] [ ' content ' ]
reply . body_html = markdown_to_html ( reply . body )
elif ' content ' in request_json [ ' object ' ] :
reply . body_html = allowlist_html ( request_json [ ' object ' ] [ ' content ' ] )
reply . body = html_to_markdown ( reply . body_html )
reply . edited_at = datetime . utcnow ( )
db . session . commit ( )
activity_log . result = ' success '
2023-11-21 23:05:07 +13:00
elif request_json [ ' type ' ] == ' Delete ' :
2023-11-23 22:36:12 +13:00
if isinstance ( request_json [ ' object ' ] , str ) :
ap_id = request_json [ ' object ' ] # lemmy
else :
ap_id = request_json [ ' object ' ] [ ' id ' ] # kbin
post = Post . query . filter_by ( ap_id = ap_id ) . first ( )
2023-11-21 23:05:07 +13:00
if post :
post . delete_dependencies ( )
db . session . delete ( post )
else :
2023-11-23 22:36:12 +13:00
reply = PostReply . query . filter_by ( ap_id = ap_id ) . first ( )
2023-11-21 23:05:07 +13:00
if reply :
reply . body_html = ' <p><em>deleted</em></p> '
reply . body = ' deleted '
db . session . commit ( )
activity_log . result = ' success '
2023-11-24 22:52:42 +13:00
elif request_json [ ' type ' ] == ' Like ' : # Upvote
2023-11-23 22:36:12 +13:00
activity_log . activity_type = request_json [ ' type ' ]
user_ap_id = request_json [ ' actor ' ]
user = find_actor_or_create ( user_ap_id )
target_ap_id = request_json [ ' object ' ]
post = None
comment = None
2023-11-24 22:52:42 +13:00
effect = instance_weight ( user . ap_domain )
2023-11-23 22:36:12 +13:00
if ' /comment/ ' in target_ap_id :
comment = PostReply . query . filter_by ( ap_id = target_ap_id ) . first ( )
if ' /post/ ' in target_ap_id :
post = Post . query . filter_by ( ap_id = target_ap_id ) . first ( )
if user and post :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-11-24 22:28:31 +13:00
existing_vote = PostVote . query . filter_by ( user_id = user . id , post_id = post . id ) . first ( )
if not existing_vote :
post . up_votes + = 1
post . score + = effect
vote = PostVote ( user_id = user . id , post_id = post . id , author_id = post . author . id ,
effect = effect )
post . author . reputation + = effect
db . session . add ( vote )
else :
# remove previous cast downvote
if existing_vote . effect < 0 :
post . author . reputation - = existing_vote . effect
post . down_votes - = 1
post . score - = existing_vote . effect
db . session . delete ( existing_vote )
# apply up vote
post . up_votes + = 1
post . score + = effect
vote = PostVote ( user_id = user . id , post_id = post . id , author_id = post . author . id ,
effect = effect )
post . author . reputation + = effect
db . session . add ( vote )
2023-11-23 22:36:12 +13:00
activity_log . result = ' success '
elif user and comment :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-11-23 22:36:12 +13:00
existing_vote = PostReplyVote . query . filter_by ( user_id = user . id ,
post_reply_id = comment . id ) . first ( )
if not existing_vote :
2023-11-24 22:28:31 +13:00
comment . up_votes + = 1
comment . score + = effect
2023-11-23 22:36:12 +13:00
vote = PostReplyVote ( user_id = user . id , post_reply_id = comment . id ,
author_id = comment . author . id , effect = effect )
comment . author . reputation + = effect
db . session . add ( vote )
else :
2023-11-24 22:28:31 +13:00
# remove previously cast downvote
if existing_vote . effect < 0 :
2023-11-23 22:36:12 +13:00
comment . author . reputation - = existing_vote . effect
2023-11-24 22:28:31 +13:00
comment . down_votes - = 1
comment . score - = existing_vote . effect
2023-11-23 22:36:12 +13:00
db . session . delete ( existing_vote )
2023-11-24 22:28:31 +13:00
# apply up vote
comment . up_votes + = 1
comment . score + = effect
2023-11-23 22:36:12 +13:00
vote = PostReplyVote ( user_id = user . id , post_reply_id = comment . id ,
author_id = comment . author . id , effect = effect )
comment . author . reputation + = effect
db . session . add ( vote )
else :
2023-11-24 22:28:31 +13:00
pass # they have already upvoted this reply
2023-11-23 22:36:12 +13:00
activity_log . result = ' success '
2023-11-24 22:52:42 +13:00
elif request_json [ ' type ' ] == ' Dislike ' : # Downvote
2023-11-24 22:28:31 +13:00
if get_setting ( ' allow_dislike ' , True ) is False :
activity_log . exception_message = ' Dislike ignored because of allow_dislike setting '
else :
activity_log . activity_type = request_json [ ' type ' ]
user_ap_id = request_json [ ' actor ' ]
user = find_actor_or_create ( user_ap_id )
target_ap_id = request_json [ ' object ' ]
post = None
comment = None
if ' /comment/ ' in target_ap_id :
comment = PostReply . query . filter_by ( ap_id = target_ap_id ) . first ( )
if ' /post/ ' in target_ap_id :
post = Post . query . filter_by ( ap_id = target_ap_id ) . first ( )
if user and comment :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-11-24 22:28:31 +13:00
existing_vote = PostReplyVote . query . filter_by ( user_id = user . id ,
post_reply_id = comment . id ) . first ( )
if not existing_vote :
effect = - 1.0
comment . down_votes + = 1
comment . score - = 1.0
vote = PostReplyVote ( user_id = user . id , post_reply_id = comment . id ,
author_id = comment . author . id , effect = effect )
comment . author . reputation + = effect
db . session . add ( vote )
else :
# remove previously cast upvote
if existing_vote . effect > 0 :
comment . author . reputation - = existing_vote . effect
comment . up_votes - = 1
comment . score - = existing_vote . effect
db . session . delete ( existing_vote )
# apply down vote
effect = - 1.0
comment . down_votes + = 1
comment . score - = 1.0
vote = PostReplyVote ( user_id = user . id , post_reply_id = comment . id ,
author_id = comment . author . id , effect = effect )
comment . author . reputation + = effect
db . session . add ( vote )
else :
pass # they have already downvoted this reply
activity_log . result = ' success '
elif user and post :
2023-12-09 22:28:20 +13:00
user . last_seen = datetime . utcnow ( )
2023-11-24 22:28:31 +13:00
existing_vote = PostVote . query . filter_by ( user_id = user . id , post_id = post . id ) . first ( )
if not existing_vote :
effect = - 1.0
2023-11-23 22:36:12 +13:00
post . down_votes + = 1
2023-11-24 22:28:31 +13:00
post . score - = 1.0
2023-11-23 22:36:12 +13:00
vote = PostVote ( user_id = user . id , post_id = post . id , author_id = post . author . id ,
effect = effect )
post . author . reputation + = effect
db . session . add ( vote )
else :
2023-11-24 22:28:31 +13:00
# remove previously cast upvote
if existing_vote . effect > 0 :
post . author . reputation - = existing_vote . effect
post . up_votes - = 1
post . score - = existing_vote . effect
db . session . delete ( existing_vote )
# apply down vote
effect = - 1.0
post . down_votes + = 1
post . score - = 1.0
vote = PostVote ( user_id = user . id , post_id = post . id , author_id = post . author . id ,
effect = effect )
post . author . reputation + = effect
db . session . add ( vote )
else :
pass # they have already downvoted this post
activity_log . result = ' success '
else :
activity_log . exception_message = ' Could not find user or content for vote '
2023-09-16 19:09:04 +12:00
else :
activity_log . exception_message = ' Instance banned '
2023-09-09 20:46:40 +12:00
else :
activity_log . exception_message = ' Could not verify signature '
else :
activity_log . exception_message = ' Actor could not be found: ' + request_json [ ' actor ' ]
2023-09-08 20:04:01 +12:00
2023-09-16 19:09:04 +12:00
if activity_log . exception_message is not None :
activity_log . result = ' failure '
2023-09-09 20:46:40 +12:00
db . session . add ( activity_log )
db . session . commit ( )
2023-11-16 22:31:14 +13:00
return ' '
2023-09-08 20:04:01 +12:00
2023-08-10 21:13:37 +12:00
@bp.route ( ' /c/<actor>/outbox ' , methods = [ ' GET ' ] )
def community_outbox ( actor ) :
actor = actor . strip ( )
community = Community . query . filter_by ( name = actor , banned = False , ap_id = None ) . first ( )
if community is not None :
posts = community . posts . limit ( 50 ) . all ( )
community_data = {
2023-09-16 19:09:04 +12:00
" @context " : default_context ( ) ,
2023-08-10 21:13:37 +12:00
" type " : " OrderedCollection " ,
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /c/ { actor } /outbox " ,
" totalItems " : len ( posts ) ,
" orderedItems " : [ ]
}
for post in posts :
community_data [ ' orderedItems ' ] . append ( post_to_activity ( post , community ) )
2023-08-05 21:24:10 +12:00
2023-08-10 21:13:37 +12:00
return jsonify ( community_data )
2023-08-05 21:24:10 +12:00
2023-11-26 23:20:51 +13:00
@bp.route ( ' /c/<actor>/moderators ' , methods = [ ' GET ' ] )
def community_moderators ( actor ) :
actor = actor . strip ( )
community = Community . query . filter_by ( name = actor , banned = False , ap_id = None ) . first ( )
if community is not None :
moderator_ids = community . moderators ( )
moderators = User . query . filter ( User . id . in_ ( [ mod . user_id for mod in moderator_ids ] ) ) . all ( )
community_data = {
" @context " : default_context ( ) ,
" type " : " OrderedCollection " ,
" id " : f " https:// { current_app . config [ ' SERVER_NAME ' ] } /c/ { actor } /moderators " ,
" totalItems " : len ( moderators ) ,
" orderedItems " : [ ]
}
for moderator in moderators :
community_data [ ' orderedItems ' ] . append ( moderator . ap_profile_id )
return jsonify ( community_data )
2023-08-05 21:24:10 +12:00
@bp.route ( ' /inspect ' )
def inspect ( ) :
return Response ( b ' <br><br> ' . join ( INBOX ) , status = 200 )
@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
"""
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 )
2023-12-09 22:14:16 +13:00
@bp.route ( ' /comment/<int:comment_id> ' , methods = [ ' GET ' ] )
def comment_ap ( comment_id ) :
if is_activitypub_request ( ) :
reply = PostReply . query . get_or_404 ( comment_id )
reply_data = {
" @context " : default_context ( ) ,
" type " : " Note " ,
" id " : reply . ap_id ,
" attributedTo " : reply . author . profile_id ( ) ,
" inReplyTo " : reply . in_reply_to ( ) ,
" to " : [
" https://www.w3.org/ns/activitystreams#Public " ,
reply . to ( )
] ,
" cc " : [
reply . community . profile_id ( ) ,
reply . author . followers_url ( )
] ,
' content ' : reply . body_html ,
' mediaType ' : ' text/html ' ,
' published ' : ap_datetime ( reply . created_at ) ,
' distinguished ' : False ,
' audience ' : reply . community . profile_id ( )
}
if reply . edited_at :
reply_data [ ' updated ' ] = ap_datetime ( reply . edited_at )
if reply . body . strip ( ) :
reply_data [ ' source ' ] = {
' content ' : reply . body ,
' mediaType ' : ' text/markdown '
}
resp = jsonify ( reply_data )
resp . content_type = ' application/activity+json '
return resp
else :
reply = PostReply . query . get ( comment_id )
continue_discussion ( reply . post . id , comment_id )
@bp.route ( ' /post/<int:post_id> ' , methods = [ ' GET ' , ' POST ' ] )
def post_ap ( post_id ) :
if request . method == ' GET ' and is_activitypub_request ( ) :
post = Post . query . get_or_404 ( post_id )
post_data = post_to_activity ( post , post . community )
post_data = post_data [ ' object ' ] [ ' object ' ]
post_data [ ' @context ' ] = default_context ( )
resp = jsonify ( post_data )
resp . content_type = ' application/activity+json '
return resp
else :
return show_post ( post_id )