Add new shares/likes/replies collection for notes

This commit is contained in:
Thomas Sileo 2018-06-01 01:26:23 +02:00
parent 848b8b23a8
commit 6711722bd0
3 changed files with 127 additions and 39 deletions

View file

@ -380,6 +380,16 @@ class BaseActivity(object):
else: else:
try: try:
actor = Person(**ACTOR_SERVICE.get(recipient)) actor = Person(**ACTOR_SERVICE.get(recipient))
if actor.endpoints:
shared_inbox = actor.endpoints.get('sharedInbox')
if shared_inbox not in out:
out.append(shared_inbox)
continue
if actor.inbox and actor.inbox not in out:
out.append(actor.inbox)
except NotAnActorError as error: except NotAnActorError as error:
# Is the activity a `Collection`/`OrderedCollection`? # Is the activity a `Collection`/`OrderedCollection`?
if error.activity and error.activity['type'] in [ActivityType.COLLECTION.value, if error.activity and error.activity['type'] in [ActivityType.COLLECTION.value,
@ -400,17 +410,6 @@ class BaseActivity(object):
if col_actor.inbox and col_actor.inbox not in out: if col_actor.inbox and col_actor.inbox not in out:
out.append(col_actor.inbox) out.append(col_actor.inbox)
continue
if actor.endpoints:
shared_inbox = actor.endpoints.get('sharedInbox')
if shared_inbox not in out:
out.append(shared_inbox)
continue
if actor.inbox and actor.inbox not in out:
out.append(actor.inbox)
return out return out
def build_undo(self) -> 'BaseActivity': def build_undo(self) -> 'BaseActivity':
@ -1076,11 +1075,11 @@ def parse_collection(payload: Optional[Dict[str, Any]] = None, url: Optional[str
return activitypub_utils.parse_collection(payload, url) return activitypub_utils.parse_collection(payload, url)
def embed_collection(data): def embed_collection(total_items, first_page_id):
return { return {
"type": "Collection", "type": ActivityType.ORDERED_COLLECTION.value,
"totalItems": len(data), "totalItems": total_items,
"items": data, "first": first_page_id,
} }
@ -1097,7 +1096,7 @@ def build_ordered_collection(col, q=None, cursor=None, map_func=None, limit=50,
return { return {
'id': BASE_URL + '/' + col_name, 'id': BASE_URL + '/' + col_name,
'totalItems': 0, 'totalItems': 0,
'type': 'OrderedCollection', 'type': ActivityType.ORDERED_COLLECTION.value,
'orederedItems': [], 'orederedItems': [],
} }
@ -1115,13 +1114,13 @@ def build_ordered_collection(col, q=None, cursor=None, map_func=None, limit=50,
'@context': CTX_AS, '@context': CTX_AS,
'id': f'{BASE_URL}/{col_name}', 'id': f'{BASE_URL}/{col_name}',
'totalItems': total_items, 'totalItems': total_items,
'type': 'OrderedCollection', 'type': ActivityType.ORDERED_COLLECTION.value,
'first': { 'first': {
'id': f'{BASE_URL}/{col_name}?cursor={start_cursor}', 'id': f'{BASE_URL}/{col_name}?cursor={start_cursor}',
'orderedItems': data, 'orderedItems': data,
'partOf': f'{BASE_URL}/{col_name}', 'partOf': f'{BASE_URL}/{col_name}',
'totalItems': total_items, 'totalItems': total_items,
'type': 'OrderedCollectionPage' 'type': ActivityType.ORDERED_COLLECTION_PAGE.value,
}, },
} }
@ -1130,10 +1129,11 @@ def build_ordered_collection(col, q=None, cursor=None, map_func=None, limit=50,
return resp return resp
# If there's a cursor, then we return an OrderedCollectionPage # If there's a cursor, then we return an OrderedCollectionPage
resp = { resp = {
'@context': CTX_AS, '@context': CTX_AS,
'type': 'OrderedCollectionPage', 'type': ActivityType.ORDERED_COLLECTION_PAGE.value,
'id': BASE_URL + '/' + col_name + '?cursor=' + start_cursor, 'id': BASE_URL + '/' + col_name + '?cursor=' + start_cursor,
'totalItems': total_items, 'totalItems': total_items,
'partOf': BASE_URL + '/' + col_name, 'partOf': BASE_URL + '/' + col_name,
@ -1142,4 +1142,6 @@ def build_ordered_collection(col, q=None, cursor=None, map_func=None, limit=50,
if len(data) == limit: if len(data) == limit:
resp['next'] = BASE_URL + '/' + col_name + '?cursor=' + next_page_cursor resp['next'] = BASE_URL + '/' + col_name + '?cursor=' + next_page_cursor
# TODO(tsileo): implements prev with prev=<first item cursor>
return resp return resp

103
app.py
View file

@ -471,18 +471,21 @@ def add_extra_collection(raw_doc: Dict[str, Any]) -> Dict[str, Any]:
if raw_doc['activity']['type'] != ActivityType.CREATE.value: if raw_doc['activity']['type'] != ActivityType.CREATE.value:
return raw_doc return raw_doc
if 'col_likes' in raw_doc.get('meta', {}): raw_doc['activity']['object']['replies'] = embed_collection(
col_likes = raw_doc['meta']['col_likes'] raw_doc.get('meta', {}).get('count_direct_reply', 0),
raw_doc['activity']['object']['likes'] = embed_collection(col_likes) f'{ID}/outbox/{raw_doc["id"]}/replies',
)
if 'col_shares' in raw_doc.get('meta', {}): raw_doc['activity']['object']['likes'] = embed_collection(
col_shares = raw_doc['meta']['col_shares'] raw_doc.get('meta', {}).get('count_like', 0),
raw_doc['activity']['object']['shares'] = embed_collection(col_shares) f'{ID}/outbox/{raw_doc["id"]}/likes',
)
raw_doc['activity']['object']['shares'] = embed_collection(
raw_doc.get('meta', {}).get('count_boost', 0),
f'{ID}/outbox/{raw_doc["id"]}/shares',
)
if 'count_direct_reply' in raw_doc.get('meta', {}):
# FIXME(tsileo): implements the collection handler
raw_doc['activity']['object']['replies'] = {'type': 'Collection', 'totalItems': raw_doc['meta']['count_direct_reply']}
return raw_doc return raw_doc
@ -547,6 +550,86 @@ def outbox_activity(item_id):
return jsonify(**obj['object']) return jsonify(**obj['object'])
@app.route('/outbox/<item_id>/replies')
def outbox_activity_replies(item_id):
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)
if obj.type_enum != ActivityType.CREATE:
abort(404)
q = {
'meta.deleted': False,
'type': ActivityType.CREATE.value,
'activity.object.inReplyTo': 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}/replies',
))
@app.route('/outbox/<item_id>/likes')
def outbox_activity_likes(item_id):
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)
if obj.type_enum != ActivityType.CREATE:
abort(404)
q = {
'meta.undo': False,
'type': ActivityType.LIKE.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}/likes',
))
@app.route('/outbox/<item_id>/shares')
def outbox_activity_shares(item_id):
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)
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',
))
@app.route('/admin', methods=['GET']) @app.route('/admin', methods=['GET'])
@login_required @login_required
def admin(): def admin():

