summaryrefslogtreecommitdiff
path: root/html.c
diff options
context:
space:
mode:
authorGravatar Louis Brauer2024-05-25 08:05:36 +0000
committerGravatar Louis Brauer2024-05-25 08:05:36 +0000
commit84a767dd0878013194ed7551b5ae6ef715e841a6 (patch)
tree9fb1b2b89e0bfbb4b8bf1e85d840c8653e646bb7 /html.c
parentPrevent some browsers from caching servers basic auth request (diff)
parentBackport from xs (fix regex.h compilation with tcc). (diff)
downloadsnac2-84a767dd0878013194ed7551b5ae6ef715e841a6.tar.gz
snac2-84a767dd0878013194ed7551b5ae6ef715e841a6.tar.xz
snac2-84a767dd0878013194ed7551b5ae6ef715e841a6.zip
Merge pull request 'master' (#1) from grunfink/snac2:master into master
Reviewed-on: https://codeberg.org/louis77/snac2/pulls/1
Diffstat (limited to 'html.c')
-rw-r--r--html.c703
1 files changed, 453 insertions, 250 deletions
diff --git a/html.c b/html.c
index f50fb7d..f97c45d 100644
--- a/html.c
+++ b/html.c
@@ -41,7 +41,7 @@ int login(snac *snac, const xs_dict *headers)
41} 41}
42 42
43 43
44xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems) 44xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems)
45/* replaces all the :shortnames: with the emojis in tag */ 45/* replaces all the :shortnames: with the emojis in tag */
46{ 46{
47 if (!xs_is_null(tag)) { 47 if (!xs_is_null(tag)) {
@@ -55,20 +55,20 @@ xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems)
55 tag_list = xs_dup(tag); 55 tag_list = xs_dup(tag);
56 } 56 }
57 57
58 xs *style = xs_fmt("height: %dem; vertical-align: middle;", ems); 58 xs *style = xs_fmt("height: %dem; width: %dem; vertical-align: middle;", ems, ems);
59 59
60 xs_list *p = tag_list; 60 const char *v;
61 char *v; 61 int c = 0;
62 62
63 while (xs_list_iter(&p, &v)) { 63 while (xs_list_next(tag_list, &v, &c)) {
64 char *t = xs_dict_get(v, "type"); 64 const char *t = xs_dict_get(v, "type");
65 65
66 if (t && strcmp(t, "Emoji") == 0) { 66 if (t && strcmp(t, "Emoji") == 0) {
67 char *n = xs_dict_get(v, "name"); 67 const char *n = xs_dict_get(v, "name");
68 char *i = xs_dict_get(v, "icon"); 68 const char *i = xs_dict_get(v, "icon");
69 69
70 if (n && i) { 70 if (n && i) {
71 char *u = xs_dict_get(i, "url"); 71 const char *u = xs_dict_get(i, "url");
72 xs_html *img = xs_html_sctag("img", 72 xs_html *img = xs_html_sctag("img",
73 xs_html_attr("loading", "lazy"), 73 xs_html_attr("loading", "lazy"),
74 xs_html_attr("src", u), 74 xs_html_attr("src", u),
@@ -88,7 +88,7 @@ xs_str *replace_shortnames(xs_str *s, xs_list *tag, int ems)
88xs_str *actor_name(xs_dict *actor) 88xs_str *actor_name(xs_dict *actor)
89/* gets the actor name */ 89/* gets the actor name */
90{ 90{
91 char *v; 91 const char *v;
92 92
93 if (xs_is_null((v = xs_dict_get(actor, "name"))) || *v == '\0') { 93 if (xs_is_null((v = xs_dict_get(actor, "name"))) || *v == '\0') {
94 if (xs_is_null(v = xs_dict_get(actor, "preferredUsername")) || *v == '\0') { 94 if (xs_is_null(v = xs_dict_get(actor, "preferredUsername")) || *v == '\0') {
@@ -106,7 +106,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
106 xs_html *actor_icon = xs_html_tag("p", NULL); 106 xs_html *actor_icon = xs_html_tag("p", NULL);
107 107
108 xs *avatar = NULL; 108 xs *avatar = NULL;
109 char *v; 109 const char *v;
110 int fwing = 0; 110 int fwing = 0;
111 int fwer = 0; 111 int fwer = 0;
112 112
@@ -125,7 +125,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
125 if (avatar == NULL) 125 if (avatar == NULL)
126 avatar = xs_fmt("data:image/png;base64, %s", default_avatar_base64()); 126 avatar = xs_fmt("data:image/png;base64, %s", default_avatar_base64());
127 127
128 char *actor_id = xs_dict_get(actor, "id"); 128 const char *actor_id = xs_dict_get(actor, "id");
129 xs *href = NULL; 129 xs *href = NULL;
130 130
131 if (user) { 131 if (user) {
@@ -216,7 +216,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
216 } 216 }
217 217
218 { 218 {
219 char *username, *id; 219 const char *username, *id;
220 220
221 if (xs_is_null(username = xs_dict_get(actor, "preferredUsername")) || *username == '\0') { 221 if (xs_is_null(username = xs_dict_get(actor, "preferredUsername")) || *username == '\0') {
222 /* This should never be reached */ 222 /* This should never be reached */
@@ -244,19 +244,19 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
244} 244}
245 245
246 246
247xs_html *html_msg_icon(snac *user, char *actor_id, const xs_dict *msg) 247xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg)
248{ 248{
249 xs *actor = NULL; 249 xs *actor = NULL;
250 xs_html *actor_icon = NULL; 250 xs_html *actor_icon = NULL;
251 251
252 if (actor_id && valid_status(actor_get_refresh(user, actor_id, &actor))) { 252 if (actor_id && valid_status(actor_get_refresh(user, actor_id, &actor))) {
253 char *date = NULL; 253 const char *date = NULL;
254 char *udate = NULL; 254 const char *udate = NULL;
255 char *url = NULL; 255 const char *url = NULL;
256 int priv = 0; 256 int priv = 0;
257 const char *type = xs_dict_get(msg, "type"); 257 const char *type = xs_dict_get(msg, "type");
258 258
259 if (xs_match(type, "Note|Question|Page|Article|Video")) 259 if (xs_match(type, POSTLIKE_OBJECT_TYPE))
260 url = xs_dict_get(msg, "id"); 260 url = xs_dict_get(msg, "id");
261 261
262 priv = !is_msg_public(msg); 262 priv = !is_msg_public(msg);
@@ -271,14 +271,14 @@ xs_html *html_msg_icon(snac *user, char *actor_id, const xs_dict *msg)
271} 271}
272 272
273 273
274xs_html *html_note(snac *user, char *summary, 274xs_html *html_note(snac *user, const char *summary,
275 char *div_id, char *form_id, 275 const char *div_id, const char *form_id,
276 char *ta_plh, char *ta_content, 276 const char *ta_plh, const char *ta_content,
277 char *edit_id, char *actor_id, 277 const char *edit_id, const char *actor_id,
278 xs_val *cw_yn, char *cw_text, 278 const xs_val *cw_yn, const char *cw_text,
279 xs_val *mnt_only, char *redir, 279 const xs_val *mnt_only, const char *redir,
280 char *in_reply_to, int poll, 280 const char *in_reply_to, int poll,
281 char *att_file, char *att_alt_text) 281 const char *att_file, const char *att_alt_text)
282{ 282{
283 xs *action = xs_fmt("%s/admin/note", user->actor); 283 xs *action = xs_fmt("%s/admin/note", user->actor);
284 284
@@ -460,9 +460,11 @@ static xs_html *html_base_head(void)
460 /* add server CSS and favicon */ 460 /* add server CSS and favicon */
461 xs *f; 461 xs *f;
462 f = xs_fmt("%s/favicon.ico", srv_baseurl); 462 f = xs_fmt("%s/favicon.ico", srv_baseurl);
463 xs_list *p = xs_dict_get(srv_config, "cssurls"); 463 const xs_list *p = xs_dict_get(srv_config, "cssurls");
464 char *v; 464 const char *v;
465 while (xs_list_iter(&p, &v)) { 465 int c = 0;
466
467 while (xs_list_next(p, &v, &c)) {
466 xs_html_add(head, 468 xs_html_add(head,
467 xs_html_sctag("link", 469 xs_html_sctag("link",
468 xs_html_attr("rel", "stylesheet"), 470 xs_html_attr("rel", "stylesheet"),
@@ -498,8 +500,8 @@ xs_html *html_instance_head(void)
498 } 500 }
499 } 501 }
500 502
501 char *host = xs_dict_get(srv_config, "host"); 503 const char *host = xs_dict_get(srv_config, "host");
502 char *title = xs_dict_get(srv_config, "title"); 504 const char *title = xs_dict_get(srv_config, "title");
503 505
504 xs_html_add(head, 506 xs_html_add(head,
505 xs_html_tag("title", 507 xs_html_tag("title",
@@ -509,12 +511,12 @@ xs_html *html_instance_head(void)
509} 511}
510 512
511 513
512static xs_html *html_instance_body(char *tag) 514static xs_html *html_instance_body(void)
513{ 515{
514 char *host = xs_dict_get(srv_config, "host"); 516 const char *host = xs_dict_get(srv_config, "host");
515 char *sdesc = xs_dict_get(srv_config, "short_description"); 517 const char *sdesc = xs_dict_get(srv_config, "short_description");
516 char *email = xs_dict_get(srv_config, "admin_email"); 518 const char *email = xs_dict_get(srv_config, "admin_email");
517 char *acct = xs_dict_get(srv_config, "admin_account"); 519 const char *acct = xs_dict_get(srv_config, "admin_account");
518 520
519 xs *blurb = xs_replace(snac_blurb, "%host%", host); 521 xs *blurb = xs_replace(snac_blurb, "%host%", host);
520 522
@@ -560,16 +562,6 @@ static xs_html *html_instance_body(char *tag)
560 xs_html_text(handle))))); 562 xs_html_text(handle)))));
561 } 563 }
562 564
563 {
564 xs *l = tag ? xs_fmt(L("Search results for #%s"), tag) :
565 xs_dup(L("Recent posts by users in this instance"));
566
567 xs_html_add(body,
568 xs_html_tag("h2",
569 xs_html_attr("class", "snac-header"),
570 xs_html_text(l)));
571 }
572
573 return body; 565 return body;
574} 566}
575 567
@@ -749,7 +741,17 @@ static xs_html *html_user_body(snac *user, int read_only)
749 xs_html_text(" - "), 741 xs_html_text(" - "),
750 xs_html_tag("a", 742 xs_html_tag("a",
751 xs_html_attr("href", instance_url), 743 xs_html_attr("href", instance_url),
752 xs_html_text(L("instance")))); 744 xs_html_text(L("instance"))),
745 xs_html_text(" "),
746 xs_html_tag("form",
747 xs_html_attr("style", "display: inline!important"),
748 xs_html_attr("class", "snac-search-box"),
749 xs_html_attr("action", admin_url),
750 xs_html_sctag("input",
751 xs_html_attr("type", "text"),
752 xs_html_attr("name", "q"),
753 xs_html_attr("title", L("Search posts by content (regular expression) or #tag")),
754 xs_html_attr("placeholder", L("Content search")))));
753 } 755 }
754 756
755 xs_html_add(body, 757 xs_html_add(body,
@@ -760,7 +762,7 @@ static xs_html *html_user_body(snac *user, int read_only)
760 xs_html_attr("class", "h-card snac-top-user")); 762 xs_html_attr("class", "h-card snac-top-user"));
761 763
762 if (read_only) { 764 if (read_only) {
763 char *header = xs_dict_get(user->config, "header"); 765 const char *header = xs_dict_get(user->config, "header");
764 if (header && *header) { 766 if (header && *header) {
765 xs_html_add(top_user, 767 xs_html_add(top_user,
766 xs_html_tag("div", 768 xs_html_tag("div",
@@ -797,10 +799,10 @@ static xs_html *html_user_body(snac *user, int read_only)
797 xs_html_add(top_user, 799 xs_html_add(top_user,
798 top_user_bio); 800 top_user_bio);
799 801
800 xs_dict *metadata = xs_dict_get(user->config, "metadata"); 802 const xs_dict *metadata = xs_dict_get(user->config, "metadata");
801 if (xs_type(metadata) == XSTYPE_DICT) { 803 if (xs_type(metadata) == XSTYPE_DICT) {
802 xs_str *k; 804 const xs_str *k;
803 xs_str *v; 805 const xs_str *v;
804 806
805 xs_dict *val_links = user->links; 807 xs_dict *val_links = user->links;
806 if (xs_is_null(val_links)) 808 if (xs_is_null(val_links))
@@ -813,10 +815,10 @@ static xs_html *html_user_body(snac *user, int read_only)
813 while (xs_dict_next(metadata, &k, &v, &c)) { 815 while (xs_dict_next(metadata, &k, &v, &c)) {
814 xs_html *value; 816 xs_html *value;
815 817
816 if (xs_startswith(v, "https:/" "/")) { 818 if (xs_startswith(v, "https:/") || xs_startswith(v, "http:/")) {
817 /* is this link validated? */ 819 /* is this link validated? */
818 xs *verified_link = NULL; 820 xs *verified_link = NULL;
819 xs_number *val_time = xs_dict_get(val_links, v); 821 const xs_number *val_time = xs_dict_get(val_links, v);
820 822
821 if (xs_type(val_time) == XSTYPE_NUMBER) { 823 if (xs_type(val_time) == XSTYPE_NUMBER) {
822 time_t t = xs_number_get(val_time); 824 time_t t = xs_number_get(val_time);
@@ -928,7 +930,7 @@ xs_html *html_top_controls(snac *snac)
928 930
929 /** user settings **/ 931 /** user settings **/
930 932
931 char *email = "[disabled by admin]"; 933 const char *email = "[disabled by admin]";
932 934
933 if (xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE) { 935 if (xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE) {
934 email = xs_dict_get(snac->config_o, "email"); 936 email = xs_dict_get(snac->config_o, "email");
@@ -940,40 +942,40 @@ xs_html *html_top_controls(snac *snac)
940 } 942 }
941 } 943 }
942 944
943 char *cw = xs_dict_get(snac->config, "cw"); 945 const char *cw = xs_dict_get(snac->config, "cw");
944 if (xs_is_null(cw)) 946 if (xs_is_null(cw))
945 cw = ""; 947 cw = "";
946 948
947 char *telegram_bot = xs_dict_get(snac->config, "telegram_bot"); 949 const char *telegram_bot = xs_dict_get(snac->config, "telegram_bot");
948 if (xs_is_null(telegram_bot)) 950 if (xs_is_null(telegram_bot))
949 telegram_bot = ""; 951 telegram_bot = "";
950 952
951 char *telegram_chat_id = xs_dict_get(snac->config, "telegram_chat_id"); 953 const char *telegram_chat_id = xs_dict_get(snac->config, "telegram_chat_id");
952 if (xs_is_null(telegram_chat_id)) 954 if (xs_is_null(telegram_chat_id))
953 telegram_chat_id = ""; 955 telegram_chat_id = "";
954 956
955 char *ntfy_server = xs_dict_get(snac->config, "ntfy_server"); 957 const char *ntfy_server = xs_dict_get(snac->config, "ntfy_server");
956 if (xs_is_null(ntfy_server)) 958 if (xs_is_null(ntfy_server))
957 ntfy_server = ""; 959 ntfy_server = "";
958 960
959 char *ntfy_token = xs_dict_get(snac->config, "ntfy_token"); 961 const char *ntfy_token = xs_dict_get(snac->config, "ntfy_token");
960 if (xs_is_null(ntfy_token)) 962 if (xs_is_null(ntfy_token))
961 ntfy_token = ""; 963 ntfy_token = "";
962 964
963 char *purge_days = xs_dict_get(snac->config, "purge_days"); 965 const char *purge_days = xs_dict_get(snac->config, "purge_days");
964 if (!xs_is_null(purge_days) && xs_type(purge_days) == XSTYPE_NUMBER) 966 if (!xs_is_null(purge_days) && xs_type(purge_days) == XSTYPE_NUMBER)
965 purge_days = (char *)xs_number_str(purge_days); 967 purge_days = (char *)xs_number_str(purge_days);
966 else 968 else
967 purge_days = "0"; 969 purge_days = "0";
968 970
969 xs_val *d_dm_f_u = xs_dict_get(snac->config, "drop_dm_from_unknown"); 971 const xs_val *d_dm_f_u = xs_dict_get(snac->config, "drop_dm_from_unknown");
970 xs_val *bot = xs_dict_get(snac->config, "bot"); 972 const xs_val *bot = xs_dict_get(snac->config, "bot");
971 xs_val *a_private = xs_dict_get(snac->config, "private"); 973 const xs_val *a_private = xs_dict_get(snac->config, "private");
972 974
973 xs *metadata = xs_str_new(NULL); 975 xs *metadata = xs_str_new(NULL);
974 xs_dict *md = xs_dict_get(snac->config, "metadata"); 976 const xs_dict *md = xs_dict_get(snac->config, "metadata");
975 xs_str *k; 977 const xs_str *k;
976 xs_str *v; 978 const xs_str *v;
977 979
978 int c = 0; 980 int c = 0;
979 while (xs_dict_next(md, &k, &v, &c)) { 981 while (xs_dict_next(md, &k, &v, &c)) {
@@ -1158,13 +1160,14 @@ xs_str *build_mentions(snac *snac, const xs_dict *msg)
1158/* returns a string with the mentions in msg */ 1160/* returns a string with the mentions in msg */
1159{ 1161{
1160 xs_str *s = xs_str_new(NULL); 1162 xs_str *s = xs_str_new(NULL);
1161 char *list = xs_dict_get(msg, "tag"); 1163 const char *list = xs_dict_get(msg, "tag");
1162 char *v; 1164 const char *v;
1165 int c = 0;
1163 1166
1164 while (xs_list_iter(&list, &v)) { 1167 while (xs_list_next(list, &v, &c)) {
1165 char *type = xs_dict_get(v, "type"); 1168 const char *type = xs_dict_get(v, "type");
1166 char *href = xs_dict_get(v, "href"); 1169 const char *href = xs_dict_get(v, "href");
1167 char *name = xs_dict_get(v, "name"); 1170 const char *name = xs_dict_get(v, "name");
1168 1171
1169 if (type && strcmp(type, "Mention") == 0 && 1172 if (type && strcmp(type, "Mention") == 0 &&
1170 href && strcmp(href, snac->actor) != 0 && name) { 1173 href && strcmp(href, snac->actor) != 0 && name) {
@@ -1208,10 +1211,11 @@ xs_str *build_mentions(snac *snac, const xs_dict *msg)
1208} 1211}
1209 1212
1210 1213
1211xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const char *md5) 1214xs_html *html_entry_controls(snac *snac, const char *actor,
1215 const xs_dict *msg, const char *md5)
1212{ 1216{
1213 char *id = xs_dict_get(msg, "id"); 1217 const char *id = xs_dict_get(msg, "id");
1214 char *group = xs_dict_get(msg, "audience"); 1218 const char *group = xs_dict_get(msg, "audience");
1215 1219
1216 xs *likes = object_likes(id); 1220 xs *likes = object_likes(id);
1217 xs *boosts = object_announces(id); 1221 xs *boosts = object_announces(id);
@@ -1265,8 +1269,8 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1265 } 1269 }
1266 1270
1267 if (is_msg_public(msg)) { 1271 if (is_msg_public(msg)) {
1268 if (strcmp(actor, snac->actor) == 0 || xs_list_in(boosts, snac->md5) == -1) { 1272 if (xs_list_in(boosts, snac->md5) == -1) {
1269 /* not already boosted or us; add button */ 1273 /* not already boosted; add button */
1270 xs_html_add(form, 1274 xs_html_add(form,
1271 html_button("boost", L("Boost"), L("Announce this post to your followers"))); 1275 html_button("boost", L("Boost"), L("Announce this post to your followers")));
1272 } 1276 }
@@ -1310,7 +1314,7 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1310 html_button("delete", L("Delete"), L("Delete this post")), 1314 html_button("delete", L("Delete"), L("Delete this post")),
1311 html_button("hide", L("Hide"), L("Hide this post and its children"))); 1315 html_button("hide", L("Hide"), L("Hide this post and its children")));
1312 1316
1313 char *prev_src = xs_dict_get(msg, "sourceContent"); 1317 const char *prev_src = xs_dict_get(msg, "sourceContent");
1314 1318
1315 if (!xs_is_null(prev_src) && strcmp(actor, snac->actor) == 0) { /** edit **/ 1319 if (!xs_is_null(prev_src) && strcmp(actor, snac->actor) == 0) { /** edit **/
1316 /* post can be edited */ 1320 /* post can be edited */
@@ -1318,13 +1322,13 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1318 xs *form_id = xs_fmt("%s_edit_form", md5); 1322 xs *form_id = xs_fmt("%s_edit_form", md5);
1319 xs *redir = xs_fmt("%s_entry", md5); 1323 xs *redir = xs_fmt("%s_entry", md5);
1320 1324
1321 char *att_file = ""; 1325 const char *att_file = "";
1322 char *att_alt_text = ""; 1326 const char *att_alt_text = "";
1323 xs_list *att_list = xs_dict_get(msg, "attachment"); 1327 const xs_list *att_list = xs_dict_get(msg, "attachment");
1324 1328
1325 /* does it have an attachment? */ 1329 /* does it have an attachment? */
1326 if (xs_type(att_list) == XSTYPE_LIST && xs_list_len(att_list)) { 1330 if (xs_type(att_list) == XSTYPE_LIST && xs_list_len(att_list)) {
1327 xs_dict *d = xs_list_get(att_list, 0); 1331 const xs_dict *d = xs_list_get(att_list, 0);
1328 1332
1329 if (xs_type(d) == XSTYPE_DICT) { 1333 if (xs_type(d) == XSTYPE_DICT) {
1330 att_file = xs_dict_get_def(d, "url", ""); 1334 att_file = xs_dict_get_def(d, "url", "");
@@ -1368,12 +1372,13 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1368 1372
1369 1373
1370xs_html *html_entry(snac *user, xs_dict *msg, int read_only, 1374xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1371 int level, char *md5, int hide_children) 1375 int level, const char *md5, int hide_children)
1372{ 1376{
1373 char *id = xs_dict_get(msg, "id"); 1377 const char *id = xs_dict_get(msg, "id");
1374 char *type = xs_dict_get(msg, "type"); 1378 const char *type = xs_dict_get(msg, "type");
1375 char *actor; 1379 const char *actor;
1376 char *v; 1380 const char *v;
1381 int has_title = 0;
1377 1382
1378 /* do not show non-public messages in the public timeline */ 1383 /* do not show non-public messages in the public timeline */
1379 if ((read_only || !user) && !is_msg_public(msg)) 1384 if ((read_only || !user) && !is_msg_public(msg))
@@ -1405,8 +1410,9 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1405 html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg))); 1410 html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg)));
1406 } 1411 }
1407 else 1412 else
1408 if (!xs_match(type, "Note|Question|Page|Article|Video")) { 1413 if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) {
1409 /* skip oddities */ 1414 /* skip oddities */
1415 snac_debug(user, 1, xs_fmt("html_entry: ignoring object type '%s' %s", type, id));
1410 return NULL; 1416 return NULL;
1411 } 1417 }
1412 1418
@@ -1483,6 +1489,14 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1483 } 1489 }
1484 } 1490 }
1485 1491
1492 if (strcmp(type, "Event") == 0) {
1493 /* add the calendar emoji */
1494 xs_html_add(score,
1495 xs_html_tag("span",
1496 xs_html_attr("title", L("Event")),
1497 xs_html_raw(" 📅 ")));
1498 }
1499
1486 /* if it's a user from this same instance, add the score */ 1500 /* if it's a user from this same instance, add the score */
1487 if (xs_startswith(id, srv_baseurl)) { 1501 if (xs_startswith(id, srv_baseurl)) {
1488 int n_likes = object_likes_len(id); 1502 int n_likes = object_likes_len(id);
@@ -1499,7 +1513,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1499 1513
1500 if (xs_list_len(boosts)) { 1514 if (xs_list_len(boosts)) {
1501 /* if somebody boosted this, show as origin */ 1515 /* if somebody boosted this, show as origin */
1502 char *p = xs_list_get(boosts, -1); 1516 const char *p = xs_list_get(boosts, -1);
1503 xs *actor_r = NULL; 1517 xs *actor_r = NULL;
1504 1518
1505 if (user && xs_list_in(boosts, user->md5) != -1) { 1519 if (user && xs_list_in(boosts, user->md5) != -1) {
@@ -1519,7 +1533,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1519 1533
1520 if (!xs_is_null(name)) { 1534 if (!xs_is_null(name)) {
1521 xs *href = NULL; 1535 xs *href = NULL;
1522 char *id = xs_dict_get(actor_r, "id"); 1536 const char *id = xs_dict_get(actor_r, "id");
1523 int fwers = 0; 1537 int fwers = 0;
1524 int fwing = 0; 1538 int fwing = 0;
1525 1539
@@ -1548,7 +1562,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1548 if (strcmp(type, "Note") == 0) { 1562 if (strcmp(type, "Note") == 0) {
1549 if (level == 0) { 1563 if (level == 0) {
1550 /* is the parent not here? */ 1564 /* is the parent not here? */
1551 char *parent = xs_dict_get(msg, "inReplyTo"); 1565 const char *parent = xs_dict_get(msg, "inReplyTo");
1552 1566
1553 if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) { 1567 if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) {
1554 xs_html_add(post_header, 1568 xs_html_add(post_header,
@@ -1574,11 +1588,13 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1574 xs_html_add(entry, 1588 xs_html_add(entry,
1575 snac_content_wrap); 1589 snac_content_wrap);
1576 1590
1577 if (!xs_is_null(v = xs_dict_get(msg, "name"))) { 1591 if (!has_title && !xs_is_null(v = xs_dict_get(msg, "name"))) {
1578 xs_html_add(snac_content_wrap, 1592 xs_html_add(snac_content_wrap,
1579 xs_html_tag("h3", 1593 xs_html_tag("h3",
1580 xs_html_attr("class", "snac-entry-title"), 1594 xs_html_attr("class", "snac-entry-title"),
1581 xs_html_text(v))); 1595 xs_html_text(v)));
1596
1597 has_title = 1;
1582 } 1598 }
1583 1599
1584 xs_html *snac_content = NULL; 1600 xs_html *snac_content = NULL;
@@ -1591,7 +1607,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1591 v = "..."; 1607 v = "...";
1592 1608
1593 /* only show it when not in the public timeline and the config setting is "open" */ 1609 /* only show it when not in the public timeline and the config setting is "open" */
1594 char *cw = xs_dict_get(user->config, "cw"); 1610 const char *cw = xs_dict_get(user->config, "cw");
1595 if (xs_is_null(cw) || read_only) 1611 if (xs_is_null(cw) || read_only)
1596 cw = ""; 1612 cw = "";
1597 1613
@@ -1603,12 +1619,15 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1603 } 1619 }
1604 else { 1620 else {
1605 /* print the summary as a header (sites like e.g. Friendica can contain one) */ 1621 /* print the summary as a header (sites like e.g. Friendica can contain one) */
1606 if (!xs_is_null(v) && *v) 1622 if (!has_title && !xs_is_null(v) && *v) {
1607 xs_html_add(snac_content_wrap, 1623 xs_html_add(snac_content_wrap,
1608 xs_html_tag("h3", 1624 xs_html_tag("h3",
1609 xs_html_attr("class", "snac-entry-title"), 1625 xs_html_attr("class", "snac-entry-title"),
1610 xs_html_text(v))); 1626 xs_html_text(v)));
1611 1627
1628 has_title = 1;
1629 }
1630
1612 snac_content = xs_html_tag("div", NULL); 1631 snac_content = xs_html_tag("div", NULL);
1613 } 1632 }
1614 1633
@@ -1617,7 +1636,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1617 1636
1618 { 1637 {
1619 /** build the content string **/ 1638 /** build the content string **/
1620 char *content = xs_dict_get(msg, "content"); 1639 const char *content = xs_dict_get(msg, "content");
1621 1640
1622 xs *c = sanitize(xs_is_null(content) ? "" : content); 1641 xs *c = sanitize(xs_is_null(content) ? "" : content);
1623 1642
@@ -1635,7 +1654,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1635 c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2); 1654 c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2);
1636 1655
1637 /* Peertube videos content is in markdown */ 1656 /* Peertube videos content is in markdown */
1638 char *mtype = xs_dict_get(msg, "mediaType"); 1657 const char *mtype = xs_dict_get(msg, "mediaType");
1639 if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "text/markdown") == 0) { 1658 if (xs_type(mtype) == XSTYPE_STRING && strcmp(mtype, "text/markdown") == 0) {
1640 /* a full conversion could be better */ 1659 /* a full conversion could be better */
1641 c = xs_replace_i(c, "\r", ""); 1660 c = xs_replace_i(c, "\r", "");
@@ -1648,26 +1667,33 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1648 } 1667 }
1649 1668
1650 if (strcmp(type, "Question") == 0) { /** question content **/ 1669 if (strcmp(type, "Question") == 0) { /** question content **/
1651 xs_list *oo = xs_dict_get(msg, "oneOf"); 1670 const xs_list *oo = xs_dict_get(msg, "oneOf");
1652 xs_list *ao = xs_dict_get(msg, "anyOf"); 1671 const xs_list *ao = xs_dict_get(msg, "anyOf");
1653 xs_list *p; 1672 const xs_list *p;
1654 xs_dict *v; 1673 const xs_dict *v;
1655 int closed = 0; 1674 int closed = 0;
1675 const char *f_closed = NULL;
1656 1676
1657 xs_html *poll = xs_html_tag("div", NULL); 1677 xs_html *poll = xs_html_tag("div", NULL);
1658 1678
1659 if (read_only) 1679 if (read_only)
1660 closed = 1; /* non-identified page; show as closed */ 1680 closed = 1; /* non-identified page; show as closed */
1661 else 1681 else
1662 if (xs_dict_get(msg, "closed"))
1663 closed = 2;
1664 else
1665 if (user && xs_startswith(id, user->actor)) 1682 if (user && xs_startswith(id, user->actor))
1666 closed = 1; /* we questioned; closed for us */ 1683 closed = 1; /* we questioned; closed for us */
1667 else 1684 else
1668 if (user && was_question_voted(user, id)) 1685 if (user && was_question_voted(user, id))
1669 closed = 1; /* we already voted; closed for us */ 1686 closed = 1; /* we already voted; closed for us */
1670 1687
1688 if ((f_closed = xs_dict_get(msg, "closed")) != NULL) {
1689 /* it has a closed date... but is it in the past? */
1690 time_t t0 = time(NULL);
1691 time_t t1 = xs_parse_iso_date(f_closed, 0);
1692
1693 if (t1 < t0)
1694 closed = 2;
1695 }
1696
1671 /* get the appropriate list of options */ 1697 /* get the appropriate list of options */
1672 p = oo != NULL ? oo : ao; 1698 p = oo != NULL ? oo : ao;
1673 1699
@@ -1675,10 +1701,11 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1675 /* closed poll */ 1701 /* closed poll */
1676 xs_html *poll_result = xs_html_tag("table", 1702 xs_html *poll_result = xs_html_tag("table",
1677 xs_html_attr("class", "snac-poll-result")); 1703 xs_html_attr("class", "snac-poll-result"));
1704 int c = 0;
1678 1705
1679 while (xs_list_iter(&p, &v)) { 1706 while (xs_list_next(p, &v, &c)) {
1680 char *name = xs_dict_get(v, "name"); 1707 const char *name = xs_dict_get(v, "name");
1681 xs_dict *replies = xs_dict_get(v, "replies"); 1708 const xs_dict *replies = xs_dict_get(v, "replies");
1682 1709
1683 if (name && replies) { 1710 if (name && replies) {
1684 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); 1711 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems"));
@@ -1715,9 +1742,10 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1715 xs_html_attr("name", "irt"), 1742 xs_html_attr("name", "irt"),
1716 xs_html_attr("value", id)))); 1743 xs_html_attr("value", id))));
1717 1744
1718 while (xs_list_iter(&p, &v)) { 1745 int c = 0;
1719 char *name = xs_dict_get(v, "name"); 1746 while (xs_list_next(p, &v, &c)) {
1720 xs_dict *replies = xs_dict_get(v, "replies"); 1747 const char *name = xs_dict_get(v, "name");
1748 const xs_dict *replies = xs_dict_get(v, "replies");
1721 1749
1722 if (name) { 1750 if (name) {
1723 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems")); 1751 char *ti = (char *)xs_number_str(xs_dict_get(replies, "totalItems"));
@@ -1755,7 +1783,13 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1755 } 1783 }
1756 else { 1784 else {
1757 /* show when the poll closes */ 1785 /* show when the poll closes */
1758 char *end_time = xs_dict_get(msg, "endTime"); 1786 const char *end_time = xs_dict_get(msg, "endTime");
1787
1788 /* Pleroma does not have an endTime field;
1789 it has a closed time in the future */
1790 if (xs_is_null(end_time))
1791 end_time = xs_dict_get(msg, "closed");
1792
1759 if (!xs_is_null(end_time)) { 1793 if (!xs_is_null(end_time)) {
1760 time_t t0 = time(NULL); 1794 time_t t0 = time(NULL);
1761 time_t t1 = xs_parse_iso_date(end_time, 0); 1795 time_t t1 = xs_parse_iso_date(end_time, 0);
@@ -1792,12 +1826,12 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1792 xs_html_add(snac_content, 1826 xs_html_add(snac_content,
1793 content_attachments); 1827 content_attachments);
1794 1828
1795 xs_list *p = attach; 1829 int c = 0;
1796 1830 const xs_dict *a;
1797 while (xs_list_iter(&p, &v)) { 1831 while (xs_list_next(attach, &a, &c)) {
1798 char *type = xs_dict_get(v, "type"); 1832 const char *type = xs_dict_get(a, "type");
1799 char *href = xs_dict_get(v, "href"); 1833 const char *href = xs_dict_get(a, "href");
1800 char *name = xs_dict_get(v, "name"); 1834 const char *name = xs_dict_get(a, "name");
1801 1835
1802 if (xs_startswith(type, "image/") || strcmp(type, "Image") == 0) { 1836 if (xs_startswith(type, "image/") || strcmp(type, "Image") == 0) {
1803 xs_html_add(content_attachments, 1837 xs_html_add(content_attachments,
@@ -1861,7 +1895,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1861 } 1895 }
1862 1896
1863 /* has this message an audience (i.e., comes from a channel or community)? */ 1897 /* has this message an audience (i.e., comes from a channel or community)? */
1864 char *audience = xs_dict_get(msg, "audience"); 1898 const char *audience = xs_dict_get(msg, "audience");
1865 if (strcmp(type, "Page") == 0 && !xs_is_null(audience)) { 1899 if (strcmp(type, "Page") == 0 && !xs_is_null(audience)) {
1866 xs_html *au_tag = xs_html_tag("p", 1900 xs_html *au_tag = xs_html_tag("p",
1867 xs_html_text("("), 1901 xs_html_text("("),
@@ -1911,7 +1945,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1911 } 1945 }
1912 1946
1913 xs_list *p = children; 1947 xs_list *p = children;
1914 char *cmd5; 1948 const char *cmd5;
1915 int cnt = 0; 1949 int cnt = 0;
1916 int o_cnt = 0; 1950 int o_cnt = 0;
1917 1951
@@ -1983,11 +2017,11 @@ xs_html *html_footer(void)
1983 2017
1984xs_str *html_timeline(snac *user, const xs_list *list, int read_only, 2018xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
1985 int skip, int show, int show_more, 2019 int skip, int show, int show_more,
1986 char *tag, char *page, int utl) 2020 char *title, char *page, int utl)
1987/* returns the HTML for the timeline */ 2021/* returns the HTML for the timeline */
1988{ 2022{
1989 xs_list *p = (xs_list *)list; 2023 xs_list *p = (xs_list *)list;
1990 char *v; 2024 const char *v;
1991 double t = ftime(); 2025 double t = ftime();
1992 2026
1993 xs *desc = NULL; 2027 xs *desc = NULL;
@@ -1995,11 +2029,12 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
1995 2029
1996 if (xs_list_len(list) == 1) { 2030 if (xs_list_len(list) == 1) {
1997 /* only one element? pick the description from the source */ 2031 /* only one element? pick the description from the source */
1998 char *id = xs_list_get(list, 0); 2032 const char *id = xs_list_get(list, 0);
1999 xs *d = NULL; 2033 xs *d = NULL;
2000 object_get_by_md5(id, &d); 2034 object_get_by_md5(id, &d);
2001 if (d && (v = xs_dict_get(d, "sourceContent")) != NULL) 2035 const char *sc = xs_dict_get(d, "sourceContent");
2002 desc = xs_dup(v); 2036 if (d && sc != NULL)
2037 desc = xs_dup(sc);
2003 2038
2004 alternate = xs_dup(xs_dict_get(d, "id")); 2039 alternate = xs_dup(xs_dict_get(d, "id"));
2005 } 2040 }
@@ -2013,7 +2048,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2013 } 2048 }
2014 else { 2049 else {
2015 head = html_instance_head(); 2050 head = html_instance_head();
2016 body = html_instance_body(tag); 2051 body = html_instance_body();
2017 } 2052 }
2018 2053
2019 xs_html *html = xs_html_tag("html", 2054 xs_html *html = xs_html_tag("html",
@@ -2024,6 +2059,41 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2024 xs_html_add(body, 2059 xs_html_add(body,
2025 html_top_controls(user)); 2060 html_top_controls(user));
2026 2061
2062 /* show links to the available lists */
2063 if (user && !read_only) {
2064 xs *lists = list_maint(user, NULL, 0); /* get list of lists */
2065
2066 if (xs_list_len(lists)) {
2067 int ct = 0;
2068 const char *v;
2069
2070 xs_html *lol = xs_html_tag("ul",
2071 xs_html_attr("class", "snac-list-of-lists"));
2072 xs_html_add(body, lol);
2073
2074 while (xs_list_next(lists, &v, &ct)) {
2075 const char *lname = xs_list_get(v, 1);
2076 xs *url = xs_fmt("%s/list/%s", user->actor, xs_list_get(v, 0));
2077 xs *ttl = xs_fmt(L("Timeline for list '%s'"), lname);
2078
2079 xs_html_add(lol,
2080 xs_html_tag("li",
2081 xs_html_tag("a",
2082 xs_html_attr("href", url),
2083 xs_html_attr("class", "snac-list-link"),
2084 xs_html_attr("title", ttl),
2085 xs_html_text(lname))));
2086 }
2087 }
2088 }
2089
2090 if (title) {
2091 xs_html_add(body,
2092 xs_html_tag("h2",
2093 xs_html_attr("class", "snac-header"),
2094 xs_html_text(title)));
2095 }
2096
2027 xs_html_add(body, 2097 xs_html_add(body,
2028 xs_html_tag("a", 2098 xs_html_tag("a",
2029 xs_html_attr("name", "snac-posts"))); 2099 xs_html_attr("name", "snac-posts")));
@@ -2052,11 +2122,18 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2052 2122
2053 /* is this message a non-public reply? */ 2123 /* is this message a non-public reply? */
2054 if (user != NULL && !is_msg_public(msg)) { 2124 if (user != NULL && !is_msg_public(msg)) {
2055 char *irt = xs_dict_get(msg, "inReplyTo"); 2125 const char *irt = xs_dict_get(msg, "inReplyTo");
2056 2126
2127 /* is it a reply to something not in the storage? */
2057 if (!xs_is_null(irt) && !object_here(irt)) { 2128 if (!xs_is_null(irt) && !object_here(irt)) {
2058 snac_debug(user, 1, xs_fmt("skipping non-public reply to an unknown post %s", v)); 2129 /* is it for me? */
2059 continue; 2130 const xs_list *to = xs_dict_get_def(msg, "to", xs_stock(XSTYPE_LIST));
2131 const xs_list *cc = xs_dict_get_def(msg, "cc", xs_stock(XSTYPE_LIST));
2132
2133 if (xs_list_in(to, user->actor) == -1 && xs_list_in(cc, user->actor) == -1) {
2134 snac_debug(user, 1, xs_fmt("skipping non-public reply to an unknown post %s", v));
2135 continue;
2136 }
2060 } 2137 }
2061 } 2138 }
2062 2139
@@ -2081,7 +2158,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2081 2158
2082 xs *list = history_list(user); 2159 xs *list = history_list(user);
2083 xs_list *p = list; 2160 xs_list *p = list;
2084 char *v; 2161 const char *v;
2085 2162
2086 while (xs_list_iter(&p, &v)) { 2163 while (xs_list_iter(&p, &v)) {
2087 xs *fn = xs_replace(v, ".html", ""); 2164 xs *fn = xs_replace(v, ".html", "");
@@ -2106,25 +2183,22 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2106 } 2183 }
2107 2184
2108 if (show_more) { 2185 if (show_more) {
2109 xs *t = NULL;
2110 xs *m = NULL; 2186 xs *m = NULL;
2111 xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show); 2187 xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show);
2112 2188
2113 xs *url = page == NULL || user == NULL ? 2189 xs *url = xs_dup(user == NULL ? srv_baseurl : user->actor);
2114 xs_dup(srv_baseurl) : xs_fmt("%s%s", user->actor, page);
2115 2190
2116 if (tag) { 2191 if (page != NULL)
2117 t = xs_fmt("%s?t=%s", url, tag); 2192 url = xs_str_cat(url, page);
2118 m = xs_fmt("%s&%s", t, ss); 2193
2119 } 2194 if (xs_str_in(url, "?") != -1)
2120 else { 2195 m = xs_fmt("%s&%s", url, ss);
2121 t = xs_dup(url); 2196 else
2122 m = xs_fmt("%s?%s", t, ss); 2197 m = xs_fmt("%s?%s", url, ss);
2123 }
2124 2198
2125 xs_html *more_links = xs_html_tag("p", 2199 xs_html *more_links = xs_html_tag("p",
2126 xs_html_tag("a", 2200 xs_html_tag("a",
2127 xs_html_attr("href", t), 2201 xs_html_attr("href", url),
2128 xs_html_attr("name", "snac-more"), 2202 xs_html_attr("name", "snac-more"),
2129 xs_html_text(L("Back to top"))), 2203 xs_html_text(L("Back to top"))),
2130 xs_html_text(" - "), 2204 xs_html_text(" - "),
@@ -2157,7 +2231,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t)
2157 xs_html_text("...")))); 2231 xs_html_text("..."))));
2158 2232
2159 xs_list *p = list; 2233 xs_list *p = list;
2160 char *actor_id; 2234 const char *actor_id;
2161 2235
2162 while (xs_list_iter(&p, &actor_id)) { 2236 while (xs_list_iter(&p, &actor_id)) {
2163 xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); 2237 xs *md5 = xs_md5_hex(actor_id, strlen(actor_id));
@@ -2173,7 +2247,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t)
2173 html_actor_icon(snac, actor, xs_dict_get(actor, "published"), NULL, NULL, 0, 1))); 2247 html_actor_icon(snac, actor, xs_dict_get(actor, "published"), NULL, NULL, 0, 1)));
2174 2248
2175 /* content (user bio) */ 2249 /* content (user bio) */
2176 char *c = xs_dict_get(actor, "summary"); 2250 const char *c = xs_dict_get(actor, "summary");
2177 2251
2178 if (!xs_is_null(c)) { 2252 if (!xs_is_null(c)) {
2179 xs *sc = sanitize(c); 2253 xs *sc = sanitize(c);
@@ -2317,7 +2391,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
2317 xs_html *noti_seen = NULL; 2391 xs_html *noti_seen = NULL;
2318 2392
2319 xs_list *p = n_list; 2393 xs_list *p = n_list;
2320 xs_str *v; 2394 const xs_str *v;
2321 while (xs_list_iter(&p, &v)) { 2395 while (xs_list_iter(&p, &v)) {
2322 xs *noti = notify_get(user, v); 2396 xs *noti = notify_get(user, v);
2323 2397
@@ -2325,10 +2399,10 @@ xs_str *html_notifications(snac *user, int skip, int show)
2325 continue; 2399 continue;
2326 2400
2327 xs *obj = NULL; 2401 xs *obj = NULL;
2328 char *type = xs_dict_get(noti, "type"); 2402 const char *type = xs_dict_get(noti, "type");
2329 char *utype = xs_dict_get(noti, "utype"); 2403 const char *utype = xs_dict_get(noti, "utype");
2330 char *id = xs_dict_get(noti, "objid"); 2404 const char *id = xs_dict_get(noti, "objid");
2331 char *date = xs_dict_get(noti, "date"); 2405 const char *date = xs_dict_get(noti, "date");
2332 2406
2333 if (xs_is_null(id) || !valid_status(object_get(id, &obj))) 2407 if (xs_is_null(id) || !valid_status(object_get(id, &obj)))
2334 continue; 2408 continue;
@@ -2336,14 +2410,14 @@ xs_str *html_notifications(snac *user, int skip, int show)
2336 if (is_hidden(user, id)) 2410 if (is_hidden(user, id))
2337 continue; 2411 continue;
2338 2412
2339 char *actor_id = xs_dict_get(noti, "actor"); 2413 const char *actor_id = xs_dict_get(noti, "actor");
2340 xs *actor = NULL; 2414 xs *actor = NULL;
2341 2415
2342 if (!valid_status(actor_get(actor_id, &actor))) 2416 if (!valid_status(actor_get(actor_id, &actor)))
2343 continue; 2417 continue;
2344 2418
2345 xs *a_name = actor_name(actor); 2419 xs *a_name = actor_name(actor);
2346 char *label = type; 2420 const char *label = type;
2347 2421
2348 if (strcmp(type, "Create") == 0) 2422 if (strcmp(type, "Create") == 0)
2349 label = L("Mention"); 2423 label = L("Mention");
@@ -2455,14 +2529,14 @@ xs_str *html_notifications(snac *user, int skip, int show)
2455int html_get_handler(const xs_dict *req, const char *q_path, 2529int html_get_handler(const xs_dict *req, const char *q_path,
2456 char **body, int *b_size, char **ctype, xs_str **etag) 2530 char **body, int *b_size, char **ctype, xs_str **etag)
2457{ 2531{
2458 char *accept = xs_dict_get(req, "accept"); 2532 const char *accept = xs_dict_get(req, "accept");
2459 int status = 404; 2533 int status = 404;
2460 snac snac; 2534 snac snac;
2461 xs *uid = NULL; 2535 xs *uid = NULL;
2462 char *p_path; 2536 const char *p_path;
2463 int cache = 1; 2537 int cache = 1;
2464 int save = 1; 2538 int save = 1;
2465 char *v; 2539 const char *v;
2466 2540
2467 xs *l = xs_split_n(q_path, "/", 2); 2541 xs *l = xs_split_n(q_path, "/", 2);
2468 v = xs_list_get(l, 1); 2542 v = xs_list_get(l, 1);
@@ -2501,7 +2575,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2501 2575
2502 int skip = 0; 2576 int skip = 0;
2503 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); 2577 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries"));
2504 char *q_vars = xs_dict_get(req, "q_vars"); 2578 const xs_dict *q_vars = xs_dict_get(req, "q_vars");
2505 if ((v = xs_dict_get(q_vars, "skip")) != NULL) 2579 if ((v = xs_dict_get(q_vars, "skip")) != NULL)
2506 skip = atoi(v), cache = 0, save = 0; 2580 skip = atoi(v), cache = 0, save = 0;
2507 if ((v = xs_dict_get(q_vars, "show")) != NULL) 2581 if ((v = xs_dict_get(q_vars, "show")) != NULL)
@@ -2546,35 +2620,99 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2546 status = 401; 2620 status = 401;
2547 } 2621 }
2548 else { 2622 else {
2549 double t = history_mtime(&snac, "timeline.html_"); 2623 const char *q = xs_dict_get(q_vars, "q");
2624
2625 if (q && *q) {
2626 if (*q == '#') {
2627 /** search by tag **/
2628 xs *tl = tag_search(q, skip, show + 1);
2629 int more = 0;
2630 if (xs_list_len(tl) >= show + 1) {
2631 /* drop the last one */
2632 tl = xs_list_del(tl, -1);
2633 more = 1;
2634 }
2550 2635
2551 /* if enabled by admin, return a cached page if its timestamp is: 2636 xs *page = xs_fmt("/admin?q=%%23%s", q + 1);
2552 a) newer than the timeline timestamp 2637 xs *title = xs_fmt(xs_list_len(tl) ?
2553 b) newer than the start time of the server 2638 L("Search results for tag %s") : L("Nothing found for tag %s"), q);
2554 */
2555 if (cache && t > timeline_mtime(&snac) && t > p_state->srv_start_time) {
2556 snac_debug(&snac, 1, xs_fmt("serving cached timeline"));
2557 2639
2558 status = history_get(&snac, "timeline.html_", body, b_size, 2640 *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0);
2559 xs_dict_get(req, "if-none-match"), etag); 2641 *b_size = strlen(*body);
2642 status = 200;
2643 }
2644 else {
2645 /** search by content **/
2646 int to = 0;
2647 int msecs = atoi(xs_dict_get_def(q_vars, "msecs", "0"));
2648 xs *tl = content_search(&snac, q, 1, skip, show, msecs, &to);
2649 xs *title = NULL;
2650 xs *page = xs_fmt("/admin?q=%s&msecs=%d", q, msecs + 10);
2651 int tl_len = xs_list_len(tl);
2652
2653 if (tl_len)
2654 title = xs_fmt(L("Search results for '%s'"), q);
2655 else
2656 if (skip)
2657 title = xs_fmt(L("No more matches for '%s'"), q);
2658 else
2659 title = xs_fmt(L("Nothing found for '%s'"), q);
2660
2661 *body = html_timeline(&snac, tl, 0, skip, tl_len, to || tl_len == show, title, page, 0);
2662 *b_size = strlen(*body);
2663 status = 200;
2664 }
2560 } 2665 }
2561 else { 2666 else {
2562 snac_debug(&snac, 1, xs_fmt("building timeline")); 2667 double t = history_mtime(&snac, "timeline.html_");
2668
2669 /* if enabled by admin, return a cached page if its timestamp is:
2670 a) newer than the timeline timestamp
2671 b) newer than the start time of the server
2672 */
2673 if (cache && t > timeline_mtime(&snac) && t > p_state->srv_start_time) {
2674 snac_debug(&snac, 1, xs_fmt("serving cached timeline"));
2563 2675
2564 xs *list = timeline_list(&snac, "private", skip, show); 2676 status = history_get(&snac, "timeline.html_", body, b_size,
2565 xs *next = timeline_list(&snac, "private", skip + show, 1); 2677 xs_dict_get(req, "if-none-match"), etag);
2678 }
2679 else {
2680 snac_debug(&snac, 1, xs_fmt("building timeline"));
2681
2682 xs *list = timeline_list(&snac, "private", skip, show);
2683 xs *next = timeline_list(&snac, "private", skip + show, 1);
2684
2685 xs *pins = pinned_list(&snac);
2686 pins = xs_list_cat(pins, list);
2566 2687
2567 xs *pins = pinned_list(&snac); 2688 *body = html_timeline(&snac, pins, 0, skip, show,
2568 pins = xs_list_cat(pins, list); 2689 xs_list_len(next), NULL, "/admin", 1);
2569 2690
2570 *body = html_timeline(&snac, pins, 0, skip, show, 2691 *b_size = strlen(*body);
2571 xs_list_len(next), NULL, "/admin", 1); 2692 status = 200;
2572 2693
2694 if (save)
2695 history_add(&snac, "timeline.html_", *body, *b_size, etag);
2696 }
2697 }
2698 }
2699 }
2700 else
2701 if (xs_startswith(p_path, "admin/p/")) { /** unique post by md5 **/
2702 if (!login(&snac, req)) {
2703 *body = xs_dup(uid);
2704 status = 401;
2705 }
2706 else {
2707 xs *l = xs_split(p_path, "/");
2708 const char *md5 = xs_list_get(l, -1);
2709
2710 if (md5 && *md5 && timeline_here(&snac, md5)) {
2711 xs *list = xs_list_append(xs_list_new(), md5);
2712
2713 *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1);
2573 *b_size = strlen(*body); 2714 *b_size = strlen(*body);
2574 status = 200; 2715 status = 200;
2575
2576 if (save)
2577 history_add(&snac, "timeline.html_", *body, *b_size, etag);
2578 } 2716 }
2579 } 2717 }
2580 } 2718 }
@@ -2613,12 +2751,37 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2613 xs *next = timeline_instance_list(skip + show, 1); 2751 xs *next = timeline_instance_list(skip + show, 1);
2614 2752
2615 *body = html_timeline(&snac, list, 0, skip, show, 2753 *body = html_timeline(&snac, list, 0, skip, show,
2616 xs_list_len(next), NULL, "/instance", 0); 2754 xs_list_len(next), L("Showing instance timeline"), "/instance", 0);
2617 *b_size = strlen(*body); 2755 *b_size = strlen(*body);
2618 status = 200; 2756 status = 200;
2619 } 2757 }
2620 } 2758 }
2621 else 2759 else
2760 if (xs_startswith(p_path, "list/")) { /** list timelines **/
2761 if (!login(&snac, req)) {
2762 *body = xs_dup(uid);
2763 status = 401;
2764 }
2765 else {
2766 xs *l = xs_split(p_path, "/");
2767 const char *lid = xs_list_get(l, -1);
2768
2769 xs *list = list_timeline(&snac, lid, skip, show);
2770 xs *next = list_timeline(&snac, lid, skip + show, 1);
2771
2772 if (list != NULL) {
2773 xs *base = xs_fmt("/list/%s", lid);
2774 xs *name = list_maint(&snac, lid, 3);
2775 xs *title = xs_fmt(L("Showing timeline for list '%s'"), name);
2776
2777 *body = html_timeline(&snac, list, 0, skip, show,
2778 xs_list_len(next), title, base, 1);
2779 *b_size = strlen(*body);
2780 status = 200;
2781 }
2782 }
2783 }
2784 else
2622 if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/ 2785 if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/
2623 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) 2786 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE)
2624 return 403; 2787 return 403;
@@ -2640,7 +2803,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2640 else 2803 else
2641 if (xs_startswith(p_path, "s/")) { /** a static file **/ 2804 if (xs_startswith(p_path, "s/")) { /** a static file **/
2642 xs *l = xs_split(p_path, "/"); 2805 xs *l = xs_split(p_path, "/");
2643 char *id = xs_list_get(l, 1); 2806 const char *id = xs_list_get(l, 1);
2644 int sz; 2807 int sz;
2645 2808
2646 if (id && *id) { 2809 if (id && *id) {
@@ -2661,8 +2824,8 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2661 if (xs_type(xs_dict_get(srv_config, "disable_history")) == XSTYPE_TRUE) 2824 if (xs_type(xs_dict_get(srv_config, "disable_history")) == XSTYPE_TRUE)
2662 return 403; 2825 return 403;
2663 2826
2664 xs *l = xs_split(p_path, "/"); 2827 xs *l = xs_split(p_path, "/");
2665 char *id = xs_list_get(l, 1); 2828 const char *id = xs_list_get(l, 1);
2666 2829
2667 if (id && *id) { 2830 if (id && *id) {
2668 if (xs_endswith(id, "timeline.html_")) { 2831 if (xs_endswith(id, "timeline.html_")) {
@@ -2689,55 +2852,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2689 xs_dict_get(srv_config, "host")); 2852 xs_dict_get(srv_config, "host"));
2690 xs *rss_link = xs_fmt("%s.rss", snac.actor); 2853 xs *rss_link = xs_fmt("%s.rss", snac.actor);
2691 2854
2692 xs_html *rss = xs_html_tag("rss", 2855 *body = timeline_to_rss(&snac, elems, rss_title, rss_link, bio);
2693 xs_html_attr("version", "0.91"));
2694
2695 xs_html *channel = xs_html_tag("channel",
2696 xs_html_tag("title",
2697 xs_html_text(rss_title)),
2698 xs_html_tag("language",
2699 xs_html_text("en")),
2700 xs_html_tag("link",
2701 xs_html_text(rss_link)),
2702 xs_html_tag("description",
2703 xs_html_text(bio)));
2704
2705 xs_html_add(rss, channel);
2706
2707 xs_list *p = elems;
2708 char *v;
2709
2710 while (xs_list_iter(&p, &v)) {
2711 xs *msg = NULL;
2712
2713 if (!valid_status(timeline_get_by_md5(&snac, v, &msg)))
2714 continue;
2715
2716 char *id = xs_dict_get(msg, "id");
2717 char *content = xs_dict_get(msg, "content");
2718
2719 if (!xs_startswith(id, snac.actor))
2720 continue;
2721
2722 /* create a title with the first line of the content */
2723 xs *es_title = xs_replace(content, "<br>", "\n");
2724 xs *title = xs_str_new(NULL);
2725 int i;
2726
2727 for (i = 0; es_title[i] && es_title[i] != '\n' && es_title[i] != '&' && i < 50; i++)
2728 title = xs_append_m(title, &es_title[i], 1);
2729
2730 xs_html_add(channel,
2731 xs_html_tag("item",
2732 xs_html_tag("title",
2733 xs_html_text(title)),
2734 xs_html_tag("link",
2735 xs_html_text(id)),
2736 xs_html_tag("description",
2737 xs_html_text(content))));
2738 }
2739
2740 *body = xs_html_render_s(rss, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
2741 *b_size = strlen(*body); 2856 *b_size = strlen(*body);
2742 *ctype = "application/rss+xml; charset=utf-8"; 2857 *ctype = "application/rss+xml; charset=utf-8";
2743 status = 200; 2858 status = 200;
@@ -2766,8 +2881,9 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2766 2881
2767 int status = 0; 2882 int status = 0;
2768 snac snac; 2883 snac snac;
2769 char *uid, *p_path; 2884 const char *uid;
2770 xs_dict *p_vars; 2885 const char *p_path;
2886 const xs_dict *p_vars;
2771 2887
2772 xs *l = xs_split_n(q_path, "/", 2); 2888 xs *l = xs_split_n(q_path, "/", 2);
2773 2889
@@ -2795,15 +2911,15 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2795 2911
2796 if (p_path && strcmp(p_path, "admin/note") == 0) { /** **/ 2912 if (p_path && strcmp(p_path, "admin/note") == 0) { /** **/
2797 /* post note */ 2913 /* post note */
2798 xs_str *content = xs_dict_get(p_vars, "content"); 2914 const xs_str *content = xs_dict_get(p_vars, "content");
2799 xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to"); 2915 const xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to");
2800 xs_str *attach_url = xs_dict_get(p_vars, "attach_url"); 2916 const xs_str *attach_url = xs_dict_get(p_vars, "attach_url");
2801 xs_list *attach_file = xs_dict_get(p_vars, "attach"); 2917 const xs_list *attach_file = xs_dict_get(p_vars, "attach");
2802 xs_str *to = xs_dict_get(p_vars, "to"); 2918 const xs_str *to = xs_dict_get(p_vars, "to");
2803 xs_str *sensitive = xs_dict_get(p_vars, "sensitive"); 2919 const xs_str *sensitive = xs_dict_get(p_vars, "sensitive");
2804 xs_str *summary = xs_dict_get(p_vars, "summary"); 2920 const xs_str *summary = xs_dict_get(p_vars, "summary");
2805 xs_str *edit_id = xs_dict_get(p_vars, "edit_id"); 2921 const xs_str *edit_id = xs_dict_get(p_vars, "edit_id");
2806 xs_str *alt_text = xs_dict_get(p_vars, "alt_text"); 2922 const xs_str *alt_text = xs_dict_get(p_vars, "alt_text");
2807 int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only")); 2923 int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only"));
2808 xs *attach_list = xs_list_new(); 2924 xs *attach_list = xs_list_new();
2809 2925
@@ -2823,7 +2939,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2823 2939
2824 /* is attach_file set? */ 2940 /* is attach_file set? */
2825 if (!xs_is_null(attach_file) && xs_type(attach_file) == XSTYPE_LIST) { 2941 if (!xs_is_null(attach_file) && xs_type(attach_file) == XSTYPE_LIST) {
2826 char *fn = xs_list_get(attach_file, 0); 2942 const char *fn = xs_list_get(attach_file, 0);
2827 2943
2828 if (*fn != '\0') { 2944 if (*fn != '\0') {
2829 char *ext = strrchr(fn, '.'); 2945 char *ext = strrchr(fn, '.');
@@ -2899,7 +3015,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2899 int n; 3015 int n;
2900 3016
2901 for (n = 0; fields[n]; n++) { 3017 for (n = 0; fields[n]; n++) {
2902 char *v = xs_dict_get(p_msg, fields[n]); 3018 const char *v = xs_dict_get(p_msg, fields[n]);
2903 msg = xs_dict_set(msg, fields[n], v); 3019 msg = xs_dict_set(msg, fields[n], v);
2904 } 3020 }
2905 3021
@@ -2928,10 +3044,10 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2928 else 3044 else
2929 if (p_path && strcmp(p_path, "admin/action") == 0) { /** **/ 3045 if (p_path && strcmp(p_path, "admin/action") == 0) { /** **/
2930 /* action on an entry */ 3046 /* action on an entry */
2931 char *id = xs_dict_get(p_vars, "id"); 3047 const char *id = xs_dict_get(p_vars, "id");
2932 char *actor = xs_dict_get(p_vars, "actor"); 3048 const char *actor = xs_dict_get(p_vars, "actor");
2933 char *action = xs_dict_get(p_vars, "action"); 3049 const char *action = xs_dict_get(p_vars, "action");
2934 char *group = xs_dict_get(p_vars, "group"); 3050 const char *group = xs_dict_get(p_vars, "group");
2935 3051
2936 if (action == NULL) 3052 if (action == NULL)
2937 return 404; 3053 return 404;
@@ -3055,7 +3171,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3055 } 3171 }
3056 else 3172 else
3057 if (strcmp(action, L("Delete")) == 0) { /** **/ 3173 if (strcmp(action, L("Delete")) == 0) { /** **/
3058 char *actor_form = xs_dict_get(p_vars, "actor-form"); 3174 const char *actor_form = xs_dict_get(p_vars, "actor-form");
3059 if (actor_form != NULL) { 3175 if (actor_form != NULL) {
3060 /* delete follower */ 3176 /* delete follower */
3061 if (valid_status(follower_del(&snac, actor))) 3177 if (valid_status(follower_del(&snac, actor)))
@@ -3099,8 +3215,8 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3099 else 3215 else
3100 if (p_path && strcmp(p_path, "admin/user-setup") == 0) { /** **/ 3216 if (p_path && strcmp(p_path, "admin/user-setup") == 0) { /** **/
3101 /* change of user data */ 3217 /* change of user data */
3102 char *v; 3218 const char *v;
3103 char *p1, *p2; 3219 const char *p1, *p2;
3104 3220
3105 if ((v = xs_dict_get(p_vars, "name")) != NULL) 3221 if ((v = xs_dict_get(p_vars, "name")) != NULL)
3106 snac.config = xs_dict_set(snac.config, "name", v); 3222 snac.config = xs_dict_set(snac.config, "name", v);
@@ -3145,7 +3261,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3145 xs_dict *md = xs_dict_new(); 3261 xs_dict *md = xs_dict_new();
3146 xs *l = xs_split(v, "\n"); 3262 xs *l = xs_split(v, "\n");
3147 xs_list *p = l; 3263 xs_list *p = l;
3148 xs_str *kp; 3264 const xs_str *kp;
3149 3265
3150 while (xs_list_iter(&p, &kp)) { 3266 while (xs_list_iter(&p, &kp)) {
3151 xs *kpl = xs_split_n(kp, "=", 1); 3267 xs *kpl = xs_split_n(kp, "=", 1);
@@ -3166,7 +3282,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3166 for (n = 0; uploads[n]; n++) { 3282 for (n = 0; uploads[n]; n++) {
3167 xs *var_name = xs_fmt("%s_file", uploads[n]); 3283 xs *var_name = xs_fmt("%s_file", uploads[n]);
3168 3284
3169 xs_list *uploaded_file = xs_dict_get(p_vars, var_name); 3285 const xs_list *uploaded_file = xs_dict_get(p_vars, var_name);
3170 if (xs_type(uploaded_file) == XSTYPE_LIST) { 3286 if (xs_type(uploaded_file) == XSTYPE_LIST) {
3171 const char *fn = xs_list_get(uploaded_file, 0); 3287 const char *fn = xs_list_get(uploaded_file, 0);
3172 3288
@@ -3231,7 +3347,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3231 } 3347 }
3232 else 3348 else
3233 if (p_path && strcmp(p_path, "admin/vote") == 0) { /** **/ 3349 if (p_path && strcmp(p_path, "admin/vote") == 0) { /** **/
3234 char *irt = xs_dict_get(p_vars, "irt"); 3350 const char *irt = xs_dict_get(p_vars, "irt");
3235 const char *opt = xs_dict_get(p_vars, "question"); 3351 const char *opt = xs_dict_get(p_vars, "question");
3236 const char *actor = xs_dict_get(p_vars, "actor"); 3352 const char *actor = xs_dict_get(p_vars, "actor");
3237 3353
@@ -3246,7 +3362,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3246 } 3362 }
3247 3363
3248 xs_list *p = ls; 3364 xs_list *p = ls;
3249 xs_str *v; 3365 const xs_str *v;
3250 3366
3251 while (xs_list_iter(&p, &v)) { 3367 while (xs_list_iter(&p, &v)) {
3252 xs *msg = msg_note(&snac, "", actor, irt, NULL, 1); 3368 xs *msg = msg_note(&snac, "", actor, irt, NULL, 1);
@@ -3261,11 +3377,30 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3261 timeline_add(&snac, xs_dict_get(msg, "id"), msg); 3377 timeline_add(&snac, xs_dict_get(msg, "id"), msg);
3262 } 3378 }
3263 3379
3380 {
3381 /* get the poll object */
3382 xs *poll = NULL;
3383
3384 if (valid_status(object_get(irt, &poll))) {
3385 const char *date = xs_dict_get(poll, "endTime");
3386 if (xs_is_null(date))
3387 date = xs_dict_get(poll, "closed");
3388
3389 if (!xs_is_null(date)) {
3390 time_t t = xs_parse_iso_date(date, 0) - time(NULL);
3391
3392 /* request the poll when it's closed;
3393 Pleroma does not send and update when the poll closes */
3394 enqueue_object_request(&snac, irt, t + 2);
3395 }
3396 }
3397 }
3398
3264 status = 303; 3399 status = 303;
3265 } 3400 }
3266 3401
3267 if (status == 303) { 3402 if (status == 303) {
3268 char *redir = xs_dict_get(p_vars, "redir"); 3403 const char *redir = xs_dict_get(p_vars, "redir");
3269 3404
3270 if (xs_is_null(redir)) 3405 if (xs_is_null(redir))
3271 redir = "top"; 3406 redir = "top";
@@ -3278,3 +3413,71 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3278 3413
3279 return status; 3414 return status;
3280} 3415}
3416
3417
3418xs_str *timeline_to_rss(snac *user, const xs_list *timeline, char *title, char *link, char *desc)
3419/* converts a timeline to rss */
3420{
3421 xs_html *rss = xs_html_tag("rss",
3422 xs_html_attr("version", "0.91"));
3423
3424 xs_html *channel = xs_html_tag("channel",
3425 xs_html_tag("title",
3426 xs_html_text(title)),
3427 xs_html_tag("language",
3428 xs_html_text("en")),
3429 xs_html_tag("link",
3430 xs_html_text(link)),
3431 xs_html_tag("description",
3432 xs_html_text(desc)));
3433
3434 xs_html_add(rss, channel);
3435
3436 int c = 0;
3437 const char *v;
3438
3439 while (xs_list_next(timeline, &v, &c)) {
3440 xs *msg = NULL;
3441
3442 if (user) {
3443 if (!valid_status(timeline_get_by_md5(user, v, &msg)))
3444 continue;
3445 }
3446 else {
3447 if (!valid_status(object_get_by_md5(v, &msg)))
3448 continue;
3449 }
3450
3451 const char *id = xs_dict_get(msg, "id");
3452 const char *content = xs_dict_get(msg, "content");
3453
3454 if (user && !xs_startswith(id, user->actor))
3455 continue;
3456
3457 /* create a title with the first line of the content */
3458 xs *title = xs_replace(content, "<br>", "\n");
3459 title = xs_regex_replace_i(title, "<[^>]+>", " ");
3460 title = xs_regex_replace_i(title, "&[^;]+;", " ");
3461 int i;
3462
3463 for (i = 0; title[i] && title[i] != '\n' && i < 50; i++);
3464
3465 if (title[i] != '\0') {
3466 title[i] = '\0';
3467 title = xs_str_cat(title, "...");
3468 }
3469
3470 title = xs_strip_i(title);
3471
3472 xs_html_add(channel,
3473 xs_html_tag("item",
3474 xs_html_tag("title",
3475 xs_html_text(title)),
3476 xs_html_tag("link",
3477 xs_html_text(id)),
3478 xs_html_tag("description",
3479 xs_html_text(content))));
3480 }
3481
3482 return xs_html_render_s(rss, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
3483}