diff options
| -rw-r--r-- | RELEASE_NOTES.md | 6 | ||||
| -rw-r--r-- | activitypub.c | 236 | ||||
| -rw-r--r-- | data.c | 19 | ||||
| -rw-r--r-- | doc/snac.1 | 2 | ||||
| -rw-r--r-- | main.c | 12 | ||||
| -rw-r--r-- | mastoapi.c | 8 | ||||
| -rw-r--r-- | snac.h | 7 | ||||
| -rw-r--r-- | utils.c | 12 |
8 files changed, 262 insertions, 40 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0a3d07f..8dfc18c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | # Release Notes | 1 | # Release Notes |
| 2 | 2 | ||
| 3 | ## UNRELEASED | 3 | ## 2.82 |
| 4 | 4 | ||
| 5 | The language in which a post is written can now be set from the UI; you must configure the list of languages you usually post in in the User Settings. | 5 | The language in which a post is written can now be set from the UI; you must configure the list of languages you usually post in in the User Settings. |
| 6 | 6 | ||
| @@ -16,6 +16,10 @@ Added nodeinfo 2.1 support. | |||
| 16 | 16 | ||
| 17 | Fixed boosts from the command line not showing in the public timeline (contributed by xvello). | 17 | Fixed boosts from the command line not showing in the public timeline (contributed by xvello). |
| 18 | 18 | ||
| 19 | Updated several language files (contributed by zen and daltux). | ||
| 20 | |||
| 21 | Retrieving a post's replies is now possible via ActivityPub. | ||
| 22 | |||
| 19 | ## 2.81 | 23 | ## 2.81 |
| 20 | 24 | ||
| 21 | If the `propagate_local_purge` configuration variable is set to `true` in `server.json`, purged local posts generate a `Delete` activity that is sent everywhere, instead of only deleted from the filesystem. | 25 | If the `propagate_local_purge` configuration variable is set to `true` in `server.json`, purged local posts generate a `Delete` activity that is sent everywhere, instead of only deleted from the filesystem. |
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) { |
| @@ -2429,8 +2429,8 @@ xs_list *list_timeline(snac *user, const char *list, int skip, int show) | |||
| 2429 | } | 2429 | } |
| 2430 | 2430 | ||
| 2431 | 2431 | ||
| 2432 | xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op) | 2432 | xs_val *list_members(snac *user, const char *list, const char *actor_md5, int op) |
| 2433 | /* list content management */ | 2433 | /* list member management */ |
| 2434 | { | 2434 | { |
| 2435 | xs_val *l = NULL; | 2435 | xs_val *l = NULL; |
| 2436 | 2436 | ||
| @@ -2443,7 +2443,7 @@ xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op | |||
| 2443 | xs *fn = xs_fmt("%s/list/%s.lst", user->basedir, list); | 2443 | xs *fn = xs_fmt("%s/list/%s.lst", user->basedir, list); |
| 2444 | 2444 | ||
| 2445 | switch (op) { | 2445 | switch (op) { |
| 2446 | case 0: /** list content **/ | 2446 | case 0: /** list members **/ |
| 2447 | l = index_list(fn, XS_ALL); | 2447 | l = index_list(fn, XS_ALL); |
| 2448 | 2448 | ||
| 2449 | break; | 2449 | break; |
| @@ -3589,6 +3589,19 @@ void enqueue_collect_replies(snac *user, const char *post) | |||
| 3589 | } | 3589 | } |
| 3590 | 3590 | ||
| 3591 | 3591 | ||
| 3592 | void enqueue_collect_outbox(snac *user, const char *actor_id) | ||
| 3593 | /* enqueues a collect outbox request */ | ||
| 3594 | { | ||
| 3595 | xs *qmsg = _new_qmsg("collect_outbox", actor_id, 0); | ||
| 3596 | const char *ntid = xs_dict_get(qmsg, "ntid"); | ||
| 3597 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); | ||
| 3598 | |||
| 3599 | qmsg = _enqueue_put(fn, qmsg); | ||
| 3600 | |||
| 3601 | snac_debug(user, 1, xs_fmt("enqueue_collect_outbox %s", actor_id)); | ||
| 3602 | } | ||
| 3603 | |||
| 3604 | |||
| 3592 | int was_question_voted(snac *user, const char *id) | 3605 | int was_question_voted(snac *user, const char *id) |
| 3593 | /* returns true if the user voted in this poll */ | 3606 | /* returns true if the user voted in this poll */ |
| 3594 | { | 3607 | { |
| @@ -282,6 +282,8 @@ necessary information will be prompted for. | |||
| 282 | Deletes a user, unfollowing all accounts first. | 282 | Deletes a user, unfollowing all accounts first. |
| 283 | .It Cm resetpwd Ar basedir Ar uid | 283 | .It Cm resetpwd Ar basedir Ar uid |
| 284 | Resets a user's password to a new, random one. | 284 | Resets a user's password to a new, random one. |
| 285 | .It Cm update Ar basedir Ar uid | ||
| 286 | Sends a user's updated profile to following instances. | ||
| 285 | .It Cm queue Ar basedir Ar uid | 287 | .It Cm queue Ar basedir Ar uid |
| 286 | Processes the output queue of the specified user, sending all | 288 | Processes the output queue of the specified user, sending all |
| 287 | enqueued messages and re-enqueing the failing ones. This command | 289 | enqueued messages and re-enqueing the failing ones. This command |
| @@ -30,6 +30,7 @@ int usage(const char *cmd) | |||
| 30 | "upgrade {basedir} Upgrade to a new version\n" | 30 | "upgrade {basedir} Upgrade to a new version\n" |
| 31 | "adduser {basedir} [{uid}] Adds a new user\n" | 31 | "adduser {basedir} [{uid}] Adds a new user\n" |
| 32 | "deluser {basedir} {uid} Deletes a user\n" | 32 | "deluser {basedir} {uid} Deletes a user\n" |
| 33 | "update {basedir} {uid} Sends a user's updated profile\n" | ||
| 33 | "httpd {basedir} Starts the HTTPD daemon\n" | 34 | "httpd {basedir} Starts the HTTPD daemon\n" |
| 34 | "purge {basedir} Purges old data\n" | 35 | "purge {basedir} Purges old data\n" |
| 35 | "state {basedir} Prints server state\n" | 36 | "state {basedir} Prints server state\n" |
| @@ -354,7 +355,7 @@ int main(int argc, char *argv[]) | |||
| 354 | xs *lid = list_maint(&snac, url, 4); | 355 | xs *lid = list_maint(&snac, url, 4); |
| 355 | 356 | ||
| 356 | if (lid != NULL) { | 357 | if (lid != NULL) { |
| 357 | xs *lcont = list_content(&snac, lid, NULL, 0); | 358 | xs *lcont = list_members(&snac, lid, NULL, 0); |
| 358 | const char *md5; | 359 | const char *md5; |
| 359 | 360 | ||
| 360 | xs_list_foreach(lcont, md5) { | 361 | xs_list_foreach(lcont, md5) { |
| @@ -410,7 +411,7 @@ int main(int argc, char *argv[]) | |||
| 410 | if (valid_status(webfinger_request(account, &actor, &uid))) { | 411 | if (valid_status(webfinger_request(account, &actor, &uid))) { |
| 411 | xs *md5 = xs_md5_hex(actor, strlen(actor)); | 412 | xs *md5 = xs_md5_hex(actor, strlen(actor)); |
| 412 | 413 | ||
| 413 | list_content(&snac, lid, md5, 1); | 414 | list_members(&snac, lid, md5, 1); |
| 414 | printf("Actor %s (%s) added to list '%s' (%s)\n", actor, uid, url, lid); | 415 | printf("Actor %s (%s) added to list '%s' (%s)\n", actor, uid, url, lid); |
| 415 | } | 416 | } |
| 416 | else | 417 | else |
| @@ -433,7 +434,7 @@ int main(int argc, char *argv[]) | |||
| 433 | if (lid != NULL) { | 434 | if (lid != NULL) { |
| 434 | xs *md5 = xs_md5_hex(account, strlen(account)); | 435 | xs *md5 = xs_md5_hex(account, strlen(account)); |
| 435 | 436 | ||
| 436 | list_content(&snac, lid, md5, 2); | 437 | list_members(&snac, lid, md5, 2); |
| 437 | printf("Actor %s deleted from list '%s' (%s)\n", account, url, lid); | 438 | printf("Actor %s deleted from list '%s' (%s)\n", account, url, lid); |
| 438 | } | 439 | } |
| 439 | else | 440 | else |
| @@ -738,6 +739,11 @@ int main(int argc, char *argv[]) | |||
| 738 | return 0; | 739 | return 0; |
| 739 | } | 740 | } |
| 740 | 741 | ||
| 742 | if (strcmp(cmd, "collect_outbox") == 0) { /** **/ | ||
| 743 | enqueue_collect_outbox(&snac, url); | ||
| 744 | return 0; | ||
| 745 | } | ||
| 746 | |||
| 741 | if (strcmp(cmd, "insert") == 0) { /** **/ | 747 | if (strcmp(cmd, "insert") == 0) { /** **/ |
| 742 | int status; | 748 | int status; |
| 743 | xs *data = NULL; | 749 | xs *data = NULL; |
| @@ -1560,7 +1560,7 @@ xs_list *mastoapi_account_lists(snac *user, const char *uid) | |||
| 1560 | const char *list_id = xs_list_get(li, 0); | 1560 | const char *list_id = xs_list_get(li, 0); |
| 1561 | const char *list_title = xs_list_get(li, 1); | 1561 | const char *list_title = xs_list_get(li, 1); |
| 1562 | if (uid) { | 1562 | if (uid) { |
| 1563 | xs *users = list_content(user, list_id, NULL, 0); | 1563 | xs *users = list_members(user, list_id, NULL, 0); |
| 1564 | if (xs_list_in(users, actor_md5) == -1) | 1564 | if (xs_list_in(users, actor_md5) == -1) |
| 1565 | continue; | 1565 | continue; |
| 1566 | } | 1566 | } |
| @@ -2087,7 +2087,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 2087 | p = xs_list_get(l, -2); | 2087 | p = xs_list_get(l, -2); |
| 2088 | 2088 | ||
| 2089 | if (p && xs_is_hex(p)) { | 2089 | if (p && xs_is_hex(p)) { |
| 2090 | xs *actors = list_content(&snac1, p, NULL, 0); | 2090 | xs *actors = list_members(&snac1, p, NULL, 0); |
| 2091 | xs *out = xs_list_new(); | 2091 | xs *out = xs_list_new(); |
| 2092 | int c = 0; | 2092 | int c = 0; |
| 2093 | const char *v; | 2093 | const char *v; |
| @@ -3297,7 +3297,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, | |||
| 3297 | const char *v; | 3297 | const char *v; |
| 3298 | 3298 | ||
| 3299 | while (xs_list_next(accts, &v, &c)) { | 3299 | while (xs_list_next(accts, &v, &c)) { |
| 3300 | list_content(&snac, id, v, 1); | 3300 | list_members(&snac, id, v, 1); |
| 3301 | } | 3301 | } |
| 3302 | 3302 | ||
| 3303 | xs *out = xs_dict_new(); | 3303 | xs *out = xs_dict_new(); |
| @@ -3507,7 +3507,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, | |||
| 3507 | const char *v; | 3507 | const char *v; |
| 3508 | 3508 | ||
| 3509 | while (xs_list_next(accts, &v, &c)) { | 3509 | while (xs_list_next(accts, &v, &c)) { |
| 3510 | list_content(&snac, p, v, 2); | 3510 | list_members(&snac, p, v, 2); |
| 3511 | } | 3511 | } |
| 3512 | } | 3512 | } |
| 3513 | else { | 3513 | else { |
| @@ -1,7 +1,7 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ | 2 | /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ |
| 3 | 3 | ||
| 4 | #define VERSION "2.82-dev" | 4 | #define VERSION "2.82" |
| 5 | 5 | ||
| 6 | #define USER_AGENT "snac/" VERSION | 6 | #define USER_AGENT "snac/" VERSION |
| 7 | 7 | ||
| @@ -233,7 +233,7 @@ xs_list *tag_search(const char *tag, int skip, int show); | |||
| 233 | xs_val *list_maint(snac *user, const char *list, int op); | 233 | xs_val *list_maint(snac *user, const char *list, int op); |
| 234 | xs_str *list_timeline_fn(snac *user, const char *list); | 234 | xs_str *list_timeline_fn(snac *user, const char *list); |
| 235 | xs_list *list_timeline(snac *user, const char *list, int skip, int show); | 235 | xs_list *list_timeline(snac *user, const char *list, int skip, int show); |
| 236 | xs_val *list_content(snac *user, const char *list_id, const char *actor_md5, int op); | 236 | xs_val *list_members(snac *user, const char *list_id, const char *actor_md5, int op); |
| 237 | void list_distribute(snac *user, const char *who, const xs_dict *post); | 237 | void list_distribute(snac *user, const char *who, const xs_dict *post); |
| 238 | 238 | ||
| 239 | int actor_add(const char *actor, const xs_dict *msg); | 239 | int actor_add(const char *actor, const xs_dict *msg); |
| @@ -298,6 +298,7 @@ void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs); | |||
| 298 | void enqueue_webmention(const xs_dict *msg); | 298 | void enqueue_webmention(const xs_dict *msg); |
| 299 | void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries); | 299 | void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries); |
| 300 | void enqueue_collect_replies(snac *user, const char *post); | 300 | void enqueue_collect_replies(snac *user, const char *post); |
| 301 | void enqueue_collect_outbox(snac *user, const char *actor_id); | ||
| 301 | 302 | ||
| 302 | int was_question_voted(snac *user, const char *id); | 303 | int was_question_voted(snac *user, const char *id); |
| 303 | 304 | ||
| @@ -336,6 +337,7 @@ const char *default_avatar_base64(void); | |||
| 336 | xs_str *process_tags(snac *snac, const char *content, xs_list **tag); | 337 | xs_str *process_tags(snac *snac, const char *content, xs_list **tag); |
| 337 | 338 | ||
| 338 | void collect_replies(snac *user, const char *id); | 339 | void collect_replies(snac *user, const char *id); |
| 340 | void collect_outbox(snac *user, const char *actor_id); | ||
| 339 | 341 | ||
| 340 | const char *get_atto(const xs_dict *msg); | 342 | const char *get_atto(const xs_dict *msg); |
| 341 | const char *get_in_reply_to(const xs_dict *msg); | 343 | const char *get_in_reply_to(const xs_dict *msg); |
| @@ -360,6 +362,7 @@ xs_dict *msg_move(snac *user, const char *new_account); | |||
| 360 | xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to); | 362 | xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to); |
| 361 | xs_dict *msg_question(snac *user, const char *content, xs_list *attach, | 363 | xs_dict *msg_question(snac *user, const char *content, xs_list *attach, |
| 362 | const xs_list *opts, int multiple, int end_secs); | 364 | const xs_list *opts, int multiple, int end_secs); |
| 365 | xs_dict *msg_replies(snac *user, const char *id, int fill); | ||
| 363 | 366 | ||
| 364 | int activitypub_request(snac *snac, const char *url, xs_dict **data); | 367 | int activitypub_request(snac *snac, const char *url, xs_dict **data); |
| 365 | int actor_request(snac *user, const char *actor, xs_dict **data); | 368 | int actor_request(snac *user, const char *actor, xs_dict **data); |
| @@ -229,6 +229,9 @@ int snac_init(const char *basedir) | |||
| 229 | xs *ibdir = xs_fmt("%s/inbox", srv_basedir); | 229 | xs *ibdir = xs_fmt("%s/inbox", srv_basedir); |
| 230 | mkdirx(ibdir); | 230 | mkdirx(ibdir); |
| 231 | 231 | ||
| 232 | xs *langdir = xs_fmt("%s/lang", srv_basedir); | ||
| 233 | mkdirx(langdir); | ||
| 234 | |||
| 232 | xs *gfn = xs_fmt("%s/greeting.html", srv_basedir); | 235 | xs *gfn = xs_fmt("%s/greeting.html", srv_basedir); |
| 233 | if ((f = fopen(gfn, "w")) == NULL) { | 236 | if ((f = fopen(gfn, "w")) == NULL) { |
| 234 | printf("ERROR: cannot create '%s'\n", gfn); | 237 | printf("ERROR: cannot create '%s'\n", gfn); |
| @@ -253,7 +256,10 @@ int snac_init(const char *basedir) | |||
| 253 | xs_json_dump(srv_config, 4, f); | 256 | xs_json_dump(srv_config, 4, f); |
| 254 | fclose(f); | 257 | fclose(f); |
| 255 | 258 | ||
| 256 | printf("Done.\n"); | 259 | printf("Done.\n\n"); |
| 260 | |||
| 261 | printf("Wanted web UI language files (.po) must be copied manually to %s\n", langdir); | ||
| 262 | |||
| 257 | return 0; | 263 | return 0; |
| 258 | } | 264 | } |
| 259 | 265 | ||
| @@ -681,7 +687,7 @@ void export_csv(snac *user) | |||
| 681 | const char *lid = xs_list_get(li, 0); | 687 | const char *lid = xs_list_get(li, 0); |
| 682 | const char *ltitle = xs_list_get(li, 1); | 688 | const char *ltitle = xs_list_get(li, 1); |
| 683 | 689 | ||
| 684 | xs *actors = list_content(user, lid, NULL, 0); | 690 | xs *actors = list_members(user, lid, NULL, 0); |
| 685 | const char *md5; | 691 | const char *md5; |
| 686 | 692 | ||
| 687 | xs_list_foreach(actors, md5) { | 693 | xs_list_foreach(actors, md5) { |
| @@ -907,7 +913,7 @@ void import_list_csv(snac *user, const char *ifn) | |||
| 907 | if (valid_status(webfinger_request(acct, &url, &uid))) { | 913 | if (valid_status(webfinger_request(acct, &url, &uid))) { |
| 908 | xs *actor_md5 = xs_md5_hex(url, strlen(url)); | 914 | xs *actor_md5 = xs_md5_hex(url, strlen(url)); |
| 909 | 915 | ||
| 910 | list_content(user, list_id, actor_md5, 1); | 916 | list_members(user, list_id, actor_md5, 1); |
| 911 | snac_log(user, xs_fmt("Added %s to list %s", url, lname)); | 917 | snac_log(user, xs_fmt("Added %s to list %s", url, lname)); |
| 912 | 918 | ||
| 913 | if (!following_check(user, url)) { | 919 | if (!following_check(user, url)) { |