View file

@ -301,8 +301,8 @@ def test_post_content_and_like():
note = instance1.outbox_get(f'{create_id}/activity') note = instance1.outbox_get(f'{create_id}/activity')
assert 'likes' in note assert 'likes' in note
assert len(note['likes']['items']) == 1 assert note['likes']['totalItems'] == 1
assert note['likes']['items'][0]['id'] == like_id # assert note['likes']['items'][0]['id'] == like_id
def test_post_content_and_like_unlike(): def test_post_content_and_like_unlike():
@ -327,8 +327,9 @@ def test_post_content_and_like_unlike():
note = instance1.outbox_get(f'{create_id}/activity') note = instance1.outbox_get(f'{create_id}/activity')
assert 'likes' in note assert 'likes' in note
assert len(note['likes']['items']) == 1 assert note['likes']['totalItems'] == 1
assert note['likes']['items'][0]['id'] == like_id # FIXME(tsileo): parse the collection
# assert note['likes']['items'][0]['id'] == like_id
instance2.undo(like_id) instance2.undo(like_id)
@ -338,7 +339,7 @@ def test_post_content_and_like_unlike():
note = instance1.outbox_get(f'{create_id}/activity') note = instance1.outbox_get(f'{create_id}/activity')
assert 'likes' in note assert 'likes' in note
assert len(note['likes']['items']) == 0 assert note['likes']['totalItems'] == 0
def test_post_content_and_boost(): def test_post_content_and_boost():
@ -363,8 +364,9 @@ def test_post_content_and_boost():
note = instance1.outbox_get(f'{create_id}/activity') note = instance1.outbox_get(f'{create_id}/activity')
assert 'shares' in note assert 'shares' in note
assert len(note['shares']['items']) == 1 assert note['shares']['totalItems'] == 1
assert note['shares']['items'][0]['id'] == boost_id # FIXME(tsileo): parse the collection
# assert note['shares']['items'][0]['id'] == boost_id
def test_post_content_and_boost_unboost(): def test_post_content_and_boost_unboost():
@ -389,8 +391,9 @@ def test_post_content_and_boost_unboost():
note = instance1.outbox_get(f'{create_id}/activity') note = instance1.outbox_get(f'{create_id}/activity')
assert 'shares' in note assert 'shares' in note
assert len(note['shares']['items']) == 1 assert note['shares']['totalItems'] == 1
assert note['shares']['items'][0]['id'] == boost_id # FIXME(tsileo): parse the collection
# assert note['shares']['items'][0]['id'] == boost_id
instance2.undo(boost_id) instance2.undo(boost_id)
@ -400,7 +403,7 @@ def test_post_content_and_boost_unboost():
note = instance1.outbox_get(f'{create_id}/activity') note = instance1.outbox_get(f'{create_id}/activity')
assert 'shares' in note assert 'shares' in note
assert len(note['shares']['items']) == 0 assert note['shares']['totalItems'] == 0
def test_post_content_and_post_reply(): def test_post_content_and_post_reply():