summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data.c66
-rw-r--r--doc/snac.58
-rw-r--r--doc/style.css1
-rw-r--r--html.c28
-rw-r--r--mastoapi.c38
-rw-r--r--snac.h6
6 files changed, 143 insertions, 4 deletions
diff --git a/data.c b/data.c
index e24bf16..b25ddf8 100644
--- a/data.c
+++ b/data.c
@@ -3370,3 +3370,69 @@ void srv_archive_qitem(const char *prefix, xs_dict *q_item)
3370 fclose(f); 3370 fclose(f);
3371 } 3371 }
3372} 3372}
3373
3374
3375t_announcement *announcement(const double after)
3376/* returns announcement text or NULL if none exists or it is olde than "after" */
3377{
3378 static const long int MAX_SIZE = 2048;
3379 static t_announcement a = {
3380 .text = NULL,
3381 .timestamp = 0.0,
3382 };
3383 static xs_str *fn = NULL;
3384 if (fn == NULL)
3385 fn = xs_fmt("%s/announcement.txt", srv_basedir);
3386
3387 const double ts = mtime(fn);
3388
3389 /* file does not exist or other than what was requested */
3390 if (ts == 0.0 || ts <= after)
3391 return NULL;
3392
3393 /* nothing changed, just return the current announcement */
3394 if (a.text != NULL && ts <= a.timestamp)
3395 return &a;
3396
3397 /* read and store new announcement */
3398 FILE *f;
3399
3400 if ((f = fopen(fn, "r")) != NULL) {
3401 fseek (f, 0, SEEK_END);
3402 const long int length = ftell(f);
3403
3404 if (length > MAX_SIZE) {
3405 /* this is probably unintentional */
3406 srv_log(xs_fmt("announcement.txt too big: %ld bytes, max is %ld, ignoring.", length, MAX_SIZE));
3407 }
3408 else
3409 if (length > 0) {
3410 fseek (f, 0, SEEK_SET);
3411 char *buffer = malloc(length + 1);
3412 if (buffer) {
3413 fread(buffer, 1, length, f);
3414 buffer[length] = '\0';
3415
3416 free(a.text);
3417 a.text = buffer;
3418 a.timestamp = ts;
3419 }
3420 else {
3421 srv_log("Error allocating memory for announcement");
3422 }
3423 }
3424 else {
3425 /* an empty file means no announcement */
3426 free(a.text);
3427 a.text = NULL;
3428 a.timestamp = 0.0;
3429 }
3430
3431 fclose (f);
3432 }
3433
3434 if (a.text != NULL)
3435 return &a;
3436
3437 return NULL;
3438}
diff --git a/doc/snac.5 b/doc/snac.5
index 42b257e..fec3af3 100644
--- a/doc/snac.5
+++ b/doc/snac.5
@@ -121,6 +121,14 @@ rejected. This brings the flexibility and destruction power of regular expressio
121to your Fediverse experience. To be used wisely (see 121to your Fediverse experience. To be used wisely (see
122.Xr snac 8 122.Xr snac 8
123for more information). 123for more information).
124.It Pa announcement.txt
125If this file is present, an announcement will be shown to logged in users
126on every page with its contents. It is also available through the Mastodon API.
127Users can dismiss the announcement, which works by storing the modification time
128in the "last_announcement" field of the
129.Pa user.json
130file. When the file is modified, the announcement will then reappear. It can
131contain only text and will be ignored if it has more than 2048 bytes.
124.El 132.El
125.Pp 133.Pp
126Each user directory is a subdirectory of 134Each user directory is a subdirectory of
diff --git a/doc/style.css b/doc/style.css
index a133db6..2273e03 100644
--- a/doc/style.css
+++ b/doc/style.css
@@ -6,6 +6,7 @@ pre { overflow-x: scroll; }
6.snac-top-user { text-align: center; padding-bottom: 2em } 6.snac-top-user { text-align: center; padding-bottom: 2em }
7.snac-top-user-name { font-size: 200% } 7.snac-top-user-name { font-size: 200% }
8.snac-top-user-id { font-size: 150% } 8.snac-top-user-id { font-size: 150% }
9.snac-announcement { border: black 1px solid; padding: 0.5em }
9.snac-avatar { float: left; height: 2.5em; padding: 0.25em } 10.snac-avatar { float: left; height: 2.5em; padding: 0.25em }
10.snac-author { font-size: 90%; text-decoration: none } 11.snac-author { font-size: 90%; text-decoration: none }
11.snac-author-tag { font-size: 80% } 12.snac-author-tag { font-size: 80% }
diff --git a/html.c b/html.c
index c3a2efe..2274f74 100644
--- a/html.c
+++ b/html.c
@@ -786,6 +786,24 @@ static xs_html *html_user_body(snac *user, int read_only)
786 xs_html_attr("class", "snac-top-user-id"), 786 xs_html_attr("class", "snac-top-user-id"),
787 xs_html_text(handle))); 787 xs_html_text(handle)));
788 788
789 /** instance announcement **/
790
791 double la = 0.0;
792 xs *user_la = xs_dup(xs_dict_get(user->config, "last_announcement"));
793 if (user_la != NULL)
794 la = xs_number_get(user_la);
795
796 const t_announcement *an = announcement(la);
797 if (an != NULL && (an->text != NULL)) {
798 xs_html_add(top_user, xs_html_tag("div",
799 xs_html_attr("class", "snac-announcement"),
800 xs_html_text(an->text),
801 xs_html_text(" "),
802 xs_html_sctag("a",
803 xs_html_attr("href", xs_dup(xs_fmt("?da=%.0f", an->timestamp)))),
804 xs_html_text("Dismiss")));
805 }
806
789 if (read_only) { 807 if (read_only) {
790 xs *es1 = encode_html(xs_dict_get(user->config, "bio")); 808 xs *es1 = encode_html(xs_dict_get(user->config, "bio"));
791 xs *bio1 = not_really_markdown(es1, NULL, NULL); 809 xs *bio1 = not_really_markdown(es1, NULL, NULL);
@@ -2606,6 +2624,16 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2606 skip = atoi(v), cache = 0, save = 0; 2624 skip = atoi(v), cache = 0, save = 0;
2607 if ((v = xs_dict_get(q_vars, "show")) != NULL) 2625 if ((v = xs_dict_get(q_vars, "show")) != NULL)
2608 show = atoi(v), cache = 0, save = 0; 2626 show = atoi(v), cache = 0, save = 0;
2627 if ((v = xs_dict_get(q_vars, "da")) != NULL) {
2628 /* user dismissed an announcement */
2629 if (login(&snac, req)) {
2630 double ts = atof(v);
2631 xs *timestamp = xs_number_new(ts);
2632 srv_log(xs_fmt("user dismissed announcements until %d", ts));
2633 snac.config = xs_dict_set(snac.config, "last_announcement", timestamp);
2634 user_persist(&snac);
2635 }
2636 }
2609 2637
2610 if (p_path == NULL) { /** public timeline **/ 2638 if (p_path == NULL) { /** public timeline **/
2611 xs *h = xs_str_localtime(0, "%Y-%m.html"); 2639 xs *h = xs_str_localtime(0, "%Y-%m.html");
diff --git a/mastoapi.c b/mastoapi.c
index 9860872..2337007 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -1997,10 +1997,40 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1997 } 1997 }
1998 else 1998 else
1999 if (strcmp(cmd, "/v1/announcements") == 0) { /** **/ 1999 if (strcmp(cmd, "/v1/announcements") == 0) { /** **/
2000 /* snac has no announcements (yet?) */ 2000 if (logged_in) {
2001 *body = xs_dup("[]"); 2001 xs *resp = xs_list_new();
2002 *ctype = "application/json"; 2002 double la = 0.0;
2003 status = HTTP_STATUS_OK; 2003 xs *user_la = xs_dup(xs_dict_get(snac1.config, "last_announcement"));
2004 if (user_la != NULL)
2005 la = xs_number_get(user_la);
2006 xs *val_date = xs_str_utctime(la, ISO_DATE_SPEC);
2007
2008 /* contrary to html, we always send the announcement and set the read flag instead */
2009
2010 const t_announcement *annce = announcement(la);
2011 if (annce != NULL && annce->text != NULL) {
2012 xs *an = xs_dict_new();
2013 an = xs_dict_set(an, "id", xs_fmt("%d", annce->timestamp));
2014 an = xs_dict_set(an, "content", xs_fmt("<p>%s</p>", annce->text));
2015 an = xs_dict_set(an, "starts_at", xs_stock(XSTYPE_NULL));
2016 an = xs_dict_set(an, "ends_at", xs_stock(XSTYPE_NULL));
2017 an = xs_dict_set(an, "all_day", xs_stock(XSTYPE_TRUE));
2018 an = xs_dict_set(an, "published_at", val_date);
2019 an = xs_dict_set(an, "updated_at", val_date);
2020 an = xs_dict_set(an, "read", (annce->timestamp >= la)
2021 ? xs_stock(XSTYPE_FALSE) : xs_stock(XSTYPE_TRUE));
2022 an = xs_dict_set(an, "mentions", xs_stock(XSTYPE_LIST));
2023 an = xs_dict_set(an, "statuses", xs_stock(XSTYPE_LIST));
2024 an = xs_dict_set(an, "tags", xs_stock(XSTYPE_LIST));
2025 an = xs_dict_set(an, "emojis", xs_stock(XSTYPE_LIST));
2026 an = xs_dict_set(an, "reactions", xs_stock(XSTYPE_LIST));
2027 resp = xs_list_append(resp, an);
2028 }
2029
2030 *body = xs_json_dumps(resp, 4);
2031 *ctype = "application/json";
2032 status = HTTP_STATUS_OK;
2033 }
2004 } 2034 }
2005 else 2035 else
2006 if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/ 2036 if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/
diff --git a/snac.h b/snac.h
index 2561b6c..8193ca5 100644
--- a/snac.h
+++ b/snac.h
@@ -375,3 +375,9 @@ typedef enum {
375} http_status; 375} http_status;
376 376
377const char *http_status_text(int status); 377const char *http_status_text(int status);
378
379typedef struct {
380 double timestamp;
381 char *text;
382} t_announcement;
383t_announcement *announcement(double after);