summaryrefslogtreecommitdiff
path: root/html.c
diff options
context:
space:
mode:
Diffstat (limited to 'html.c')
-rw-r--r--html.c393
1 files changed, 374 insertions, 19 deletions
diff --git a/html.c b/html.c
index b54556d..6c42842 100644
--- a/html.c
+++ b/html.c
@@ -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
115xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, 116xs_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");