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 bacee5b..e1d4981 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);
@@ -2590,6 +2608,16 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2590 skip = atoi(v), cache = 0, save = 0; 2608 skip = atoi(v), cache = 0, save = 0;
2591 if ((v = xs_dict_get(q_vars, "show")) != NULL) 2609 if ((v = xs_dict_get(q_vars, "show")) != NULL)
2592 show = atoi(v), cache = 0, save = 0; 2610 show = atoi(v), cache = 0, save = 0;
2611 if ((v = xs_dict_get(q_vars, "da")) != NULL) {
2612 /* user dismissed an announcement */
2613 if (login(&snac, req)) {
2614 double ts = atof(v);
2615 xs *timestamp = xs_number_new(ts);
2616 srv_log(xs_fmt("user dismissed announcements until %d", ts));
2617 snac.config = xs_dict_set(snac.config, "last_announcement", timestamp);
2618 user_persist(&snac);
2619 }
2620 }
2593 2621
2594 if (p_path == NULL) { /** public timeline **/ 2622 if (p_path == NULL) { /** public timeline **/
2595 xs *h = xs_str_localtime(0, "%Y-%m.html"); 2623 xs *h = xs_str_localtime(0, "%Y-%m.html");
diff --git a/mastoapi.c b/mastoapi.c
index c1f70b9..00ea26a 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -1982,10 +1982,40 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1982 } 1982 }
1983 else 1983 else
1984 if (strcmp(cmd, "/v1/announcements") == 0) { /** **/ 1984 if (strcmp(cmd, "/v1/announcements") == 0) { /** **/
1985 /* snac has no announcements (yet?) */ 1985 if (logged_in) {
1986 *body = xs_dup("[]"); 1986 xs *resp = xs_list_new();
1987 *ctype = "application/json"; 1987 double la = 0.0;
1988 status = HTTP_STATUS_OK; 1988 xs *user_la = xs_dup(xs_dict_get(snac1.config, "last_announcement"));
1989 if (user_la != NULL)
1990 la = xs_number_get(user_la);
1991 xs *val_date = xs_str_utctime(la, ISO_DATE_SPEC);
1992
1993 /* contrary to html, we always send the announcement and set the read flag instead */
1994
1995 const t_announcement *annce = announcement(la);
1996 if (annce != NULL && annce->text != NULL) {
1997 xs *an = xs_dict_new();
1998 an = xs_dict_set(an, "id", xs_fmt("%d", annce->timestamp));
1999 an = xs_dict_set(an, "content", xs_fmt("<p>%s</p>", annce->text));
2000 an = xs_dict_set(an, "starts_at", xs_stock(XSTYPE_NULL));
2001 an = xs_dict_set(an, "ends_at", xs_stock(XSTYPE_NULL));
2002 an = xs_dict_set(an, "all_day", xs_stock(XSTYPE_TRUE));
2003 an = xs_dict_set(an, "published_at", val_date);
2004 an = xs_dict_set(an, "updated_at", val_date);
2005 an = xs_dict_set(an, "read", (annce->timestamp >= la)
2006 ? xs_stock(XSTYPE_FALSE) : xs_stock(XSTYPE_TRUE));
2007 an = xs_dict_set(an, "mentions", xs_stock(XSTYPE_LIST));
2008 an = xs_dict_set(an, "statuses", xs_stock(XSTYPE_LIST));
2009 an = xs_dict_set(an, "tags", xs_stock(XSTYPE_LIST));
2010 an = xs_dict_set(an, "emojis", xs_stock(XSTYPE_LIST));
2011 an = xs_dict_set(an, "reactions", xs_stock(XSTYPE_LIST));
2012 resp = xs_list_append(resp, an);
2013 }
2014
2015 *body = xs_json_dumps(resp, 4);
2016 *ctype = "application/json";
2017 status = HTTP_STATUS_OK;
2018 }
1989 } 2019 }
1990 else 2020 else
1991 if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/ 2021 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);