mirror of
https://codeberg.org/grunfink/snac2.git
synced 2024-11-22 13:25:04 +00:00
Implement instance announcements
This commit is contained in:
parent
af8f1ef273
commit
c3bcb2bd3b
6 changed files with 143 additions and 4 deletions
66
data.c
66
data.c
|
@ -3370,3 +3370,69 @@ void srv_archive_qitem(const char *prefix, xs_dict *q_item)
|
||||||
fclose(f);
|
fclose(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
t_announcement *announcement(const double after)
|
||||||
|
/* returns announcement text or NULL if none exists or it is olde than "after" */
|
||||||
|
{
|
||||||
|
static const long int MAX_SIZE = 2048;
|
||||||
|
static t_announcement a = {
|
||||||
|
.text = NULL,
|
||||||
|
.timestamp = 0.0,
|
||||||
|
};
|
||||||
|
static xs_str *fn = NULL;
|
||||||
|
if (fn == NULL)
|
||||||
|
fn = xs_fmt("%s/announcement.txt", srv_basedir);
|
||||||
|
|
||||||
|
const double ts = mtime(fn);
|
||||||
|
|
||||||
|
/* file does not exist or other than what was requested */
|
||||||
|
if (ts == 0.0 || ts <= after)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* nothing changed, just return the current announcement */
|
||||||
|
if (a.text != NULL && ts <= a.timestamp)
|
||||||
|
return &a;
|
||||||
|
|
||||||
|
/* read and store new announcement */
|
||||||
|
FILE *f;
|
||||||
|
|
||||||
|
if ((f = fopen(fn, "r")) != NULL) {
|
||||||
|
fseek (f, 0, SEEK_END);
|
||||||
|
const long int length = ftell(f);
|
||||||
|
|
||||||
|
if (length > MAX_SIZE) {
|
||||||
|
/* this is probably unintentional */
|
||||||
|
srv_log(xs_fmt("announcement.txt too big: %ld bytes, max is %ld, ignoring.", length, MAX_SIZE));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (length > 0) {
|
||||||
|
fseek (f, 0, SEEK_SET);
|
||||||
|
char *buffer = malloc(length + 1);
|
||||||
|
if (buffer) {
|
||||||
|
fread(buffer, 1, length, f);
|
||||||
|
buffer[length] = '\0';
|
||||||
|
|
||||||
|
free(a.text);
|
||||||
|
a.text = buffer;
|
||||||
|
a.timestamp = ts;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
srv_log("Error allocating memory for announcement");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* an empty file means no announcement */
|
||||||
|
free(a.text);
|
||||||
|
a.text = NULL;
|
||||||
|
a.timestamp = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose (f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.text != NULL)
|
||||||
|
return &a;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
|
@ -121,6 +121,14 @@ rejected. This brings the flexibility and destruction power of regular expressio
|
||||||
to your Fediverse experience. To be used wisely (see
|
to your Fediverse experience. To be used wisely (see
|
||||||
.Xr snac 8
|
.Xr snac 8
|
||||||
for more information).
|
for more information).
|
||||||
|
.It Pa announcement.txt
|
||||||
|
If this file is present, an announcement will be shown to logged in users
|
||||||
|
on every page with its contents. It is also available through the Mastodon API.
|
||||||
|
Users can dismiss the announcement, which works by storing the modification time
|
||||||
|
in the "last_announcement" field of the
|
||||||
|
.Pa user.json
|
||||||
|
file. When the file is modified, the announcement will then reappear. It can
|
||||||
|
contain only text and will be ignored if it has more than 2048 bytes.
|
||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
Each user directory is a subdirectory of
|
Each user directory is a subdirectory of
|
||||||
|
|
|
@ -6,6 +6,7 @@ pre { overflow-x: scroll; }
|
||||||
.snac-top-user { text-align: center; padding-bottom: 2em }
|
.snac-top-user { text-align: center; padding-bottom: 2em }
|
||||||
.snac-top-user-name { font-size: 200% }
|
.snac-top-user-name { font-size: 200% }
|
||||||
.snac-top-user-id { font-size: 150% }
|
.snac-top-user-id { font-size: 150% }
|
||||||
|
.snac-announcement { border: black 1px solid; padding: 0.5em }
|
||||||
.snac-avatar { float: left; height: 2.5em; padding: 0.25em }
|
.snac-avatar { float: left; height: 2.5em; padding: 0.25em }
|
||||||
.snac-author { font-size: 90%; text-decoration: none }
|
.snac-author { font-size: 90%; text-decoration: none }
|
||||||
.snac-author-tag { font-size: 80% }
|
.snac-author-tag { font-size: 80% }
|
||||||
|
|
28
html.c
28
html.c
|
@ -786,6 +786,24 @@ static xs_html *html_user_body(snac *user, int read_only)
|
||||||
xs_html_attr("class", "snac-top-user-id"),
|
xs_html_attr("class", "snac-top-user-id"),
|
||||||
xs_html_text(handle)));
|
xs_html_text(handle)));
|
||||||
|
|
||||||
|
/** instance announcement **/
|
||||||
|
|
||||||
|
double la = 0.0;
|
||||||
|
xs *user_la = xs_dup(xs_dict_get(user->config, "last_announcement"));
|
||||||
|
if (user_la != NULL)
|
||||||
|
la = xs_number_get(user_la);
|
||||||
|
|
||||||
|
const t_announcement *an = announcement(la);
|
||||||
|
if (an != NULL && (an->text != NULL)) {
|
||||||
|
xs_html_add(top_user, xs_html_tag("div",
|
||||||
|
xs_html_attr("class", "snac-announcement"),
|
||||||
|
xs_html_text(an->text),
|
||||||
|
xs_html_text(" "),
|
||||||
|
xs_html_sctag("a",
|
||||||
|
xs_html_attr("href", xs_dup(xs_fmt("?da=%.0f", an->timestamp)))),
|
||||||
|
xs_html_text("Dismiss")));
|
||||||
|
}
|
||||||
|
|
||||||
if (read_only) {
|
if (read_only) {
|
||||||
xs *es1 = encode_html(xs_dict_get(user->config, "bio"));
|
xs *es1 = encode_html(xs_dict_get(user->config, "bio"));
|
||||||
xs *bio1 = not_really_markdown(es1, NULL, NULL);
|
xs *bio1 = not_really_markdown(es1, NULL, NULL);
|
||||||
|
@ -2590,6 +2608,16 @@ int html_get_handler(const xs_dict *req, const char *q_path,
|
||||||
skip = atoi(v), cache = 0, save = 0;
|
skip = atoi(v), cache = 0, save = 0;
|
||||||
if ((v = xs_dict_get(q_vars, "show")) != NULL)
|
if ((v = xs_dict_get(q_vars, "show")) != NULL)
|
||||||
show = atoi(v), cache = 0, save = 0;
|
show = atoi(v), cache = 0, save = 0;
|
||||||
|
if ((v = xs_dict_get(q_vars, "da")) != NULL) {
|
||||||
|
/* user dismissed an announcement */
|
||||||
|
if (login(&snac, req)) {
|
||||||
|
double ts = atof(v);
|
||||||
|
xs *timestamp = xs_number_new(ts);
|
||||||
|
srv_log(xs_fmt("user dismissed announcements until %d", ts));
|
||||||
|
snac.config = xs_dict_set(snac.config, "last_announcement", timestamp);
|
||||||
|
user_persist(&snac);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (p_path == NULL) { /** public timeline **/
|
if (p_path == NULL) { /** public timeline **/
|
||||||
xs *h = xs_str_localtime(0, "%Y-%m.html");
|
xs *h = xs_str_localtime(0, "%Y-%m.html");
|
||||||
|
|
34
mastoapi.c
34
mastoapi.c
|
@ -1982,11 +1982,41 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (strcmp(cmd, "/v1/announcements") == 0) { /** **/
|
if (strcmp(cmd, "/v1/announcements") == 0) { /** **/
|
||||||
/* snac has no announcements (yet?) */
|
if (logged_in) {
|
||||||
*body = xs_dup("[]");
|
xs *resp = xs_list_new();
|
||||||
|
double la = 0.0;
|
||||||
|
xs *user_la = xs_dup(xs_dict_get(snac1.config, "last_announcement"));
|
||||||
|
if (user_la != NULL)
|
||||||
|
la = xs_number_get(user_la);
|
||||||
|
xs *val_date = xs_str_utctime(la, ISO_DATE_SPEC);
|
||||||
|
|
||||||
|
/* contrary to html, we always send the announcement and set the read flag instead */
|
||||||
|
|
||||||
|
const t_announcement *annce = announcement(la);
|
||||||
|
if (annce != NULL && annce->text != NULL) {
|
||||||
|
xs *an = xs_dict_new();
|
||||||
|
an = xs_dict_set(an, "id", xs_fmt("%d", annce->timestamp));
|
||||||
|
an = xs_dict_set(an, "content", xs_fmt("<p>%s</p>", annce->text));
|
||||||
|
an = xs_dict_set(an, "starts_at", xs_stock(XSTYPE_NULL));
|
||||||
|
an = xs_dict_set(an, "ends_at", xs_stock(XSTYPE_NULL));
|
||||||
|
an = xs_dict_set(an, "all_day", xs_stock(XSTYPE_TRUE));
|
||||||
|
an = xs_dict_set(an, "published_at", val_date);
|
||||||
|
an = xs_dict_set(an, "updated_at", val_date);
|
||||||
|
an = xs_dict_set(an, "read", (annce->timestamp >= la)
|
||||||
|
? xs_stock(XSTYPE_FALSE) : xs_stock(XSTYPE_TRUE));
|
||||||
|
an = xs_dict_set(an, "mentions", xs_stock(XSTYPE_LIST));
|
||||||
|
an = xs_dict_set(an, "statuses", xs_stock(XSTYPE_LIST));
|
||||||
|
an = xs_dict_set(an, "tags", xs_stock(XSTYPE_LIST));
|
||||||
|
an = xs_dict_set(an, "emojis", xs_stock(XSTYPE_LIST));
|
||||||
|
an = xs_dict_set(an, "reactions", xs_stock(XSTYPE_LIST));
|
||||||
|
resp = xs_list_append(resp, an);
|
||||||
|
}
|
||||||
|
|
||||||
|
*body = xs_json_dumps(resp, 4);
|
||||||
*ctype = "application/json";
|
*ctype = "application/json";
|
||||||
status = HTTP_STATUS_OK;
|
status = HTTP_STATUS_OK;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/
|
if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/
|
||||||
/* are you kidding me? */
|
/* are you kidding me? */
|
||||||
|
|
6
snac.h
6
snac.h
|
@ -375,3 +375,9 @@ typedef enum {
|
||||||
} http_status;
|
} http_status;
|
||||||
|
|
||||||
const char *http_status_text(int status);
|
const char *http_status_text(int status);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
double timestamp;
|
||||||
|
char *text;
|
||||||
|
} t_announcement;
|
||||||
|
t_announcement *announcement(double after);
|
||||||
|
|
Loading…
Reference in a new issue