def handle_inbound_accept_reject(activity, obj): ''' Incoming Accept and Reject activites on Follow objects have side effects which are handled here ''' actor = APObject.get_object_from_url( obj['actor']) #actor on local server who is following external actor if not isinstance(actor, Actor): raise Exception( 'Remote server tried to accept or reject a Follow request done by an object and not by an actor. Aborting.' ) following = db.session.query(Following).filter( db.and_(Following.follower_id == actor.id, Following.leader == activity['actor']), Following.approved == 0).first() follow_activity = db.session.query(Follow).filter( db.and_(Follow.external_object_id == obj['object'], Follow.internal_actor_id == actor.id)).first() if following is None or follow_activity is None: return error('Follow request not found.', 404) if activity['type'] == 'Accept': following.approved = True db.session.add(following) else: db.session.delete(following) return None
def route_user_outbox_paginated(actor_name, page): actor = db.session.query(Actor).filter_by( username=actor_name.lower()).first() if actor is None: return error('Actor not found', 404) activities = db.session.query(Activity).filter( db.and_(Activity.actor == actor, Activity.type != APObjectType.FOLLOW)).order_by( Activity.published.desc()).paginate(page, 20).items api_url = config['api_url'] orderedItems = [] for activity in activities: orderedItems.append(activity.to_dict()) output = { '@context': 'https://www.w3.org/ns/activitystreams', 'id': f'{api_url}/actors/{actor_name}/outbox/{page}', 'partOf': f'{api_url}/actors/{actor_name}/outbox', 'type': 'OrderedCollectionPage', 'prev': f'{api_url}/actors/{actor_name}/outbox/{page-1}', 'next': f'{api_url}/actors/{actor_name}/outbox/{page+1}', 'orderedItems': orderedItems } response = make_response(output, 200) response.headers['Content-Type'] = 'application/activity+json' return response
def route_get_actor_following_page(username, page): actor = db.session.query(Actor).filter( db.func.lower(Actor.username) == db.func.lower(username)).first() if actor is None: return error('Actor not found', 404) items = db.session.query(Following).filter( db.and_(Following.follower_id == actor.id, Following.approved == 1)).paginate(page, 20).items ordered_items = [] for item in items: ordered_items.append(item.leader) return make_response( { '@context': 'https://www.w3.org/ns/activitystreams', 'id': f'{config["api_url"]}/actors/{actor.username}/following/{page}', 'type': 'OrderedCollectionPage', 'totalItems': len(items), 'partOf': f'{config["api_url"]}/actors/{actor.username}/following/', 'orderedItems': ordered_items }, 200)
def get_note_by_id(object_id): ap_object = db.session.query(APObject).filter( db.and_(APObject.id == object_id, APObject.external_id == None)).first() if ap_object is None: return error('Object not found', 404) response = make_response(ap_object.to_dict(), 200) response.headers['Content-Type'] = 'application/activity+json' return response
def handle_follow(inbound_json, actor, base_activity, base_object, is_local): ''' Handles the various oddities associated with the correspondance nature of the Follow activity. ''' leader = resolve_ap_object(inbound_json['object']) existing_follow = db.session.query(Following).filter( db.and_(Following.follower_id == actor.id, Following.leader == leader['id'])).first() if existing_follow is not None and existing_follow.approved is True: return error('You are already following this actor.') new_follow = Following(actor.id, leader['id'], leader['followers'], approved=is_local) db.session.add(new_follow) # Due to the correspondance nature of the Follow activity, it has some very unusual requirements. # We need to detect if the incoming Follow activity is targeting a local user. If it is, we don't need # To deliver server-to-server messages about this transaction. Attempting to do so would cause problems # resulting from uncomitted database transactions and be a waste of resources. if is_local: local_leader = db.session.query(Actor).filter( db.func.lower(Actor.username) == db.func.lower( inbound_json['object'].replace( f'{os.environ["API_URL"]}/actors/', ''))).first() if local_leader is None: return make_response('Actor not found', 404) actor_dict = actor.to_dict() new_followed_by = FollowedBy( local_leader.id, actor_dict['id'], actor_dict['inbox'], follower_shared_inbox=actor_dict['endpoints']['sharedInbox'], approved=True) db.session.add( Notification( local_leader, f'{actor_dict["preferredUsername"]} has followed you.', 'Follow')) db.session.add(new_followed_by) db.session.commit() else: db.session.commit( ) # This is required so when we get an Accept activity back before the end of this request, we're able to find the Follow activity try: signed_request(actor, base_activity.to_dict(), leader['inbox']) except: return error( 'Your follow request was not able to be delivered to that server.' ) return make_response('', 200)
def follow(actor, follow_activity): ''' actor: Actor model follow_activity: Dictionary representation of the new follow request ''' leader = resolve_ap_object(follow_activity['object']) existing_follow = db.session.query(Following).filter( db.and_(Following.follower_id == actor.id, Following.leader == leader['id'])).first() if existing_follow is not None: if existing_follow.approved is True: return error('You are already following this actor.') db.session.delete(existing_follow) new_activity = Follow() new_activity.set_actor(actor) new_activity.set_object(follow_activity['object']) db.session.add(new_activity) db.session.flush() new_follow = Following(actor.id, leader['id'], leader['followers']) db.session.add(new_follow) response = signed_request(actor, new_activity.to_dict(), leader['inbox']) db.session.commit() if response.status_code >= 400: return error('Something went wrong :(') return make_response('', 200)