diff options
Diffstat (limited to 'html.c')
| -rw-r--r-- | html.c | 393 |
1 files changed, 374 insertions, 19 deletions
| @@ -1,5 +1,5 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ | 2 | /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ |
| 3 | 3 | ||
| 4 | #include "xs.h" | 4 | #include "xs.h" |
| 5 | #include "xs_io.h" | 5 | #include "xs_io.h" |
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "xs_match.h" | 12 | #include "xs_match.h" |
| 13 | #include "xs_html.h" | 13 | #include "xs_html.h" |
| 14 | #include "xs_curl.h" | 14 | #include "xs_curl.h" |
| 15 | #include "xs_unicode.h" | ||
| 15 | 16 | ||
| 16 | #include "snac.h" | 17 | #include "snac.h" |
| 17 | 18 | ||
| @@ -113,7 +114,8 @@ xs_str *actor_name(xs_dict *actor, const char *proxy) | |||
| 113 | 114 | ||
| 114 | 115 | ||
| 115 | xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | 116 | xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, |
| 116 | const char *udate, const char *url, int priv, int in_people, const char *proxy) | 117 | const char *udate, const char *url, int priv, |
| 118 | int in_people, const char *proxy, const char *lang) | ||
| 117 | { | 119 | { |
| 118 | xs_html *actor_icon = xs_html_tag("p", NULL); | 120 | xs_html *actor_icon = xs_html_tag("p", NULL); |
| 119 | 121 | ||
| @@ -219,6 +221,9 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 219 | date_title = xs_str_cat(date_title, " / ", udate); | 221 | date_title = xs_str_cat(date_title, " / ", udate); |
| 220 | } | 222 | } |
| 221 | 223 | ||
| 224 | if (xs_is_string(lang)) | ||
| 225 | date_title = xs_str_cat(date_title, " (", lang, ")"); | ||
| 226 | |||
| 222 | xs_html_add(actor_icon, | 227 | xs_html_add(actor_icon, |
| 223 | xs_html_text(" "), | 228 | xs_html_text(" "), |
| 224 | xs_html_tag("time", | 229 | xs_html_tag("time", |
| @@ -265,6 +270,7 @@ xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, con | |||
| 265 | const char *date = NULL; | 270 | const char *date = NULL; |
| 266 | const char *udate = NULL; | 271 | const char *udate = NULL; |
| 267 | const char *url = NULL; | 272 | const char *url = NULL; |
| 273 | const char *lang = NULL; | ||
| 268 | int priv = 0; | 274 | int priv = 0; |
| 269 | const char *type = xs_dict_get(msg, "type"); | 275 | const char *type = xs_dict_get(msg, "type"); |
| 270 | 276 | ||
| @@ -276,7 +282,17 @@ xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, con | |||
| 276 | date = xs_dict_get(msg, "published"); | 282 | date = xs_dict_get(msg, "published"); |
| 277 | udate = xs_dict_get(msg, "updated"); | 283 | udate = xs_dict_get(msg, "updated"); |
| 278 | 284 | ||
| 279 | actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy); | 285 | lang = xs_dict_get(msg, "contentMap"); |
| 286 | if (xs_is_dict(lang)) { | ||
| 287 | const char *v; | ||
| 288 | int c = 0; | ||
| 289 | |||
| 290 | xs_dict_next(lang, &lang, &v, &c); | ||
| 291 | } | ||
| 292 | else | ||
| 293 | lang = NULL; | ||
| 294 | |||
| 295 | actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy, lang); | ||
| 280 | } | 296 | } |
| 281 | 297 | ||
| 282 | return actor_icon; | 298 | return actor_icon; |
| @@ -933,6 +949,7 @@ static xs_html *html_user_body(snac *user, int read_only) | |||
| 933 | xs_html_raw("✔ "), | 949 | xs_html_raw("✔ "), |
| 934 | xs_html_tag("a", | 950 | xs_html_tag("a", |
| 935 | xs_html_attr("rel", "me"), | 951 | xs_html_attr("rel", "me"), |
| 952 | xs_html_attr("target", "_blank"), | ||
| 936 | xs_html_attr("href", v), | 953 | xs_html_attr("href", v), |
| 937 | xs_html_text(v))); | 954 | xs_html_text(v))); |
| 938 | } | 955 | } |
| @@ -969,6 +986,23 @@ static xs_html *html_user_body(snac *user, int read_only) | |||
| 969 | snac_metadata); | 986 | snac_metadata); |
| 970 | } | 987 | } |
| 971 | 988 | ||
| 989 | const char *latitude = xs_dict_get_def(user->config, "latitude", ""); | ||
| 990 | const char *longitude = xs_dict_get_def(user->config, "longitude", ""); | ||
| 991 | |||
| 992 | if (*latitude && *longitude) { | ||
| 993 | xs *label = xs_fmt(L("%s,%s"), latitude, longitude); | ||
| 994 | xs *url = xs_fmt(L("https://openstreetmap.org/search?query=%s,%s"), | ||
| 995 | latitude, longitude); | ||
| 996 | |||
| 997 | xs_html_add(top_user, | ||
| 998 | xs_html_tag("p", | ||
| 999 | xs_html_text(L("Location: ")), | ||
| 1000 | xs_html_tag("a", | ||
| 1001 | xs_html_attr("href", url), | ||
| 1002 | xs_html_attr("target", "_blank"), | ||
| 1003 | xs_html_text(label)))); | ||
| 1004 | } | ||
| 1005 | |||
| 972 | if (xs_is_true(xs_dict_get(user->config, "show_contact_metrics"))) { | 1006 | if (xs_is_true(xs_dict_get(user->config, "show_contact_metrics"))) { |
| 973 | xs *fwers = follower_list(user); | 1007 | xs *fwers = follower_list(user); |
| 974 | xs *fwing = following_list(user); | 1008 | xs *fwing = following_list(user); |
| @@ -1110,6 +1144,8 @@ xs_html *html_top_controls(snac *snac) | |||
| 1110 | const xs_val *coll_thrds = xs_dict_get(snac->config, "collapse_threads"); | 1144 | const xs_val *coll_thrds = xs_dict_get(snac->config, "collapse_threads"); |
| 1111 | const xs_val *pending = xs_dict_get(snac->config, "approve_followers"); | 1145 | const xs_val *pending = xs_dict_get(snac->config, "approve_followers"); |
| 1112 | const xs_val *show_foll = xs_dict_get(snac->config, "show_contact_metrics"); | 1146 | const xs_val *show_foll = xs_dict_get(snac->config, "show_contact_metrics"); |
| 1147 | const char *latitude = xs_dict_get_def(snac->config, "latitude", ""); | ||
| 1148 | const char *longitude = xs_dict_get_def(snac->config, "longitude", ""); | ||
| 1113 | 1149 | ||
| 1114 | xs *metadata = NULL; | 1150 | xs *metadata = NULL; |
| 1115 | const xs_dict *md = xs_dict_get(snac->config, "metadata"); | 1151 | const xs_dict *md = xs_dict_get(snac->config, "metadata"); |
| @@ -1300,6 +1336,20 @@ xs_html *html_top_controls(snac *snac) | |||
| 1300 | xs_html_attr("for", "show_contact_metrics"), | 1336 | xs_html_attr("for", "show_contact_metrics"), |
| 1301 | xs_html_text(L("Publish follower and following metrics")))), | 1337 | xs_html_text(L("Publish follower and following metrics")))), |
| 1302 | xs_html_tag("p", | 1338 | xs_html_tag("p", |
| 1339 | xs_html_text(L("Current location:")), | ||
| 1340 | xs_html_sctag("br", NULL), | ||
| 1341 | xs_html_sctag("input", | ||
| 1342 | xs_html_attr("type", "text"), | ||
| 1343 | xs_html_attr("name", "latitude"), | ||
| 1344 | xs_html_attr("value", latitude), | ||
| 1345 | xs_html_attr("placeholder", "latitude")), | ||
| 1346 | xs_html_text(" "), | ||
| 1347 | xs_html_sctag("input", | ||
| 1348 | xs_html_attr("type", "text"), | ||
| 1349 | xs_html_attr("name", "longitude"), | ||
| 1350 | xs_html_attr("value", longitude), | ||
| 1351 | xs_html_attr("placeholder", "longitude"))), | ||
| 1352 | xs_html_tag("p", | ||
| 1303 | xs_html_text(L("Profile metadata (key=value pairs in each line):")), | 1353 | xs_html_text(L("Profile metadata (key=value pairs in each line):")), |
| 1304 | xs_html_sctag("br", NULL), | 1354 | xs_html_sctag("br", NULL), |
| 1305 | xs_html_tag("textarea", | 1355 | xs_html_tag("textarea", |
| @@ -1328,7 +1378,41 @@ xs_html *html_top_controls(snac *snac) | |||
| 1328 | xs_html_sctag("input", | 1378 | xs_html_sctag("input", |
| 1329 | xs_html_attr("type", "submit"), | 1379 | xs_html_attr("type", "submit"), |
| 1330 | xs_html_attr("class", "button"), | 1380 | xs_html_attr("class", "button"), |
| 1331 | xs_html_attr("value", L("Update user info"))))))); | 1381 | xs_html_attr("value", L("Update user info"))), |
| 1382 | |||
| 1383 | xs_html_tag("p", NULL))))); | ||
| 1384 | |||
| 1385 | xs *followed_hashtags_action = xs_fmt("%s/admin/followed-hashtags", snac->actor); | ||
| 1386 | xs *followed_hashtags = xs_join(xs_dict_get_def(snac->config, | ||
| 1387 | "followed_hashtags", xs_stock(XSTYPE_LIST)), "\n"); | ||
| 1388 | |||
| 1389 | xs_html_add(top_controls, | ||
| 1390 | xs_html_tag("details", | ||
| 1391 | xs_html_tag("summary", | ||
| 1392 | xs_html_text(L("Followed hashtags..."))), | ||
| 1393 | xs_html_tag("p", | ||
| 1394 | xs_html_text(L("One hashtag per line"))), | ||
| 1395 | xs_html_tag("div", | ||
| 1396 | xs_html_attr("class", "snac-followed-hashtags"), | ||
| 1397 | xs_html_tag("form", | ||
| 1398 | xs_html_attr("autocomplete", "off"), | ||
| 1399 | xs_html_attr("method", "post"), | ||
| 1400 | xs_html_attr("action", followed_hashtags_action), | ||
| 1401 | xs_html_attr("enctype", "multipart/form-data"), | ||
| 1402 | |||
| 1403 | xs_html_tag("textarea", | ||
| 1404 | xs_html_attr("name", "followed_hashtags"), | ||
| 1405 | xs_html_attr("cols", "40"), | ||
| 1406 | xs_html_attr("rows", "4"), | ||
| 1407 | xs_html_attr("placeholder", "#cats\n#windowfriday\n#classicalmusic"), | ||
| 1408 | xs_html_text(followed_hashtags)), | ||
| 1409 | |||
| 1410 | xs_html_tag("br", NULL), | ||
| 1411 | |||
| 1412 | xs_html_sctag("input", | ||
| 1413 | xs_html_attr("type", "submit"), | ||
| 1414 | xs_html_attr("class", "button"), | ||
| 1415 | xs_html_attr("value", L("Update hashtags"))))))); | ||
| 1332 | 1416 | ||
| 1333 | return top_controls; | 1417 | return top_controls; |
| 1334 | } | 1418 | } |
| @@ -1781,13 +1865,15 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1781 | } | 1865 | } |
| 1782 | } | 1866 | } |
| 1783 | } | 1867 | } |
| 1784 | else | ||
| 1785 | if (strcmp(type, "Note") == 0) { | ||
| 1786 | if (level == 0) { | ||
| 1787 | /* is the parent not here? */ | ||
| 1788 | const char *parent = get_in_reply_to(msg); | ||
| 1789 | 1868 | ||
| 1790 | if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) { | 1869 | if (user && strcmp(type, "Note") == 0) { |
| 1870 | /* is the parent not here? */ | ||
| 1871 | const char *parent = get_in_reply_to(msg); | ||
| 1872 | |||
| 1873 | if (!xs_is_null(parent) && *parent) { | ||
| 1874 | xs *md5 = xs_md5_hex(parent, strlen(parent)); | ||
| 1875 | |||
| 1876 | if (!timeline_here(user, md5)) { | ||
| 1791 | xs_html_add(post_header, | 1877 | xs_html_add(post_header, |
| 1792 | xs_html_tag("div", | 1878 | xs_html_tag("div", |
| 1793 | xs_html_attr("class", "snac-origin"), | 1879 | xs_html_attr("class", "snac-origin"), |
| @@ -2199,6 +2285,135 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2199 | au_tag); | 2285 | au_tag); |
| 2200 | } | 2286 | } |
| 2201 | 2287 | ||
| 2288 | /* does it have a location? */ | ||
| 2289 | const xs_dict *location = xs_dict_get(msg, "location"); | ||
| 2290 | if (xs_type(location) == XSTYPE_DICT) { | ||
| 2291 | const xs_number *latitude = xs_dict_get(location, "latitude"); | ||
| 2292 | const xs_number *longitude = xs_dict_get(location, "longitude"); | ||
| 2293 | const char *name = xs_dict_get(location, "name"); | ||
| 2294 | const char *address = xs_dict_get(location, "address"); | ||
| 2295 | xs *label_list = xs_list_new(); | ||
| 2296 | |||
| 2297 | if (xs_type(name) == XSTYPE_STRING) | ||
| 2298 | label_list = xs_list_append(label_list, name); | ||
| 2299 | if (xs_type(address) == XSTYPE_STRING) | ||
| 2300 | label_list = xs_list_append(label_list, address); | ||
| 2301 | |||
| 2302 | if (xs_list_len(label_list)) { | ||
| 2303 | const char *url = xs_dict_get(location, "url"); | ||
| 2304 | xs *label = xs_join(label_list, ", "); | ||
| 2305 | |||
| 2306 | if (xs_type(url) == XSTYPE_STRING) { | ||
| 2307 | xs_html_add(snac_content_wrap, | ||
| 2308 | xs_html_tag("p", | ||
| 2309 | xs_html_text(L("Location: ")), | ||
| 2310 | xs_html_tag("a", | ||
| 2311 | xs_html_attr("href", url), | ||
| 2312 | xs_html_attr("target", "_blank"), | ||
| 2313 | xs_html_text(label)))); | ||
| 2314 | } | ||
| 2315 | else | ||
| 2316 | if (!xs_is_null(latitude) && !xs_is_null(longitude)) { | ||
| 2317 | xs *url = xs_fmt("https://openstreetmap.org/search/?query=%s,%s", | ||
| 2318 | xs_number_str(latitude), xs_number_str(longitude)); | ||
| 2319 | |||
| 2320 | xs_html_add(snac_content_wrap, | ||
| 2321 | xs_html_tag("p", | ||
| 2322 | xs_html_text(L("Location: ")), | ||
| 2323 | xs_html_tag("a", | ||
| 2324 | xs_html_attr("href", url), | ||
| 2325 | xs_html_attr("target", "_blank"), | ||
| 2326 | xs_html_text(label)))); | ||
| 2327 | } | ||
| 2328 | else | ||
| 2329 | xs_html_add(snac_content_wrap, | ||
| 2330 | xs_html_tag("p", | ||
| 2331 | xs_html_text(L("Location: ")), | ||
| 2332 | xs_html_text(label))); | ||
| 2333 | } | ||
| 2334 | } | ||
| 2335 | |||
| 2336 | if (strcmp(type, "Event") == 0) { /** Event start and end times **/ | ||
| 2337 | const char *s_time = xs_dict_get(msg, "startTime"); | ||
| 2338 | |||
| 2339 | if (xs_is_string(s_time) && strlen(s_time) > 20) { | ||
| 2340 | const char *e_time = xs_dict_get(msg, "endTime"); | ||
| 2341 | const char *tz = xs_dict_get(msg, "timezone"); | ||
| 2342 | |||
| 2343 | xs *s = xs_replace_i(xs_dup(s_time), "T", " "); | ||
| 2344 | xs *e = NULL; | ||
| 2345 | |||
| 2346 | if (xs_is_string(e_time) && strlen(e_time) > 20) | ||
| 2347 | e = xs_replace_i(xs_dup(e_time), "T", " "); | ||
| 2348 | |||
| 2349 | /* if the event has a timezone, crop the offsets */ | ||
| 2350 | if (xs_is_string(tz)) { | ||
| 2351 | s = xs_crop_i(s, 0, 19); | ||
| 2352 | |||
| 2353 | if (e) | ||
| 2354 | e = xs_crop_i(e, 0, 19); | ||
| 2355 | } | ||
| 2356 | else | ||
| 2357 | tz = ""; | ||
| 2358 | |||
| 2359 | /* if start and end share the same day, crop it from the end */ | ||
| 2360 | if (e && memcmp(s, e, 11) == 0) | ||
| 2361 | e = xs_crop_i(e, 11, 0); | ||
| 2362 | |||
| 2363 | if (e) | ||
| 2364 | s = xs_str_cat(s, " / ", e); | ||
| 2365 | |||
| 2366 | if (*tz) | ||
| 2367 | s = xs_str_cat(s, " (", tz, ")"); | ||
| 2368 | |||
| 2369 | /* replace ugly decimals */ | ||
| 2370 | s = xs_replace_i(s, ".000", ""); | ||
| 2371 | |||
| 2372 | xs_html_add(snac_content_wrap, | ||
| 2373 | xs_html_tag("p", | ||
| 2374 | xs_html_text(L("Time: ")), | ||
| 2375 | xs_html_text(s))); | ||
| 2376 | } | ||
| 2377 | } | ||
| 2378 | |||
| 2379 | /* show all hashtags that has not been shown previously in the content */ | ||
| 2380 | const xs_list *tags = xs_dict_get(msg, "tag"); | ||
| 2381 | const char *o_content = xs_dict_get_def(msg, "content", ""); | ||
| 2382 | |||
| 2383 | if (xs_is_string(o_content) && xs_is_list(tags) && xs_list_len(tags)) { | ||
| 2384 | xs *content = xs_utf8_to_lower(o_content); | ||
| 2385 | const xs_dict *tag; | ||
| 2386 | |||
| 2387 | xs_html *add_hashtags = xs_html_tag("ul", | ||
| 2388 | xs_html_attr("class", "snac-more-hashtags")); | ||
| 2389 | |||
| 2390 | xs_list_foreach(tags, tag) { | ||
| 2391 | const char *type = xs_dict_get(tag, "type"); | ||
| 2392 | |||
| 2393 | if (xs_is_string(type) && strcmp(type, "Hashtag") == 0) { | ||
| 2394 | const char *o_href = xs_dict_get(tag, "href"); | ||
| 2395 | const char *name = xs_dict_get(tag, "name"); | ||
| 2396 | |||
| 2397 | if (xs_is_string(o_href) && xs_is_string(name)) { | ||
| 2398 | xs *href = xs_utf8_to_lower(o_href); | ||
| 2399 | |||
| 2400 | if (xs_str_in(content, href) == -1 && xs_str_in(content, name) == -1) { | ||
| 2401 | /* not in the content: add here */ | ||
| 2402 | xs_html_add(add_hashtags, | ||
| 2403 | xs_html_tag("li", | ||
| 2404 | xs_html_tag("a", | ||
| 2405 | xs_html_attr("href", href), | ||
| 2406 | xs_html_text(name), | ||
| 2407 | xs_html_text(" ")))); | ||
| 2408 | } | ||
| 2409 | } | ||
| 2410 | } | ||
| 2411 | } | ||
| 2412 | |||
| 2413 | xs_html_add(snac_content_wrap, | ||
| 2414 | add_hashtags); | ||
| 2415 | } | ||
| 2416 | |||
| 2202 | /** controls **/ | 2417 | /** controls **/ |
| 2203 | 2418 | ||
| 2204 | if (!read_only && user) { | 2419 | if (!read_only && user) { |
| @@ -2583,7 +2798,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons | |||
| 2583 | xs_html_tag("div", | 2798 | xs_html_tag("div", |
| 2584 | xs_html_attr("class", "snac-post-header"), | 2799 | xs_html_attr("class", "snac-post-header"), |
| 2585 | html_actor_icon(snac, actor, xs_dict_get(actor, "published"), | 2800 | html_actor_icon(snac, actor, xs_dict_get(actor, "published"), |
| 2586 | NULL, NULL, 0, 1, proxy))); | 2801 | NULL, NULL, 0, 1, proxy, NULL))); |
| 2587 | 2802 | ||
| 2588 | /* content (user bio) */ | 2803 | /* content (user bio) */ |
| 2589 | const char *c = xs_dict_get(actor, "summary"); | 2804 | const char *c = xs_dict_get(actor, "summary"); |
| @@ -2762,9 +2977,15 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2762 | xs_html_attr("class", "snac-posts")); | 2977 | xs_html_attr("class", "snac-posts")); |
| 2763 | xs_html_add(body, posts); | 2978 | xs_html_add(body, posts); |
| 2764 | 2979 | ||
| 2765 | xs_list *p = n_list; | 2980 | xs_set rep; |
| 2981 | xs_set_init(&rep); | ||
| 2982 | |||
| 2983 | /* dict to store previous notification labels */ | ||
| 2984 | xs *admiration_labels = xs_dict_new(); | ||
| 2985 | |||
| 2766 | const xs_str *v; | 2986 | const xs_str *v; |
| 2767 | while (xs_list_iter(&p, &v)) { | 2987 | |
| 2988 | xs_list_foreach(n_list, v) { | ||
| 2768 | xs *noti = notify_get(user, v); | 2989 | xs *noti = notify_get(user, v); |
| 2769 | 2990 | ||
| 2770 | if (noti == NULL) | 2991 | if (noti == NULL) |
| @@ -2775,6 +2996,7 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2775 | const char *utype = xs_dict_get(noti, "utype"); | 2996 | const char *utype = xs_dict_get(noti, "utype"); |
| 2776 | const char *id = xs_dict_get(noti, "objid"); | 2997 | const char *id = xs_dict_get(noti, "objid"); |
| 2777 | const char *date = xs_dict_get(noti, "date"); | 2998 | const char *date = xs_dict_get(noti, "date"); |
| 2999 | const char *id2 = xs_dict_get_path(noti, "msg.id"); | ||
| 2778 | xs *wrk = NULL; | 3000 | xs *wrk = NULL; |
| 2779 | 3001 | ||
| 2780 | if (xs_is_null(id)) | 3002 | if (xs_is_null(id)) |
| @@ -2783,8 +3005,16 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2783 | if (is_hidden(user, id)) | 3005 | if (is_hidden(user, id)) |
| 2784 | continue; | 3006 | continue; |
| 2785 | 3007 | ||
| 3008 | if (xs_is_string(id2) && xs_set_add(&rep, id2) != 1) | ||
| 3009 | continue; | ||
| 3010 | |||
| 2786 | object_get(id, &obj); | 3011 | object_get(id, &obj); |
| 2787 | 3012 | ||
| 3013 | const char *msg_id = NULL; | ||
| 3014 | |||
| 3015 | if (xs_is_dict(obj)) | ||
| 3016 | msg_id = xs_dict_get(obj, "id"); | ||
| 3017 | |||
| 2788 | const char *actor_id = xs_dict_get(noti, "actor"); | 3018 | const char *actor_id = xs_dict_get(noti, "actor"); |
| 2789 | xs *actor = NULL; | 3019 | xs *actor = NULL; |
| 2790 | 3020 | ||
| @@ -2817,9 +3047,7 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2817 | 3047 | ||
| 2818 | xs *s_date = xs_crop_i(xs_dup(date), 0, 10); | 3048 | xs *s_date = xs_crop_i(xs_dup(date), 0, 10); |
| 2819 | 3049 | ||
| 2820 | xs_html *entry = xs_html_tag("div", | 3050 | xs_html *this_html_label = xs_html_container( |
| 2821 | xs_html_attr("class", "snac-post-with-desc"), | ||
| 2822 | xs_html_tag("p", | ||
| 2823 | xs_html_tag("b", | 3051 | xs_html_tag("b", |
| 2824 | xs_html_text(label), | 3052 | xs_html_text(label), |
| 2825 | xs_html_text(" by "), | 3053 | xs_html_text(" by "), |
| @@ -2830,13 +3058,45 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2830 | xs_html_tag("time", | 3058 | xs_html_tag("time", |
| 2831 | xs_html_attr("class", "dt-published snac-pubdate"), | 3059 | xs_html_attr("class", "dt-published snac-pubdate"), |
| 2832 | xs_html_attr("title", date), | 3060 | xs_html_attr("title", date), |
| 2833 | xs_html_text(s_date)))); | 3061 | xs_html_text(s_date))); |
| 3062 | |||
| 3063 | xs_html *html_label = NULL; | ||
| 3064 | |||
| 3065 | if (xs_is_string(msg_id)) { | ||
| 3066 | const xs_val *prev_label = xs_dict_get(admiration_labels, msg_id); | ||
| 3067 | |||
| 3068 | if (xs_type(prev_label) == XSTYPE_DATA) { | ||
| 3069 | /* there is a previous list of admiration labels! */ | ||
| 3070 | xs_data_get(&html_label, prev_label); | ||
| 3071 | |||
| 3072 | xs_html_add(html_label, | ||
| 3073 | xs_html_sctag("br", NULL), | ||
| 3074 | this_html_label); | ||
| 3075 | |||
| 3076 | continue; | ||
| 3077 | } | ||
| 3078 | } | ||
| 3079 | |||
| 3080 | xs_html *entry = NULL; | ||
| 3081 | |||
| 3082 | html_label = xs_html_tag("p", | ||
| 3083 | this_html_label); | ||
| 3084 | |||
| 3085 | /* store in the admiration labels dict */ | ||
| 3086 | xs *pl = xs_data_new(&html_label, sizeof(html_label)); | ||
| 3087 | |||
| 3088 | if (xs_is_string(msg_id)) | ||
| 3089 | admiration_labels = xs_dict_set(admiration_labels, msg_id, pl); | ||
| 3090 | |||
| 3091 | entry = xs_html_tag("div", | ||
| 3092 | xs_html_attr("class", "snac-post-with-desc"), | ||
| 3093 | html_label); | ||
| 2834 | 3094 | ||
| 2835 | if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0 || strcmp(type, "Block") == 0) { | 3095 | if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0 || strcmp(type, "Block") == 0) { |
| 2836 | xs_html_add(entry, | 3096 | xs_html_add(entry, |
| 2837 | xs_html_tag("div", | 3097 | xs_html_tag("div", |
| 2838 | xs_html_attr("class", "snac-post"), | 3098 | xs_html_attr("class", "snac-post"), |
| 2839 | html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy))); | 3099 | html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL))); |
| 2840 | } | 3100 | } |
| 2841 | else | 3101 | else |
| 2842 | if (strcmp(type, "Move") == 0) { | 3102 | if (strcmp(type, "Move") == 0) { |
| @@ -2850,7 +3110,7 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2850 | xs_html_add(entry, | 3110 | xs_html_add(entry, |
| 2851 | xs_html_tag("div", | 3111 | xs_html_tag("div", |
| 2852 | xs_html_attr("class", "snac-post"), | 3112 | xs_html_attr("class", "snac-post"), |
| 2853 | html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy))); | 3113 | html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy, NULL))); |
| 2854 | } | 3114 | } |
| 2855 | } | 3115 | } |
| 2856 | } | 3116 | } |
| @@ -2917,6 +3177,8 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 2917 | xs_html_text(L("More..."))))); | 3177 | xs_html_text(L("More..."))))); |
| 2918 | } | 3178 | } |
| 2919 | 3179 | ||
| 3180 | xs_set_free(&rep); | ||
| 3181 | |||
| 2920 | xs_html_add(body, | 3182 | xs_html_add(body, |
| 2921 | html_footer()); | 3183 | html_footer()); |
| 2922 | 3184 | ||
| @@ -2970,6 +3232,21 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 2970 | else | 3232 | else |
| 2971 | return HTTP_STATUS_NOT_FOUND; | 3233 | return HTTP_STATUS_NOT_FOUND; |
| 2972 | } | 3234 | } |
| 3235 | else | ||
| 3236 | if (strcmp(v, "auth-int-bridge") == 0) { | ||
| 3237 | const char *login = xs_dict_get(q_vars, "login"); | ||
| 3238 | const char *id = xs_dict_get(q_vars, "id"); | ||
| 3239 | const char *action = xs_dict_get(q_vars, "action"); | ||
| 3240 | |||
| 3241 | if (xs_is_string(login) && xs_is_string(id) && xs_is_string(action)) { | ||
| 3242 | *body = xs_fmt("%s/%s/authorize_interaction?action=%s&id=%s", | ||
| 3243 | srv_baseurl, login, action, id); | ||
| 3244 | |||
| 3245 | return HTTP_STATUS_SEE_OTHER; | ||
| 3246 | } | ||
| 3247 | else | ||
| 3248 | return HTTP_STATUS_NOT_FOUND; | ||
| 3249 | } | ||
| 2973 | 3250 | ||
| 2974 | uid = xs_dup(v); | 3251 | uid = xs_dup(v); |
| 2975 | 3252 | ||
| @@ -3542,6 +3819,52 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 3542 | } | 3819 | } |
| 3543 | } | 3820 | } |
| 3544 | else | 3821 | else |
| 3822 | if (strcmp(p_path, "authorize_interaction") == 0) { /** follow, like or boost from Mastodon **/ | ||
| 3823 | if (!login(&snac, req)) { | ||
| 3824 | *body = xs_dup(uid); | ||
| 3825 | status = HTTP_STATUS_UNAUTHORIZED; | ||
| 3826 | } | ||
| 3827 | else { | ||
| 3828 | status = HTTP_STATUS_NOT_FOUND; | ||
| 3829 | |||
| 3830 | const char *id = xs_dict_get(q_vars, "id"); | ||
| 3831 | const char *action = xs_dict_get(q_vars, "action"); | ||
| 3832 | |||
| 3833 | if (xs_is_string(id) && xs_is_string(action)) { | ||
| 3834 | if (strcmp(action, "Follow") == 0) { | ||
| 3835 | xs *msg = msg_follow(&snac, id); | ||
| 3836 | |||
| 3837 | if (msg != NULL) { | ||
| 3838 | const char *actor = xs_dict_get(msg, "object"); | ||
| 3839 | |||
| 3840 | following_add(&snac, actor, msg); | ||
| 3841 | |||
| 3842 | enqueue_output_by_actor(&snac, msg, actor, 0); | ||
| 3843 | |||
| 3844 | status = HTTP_STATUS_SEE_OTHER; | ||
| 3845 | } | ||
| 3846 | } | ||
| 3847 | else | ||
| 3848 | if (xs_match(action, "Like|Boost|Announce")) { | ||
| 3849 | /* bring the post */ | ||
| 3850 | xs *msg = msg_admiration(&snac, id, *action == 'L' ? "Like" : "Announce"); | ||
| 3851 | |||
| 3852 | if (msg != NULL) { | ||
| 3853 | enqueue_message(&snac, msg); | ||
| 3854 | timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, *action == 'L' ? 1 : 0); | ||
| 3855 | |||
| 3856 | status = HTTP_STATUS_SEE_OTHER; | ||
| 3857 | } | ||
| 3858 | } | ||
| 3859 | } | ||
| 3860 | |||
| 3861 | if (status == HTTP_STATUS_SEE_OTHER) { | ||
| 3862 | *body = xs_fmt("%s/admin", snac.actor); | ||
| 3863 | *b_size = strlen(*body); | ||
| 3864 | } | ||
| 3865 | } | ||
| 3866 | } | ||
| 3867 | else | ||
| 3545 | status = HTTP_STATUS_NOT_FOUND; | 3868 | status = HTTP_STATUS_NOT_FOUND; |
| 3546 | 3869 | ||
| 3547 | user_free(&snac); | 3870 | user_free(&snac); |
| @@ -4024,6 +4347,9 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 4024 | else | 4347 | else |
| 4025 | snac.config = xs_dict_set(snac.config, "show_contact_metrics", xs_stock(XSTYPE_FALSE)); | 4348 | snac.config = xs_dict_set(snac.config, "show_contact_metrics", xs_stock(XSTYPE_FALSE)); |
| 4026 | 4349 | ||
| 4350 | snac.config = xs_dict_set(snac.config, "latitude", xs_dict_get_def(p_vars, "latitude", "")); | ||
| 4351 | snac.config = xs_dict_set(snac.config, "longitude", xs_dict_get_def(p_vars, "longitude", "")); | ||
| 4352 | |||
| 4027 | if ((v = xs_dict_get(p_vars, "metadata")) != NULL) | 4353 | if ((v = xs_dict_get(p_vars, "metadata")) != NULL) |
| 4028 | snac.config = xs_dict_set(snac.config, "metadata", v); | 4354 | snac.config = xs_dict_set(snac.config, "metadata", v); |
| 4029 | 4355 | ||
| @@ -4140,6 +4466,35 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 4140 | 4466 | ||
| 4141 | status = HTTP_STATUS_SEE_OTHER; | 4467 | status = HTTP_STATUS_SEE_OTHER; |
| 4142 | } | 4468 | } |
| 4469 | else | ||
| 4470 | if (p_path && strcmp(p_path, "admin/followed-hashtags") == 0) { /** **/ | ||
| 4471 | const char *followed_hashtags = xs_dict_get(p_vars, "followed_hashtags"); | ||
| 4472 | |||
| 4473 | if (xs_is_string(followed_hashtags)) { | ||
| 4474 | xs *new_hashtags = xs_list_new(); | ||
| 4475 | xs *l = xs_split(followed_hashtags, "\n"); | ||
| 4476 | const char *v; | ||
| 4477 | |||
| 4478 | xs_list_foreach(l, v) { | ||
| 4479 | xs *s1 = xs_strip_i(xs_dup(v)); | ||
| 4480 | s1 = xs_replace_i(s1, " ", ""); | ||
| 4481 | |||
| 4482 | if (*s1 == '\0') | ||
| 4483 | continue; | ||
| 4484 | |||
| 4485 | xs *s2 = xs_utf8_to_lower(s1); | ||
| 4486 | if (*s2 != '#') | ||
| 4487 | s2 = xs_str_prepend_i(s2, "#"); | ||
| 4488 | |||
| 4489 | new_hashtags = xs_list_append(new_hashtags, s2); | ||
| 4490 | } | ||
| 4491 | |||
| 4492 | snac.config = xs_dict_set(snac.config, "followed_hashtags", new_hashtags); | ||
| 4493 | user_persist(&snac, 0); | ||
| 4494 | } | ||
| 4495 | |||
| 4496 | status = HTTP_STATUS_SEE_OTHER; | ||
| 4497 | } | ||
| 4143 | 4498 | ||
| 4144 | if (status == HTTP_STATUS_SEE_OTHER) { | 4499 | if (status == HTTP_STATUS_SEE_OTHER) { |
| 4145 | const char *redir = xs_dict_get(p_vars, "redir"); | 4500 | const char *redir = xs_dict_get(p_vars, "redir"); |