From 19db6c8f2be567bf3759455c472a85b0d11c9fed Mon Sep 17 00:00:00 2001 From: sn4il Date: Thu, 20 Mar 2025 09:52:51 +0000 Subject: Small fix for russian translation --- po/ru.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/po/ru.po b/po/ru.po index d641860..ef7be81 100644 --- a/po/ru.po +++ b/po/ru.po @@ -446,7 +446,7 @@ msgstr "Событие" #: html.c:1976 html.c:2005 msgid "boosted" -msgstr "продвинуто" +msgstr "поделился" #: html.c:2021 msgid "in reply to" -- cgit v1.2.3 From 20573275ec8f7cc7f5744a3280590040a6f14203 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 22 Mar 2025 08:32:59 +0100 Subject: mastoapi: Added support for /api/v1/instance/peers. --- mastoapi.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mastoapi.c b/mastoapi.c index 8d61681..14dd251 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -2256,6 +2256,15 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, status = HTTP_STATUS_OK; } else + if (strcmp(cmd, "/v1/instance/peers") == 0) { /** **/ + /* get the collected inbox list as the instances "this domain is aware of" */ + xs *list = inbox_list(); + + *body = xs_json_dumps(list, 4); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else if (xs_startswith(cmd, "/v1/statuses/")) { /** **/ /* information about a status */ if (logged_in) { -- cgit v1.2.3 From 0b4e8cac069d31de736137de08543ace912b728a Mon Sep 17 00:00:00 2001 From: default Date: Sat, 22 Mar 2025 08:39:04 +0100 Subject: mastoapi: fixed instance peers to return only the domains. --- mastoapi.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/mastoapi.c b/mastoapi.c index 14dd251..7b1e0ad 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -2259,8 +2259,18 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, if (strcmp(cmd, "/v1/instance/peers") == 0) { /** **/ /* get the collected inbox list as the instances "this domain is aware of" */ xs *list = inbox_list(); + xs *peers = xs_list_new(); + const char *inbox; - *body = xs_json_dumps(list, 4); + xs_list_foreach(list, inbox) { + xs *l = xs_split(inbox, "/"); + const char *domain = xs_list_get(l, 2); + + if (xs_is_string(domain)) + peers = xs_list_append(peers, domain); + } + + *body = xs_json_dumps(peers, 4); *ctype = "application/json"; status = HTTP_STATUS_OK; } -- cgit v1.2.3 From 770062def6f3e10bf56eae48e274709796fa6df6 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 22 Mar 2025 08:50:08 +0100 Subject: Filter out block instances from inbox_list(). --- data.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/data.c b/data.c index ce040dd..a192830 100644 --- a/data.c +++ b/data.c @@ -2619,10 +2619,9 @@ xs_list *inbox_list(void) xs_list *ibl = xs_list_new(); xs *spec = xs_fmt("%s/inbox/" "*", srv_basedir); xs *files = xs_glob(spec, 0, 0); - xs_list *p = files; const xs_val *v; - while (xs_list_iter(&p, &v)) { + xs_list_foreach(files, v) { FILE *f; if ((f = fopen(v, "r")) != NULL) { @@ -2630,7 +2629,9 @@ xs_list *inbox_list(void) if (line && *line) { line = xs_strip_i(line); - ibl = xs_list_append(ibl, line); + + if (!is_instance_blocked(line)) + ibl = xs_list_append(ibl, line); } fclose(f); -- cgit v1.2.3 From 128cc54608fc4ecbf8c9f93eb2c45eddd81a2fe6 Mon Sep 17 00:00:00 2001 From: green Date: Sat, 22 Mar 2025 19:37:23 +0100 Subject: Added tittle text and class to custom emojis --- html.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/html.c b/html.c index 78a9854..3d8f0e1 100644 --- a/html.c +++ b/html.c @@ -93,6 +93,8 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p xs_html_attr("loading", "lazy"), xs_html_attr("src", url), xs_html_attr("alt", n), + xs_html_attr("title", n), + xs_html_attr("class", "snac-emoji"), xs_html_attr("style", style)); xs *s1 = xs_html_render(img); -- cgit v1.2.3 From b107107fb548dd0ad6c3f11a3019850bead8b8ae Mon Sep 17 00:00:00 2001 From: default Date: Sun, 23 Mar 2025 15:39:42 +0100 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a8f04e8..3463b38 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,12 @@ # Release Notes -## 2.74 +## UNRELEASED + +Mastodon API: added support for `/api/v1/instance/peers`. + +Some Czech and Russian translation fixes. + +## 2.74 "The Days of Nicole, the Fediverse Chick" Added Spanish (default, Argentina and Uruguay) translation (contributed by gnemmi). @@ -22,7 +28,7 @@ Added Greek translation (contributed by uhuru). Added Italian translation (contributed by anzu). -Mastodon API: added support for /api/v1/custom_emojis (contributed by violette). +Mastodon API: added support for `/api/v1/custom_emojis` (contributed by violette). Improved Undo+Follow logic (contributed by rozenglass). -- cgit v1.2.3 From 91e0f144a67d2176e202ed6fe3f8b4a10f2ef4da Mon Sep 17 00:00:00 2001 From: default Date: Sun, 23 Mar 2025 15:46:14 +0100 Subject: Updated TODO. --- TODO.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/TODO.md b/TODO.md index bdb860c..baa910a 100644 --- a/TODO.md +++ b/TODO.md @@ -6,16 +6,12 @@ Investigate the problem with boosts inside the same instance (see https://codebe Editing / Updating a post does not index newly added hashtags. -Wrong level of message visibility when using the Mastodon API: https://codeberg.org/grunfink/snac2/issues/200#issuecomment-2351042 - Unfollowing guppe groups seems to work (http status of 200), but messages continue to arrive as if it didn't. Important: deleting a follower should do more that just delete the object, see https://codeberg.org/grunfink/snac2/issues/43#issuecomment-956721 ## Wishlist -Each notification should show a link to the full thread, to see it in context. - The instance timeline should also show boosts from users. Mastoapi: implement /v1/conversations. @@ -30,14 +26,10 @@ Integrate "Added handling for International Domain Names" PR https://codeberg.or Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` field instead of `inReplyTo`). -Add a list of hashtags to drop. - Take a look at crashes in the brittle Mastodon official app (crashes when hitting the reply button, crashes or 'ownVotes is null' errors when trying to show polls). The 'history' pages are just monthly HTML snapshots of the local timeline. This is ok and cheap and easy, but is problematic if you e.g. intentionally delete a post because it will remain there in the history forever. If you activate local timeline purging, purged entries will remain in the history as 'ghosts', which may or may not be what the user wants. -The actual storage system wastes too much disk space (lots of small files that really consume 4k of storage). Consider alternatives. - ## Closed Start a TODO file (2022-08-25T10:07:44+0200). @@ -367,3 +359,11 @@ Add support for /authorize_interaction (whatever it is) (2025-01-16T14:45:28+010 Implement following of hashtags (this is not trivial) (2025-01-30T16:12:16+0100). Add support for subscribing and posting to relays (see https://codeberg.org/grunfink/snac2/issues/216 for more information) (2025-01-30T16:12:34+0100). + +Wrong level of message visibility when using the Mastodon API: https://codeberg.org/grunfink/snac2/issues/200#issuecomment-2351042 (2025-03-23T15:44:35+0100). + +Each notification should show a link to the full thread, to see it in context (2025-03-23T15:44:50+0100). + +Add a list of hashtags to drop (2025-03-23T15:45:30+0100). + +The actual storage system wastes too much disk space (lots of small files that really consume 4k of storage). Consider alternatives (2025-03-23T15:46:02+0100). -- cgit v1.2.3 From 307aab92be9b9e0ea7676e05b1c752371ed0f7b9 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 24 Mar 2025 17:29:17 +0100 Subject: In replace_shortnames(), avoid repeated emojis. --- html.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/html.c b/html.c index 78a9854..abb8562 100644 --- a/html.c +++ b/html.c @@ -72,6 +72,9 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p const xs_dict *v; int c = 0; + xs_set rep_emoji; + xs_set_init(&rep_emoji); + while (xs_list_next(tag_list, &v, &c)) { const char *t = xs_dict_get(v, "type"); @@ -79,6 +82,10 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p const char *n = xs_dict_get(v, "name"); const xs_dict *i = xs_dict_get(v, "icon"); + /* avoid repeated emojis (Misskey seems to return this) */ + if (xs_set_add(&rep_emoji, n) == 0) + continue; + if (xs_is_string(n) && xs_is_dict(i)) { const char *u = xs_dict_get(i, "url"); const char *mt = xs_dict_get(i, "mediaType"); @@ -104,6 +111,8 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p } } } + + xs_set_free(&rep_emoji); } return s; -- cgit v1.2.3 From 84e72a52286db8e0af2d4a671f4b042d3e69f0d2 Mon Sep 17 00:00:00 2001 From: shtrophic Date: Sun, 30 Mar 2025 22:13:54 +0200 Subject: add snac-admin --- examples/snac-admin | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 examples/snac-admin diff --git a/examples/snac-admin b/examples/snac-admin new file mode 100644 index 0000000..971618b --- /dev/null +++ b/examples/snac-admin @@ -0,0 +1,43 @@ +#!/usr/bin/env fish +## +## SNAC-ADMIN +## a simple script that is supposed to improve +## a snac admin's life, especially when snac +## is being run as a systemd.unit with +## DynamicUser=yes enabled. +## Please make sure to adjust SNAC_BASEDIR +## down below according to your setup. +## +## USAGE +## snac-admin state +## snac-admin adduser rikkert +## +## Author: @chris@social.shtrophic.net +## +## Released into the public domain +## + +set -l SNAC_PID $(pidof snac) +set -l SNAC_BASEDIR /var/lib/snac + +if test -z $SNAC_PID + echo "no such process" 1>&2 + exit 1 +end + +if test $(id -u) -ne 0 + echo "not root" 1>&2 + exit 1 +end + +if ! test -d $SNAC_BASEDIR + echo "$SNAC_BASEDIR does not exist" 1>&2 + exit 1 +end + +if test -z $argv[1] + echo "no arguments" 1>&2 + exit 1 +end + +nsenter -ae -S follow -G follow -t $SNAC_PID -- snac $argv[1] $SNAC_BASEDIR $argv[2..] -- cgit v1.2.3 From e8b00b31906e316598e26e92a9b156a42ff5a860 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 31 Mar 2025 20:23:24 +0200 Subject: Added some code for the future. --- html.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/html.c b/html.c index d549f75..115e8c4 100644 --- a/html.c +++ b/html.c @@ -559,6 +559,30 @@ xs_html *html_note(snac *user, const char *summary, xs_html_text(L("End in 1 day")))))); } +#if 0 + /* scheduled post data */ + xs *sched_date = xs_dup(""); + xs *sched_time = xs_dup(""); + + xs_html_add(form, + xs_html_tag("p", NULL), + xs_html_tag("details", + xs_html_tag("summary", + xs_html_text(L("Scheduled post..."))), + xs_html_tag("p", + xs_html_text(L("Post date: ")), + xs_html_sctag("input", + xs_html_attr("type", "date"), + xs_html_attr("value", sched_date), + xs_html_attr("name", "post_date")), + xs_html_text(" "), + xs_html_text(L("Post time: ")), + xs_html_sctag("input", + xs_html_attr("type", "time"), + xs_html_attr("value", sched_time), + xs_html_attr("name", "post_time"))))); +#endif + xs_html_add(form, xs_html_tag("p", NULL), xs_html_sctag("input", @@ -4186,12 +4210,14 @@ int html_post_handler(const xs_dict *req, const char *q_path, snac_debug(&snac, 1, xs_fmt("web action '%s' received", p_path)); /* post note */ - const xs_str *content = xs_dict_get(p_vars, "content"); - const xs_str *in_reply_to = xs_dict_get(p_vars, "in_reply_to"); - const xs_str *to = xs_dict_get(p_vars, "to"); - const xs_str *sensitive = xs_dict_get(p_vars, "sensitive"); - const xs_str *summary = xs_dict_get(p_vars, "summary"); - const xs_str *edit_id = xs_dict_get(p_vars, "edit_id"); + const char *content = xs_dict_get(p_vars, "content"); + const char *in_reply_to = xs_dict_get(p_vars, "in_reply_to"); + const char *to = xs_dict_get(p_vars, "to"); + const char *sensitive = xs_dict_get(p_vars, "sensitive"); + const char *summary = xs_dict_get(p_vars, "summary"); + const char *edit_id = xs_dict_get(p_vars, "edit_id"); + const char *post_date = xs_dict_get_def(p_vars, "post_date", ""); + const char *post_time = xs_dict_get_def(p_vars, "post_time", ""); int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only")); int store_as_draft = !xs_is_null(xs_dict_get(p_vars, "is_draft")); xs *attach_list = xs_list_new(); @@ -4279,6 +4305,23 @@ int html_post_handler(const xs_dict *req, const char *q_path, msg = xs_dict_set(msg, "summary", xs_is_null(summary) ? "..." : summary); } + if (*post_date) { + /* scheduled post */ + xs *sched_date = xs_fmt("%sT%s:00", post_date, *post_time ? post_time : "12:00"); + time_t t = xs_parse_localtime(sched_date, "%Y-%m-%dT%H:%M:%S"); + + if (t != 0) { + xs *iso_date = xs_str_iso_date(t); + msg = xs_dict_set(msg, "published", iso_date); + + snac_debug(&snac, 1, xs_fmt("Scheduled date: [%s]", iso_date)); + } + else { + snac_log(&snac, xs_fmt("Invalid scheduled date: [%s]", sched_date)); + post_date = ""; + } + } + if (xs_is_null(edit_id)) { /* new message */ const char *id = xs_dict_get(msg, "id"); -- cgit v1.2.3 From 1e21c2271e0ae55dbe69ce9ff033589a239a3f95 Mon Sep 17 00:00:00 2001 From: default Date: Tue, 1 Apr 2025 05:47:08 +0200 Subject: Some more work for future posts. --- html.c | 111 +++++++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 44 deletions(-) diff --git a/html.c b/html.c index 115e8c4..d4cbb35 100644 --- a/html.c +++ b/html.c @@ -350,7 +350,7 @@ xs_html *html_note(snac *user, const char *summary, const xs_val *mnt_only, const char *redir, const char *in_reply_to, int poll, const xs_list *att_files, const xs_list *att_alt_texts, - int is_draft) + int is_draft, const char *published) /* Yes, this is a FUCKTON of arguments and I'm a bit embarrased */ { xs *action = xs_fmt("%s/admin/note", user->actor); @@ -440,6 +440,34 @@ xs_html *html_note(snac *user, const char *summary, xs_html_attr("name", "is_draft"), xs_html_attr(is_draft ? "checked" : "", NULL)))); + /* post date and time */ + xs *post_date = NULL; + xs *post_time = NULL; + + if (xs_is_string(published)) { + time_t t = xs_parse_iso_date(published, 0); + + if (t > 0) { + post_date = xs_str_time(t, "%Y-%m-%d", 1); + post_time = xs_str_time(t, "%H:%M:%S", 1); + } + } + + xs_html_add(form, + xs_html_tag("p", + xs_html_text(L("Post date and time (empty, right now; in the future, schedule for later):")), + xs_html_sctag("br", NULL), + xs_html_sctag("input", + xs_html_attr("type", "date"), + xs_html_attr("value", post_date ? post_date : ""), + xs_html_attr("name", "post_date")), + xs_html_text(" "), + xs_html_sctag("input", + xs_html_attr("type", "time"), + xs_html_attr("value", post_time ? post_time : ""), + xs_html_attr("step", "1"), + xs_html_attr("name", "post_time")))); + if (edit_id) xs_html_add(form, xs_html_sctag("input", @@ -559,30 +587,6 @@ xs_html *html_note(snac *user, const char *summary, xs_html_text(L("End in 1 day")))))); } -#if 0 - /* scheduled post data */ - xs *sched_date = xs_dup(""); - xs *sched_time = xs_dup(""); - - xs_html_add(form, - xs_html_tag("p", NULL), - xs_html_tag("details", - xs_html_tag("summary", - xs_html_text(L("Scheduled post..."))), - xs_html_tag("p", - xs_html_text(L("Post date: ")), - xs_html_sctag("input", - xs_html_attr("type", "date"), - xs_html_attr("value", sched_date), - xs_html_attr("name", "post_date")), - xs_html_text(" "), - xs_html_text(L("Post time: ")), - xs_html_sctag("input", - xs_html_attr("type", "time"), - xs_html_attr("value", sched_time), - xs_html_attr("name", "post_time"))))); -#endif - xs_html_add(form, xs_html_tag("p", NULL), xs_html_sctag("input", @@ -1151,7 +1155,7 @@ xs_html *html_top_controls(snac *user) NULL, NULL, xs_stock(XSTYPE_FALSE), "", xs_stock(XSTYPE_FALSE), NULL, - NULL, 1, NULL, NULL, 0), + NULL, 1, NULL, NULL, 0, NULL), /** operations **/ xs_html_tag("details", @@ -1809,7 +1813,8 @@ xs_html *html_entry_controls(snac *user, const char *actor, id, NULL, xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, - NULL, 0, att_files, att_alt_texts, is_draft(user, id))), + NULL, 0, att_files, att_alt_texts, is_draft(user, id), + xs_dict_get(msg, "published"))), xs_html_tag("p", NULL)); } @@ -1828,7 +1833,7 @@ xs_html *html_entry_controls(snac *user, const char *actor, NULL, NULL, xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, - id, 0, NULL, NULL, 0)), + id, 0, NULL, NULL, 0, NULL)), xs_html_tag("p", NULL)); } @@ -3165,7 +3170,7 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c NULL, actor_id, xs_stock(XSTYPE_FALSE), "", xs_stock(XSTYPE_FALSE), NULL, - NULL, 0, NULL, NULL, 0), + NULL, 0, NULL, NULL, 0, NULL), xs_html_tag("p", NULL)); xs_html_add(snac_post, snac_controls); @@ -4305,23 +4310,29 @@ int html_post_handler(const xs_dict *req, const char *q_path, msg = xs_dict_set(msg, "summary", xs_is_null(summary) ? "..." : summary); } - if (*post_date) { - /* scheduled post */ - xs *sched_date = xs_fmt("%sT%s:00", post_date, *post_time ? post_time : "12:00"); - time_t t = xs_parse_localtime(sched_date, "%Y-%m-%dT%H:%M:%S"); + if (xs_is_string(post_date) && *post_date) { + xs *local_pubdate = xs_fmt("%sT%s", post_date, + xs_is_string(post_time) && *post_time ? post_time : "00:00:00"); + + time_t t = xs_parse_iso_date(local_pubdate, 1); if (t != 0) { xs *iso_date = xs_str_iso_date(t); msg = xs_dict_set(msg, "published", iso_date); - snac_debug(&snac, 1, xs_fmt("Scheduled date: [%s]", iso_date)); - } - else { - snac_log(&snac, xs_fmt("Invalid scheduled date: [%s]", sched_date)); - post_date = ""; + snac_debug(&snac, 1, xs_fmt("Published date: [%s]", iso_date)); } + else + snac_log(&snac, xs_fmt("Invalid post date: [%s]", local_pubdate)); } + /* is the published date from the future? */ + int future_post = 0; + xs *right_now = xs_str_utctime(0, ISO_DATE_SPEC); + + if (strcmp(xs_dict_get(msg, "published"), right_now) > 0) + future_post = 1; + if (xs_is_null(edit_id)) { /* new message */ const char *id = xs_dict_get(msg, "id"); @@ -4329,6 +4340,10 @@ int html_post_handler(const xs_dict *req, const char *q_path, if (store_as_draft) { draft_add(&snac, id, msg); } + else + if (future_post) { + snac_log(&snac, xs_fmt("DUMMY scheduled post 1 %s", id)); + } else { c_msg = msg_create(&snac, msg); timeline_add(&snac, id, msg); @@ -4340,7 +4355,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, if (valid_status(object_get(edit_id, &p_msg))) { /* copy relevant fields from previous version */ - char *fields[] = { "id", "context", "url", "published", + char *fields[] = { "id", "context", "url", "to", "inReplyTo", NULL }; int n; @@ -4356,15 +4371,23 @@ int html_post_handler(const xs_dict *req, const char *q_path, if (is_draft(&snac, edit_id)) { /* message was previously a draft; it's a create activity */ - /* set the published field to now */ - xs *published = xs_str_utctime(0, ISO_DATE_SPEC); - msg = xs_dict_set(msg, "published", published); + /* if the date is from the past, overwrite it with right_now */ + if (strcmp(xs_dict_get(msg, "published"), right_now) < 0) { + snac_debug(&snac, 1, xs_fmt("setting draft ancient date to %s", right_now)); + msg = xs_dict_set(msg, "published", right_now); + } /* overwrite object */ object_add_ow(edit_id, msg); - c_msg = msg_create(&snac, msg); - timeline_add(&snac, edit_id, msg); + if (future_post) { + snac_log(&snac, xs_fmt("DUMMY scheduled post 2 %s", edit_id)); + } + else { + c_msg = msg_create(&snac, msg); + timeline_add(&snac, edit_id, msg); + } + draft_del(&snac, edit_id); } else { -- cgit v1.2.3 From 5090e4e77489d7e4e2d358c417c83be8f76307cb Mon Sep 17 00:00:00 2001 From: default Date: Tue, 1 Apr 2025 06:14:46 +0200 Subject: Added more scheduling code. --- data.c | 37 +++++++++++++++++++++++++++++++++++++ html.c | 44 ++++++++++++++++++++++++++++++++++++++++++-- snac.h | 5 +++++ 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/data.c b/data.c index a192830..8f68ee2 100644 --- a/data.c +++ b/data.c @@ -1929,6 +1929,43 @@ xs_list *draft_list(snac *user) } +/** scheduled posts **/ + +int is_scheduled(snac *user, const char *id) +/* returns true if this note is scheduled for future sending */ +{ + return object_user_cache_in(user, id, "sched"); +} + + +void schedule_del(snac *user, const char *id) +/* deletes an scheduled post */ +{ + object_user_cache_del(user, id, "sched"); +} + + +void schedule_add(snac *user, const char *id, const xs_dict *msg) +/* schedules this post for later */ +{ + /* delete from the index, in case it was already there */ + schedule_del(user, id); + + /* overwrite object */ + object_add_ow(id, msg); + + /* [re]add to the index */ + object_user_cache_add(user, id, "sched"); +} + + +xs_list *scheduled_list(snac *user) +/* return the list of scheduled posts */ +{ + return object_user_cache_list(user, "sched", XS_ALL, 1); +} + + /** hiding **/ xs_str *_hidden_fn(snac *snac, const char *id) diff --git a/html.c b/html.c index d4cbb35..bc8a645 100644 --- a/html.c +++ b/html.c @@ -2870,6 +2870,18 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, xs_html_text(L("drafts"))))); } + { + /* show the list of scheduled posts */ + xs *url = xs_fmt("%s/sched", user->actor); + xs_html_add(lol, + xs_html_tag("li", + xs_html_tag("a", + xs_html_attr("href", url), + xs_html_attr("class", "snac-list-link"), + xs_html_attr("title", L("Scheduled posts")), + xs_html_text(L("scheduled posts"))))); + } + /* the list of followed hashtags */ const char *followed_hashtags = xs_dict_get(user->config, "followed_hashtags"); @@ -3919,6 +3931,21 @@ int html_get_handler(const xs_dict *req, const char *q_path, } } else + if (strcmp(p_path, "sched") == 0) { /** list of scheduled posts **/ + if (!login(&snac, req)) { + *body = xs_dup(uid); + status = HTTP_STATUS_UNAUTHORIZED; + } + else { + xs *list = scheduled_list(&snac); + + *body = html_timeline(&snac, list, 0, skip, show, + 0, L("Scheduled posts"), "", 0, error); + *b_size = strlen(*body); + status = HTTP_STATUS_OK; + } + } + else if (xs_startswith(p_path, "list/")) { /** list timelines **/ if (!login(&snac, req)) { *body = xs_dup(uid); @@ -4342,7 +4369,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, } else if (future_post) { - snac_log(&snac, xs_fmt("DUMMY scheduled post 1 %s", id)); + schedule_add(&snac, id, msg); } else { c_msg = msg_create(&snac, msg); @@ -4381,7 +4408,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, object_add_ow(edit_id, msg); if (future_post) { - snac_log(&snac, xs_fmt("DUMMY scheduled post 2 %s", edit_id)); + schedule_add(&snac, edit_id, msg); } else { c_msg = msg_create(&snac, msg); @@ -4390,7 +4417,15 @@ int html_post_handler(const xs_dict *req, const char *q_path, draft_del(&snac, edit_id); } + else + if (is_scheduled(&snac, edit_id)) { + /* editing an scheduled post; just update it */ + schedule_add(&snac, edit_id, msg); + } else { + /* ignore the (possibly changed) published date */ + msg = xs_dict_set(msg, "published", xs_dict_get(p_msg, "published")); + /* set the updated field */ xs *updated = xs_str_utctime(0, ISO_DATE_SPEC); msg = xs_dict_set(msg, "updated", updated); @@ -4474,6 +4509,9 @@ int html_post_handler(const xs_dict *req, const char *q_path, if (strcmp(action, L("Hide")) == 0) { /** **/ if (is_draft(&snac, id)) draft_del(&snac, id); + else + if (is_scheduled(&snac, id)) + schedule_del(&snac, id); else hide(&snac, id); } @@ -4570,6 +4608,8 @@ int html_post_handler(const xs_dict *req, const char *q_path, draft_del(&snac, id); + schedule_del(&snac, id); + snac_log(&snac, xs_fmt("deleted entry %s", id)); } } diff --git a/snac.h b/snac.h index 142ebc1..7e44039 100644 --- a/snac.h +++ b/snac.h @@ -205,6 +205,11 @@ void draft_del(snac *user, const char *id); void draft_add(snac *user, const char *id, const xs_dict *msg); xs_list *draft_list(snac *user); +int is_scheduled(snac *user, const char *id); +void schedule_del(snac *user, const char *id); +void schedule_add(snac *user, const char *id, const xs_dict *msg); +xs_list *scheduled_list(snac *user); + int limited(snac *user, const char *id, int cmd); #define is_limited(user, id) limited((user), (id), 0) #define limit(user, id) limited((user), (id), 1) -- cgit v1.2.3 From 9b2d0381ba734102c20d2111f0a2b64a3c438ef7 Mon Sep 17 00:00:00 2001 From: default Date: Tue, 1 Apr 2025 06:32:53 +0200 Subject: More scheduled post code. --- activitypub.c | 2 ++ data.c | 29 ++++++++++++++++++++++++++++- snac.h | 3 ++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/activitypub.c b/activitypub.c index c00c371..4c22c25 100644 --- a/activitypub.c +++ b/activitypub.c @@ -2759,6 +2759,8 @@ int process_user_queue(snac *snac) cnt++; } + scheduled_process(snac); + return cnt; } diff --git a/data.c b/data.c index 8f68ee2..6661472 100644 --- a/data.c +++ b/data.c @@ -1966,6 +1966,33 @@ xs_list *scheduled_list(snac *user) } +void scheduled_process(snac *user) +/* processes the scheduled list, sending those ready to be sent */ +{ + xs *posts = scheduled_list(user); + const char *md5; + xs *right_now = xs_str_utctime(0, ISO_DATE_SPEC); + + xs_list_foreach(posts, md5) { + xs *msg = NULL; + + if (valid_status(object_get_by_md5(md5, &msg))) { + if (strcmp(xs_dict_get(msg, "published"), right_now) < 0) { + /* due date! */ + const char *id = xs_dict_get(msg, "id"); + + timeline_add(user, id, msg); + + xs *c_msg = msg_create(user, msg); + enqueue_message(user, c_msg); + + schedule_del(user, id); + } + } + } +} + + /** hiding **/ xs_str *_hidden_fn(snac *snac, const char *id) @@ -3734,7 +3761,7 @@ void purge_user(snac *snac) _purge_user_subdir(snac, "public", pub_days); const char *idxs[] = { "followers.idx", "private.idx", "public.idx", - "pinned.idx", "bookmark.idx", "draft.idx", NULL }; + "pinned.idx", "bookmark.idx", "draft.idx", "sched.idx", NULL }; for (n = 0; idxs[n]; n++) { xs *idx = xs_fmt("%s/%s", snac->basedir, idxs[n]); diff --git a/snac.h b/snac.h index 7e44039..0d2aafe 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.74" +#define VERSION "2.75-dev" #define USER_AGENT "snac/" VERSION @@ -209,6 +209,7 @@ int is_scheduled(snac *user, const char *id); void schedule_del(snac *user, const char *id); void schedule_add(snac *user, const char *id, const xs_dict *msg); xs_list *scheduled_list(snac *user); +void scheduled_process(snac *user); int limited(snac *user, const char *id, int cmd); #define is_limited(user, id) limited((user), (id), 0) -- cgit v1.2.3 From dde907f55a6c63f7d1bb8345f7850b51cd028a7b Mon Sep 17 00:00:00 2001 From: default Date: Tue, 1 Apr 2025 09:02:35 +0200 Subject: Only show date edition controls if it's new, drafted or scheduled. --- html.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/html.c b/html.c index bc8a645..008c05b 100644 --- a/html.c +++ b/html.c @@ -453,20 +453,22 @@ xs_html *html_note(snac *user, const char *summary, } } - xs_html_add(form, - xs_html_tag("p", - xs_html_text(L("Post date and time (empty, right now; in the future, schedule for later):")), - xs_html_sctag("br", NULL), - xs_html_sctag("input", - xs_html_attr("type", "date"), - xs_html_attr("value", post_date ? post_date : ""), - xs_html_attr("name", "post_date")), - xs_html_text(" "), - xs_html_sctag("input", - xs_html_attr("type", "time"), - xs_html_attr("value", post_time ? post_time : ""), - xs_html_attr("step", "1"), - xs_html_attr("name", "post_time")))); + if (edit_id == NULL || is_draft || is_scheduled(user, edit_id)) { + xs_html_add(form, + xs_html_tag("p", + xs_html_text(L("Post date and time (empty, right now; in the future, schedule for later):")), + xs_html_sctag("br", NULL), + xs_html_sctag("input", + xs_html_attr("type", "date"), + xs_html_attr("value", post_date ? post_date : ""), + xs_html_attr("name", "post_date")), + xs_html_text(" "), + xs_html_sctag("input", + xs_html_attr("type", "time"), + xs_html_attr("value", post_time ? post_time : ""), + xs_html_attr("step", "1"), + xs_html_attr("name", "post_time")))); + } if (edit_id) xs_html_add(form, -- cgit v1.2.3 From 570a8e3d5ddad26ce2a348877e3d8d5f32f6f748 Mon Sep 17 00:00:00 2001 From: shtrophic Date: Tue, 1 Apr 2025 11:33:54 +0200 Subject: convert snac-admin to bash --- examples/snac-admin | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/examples/snac-admin b/examples/snac-admin index 971618b..e51e28b 100644 --- a/examples/snac-admin +++ b/examples/snac-admin @@ -1,43 +1,51 @@ -#!/usr/bin/env fish +#!/usr/bin/env bash ## ## SNAC-ADMIN ## a simple script that is supposed to improve ## a snac admin's life, especially when snac ## is being run as a systemd.unit with ## DynamicUser=yes enabled. -## Please make sure to adjust SNAC_BASEDIR +## Please make sure to adjust SNAC_DIR ## down below according to your setup. ## ## USAGE ## snac-admin state ## snac-admin adduser rikkert +## snac-admin block example.org +## snac-admin verify_links lisa +## ... ## ## Author: @chris@social.shtrophic.net ## ## Released into the public domain ## -set -l SNAC_PID $(pidof snac) -set -l SNAC_BASEDIR /var/lib/snac +set -e -if test -z $SNAC_PID - echo "no such process" 1>&2 +SNAC_PID=$(pidof snac) +SNAC_DIR=/var/lib/snac + +SNAC_VERB=$1 +shift + +if [ -z $SNAC_PID ]; then + echo "no such process" >&2 exit 1 -end +fi -if test $(id -u) -ne 0 - echo "not root" 1>&2 +if [ $(id -u) -ne 0 ]; then + echo "not root" >&2 exit 1 -end +fi -if ! test -d $SNAC_BASEDIR - echo "$SNAC_BASEDIR does not exist" 1>&2 +if [ ! -d $SNAC_DIR ]; then + echo "$SNAC_DIR is not a directory" >&2 exit 1 -end +fi -if test -z $argv[1] - echo "no arguments" 1>&2 +if [ -z $SNAC_VERB ]; then + echo "no arguments" >&2 exit 1 -end +fi -nsenter -ae -S follow -G follow -t $SNAC_PID -- snac $argv[1] $SNAC_BASEDIR $argv[2..] +nsenter -ae -S follow -G follow -t $SNAC_PID -- snac $SNAC_VERB $SNAC_DIR $@ -- cgit v1.2.3 From 2049ed7f532c52a107a50d83053635d02fd45dd4 Mon Sep 17 00:00:00 2001 From: default Date: Tue, 1 Apr 2025 19:02:21 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3463b38..6915ffc 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,8 @@ ## UNRELEASED +Added support for scheduled posts. + Mastodon API: added support for `/api/v1/instance/peers`. Some Czech and Russian translation fixes. -- cgit v1.2.3 From 877434218ffca350eb918fd47dfe81f29f2605ae Mon Sep 17 00:00:00 2001 From: default Date: Wed, 2 Apr 2025 09:01:31 +0200 Subject: mastoapi: added support for scheduled posts. --- mastoapi.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/mastoapi.c b/mastoapi.c index 7b1e0ad..d93afc5 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -2726,14 +2726,24 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, msg = xs_dict_set(msg, "summary", summary); } - /* store */ - timeline_add(&snac, xs_dict_get(msg, "id"), msg); + /* scheduled? */ + const char *scheduled_at = xs_dict_get(args, "scheduled_at"); - /* 'Create' message */ - xs *c_msg = msg_create(&snac, msg); - enqueue_message(&snac, c_msg); + if (xs_is_string(scheduled_at) && *scheduled_at) { + msg = xs_dict_set(msg, "published", scheduled_at); - timeline_touch(&snac); + schedule_add(&snac, xs_dict_get(msg, "id"), msg); + } + else { + /* store */ + timeline_add(&snac, xs_dict_get(msg, "id"), msg); + + /* 'Create' message */ + xs *c_msg = msg_create(&snac, msg); + enqueue_message(&snac, c_msg); + + timeline_touch(&snac); + } /* convert to a mastodon status as a response code */ xs *st = mastoapi_status(&snac, msg); -- cgit v1.2.3 From 3cec0d3ebeeef595f509c52e6c0ac3682ba67e6f Mon Sep 17 00:00:00 2001 From: default Date: Fri, 4 Apr 2025 08:07:56 +0200 Subject: Vote objects no longer have a content of "". --- html.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/html.c b/html.c index 008c05b..25a2e90 100644 --- a/html.c +++ b/html.c @@ -4824,6 +4824,9 @@ int html_post_handler(const xs_dict *req, const char *q_path, /* set the option */ msg = xs_dict_append(msg, "name", v); + /* delete the content */ + msg = xs_dict_del(msg, "content"); + xs *c_msg = msg_create(&snac, msg); enqueue_message(&snac, c_msg); -- cgit v1.2.3 From 06cc93a5dbda3432c153f745decb4b697a5d2a91 Mon Sep 17 00:00:00 2001 From: default Date: Fri, 4 Apr 2025 08:10:06 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6915ffc..4ca15e4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,8 @@ Added support for scheduled posts. +Fixed incorrect poll vote format, which was causing problems in platforms like GotoSocial. + Mastodon API: added support for `/api/v1/instance/peers`. Some Czech and Russian translation fixes. -- cgit v1.2.3 From bb5a2445dac94a4d90b7d455b7723416a3df13ba Mon Sep 17 00:00:00 2001 From: default Date: Fri, 4 Apr 2025 16:48:11 +0200 Subject: Added a (CSS hidden) hr tag after each post. --- html.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/html.c b/html.c index 25a2e90..eff55e7 100644 --- a/html.c +++ b/html.c @@ -2731,6 +2731,11 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, } } + /* add an invisible hr, to help differentiate between posts in text browsers */ + xs_html_add(entry_top, + xs_html_sctag("hr", + xs_html_attr("style", "display: none"))); + return entry_top; } -- cgit v1.2.3 From 9f6d34eddadafbd79bc608c1cb650d9c90b5a0d3 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 5 Apr 2025 13:51:03 +0200 Subject: Use instead of 'display: none' style. --- html.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html.c b/html.c index eff55e7..5ede8f9 100644 --- a/html.c +++ b/html.c @@ -2734,7 +2734,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, /* add an invisible hr, to help differentiate between posts in text browsers */ xs_html_add(entry_top, xs_html_sctag("hr", - xs_html_attr("style", "display: none"))); + xs_html_attr("hidden", NULL))); return entry_top; } -- cgit v1.2.3 From 7f7d42c52db934da6af8d3cdd7c03dd95a08cbaf Mon Sep 17 00:00:00 2001 From: default Date: Sun, 6 Apr 2025 07:44:12 +0200 Subject: Post attachments now have random names. --- html.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/html.c b/html.c index 5ede8f9..9ff5625 100644 --- a/html.c +++ b/html.c @@ -14,6 +14,7 @@ #include "xs_curl.h" #include "xs_unicode.h" #include "xs_url.h" +#include "xs_random.h" #include "snac.h" @@ -4286,9 +4287,12 @@ int html_post_handler(const xs_dict *req, const char *q_path, const char *fn = xs_list_get(attach_file, 0); if (xs_is_string(fn) && *fn != '\0') { + char rnd[32]; + xs_rnd_buf(rnd, sizeof(rnd)); + char *ext = strrchr(fn, '.'); - xs *hash = xs_md5_hex(fn, strlen(fn)); - xs *id = xs_fmt("%s%s", hash, ext); + xs *hash = xs_md5_hex(rnd, strlen(rnd)); + xs *id = xs_fmt("p-%s%s", hash, ext ? ext : ""); xs *url = xs_fmt("%s/s/%s", snac.actor, id); int fo = xs_number_get(xs_list_get(attach_file, 1)); int fs = xs_number_get(xs_list_get(attach_file, 2)); -- cgit v1.2.3 From 6f884e0d5d4eb9be41764c5ae16da52fed997e88 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 6 Apr 2025 07:50:51 +0200 Subject: The avatar and header images have a prefix in their names. --- html.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html.c b/html.c index 9ff5625..a598038 100644 --- a/html.c +++ b/html.c @@ -4290,9 +4290,9 @@ int html_post_handler(const xs_dict *req, const char *q_path, char rnd[32]; xs_rnd_buf(rnd, sizeof(rnd)); - char *ext = strrchr(fn, '.'); + const char *ext = strrchr(fn, '.'); xs *hash = xs_md5_hex(rnd, strlen(rnd)); - xs *id = xs_fmt("p-%s%s", hash, ext ? ext : ""); + xs *id = xs_fmt("post-%s%s", hash, ext ? ext : ""); xs *url = xs_fmt("%s/s/%s", snac.actor, id); int fo = xs_number_get(xs_list_get(attach_file, 1)); int fs = xs_number_get(xs_list_get(attach_file, 2)); @@ -4764,7 +4764,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, if (xs_startswith(mimetype, "image/")) { const char *ext = strrchr(fn, '.'); xs *hash = xs_md5_hex(fn, strlen(fn)); - xs *id = xs_fmt("%s%s", hash, ext); + xs *id = xs_fmt("%s-%s%s", uploads[n], hash, ext ? ext : ""); xs *url = xs_fmt("%s/s/%s", snac.actor, id); int fo = xs_number_get(xs_list_get(uploaded_file, 1)); int fs = xs_number_get(xs_list_get(uploaded_file, 2)); -- cgit v1.2.3