def outbox_activity_shares(item_id): # TODO(tsileo): handle Tombstone if not is_api_request(): abort(404) data = DB.outbox.find_one({'id': item_id, 'meta.deleted': False}) if not data: abort(404) obj = activitypub.parse_activity(data['activity']) if obj.type_enum != ActivityType.CREATE: abort(404) q = { 'meta.undo': False, 'type': ActivityType.ANNOUNCE.value, '$or': [{ 'activity.object.id': obj.get_object().id }, { 'activity.object': obj.get_object().id }], } return jsonify(**activitypub.build_ordered_collection( DB.inbox, q=q, cursor=request.args.get('cursor'), map_func=lambda doc: doc['activity'], col_name=f'outbox/{item_id}/shares', first_page=request.args.get('page') == 'first', ))
def outbox_detail(item_id): doc = DB.outbox.find_one({'id': item_id}) if doc['meta'].get('deleted', False): obj = activitypub.parse_activity(doc['activity']) resp = jsonify(**obj.get_object().get_tombstone()) resp.status_code = 410 return resp return jsonify(**activity_from_doc(doc))
def _user_api_get_note(from_outbox: bool = False): oid = _user_api_arg('id') note = activitypub.parse_activity(OBJECT_SERVICE.get(oid), expected=ActivityType.NOTE) if from_outbox and not note.id.startswith(ID): raise NotFromOutboxError( f'cannot delete {note.id}, id must be owned by the server') return note
def api_undo(): oid = _user_api_arg('id') doc = DB.outbox.find_one({'$or': [{'id': oid}, {'remote_id': oid}]}) if not doc: raise ActivityNotFoundError(f'cannot found {oid}') obj = activitypub.parse_activity(doc.get('activity')) # FIXME(tsileo): detect already undo-ed and make this API call idempotent undo = obj.build_undo() undo.post_to_outbox() return _user_api_response(activity=undo.id)
def new(): reply_id = None content = '' if request.args.get('reply'): reply = activitypub.parse_activity( OBJECT_SERVICE.get(request.args.get('reply'))) reply_id = reply.id actor = reply.get_actor() domain = urlparse(actor.id).netloc content = f'@{actor.preferredUsername}@{domain} ' return render_template('new.html', reply=reply_id, content=content)
def inbox(): if request.method == 'GET': if not is_api_request(): abort(404) try: _api_required() except BadSignature: abort(404) return jsonify(**activitypub.build_ordered_collection( DB.inbox, q={'meta.deleted': False}, cursor=request.args.get('cursor'), map_func=lambda doc: doc['activity'], )) data = request.get_json(force=True) logger.debug(f'req_headers={request.headers}') logger.debug(f'raw_data={data}') try: if not verify_request(ACTOR_SERVICE): raise Exception('failed to verify request') except Exception: logger.exception( 'failed to verify request, trying to verify the payload by fetching the remote' ) try: data = OBJECT_SERVICE.get(data['id']) except Exception: logger.exception(f'failed to fetch remote id at {data["id"]}') return Response( status=422, headers={'Content-Type': 'application/json'}, response=json.dumps({ 'error': 'failed to verify request (using HTTP signatures or fetching the IRI)' }), ) activity = activitypub.parse_activity(data) logger.debug(f'inbox activity={activity}/{data}') activity.process_from_inbox() return Response(status=201, )
def outbox(): if request.method == 'GET': if not is_api_request(): abort(404) # TODO(tsileo): filter the outbox if not authenticated # FIXME(tsileo): filter deleted, add query support for build_ordered_collection q = { 'meta.deleted': False, #'type': {'$in': [ActivityType.CREATE.value, ActivityType.ANNOUNCE.value]}, } return jsonify(**activitypub.build_ordered_collection( DB.outbox, q=q, cursor=request.args.get('cursor'), map_func=lambda doc: activity_from_doc(doc), )) # Handle POST request try: _api_required() except BadSignature: abort(401) data = request.get_json(force=True) print(data) activity = activitypub.parse_activity(data) if activity.type_enum == ActivityType.NOTE: activity = activity.build_create() activity.post_to_outbox() # Purge the cache if a custom hook is set, as new content was published custom_cache_purge_hook() return Response(status=201, headers={'Location': activity.id})
def api_new_note(): source = _user_api_arg('content') if not source: raise ValueError('missing content') _reply, reply = None, None try: _reply = _user_api_arg('reply') except ValueError: pass content, tags = parse_markdown(source) to = request.args.get('to') cc = [ID + '/followers'] if _reply: reply = activitypub.parse_activity(OBJECT_SERVICE.get(_reply)) cc.append(reply.attributedTo) for tag in tags: if tag['type'] == 'Mention': cc.append(tag['href']) note = activitypub.Note(cc=cc, to=[to if to else config.AS_PUBLIC], content=content, tag=tags, source={ 'mediaType': 'text/markdown', 'content': source }, inReplyTo=reply.id if reply else None) create = note.build_create() create.post_to_outbox() return _user_api_response(activity=create.id)