summaryrefslogtreecommitdiff
path: root/activitypub.c
diff options
context:
space:
mode:
Diffstat (limited to 'activitypub.c')
-rw-r--r--activitypub.c236
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
1024void 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
1027void notify(snac *snac, const char *type, const char *utype, const char *actor, const xs_dict *msg) 1125void 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
1368xs_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
1270xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to) 1407xs_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) {