summaryrefslogtreecommitdiff
path: root/activitypub.c
diff options
context:
space:
mode:
authorGravatar Stefano Marinelli2025-10-18 21:33:31 +0200
committerGravatar Stefano Marinelli2025-10-18 21:33:31 +0200
commit2d433afc27afe46c9641cd0b0bd076566cfb67e2 (patch)
tree826378411a16df79b16b5b351b7c56d20db3219f /activitypub.c
parentFEDERATION.md: also document outgoing Webmention. (diff)
downloadpenes-snac2-2d433afc27afe46c9641cd0b0bd076566cfb67e2.tar.gz
penes-snac2-2d433afc27afe46c9641cd0b0bd076566cfb67e2.tar.xz
penes-snac2-2d433afc27afe46c9641cd0b0bd076566cfb67e2.zip
Enhances Mastodon API compatibility by adding support for displaying remote users' follower/following/post counts and their posts when viewing profiles in Mastodon-compatible apps (Fedilab, Tusky, etc.).
Fixes for Moshidon and improvements for better compatibility with HAProxy
Diffstat (limited to 'activitypub.c')
-rw-r--r--activitypub.c78
1 files changed, 77 insertions, 1 deletions
diff --git a/activitypub.c b/activitypub.c
index f19c6fc..117bbc9 100644
--- a/activitypub.c
+++ b/activitypub.c
@@ -118,6 +118,75 @@ int activitypub_request(snac *user, const char *url, xs_dict **data)
118} 118}
119 119
120 120
121static xs_dict *actor_get_collections(snac *user, xs_dict *actor, int throttle)
122/* fetches follower/following/statuses counts from an actor's collections and adds them to the actor object */
123{
124 /* only fetch if counts are not already present in the actor object */
125 const xs_number *existing_followers = xs_dict_get(actor, "followers_count");
126 const xs_number *existing_following = xs_dict_get(actor, "following_count");
127 const xs_number *existing_statuses = xs_dict_get(actor, "statuses_count");
128
129 /* skip if we already have all counts */
130 if (xs_type(existing_followers) == XSTYPE_NUMBER &&
131 xs_type(existing_following) == XSTYPE_NUMBER &&
132 xs_type(existing_statuses) == XSTYPE_NUMBER) {
133 return actor;
134 }
135
136 /* CRITICAL: duplicate URLs BEFORE any xs_dict_set calls, as xs_dict_set can reallocate the dict */
137 xs *followers_url = xs_dup(xs_dict_get(actor, "followers"));
138 xs *following_url = xs_dup(xs_dict_get(actor, "following"));
139 xs *outbox_url = xs_dup(xs_dict_get(actor, "outbox"));
140
141 /* only fetch followers count if not already present */
142 if (xs_type(existing_followers) != XSTYPE_NUMBER && !xs_is_null(followers_url)) {
143 xs *followers_coll = NULL;
144 if (activitypub_request(user, followers_url, &followers_coll) == 200) {
145 const xs_number *total = xs_dict_get(followers_coll, "totalItems");
146 if (xs_type(total) == XSTYPE_NUMBER) {
147 xs *total_copy = xs_dup(total);
148 actor = xs_dict_set(actor, "followers_count", total_copy);
149 }
150 }
151 /* throttle to prevent resource exhaustion on low-power devices */
152 if (throttle)
153 usleep(100000); /* 100ms delay between requests */
154 }
155
156 /* only fetch following count if not already present */
157 if (xs_type(existing_following) != XSTYPE_NUMBER && !xs_is_null(following_url)) {
158 xs *following_coll = NULL;
159 if (activitypub_request(user, following_url, &following_coll) == 200) {
160 const xs_number *total = xs_dict_get(following_coll, "totalItems");
161 if (xs_type(total) == XSTYPE_NUMBER) {
162 xs *total_copy = xs_dup(total);
163 actor = xs_dict_set(actor, "following_count", total_copy);
164 }
165 }
166 /* throttle to prevent resource exhaustion on low-power devices */
167 if (throttle)
168 usleep(100000); /* 100ms delay between requests */
169 }
170
171 /* only fetch statuses count if not already present */
172 if (xs_type(existing_statuses) != XSTYPE_NUMBER && !xs_is_null(outbox_url)) {
173 xs *outbox_coll = NULL;
174 if (activitypub_request(user, outbox_url, &outbox_coll) == 200) {
175 const xs_number *total = xs_dict_get(outbox_coll, "totalItems");
176 if (xs_type(total) == XSTYPE_NUMBER) {
177 xs *total_copy = xs_dup(total);
178 actor = xs_dict_set(actor, "statuses_count", total_copy);
179 }
180 }
181 /* throttle to prevent resource exhaustion on low-power devices */
182 if (throttle)
183 usleep(100000); /* 100ms delay between requests */
184 }
185
186 return actor;
187}
188
189
121int actor_request(snac *user, const char *actor, xs_dict **data) 190int actor_request(snac *user, const char *actor, xs_dict **data)
122/* request an actor */ 191/* request an actor */
123{ 192{
@@ -135,6 +204,9 @@ int actor_request(snac *user, const char *actor, xs_dict **data)
135 status = activitypub_request(user, actor, &payload); 204 status = activitypub_request(user, actor, &payload);
136 205
137 if (valid_status(status)) { 206 if (valid_status(status)) {
207 /* fetch collection counts when initially fetching an actor (no throttle) */
208 payload = actor_get_collections(user, payload, 0);
209
138 /* renew data */ 210 /* renew data */
139 status = actor_add(actor, payload); 211 status = actor_add(actor, payload);
140 212
@@ -3118,8 +3190,12 @@ void process_user_queue_item(snac *user, xs_dict *q_item)
3118 xs *actor_o = NULL; 3190 xs *actor_o = NULL;
3119 int status; 3191 int status;
3120 3192
3121 if (valid_status((status = activitypub_request(user, actor, &actor_o)))) 3193 if (valid_status((status = activitypub_request(user, actor, &actor_o)))) {
3194 /* refresh collection counts with throttling to prevent resource exhaustion */
3195 actor_o = actor_get_collections(user, actor_o, 1);
3196
3122 actor_add(actor, actor_o); 3197 actor_add(actor, actor_o);
3198 }
3123 else { 3199 else {
3124 if (status == HTTP_STATUS_GONE) { 3200 if (status == HTTP_STATUS_GONE) {
3125 actor_failure(actor, 1); 3201 actor_failure(actor, 1);