diff options
Diffstat (limited to 'activitypub.c')
| -rw-r--r-- | activitypub.c | 236 |
1 files changed, 212 insertions, 24 deletions
diff --git a/activitypub.c b/activitypub.c index 065fbcd..2c0aa98 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -946,36 +946,33 @@ void collect_replies(snac *user, const char *id) | |||
| 946 | return; | 946 | return; |
| 947 | } | 947 | } |
| 948 | 948 | ||
| 949 | const char *next = xs_dict_get_path(obj, "replies.first.next"); | 949 | const xs_dict *replies_first = xs_dict_get_path(obj, "replies.first"); |
| 950 | if (!xs_is_string(next)) { | 950 | if (!xs_is_dict(replies_first)) { |
| 951 | snac_debug(user, 1, xs_fmt("collect_replies: object '%s' does not have a replies.first.next URL", id)); | 951 | snac_debug(user, 1, xs_fmt("collect_replies: object '%s' does not have replies.first", id)); |
| 952 | return; | 952 | return; |
| 953 | } | 953 | } |
| 954 | 954 | ||
| 955 | /* pick the first level replies (may be empty) */ | 955 | const xs_list *level0_replies = xs_dict_get(replies_first, "items"); |
| 956 | const xs_list *level0_replies = xs_dict_get_path(obj, "replies.first.items"); | 956 | const xs_list *level1_replies = NULL; |
| 957 | 957 | ||
| 958 | const char *next = xs_dict_get(replies_first, "next"); | ||
| 958 | xs *reply_obj = NULL; | 959 | xs *reply_obj = NULL; |
| 959 | 960 | ||
| 960 | if (!valid_status(object_get(next, &reply_obj))) { | 961 | if (xs_is_string(next) && !valid_status(activitypub_request(user, next, &reply_obj))) { |
| 961 | if (!valid_status(activitypub_request(user, next, &reply_obj))) { | 962 | snac_debug(user, 1, xs_fmt("collect_replies: error getting next replies object '%s'", next)); |
| 962 | snac_debug(user, 1, xs_fmt("collect_replies: cannot get replies object '%s'", next)); | ||
| 963 | return; | ||
| 964 | } | ||
| 965 | } | ||
| 966 | |||
| 967 | const xs_list *level1_replies = xs_dict_get(reply_obj, "items"); | ||
| 968 | if (!xs_is_list(level1_replies)) { | ||
| 969 | snac_debug(user, 1, xs_fmt("collect_replies: cannot get reply items from object '%s'", next)); | ||
| 970 | return; | 963 | return; |
| 971 | } | 964 | } |
| 972 | 965 | ||
| 973 | xs *items = NULL; | 966 | if (xs_is_dict(reply_obj)) |
| 967 | level1_replies = xs_dict_get(reply_obj, "items"); | ||
| 968 | |||
| 969 | xs *items = xs_list_new(); | ||
| 974 | 970 | ||
| 975 | if (xs_is_list(level0_replies)) | 971 | if (xs_is_list(level0_replies)) |
| 976 | items = xs_list_cat(xs_dup(level0_replies), level1_replies); | 972 | items = xs_list_cat(items, level0_replies); |
| 977 | else | 973 | |
| 978 | items = xs_dup(level1_replies); | 974 | if (xs_is_list(level1_replies)) |
| 975 | items = xs_list_cat(items, level1_replies); | ||
| 979 | 976 | ||
| 980 | const xs_val *v; | 977 | const xs_val *v; |
| 981 | 978 | ||
| @@ -1024,6 +1021,107 @@ void collect_replies(snac *user, const char *id) | |||
| 1024 | } | 1021 | } |
| 1025 | 1022 | ||
| 1026 | 1023 | ||
| 1024 | void collect_outbox(snac *user, const char *actor_id) | ||
| 1025 | /* gets an actor's outbox and inserts a bunch of posts in a user's timeline */ | ||
| 1026 | { | ||
| 1027 | int status; | ||
| 1028 | xs *actor = NULL; | ||
| 1029 | |||
| 1030 | if (!valid_status(status = actor_request(user, actor_id, &actor))) { | ||
| 1031 | snac_debug(user, 1, xs_fmt("collect_outbox: cannot get actor object '%s' %d", actor_id, status)); | ||
| 1032 | return; | ||
| 1033 | } | ||
| 1034 | |||
| 1035 | xs *outbox = NULL; | ||
| 1036 | const char *outbox_url = xs_dict_get(actor, "outbox"); | ||
| 1037 | |||
| 1038 | if (!xs_is_string(outbox_url)) | ||
| 1039 | return; | ||
| 1040 | |||
| 1041 | if (!valid_status(status = activitypub_request(user, outbox_url, &outbox))) { | ||
| 1042 | snac_debug(user, 1, xs_fmt("collect_outbox: cannot get actor outbox '%s' %d", outbox_url, status)); | ||
| 1043 | return; | ||
| 1044 | } | ||
| 1045 | |||
| 1046 | const xs_list *ordered_items = xs_dict_get(outbox, "orderedItems"); | ||
| 1047 | |||
| 1048 | if (!xs_is_list(ordered_items)) { | ||
| 1049 | /* the list is not here; does it have a 'first'? */ | ||
| 1050 | const char *first = xs_dict_get(outbox, "first"); | ||
| 1051 | |||
| 1052 | if (xs_is_string(first)) { | ||
| 1053 | /* download this instead */ | ||
| 1054 | xs *first2 = xs_dup(first); | ||
| 1055 | xs_free(outbox); | ||
| 1056 | |||
| 1057 | if (!valid_status(status = activitypub_request(user, first2, &outbox))) { | ||
| 1058 | snac_debug(user, 1, xs_fmt("collect_outbox: cannot get first page of outbox '%s' %d", first2, status)); | ||
| 1059 | return; | ||
| 1060 | } | ||
| 1061 | |||
| 1062 | /* last chance */ | ||
| 1063 | ordered_items = xs_dict_get(outbox, "orderedItems"); | ||
| 1064 | } | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | if (!xs_is_list(ordered_items)) { | ||
| 1068 | snac_debug(user, 1, xs_fmt("collect_outbox: cannot get list of posts for actor '%s' outbox", actor_id)); | ||
| 1069 | return; | ||
| 1070 | } | ||
| 1071 | |||
| 1072 | /* well, ok, then */ | ||
| 1073 | int max = 4; | ||
| 1074 | const xs_val *v; | ||
| 1075 | |||
| 1076 | xs_list_foreach(ordered_items, v) { | ||
| 1077 | if (max == 0) | ||
| 1078 | break; | ||
| 1079 | |||
| 1080 | xs *post = NULL; | ||
| 1081 | |||
| 1082 | if (xs_is_string(v)) { | ||
| 1083 | /* it's probably the post url */ | ||
| 1084 | if (!valid_status(activitypub_request(user, v, &post))) | ||
| 1085 | continue; | ||
| 1086 | } | ||
| 1087 | else | ||
| 1088 | if (xs_is_dict(v)) | ||
| 1089 | post = xs_dup(v); | ||
| 1090 | |||
| 1091 | if (post == NULL) | ||
| 1092 | continue; | ||
| 1093 | |||
| 1094 | const char *type = xs_dict_get(post, "type"); | ||
| 1095 | |||
| 1096 | if (!xs_is_string(type) || strcmp(type, "Create")) { | ||
| 1097 | /* not a post */ | ||
| 1098 | continue; | ||
| 1099 | } | ||
| 1100 | |||
| 1101 | const xs_dict *object = xs_dict_get(post, "object"); | ||
| 1102 | |||
| 1103 | if (!xs_is_dict(object)) | ||
| 1104 | continue; | ||
| 1105 | |||
| 1106 | type = xs_dict_get(object, "type"); | ||
| 1107 | const char *id = xs_dict_get(object, "id"); | ||
| 1108 | const char *attr_to = get_atto(object); | ||
| 1109 | |||
| 1110 | if (!xs_is_string(type) || !xs_is_string(id) || !xs_is_string(attr_to)) | ||
| 1111 | continue; | ||
| 1112 | |||
| 1113 | if (!timeline_here(user, id)) { | ||
| 1114 | timeline_add(user, id, object); | ||
| 1115 | snac_log(user, xs_fmt("new '%s' (collect_outbox) %s %s", type, attr_to, id)); | ||
| 1116 | } | ||
| 1117 | else | ||
| 1118 | snac_debug(user, 1, xs_fmt("collect_outbox: post '%s' already here", id)); | ||
| 1119 | |||
| 1120 | max--; | ||
| 1121 | } | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | |||
| 1027 | void notify(snac *snac, const char *type, const char *utype, const char *actor, const xs_dict *msg) | 1125 | void notify(snac *snac, const char *type, const char *utype, const char *actor, const xs_dict *msg) |
| 1028 | /* notifies the user of relevant events */ | 1126 | /* notifies the user of relevant events */ |
| 1029 | { | 1127 | { |
| @@ -1267,6 +1365,45 @@ xs_dict *msg_collection(snac *snac, const char *id, int items) | |||
| 1267 | } | 1365 | } |
| 1268 | 1366 | ||
| 1269 | 1367 | ||
| 1368 | xs_dict *msg_replies(snac *user, const char *id, int fill) | ||
| 1369 | /* creates a CollectionPage with replies of id */ | ||
| 1370 | { | ||
| 1371 | xs *r_id = xs_replace(id, "/p/", "/r/"); | ||
| 1372 | xs *r_idp = xs_fmt("%s#page", r_id); | ||
| 1373 | xs *r_idh = xs_fmt("%s#hdr", r_id); | ||
| 1374 | |||
| 1375 | xs_dict *msg = msg_base(user, "CollectionPage", r_idp, NULL, NULL, NULL); | ||
| 1376 | |||
| 1377 | msg = xs_dict_set(msg, "partOf", r_idh); | ||
| 1378 | |||
| 1379 | xs *items = xs_list_new(); | ||
| 1380 | if (fill) { | ||
| 1381 | xs *children = object_children(id); | ||
| 1382 | const char *md5; | ||
| 1383 | |||
| 1384 | xs_list_foreach(children, md5) { | ||
| 1385 | xs *obj = NULL; | ||
| 1386 | |||
| 1387 | if (valid_status(object_get_by_md5(md5, &obj)) && is_msg_public(obj)) { | ||
| 1388 | const char *c_id = xs_dict_get(obj, "id"); | ||
| 1389 | |||
| 1390 | if (xs_is_string(c_id)) | ||
| 1391 | items = xs_list_append(items, c_id); | ||
| 1392 | } | ||
| 1393 | } | ||
| 1394 | } | ||
| 1395 | else { | ||
| 1396 | msg = xs_dict_del(msg, "@context"); | ||
| 1397 | msg = xs_dict_del(msg, "id"); | ||
| 1398 | msg = xs_dict_set(msg, "next", r_idp); | ||
| 1399 | } | ||
| 1400 | |||
| 1401 | msg = xs_dict_set(msg, "items", items); | ||
| 1402 | |||
| 1403 | return msg; | ||
| 1404 | } | ||
| 1405 | |||
| 1406 | |||
| 1270 | xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to) | 1407 | xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to) |
| 1271 | /* creates an Accept message (as a response to a Follow) */ | 1408 | /* creates an Accept message (as a response to a Follow) */ |
| 1272 | { | 1409 | { |
| @@ -1871,6 +2008,20 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1871 | } | 2008 | } |
| 1872 | } | 2009 | } |
| 1873 | 2010 | ||
| 2011 | if (!priv) { | ||
| 2012 | /* create the replies object */ | ||
| 2013 | xs *replies = xs_dict_new(); | ||
| 2014 | xs *r_id = xs_replace(id, "/p/", "/r/"); | ||
| 2015 | xs *h_id = xs_fmt("%s#hdr", r_id); | ||
| 2016 | xs *rp = msg_replies(snac, id, 0); | ||
| 2017 | |||
| 2018 | replies = xs_dict_set(replies, "id", h_id); | ||
| 2019 | replies = xs_dict_set(replies, "type", "Collection"); | ||
| 2020 | replies = xs_dict_set(replies, "first", rp); | ||
| 2021 | |||
| 2022 | msg = xs_dict_set(msg, "replies", replies); | ||
| 2023 | } | ||
| 2024 | |||
| 1874 | return msg; | 2025 | return msg; |
| 1875 | } | 2026 | } |
| 1876 | 2027 | ||
| @@ -2437,6 +2588,9 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2437 | if (following_check(snac, actor)) { | 2588 | if (following_check(snac, actor)) { |
| 2438 | following_add(snac, actor, msg); | 2589 | following_add(snac, actor, msg); |
| 2439 | snac_log(snac, xs_fmt("confirmed follow from %s", actor)); | 2590 | snac_log(snac, xs_fmt("confirmed follow from %s", actor)); |
| 2591 | |||
| 2592 | /* request a bit of this fellow's outbox */ | ||
| 2593 | enqueue_collect_outbox(snac, actor); | ||
| 2440 | } | 2594 | } |
| 2441 | else | 2595 | else |
| 2442 | snac_log(snac, xs_fmt("spurious follow accept from %s", actor)); | 2596 | snac_log(snac, xs_fmt("spurious follow accept from %s", actor)); |
| @@ -2538,10 +2692,14 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2538 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); | 2692 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); |
| 2539 | else | 2693 | else |
| 2540 | if (object_here(id)) { | 2694 | if (object_here(id)) { |
| 2541 | object_add_ow(id, object); | 2695 | if (xs_startswith(id, srv_baseurl) && !xs_startswith(id, actor)) |
| 2542 | timeline_touch(snac); | 2696 | snac_log(snac, xs_fmt("ignored incorrect 'Update' %s %s", actor, id)); |
| 2697 | else { | ||
| 2698 | object_add_ow(id, object); | ||
| 2699 | timeline_touch(snac); | ||
| 2543 | 2700 | ||
| 2544 | snac_log(snac, xs_fmt("updated '%s' %s", utype, id)); | 2701 | snac_log(snac, xs_fmt("updated '%s' %s", utype, id)); |
| 2702 | } | ||
| 2545 | } | 2703 | } |
| 2546 | else | 2704 | else |
| 2547 | snac_log(snac, xs_fmt("dropped update for unknown '%s' %s", utype, id)); | 2705 | snac_log(snac, xs_fmt("dropped update for unknown '%s' %s", utype, id)); |
| @@ -2578,8 +2736,12 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2578 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); | 2736 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); |
| 2579 | else | 2737 | else |
| 2580 | if (object_here(object)) { | 2738 | if (object_here(object)) { |
| 2581 | timeline_del(snac, object); | 2739 | if (xs_startswith(object, srv_baseurl) && !xs_startswith(object, actor)) |
| 2582 | snac_debug(snac, 1, xs_fmt("new 'Delete' %s %s", actor, object)); | 2740 | snac_log(snac, xs_fmt("ignored incorrect 'Delete' %s %s", actor, object)); |
| 2741 | else { | ||
| 2742 | timeline_del(snac, object); | ||
| 2743 | snac_debug(snac, 1, xs_fmt("new 'Delete' %s %s", actor, object)); | ||
| 2744 | } | ||
| 2583 | } | 2745 | } |
| 2584 | else | 2746 | else |
| 2585 | snac_debug(snac, 1, xs_fmt("ignored 'Delete' for unknown object %s", object)); | 2747 | snac_debug(snac, 1, xs_fmt("ignored 'Delete' for unknown object %s", object)); |
| @@ -2921,6 +3083,12 @@ void process_user_queue_item(snac *user, xs_dict *q_item) | |||
| 2921 | collect_replies(user, post); | 3083 | collect_replies(user, post); |
| 2922 | } | 3084 | } |
| 2923 | else | 3085 | else |
| 3086 | if (strcmp(type, "collect_outbox") == 0) { | ||
| 3087 | const char *actor_id = xs_dict_get(q_item, "message"); | ||
| 3088 | |||
| 3089 | collect_outbox(user, actor_id); | ||
| 3090 | } | ||
| 3091 | else | ||
| 2924 | snac_log(user, xs_fmt("unexpected user q_item type '%s'", type)); | 3092 | snac_log(user, xs_fmt("unexpected user q_item type '%s'", type)); |
| 2925 | } | 3093 | } |
| 2926 | 3094 | ||
| @@ -3391,6 +3559,26 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, | |||
| 3391 | status = HTTP_STATUS_NOT_FOUND; | 3559 | status = HTTP_STATUS_NOT_FOUND; |
| 3392 | } | 3560 | } |
| 3393 | else | 3561 | else |
| 3562 | if (xs_startswith(p_path, "r/")) { | ||
| 3563 | /* replies to a post */ | ||
| 3564 | xs *s = xs_dup(p_path); | ||
| 3565 | s[0] = 'p'; | ||
| 3566 | |||
| 3567 | xs *id = xs_fmt("%s/%s", snac.actor, s); | ||
| 3568 | |||
| 3569 | xs *obj = NULL; | ||
| 3570 | status = object_get(id, &obj); | ||
| 3571 | |||
| 3572 | /* don't return non-public objects */ | ||
| 3573 | if (!valid_status(status)) | ||
| 3574 | status = HTTP_STATUS_NOT_FOUND; | ||
| 3575 | else | ||
| 3576 | if (!is_msg_public(obj)) | ||
| 3577 | status = HTTP_STATUS_NOT_FOUND; | ||
| 3578 | else | ||
| 3579 | msg = msg_replies(&snac, id, 1); | ||
| 3580 | } | ||
| 3581 | else | ||
| 3394 | status = HTTP_STATUS_NOT_FOUND; | 3582 | status = HTTP_STATUS_NOT_FOUND; |
| 3395 | 3583 | ||
| 3396 | if (status == HTTP_STATUS_OK && msg != NULL) { | 3584 | if (status == HTTP_STATUS_OK && msg != NULL) { |