summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELEASE_NOTES.md6
-rw-r--r--TODO.md4
-rw-r--r--data.c20
-rw-r--r--doc/snac.112
-rw-r--r--html.c2
-rw-r--r--main.c111
-rw-r--r--mastoapi.c62
-rw-r--r--po/cs.po12
-rw-r--r--po/es.po1
-rw-r--r--po/es_AR.po1
-rw-r--r--po/es_UY.po1
-rw-r--r--po/fr.po96
-rw-r--r--po/pt_BR.po19
-rw-r--r--po/ru.po12
14 files changed, 274 insertions, 85 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 13cdfb0..b4a2a92 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,11 @@
1# Release Notes 1# Release Notes
2 2
3## UNRELEASED
4
5Added new command-line options for list maintenance.
6
7Mastodon API: added `/api/v1/accounts/.../lists` endpoint (contributed by dandelions).
8
3## 2.75 "Time Is On My Side" 9## 2.75 "Time Is On My Side"
4 10
5Added support for scheduled posts (for this to work correctly, users will have to set their time zone, see below). 11Added support for scheduled posts (for this to work correctly, users will have to set their time zone, see below).
diff --git a/TODO.md b/TODO.md
index db135d5..70770db 100644
--- a/TODO.md
+++ b/TODO.md
@@ -2,6 +2,8 @@
2 2
3## Open 3## Open
4 4
5It seems that Microsoft is planning to laminate Basic HTTP Auth, so make a plan, see https://codeberg.org/grunfink/snac2/issues/350
6
5Investigate the problem with boosts inside the same instance (see https://codeberg.org/grunfink/snac2/issues/214). 7Investigate the problem with boosts inside the same instance (see https://codeberg.org/grunfink/snac2/issues/214).
6 8
7Editing / Updating a post does not index newly added hashtags. 9Editing / Updating a post does not index newly added hashtags.
@@ -369,3 +371,5 @@ Each notification should show a link to the full thread, to see it in context (2
369Add a list of hashtags to drop (2025-03-23T15:45:30+0100). 371Add a list of hashtags to drop (2025-03-23T15:45:30+0100).
370 372
371The 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). 373The 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).
374
375Add command-line tools for creating and manipulating lists (2025-04-18T10:04:41+0200).
diff --git a/data.c b/data.c
index 4a19680..7d33f77 100644
--- a/data.c
+++ b/data.c
@@ -2265,7 +2265,8 @@ xs_val *list_maint(snac *user, const char *list, int op)
2265 xs *l2 = xs_split(v2, "/"); 2265 xs *l2 = xs_split(v2, "/");
2266 2266
2267 /* return [ list_id, list_title ] */ 2267 /* return [ list_id, list_title ] */
2268 l = xs_list_append(l, xs_list_append(xs_list_new(), xs_list_get(l2, -1), title)); 2268 xs *tmp_list = xs_list_append(xs_list_new(), xs_list_get(l2, -1), title);
2269 l = xs_list_append(l, tmp_list);
2269 } 2270 }
2270 } 2271 }
2271 } 2272 }
@@ -2340,6 +2341,19 @@ xs_val *list_maint(snac *user, const char *list, int op)
2340 } 2341 }
2341 2342
2342 break; 2343 break;
2344
2345 case 4: /** find list id by name **/
2346 if (xs_is_string(list)) {
2347 xs *lol = list_maint(user, NULL, 0);
2348 const xs_list *li;
2349
2350 xs_list_foreach(lol, li) {
2351 if (strcmp(list, xs_list_get(li, 1)) == 0) {
2352 l = xs_dup(xs_list_get(li, 0));
2353 break;
2354 }
2355 }
2356 }
2343 } 2357 }
2344 2358
2345 return l; 2359 return l;
@@ -2391,7 +2405,7 @@ xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op
2391 break; 2405 break;
2392 2406
2393 case 1: /** append actor to list **/ 2407 case 1: /** append actor to list **/
2394 if (actor_md5 != NULL) { 2408 if (xs_is_string(actor_md5) && xs_is_hex(actor_md5)) {
2395 if (!index_in_md5(fn, actor_md5)) 2409 if (!index_in_md5(fn, actor_md5))
2396 index_add_md5(fn, actor_md5); 2410 index_add_md5(fn, actor_md5);
2397 } 2411 }
@@ -2399,7 +2413,7 @@ xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op
2399 break; 2413 break;
2400 2414
2401 case 2: /** delete actor from list **/ 2415 case 2: /** delete actor from list **/
2402 if (actor_md5 != NULL) 2416 if (xs_is_string(actor_md5) && xs_is_hex(actor_md5))
2403 index_del_md5(fn, actor_md5); 2417 index_del_md5(fn, actor_md5);
2404 2418
2405 break; 2419 break;
diff --git a/doc/snac.1 b/doc/snac.1
index 2f1ccfe..92e30b7 100644
--- a/doc/snac.1
+++ b/doc/snac.1
@@ -350,6 +350,18 @@ Imports a Mastodon list of accounts to be blocked in CSV format. The
350file must be stored inside the 350file must be stored inside the
351.Pa import/ 351.Pa import/
352subdirectory of a user's directory inside the server base directory. 352subdirectory of a user's directory inside the server base directory.
353.It Cm lists Ar basedir Ar uid
354Prints the name of the user created lists.
355.It Cm list_members Ar basedir Ar uid Ar name
356Prints the list of actors in the named list.
357.It Cm create_list Ar basedir Ar uid Ar name
358Creates a new list.
359.It Cm delete_list Ar basedir Ar uid Ar name
360Deletes an existing list.
361.It Cm list_add Ar basedir Ar uid Ar name Ar account
362Adds an account (by its @name@host handle or actor URL) to a list.
363.It Cm list_del Ar basedir Ar uid Ar name Ar actor_url
364Deletes an actor (by its actor URL) from a list.
353.El 365.El
354.Ss Migrating an account to/from Mastodon 366.Ss Migrating an account to/from Mastodon
355See 367See
diff --git a/html.c b/html.c
index d807f4b..e3a955b 100644
--- a/html.c
+++ b/html.c
@@ -189,7 +189,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
189 xs_html_attr("loading", "lazy"), 189 xs_html_attr("loading", "lazy"),
190 xs_html_attr("class", "snac-avatar"), 190 xs_html_attr("class", "snac-avatar"),
191 xs_html_attr("src", avatar), 191 xs_html_attr("src", avatar),
192 xs_html_attr("alt", "")), 192 xs_html_attr("alt", "[?]")),
193 xs_html_tag("a", 193 xs_html_tag("a",
194 xs_html_attr("href", href), 194 xs_html_attr("href", href),
195 xs_html_attr("class", "p-author h-card snac-author"), 195 xs_html_attr("class", "p-author h-card snac-author"),
diff --git a/main.c b/main.c
index 3cd4524..a6fb6fd 100644
--- a/main.c
+++ b/main.c
@@ -59,6 +59,12 @@ int usage(void)
59 printf("import_csv {basedir} {uid} Imports data from CSV files\n"); 59 printf("import_csv {basedir} {uid} Imports data from CSV files\n");
60 printf("import_list {basedir} {uid} {file} Imports a Mastodon CSV list file\n"); 60 printf("import_list {basedir} {uid} {file} Imports a Mastodon CSV list file\n");
61 printf("import_block_list {basedir} {uid} {file} Imports a Mastodon CSV block list file\n"); 61 printf("import_block_list {basedir} {uid} {file} Imports a Mastodon CSV block list file\n");
62 printf("lists {basedir} {uid} Returns the names of the lists created by the user\n");
63 printf("list_members {basedir} {uid} {name} Returns the list of accounts inside a list\n");
64 printf("create_list {basedir} {uid} {name} Creates a new list\n");
65 printf("delete_list {basedir} {uid} {name} Deletes an existing list\n");
66 printf("list_add {basedir} {uid} {name} {acct} Adds an account (@user@host or actor url) to a list\n");
67 printf("list_del {basedir} {uid} {name} {actor} Deletes an actor URL from a list\n");
62 68
63 return 1; 69 return 1;
64} 70}
@@ -282,9 +288,114 @@ int main(int argc, char *argv[])
282 return migrate_account(&snac); 288 return migrate_account(&snac);
283 } 289 }
284 290
291 if (strcmp(cmd, "lists") == 0) { /** **/
292 xs *lol = list_maint(&snac, NULL, 0);
293 const xs_list *l;
294
295 xs_list_foreach(lol, l) {
296 printf("%s (%s)\n", xs_list_get(l, 1), xs_list_get(l, 0));
297 }
298
299 return 0;
300 }
301
285 if ((url = GET_ARGV()) == NULL) 302 if ((url = GET_ARGV()) == NULL)
286 return usage(); 303 return usage();
287 304
305 if (strcmp(cmd, "list_members") == 0) { /** **/
306 xs *lid = list_maint(&snac, url, 4);
307
308 if (lid != NULL) {
309 xs *lcont = list_content(&snac, lid, NULL, 0);
310 const char *md5;
311
312 xs_list_foreach(lcont, md5) {
313 xs *actor = NULL;
314
315 if (valid_status(object_get_by_md5(md5, &actor))) {
316 printf("%s (%s)\n", xs_dict_get(actor, "id"), xs_dict_get_def(actor, "preferredUsername", ""));
317 }
318 }
319 }
320 else
321 fprintf(stderr, "Cannot find a list named '%s'\n", url);
322
323 return 0;
324 }
325
326 if (strcmp(cmd, "create_list") == 0) { /** **/
327 xs *lid = list_maint(&snac, url, 4);
328
329 if (lid == NULL) {
330 xs *n_lid = list_maint(&snac, url, 1);
331 printf("New list named '%s' created (%s)\n", url, n_lid);
332 }
333 else
334 fprintf(stderr, "A list named '%s' already exist\n", url);
335
336 return 0;
337 }
338
339 if (strcmp(cmd, "delete_list") == 0) { /** **/
340 xs *lid = list_maint(&snac, url, 4);
341
342 if (lid != NULL) {
343 list_maint(&snac, lid, 2);
344 printf("List '%s' (%s) deleted\n", url, lid);
345 }
346 else
347 fprintf(stderr, "Cannot find a list named '%s'\n", url);
348
349 return 0;
350 }
351
352 if (strcmp(cmd, "list_add") == 0) { /** **/
353 const char *account = GET_ARGV();
354
355 if (account != NULL) {
356 xs *lid = list_maint(&snac, url, 4);
357
358 if (lid != NULL) {
359 xs *actor = NULL;
360 xs *uid = NULL;
361
362 if (valid_status(webfinger_request(account, &actor, &uid))) {
363 xs *md5 = xs_md5_hex(actor, strlen(actor));
364
365 list_content(&snac, lid, md5, 1);
366 printf("Actor %s (%s) added to list '%s' (%s)\n", actor, uid, url, lid);
367 }
368 else
369 fprintf(stderr, "Cannot resolve account '%s'\n", account);
370 }
371 else
372 fprintf(stderr, "Cannot find a list named '%s'\n", url);
373
374 }
375
376 return 0;
377 }
378
379 if (strcmp(cmd, "list_del") == 0) { /** **/
380 const char *account = GET_ARGV();
381
382 if (account != NULL) {
383 xs *lid = list_maint(&snac, url, 4);
384
385 if (lid != NULL) {
386 xs *md5 = xs_md5_hex(account, strlen(account));
387
388 list_content(&snac, lid, md5, 2);
389 printf("Actor %s deleted from list '%s' (%s)\n", account, url, lid);
390 }
391 else
392 fprintf(stderr, "Cannot find a list named '%s'\n", url);
393
394 }
395
396 return 0;
397 }
398
288 if (strcmp(cmd, "alias") == 0) { /** **/ 399 if (strcmp(cmd, "alias") == 0) { /** **/
289 xs *actor = NULL; 400 xs *actor = NULL;
290 xs *uid = NULL; 401 xs *uid = NULL;
diff --git a/mastoapi.c b/mastoapi.c
index d93afc5..705a902 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -1500,6 +1500,44 @@ xs_str *timeline_link_header(const char *endpoint, xs_list *timeline)
1500} 1500}
1501 1501
1502 1502
1503xs_list *mastoapi_account_lists(snac *user, const char *uid)
1504/* returns the list of list an user is in */
1505{
1506 xs_list *out = xs_list_new();
1507 xs *actor_md5 = NULL;
1508 xs *lol = list_maint(user, NULL, 0);
1509
1510 if (uid) {
1511 if (!xs_is_hex(uid))
1512 actor_md5 = xs_md5_hex(uid, strlen(uid));
1513 else
1514 actor_md5 = xs_dup(uid);
1515 }
1516
1517 const xs_list *li;
1518 xs_list_foreach(lol, li) {
1519 const char *list_id = xs_list_get(li, 0);
1520 const char *list_title = xs_list_get(li, 1);
1521 if (uid) {
1522 xs *users = list_content(user, list_id, NULL, 0);
1523 if (xs_list_in(users, actor_md5) == -1)
1524 continue;
1525 }
1526
1527 xs *d = xs_dict_new();
1528
1529 d = xs_dict_append(d, "id", list_id);
1530 d = xs_dict_append(d, "title", list_title);
1531 d = xs_dict_append(d, "replies_policy", "list");
1532 d = xs_dict_append(d, "exclusive", xs_stock(XSTYPE_FALSE));
1533
1534 out = xs_list_append(out, d);
1535 }
1536
1537 return out;
1538}
1539
1540
1503int mastoapi_get_handler(const xs_dict *req, const char *q_path, 1541int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1504 char **body, int *b_size, char **ctype, xs_str **link) 1542 char **body, int *b_size, char **ctype, xs_str **link)
1505{ 1543{
@@ -1723,6 +1761,10 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1723 if (strcmp(opt, "followers") == 0) { 1761 if (strcmp(opt, "followers") == 0) {
1724 out = xs_list_new(); 1762 out = xs_list_new();
1725 } 1763 }
1764 else
1765 if (strcmp(opt, "lists") == 0) {
1766 out = mastoapi_account_lists(&snac1, uid);
1767 }
1726 1768
1727 user_free(&snac2); 1769 user_free(&snac2);
1728 } 1770 }
@@ -1744,6 +1786,10 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1744 /* implement empty response so apps like Tokodon don't show an error */ 1786 /* implement empty response so apps like Tokodon don't show an error */
1745 out = xs_list_new(); 1787 out = xs_list_new();
1746 } 1788 }
1789 else
1790 if (strcmp(opt, "lists") == 0) {
1791 out = mastoapi_account_lists(&snac1, uid);
1792 }
1747 } 1793 }
1748 } 1794 }
1749 1795
@@ -1975,21 +2021,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1975 else 2021 else
1976 if (strcmp(cmd, "/v1/lists") == 0) { /** list of lists **/ 2022 if (strcmp(cmd, "/v1/lists") == 0) { /** list of lists **/
1977 if (logged_in) { 2023 if (logged_in) {
1978 xs *lol = list_maint(&snac1, NULL, 0); 2024 xs *l = mastoapi_account_lists(&snac1, NULL);
1979 xs *l = xs_list_new();
1980 int c = 0;
1981 const xs_list *li;
1982
1983 while (xs_list_next(lol, &li, &c)) {
1984 xs *d = xs_dict_new();
1985
1986 d = xs_dict_append(d, "id", xs_list_get(li, 0));
1987 d = xs_dict_append(d, "title", xs_list_get(li, 1));
1988 d = xs_dict_append(d, "replies_policy", "list");
1989 d = xs_dict_append(d, "exclusive", xs_stock(XSTYPE_FALSE));
1990
1991 l = xs_list_append(l, d);
1992 }
1993 2025
1994 *body = xs_json_dumps(l, 4); 2026 *body = xs_json_dumps(l, 4);
1995 *ctype = "application/json"; 2027 *ctype = "application/json";
diff --git a/po/cs.po b/po/cs.po
index 759342b..627c079 100644
--- a/po/cs.po
+++ b/po/cs.po
@@ -738,24 +738,24 @@ msgstr "rozepsané"
738 738
739#: html.c:464 739#: html.c:464
740msgid "Scheduled post..." 740msgid "Scheduled post..."
741msgstr "" 741msgstr "Naplánovat příspěvek..."
742 742
743msgid "Post date and time:" 743msgid "Post date and time:"
744msgstr "" 744msgstr "Den a čas:"
745 745
746#: html.c:2927 html.c:3989 746#: html.c:2927 html.c:3989
747msgid "Scheduled posts" 747msgid "Scheduled posts"
748msgstr "" 748msgstr "Naplánované příspěvky"
749 749
750#: html.c:2928 750#: html.c:2928
751msgid "scheduled posts" 751msgid "scheduled posts"
752msgstr "" 752msgstr "naplánované příspěvky"
753 753
754#: html.c:458 754#: html.c:458
755#, c-format 755#, c-format
756msgid "Post date and time (timezone: %s):" 756msgid "Post date and time (timezone: %s):"
757msgstr "" 757msgstr "Den a čas (časové pásmo: %s)"
758 758
759#: html.c:1538 759#: html.c:1538
760msgid "Time zone:" 760msgid "Time zone:"
761msgstr "" 761msgstr "Časové pásmo:"
diff --git a/po/es.po b/po/es.po
index 875ead5..41870c0 100644
--- a/po/es.po
+++ b/po/es.po
@@ -708,6 +708,7 @@ msgstr ""
708"Opción 1...\n" 708"Opción 1...\n"
709"Opción 2...\n" 709"Opción 2...\n"
710"Opción 3...\n" 710"Opción 3...\n"
711"..."
711 712
712#: html.c:1415 713#: html.c:1415
713msgid "Bot API key" 714msgid "Bot API key"
diff --git a/po/es_AR.po b/po/es_AR.po
index 06e970f..33d1955 100644
--- a/po/es_AR.po
+++ b/po/es_AR.po
@@ -708,6 +708,7 @@ msgstr ""
708"Opción 1...\n" 708"Opción 1...\n"
709"Opción 2...\n" 709"Opción 2...\n"
710"Opción 3...\n" 710"Opción 3...\n"
711"..."
711 712
712#: html.c:1415 713#: html.c:1415
713msgid "Bot API key" 714msgid "Bot API key"
diff --git a/po/es_UY.po b/po/es_UY.po
index a24d834..4fc28a8 100644
--- a/po/es_UY.po
+++ b/po/es_UY.po
@@ -708,6 +708,7 @@ msgstr ""
708"Opción 1...\n" 708"Opción 1...\n"
709"Opción 2...\n" 709"Opción 2...\n"
710"Opción 3...\n" 710"Opción 3...\n"
711"..."
711 712
712#: html.c:1415 713#: html.c:1415
713msgid "Bot API key" 714msgid "Bot API key"
diff --git a/po/fr.po b/po/fr.po
index e049ab7..c9727f8 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -42,11 +42,11 @@ msgstr "Fichier :"
42 42
43#: html.c:521 43#: html.c:521
44msgid "Clear this field to delete the attachment" 44msgid "Clear this field to delete the attachment"
45msgstr "Nettoyer ce champs pour supprimer l'attachement" 45msgstr "Nettoyer ce champs pour supprimer la pièce jointe"
46 46
47#: html.c:530 html.c:555 47#: html.c:530 html.c:555
48msgid "Attachment description" 48msgid "Attachment description"
49msgstr "Description de l'attachement" 49msgstr "Description de la pièce jointe"
50 50
51#: html.c:566 51#: html.c:566
52msgid "Poll..." 52msgid "Poll..."
@@ -86,16 +86,16 @@ msgstr "Description du site"
86 86
87#: html.c:719 87#: html.c:719
88msgid "Admin email" 88msgid "Admin email"
89msgstr "email de l'admin" 89msgstr "Email de l'admin"
90 90
91#: html.c:732 91#: html.c:732
92msgid "Admin account" 92msgid "Admin account"
93msgstr "compte de l'admin" 93msgstr "Compte de l'admin"
94 94
95#: html.c:800 html.c:1136 95#: html.c:800 html.c:1136
96#, c-format 96#, c-format
97msgid "%d following, %d followers" 97msgid "%d following, %d followers"
98msgstr "Suit %d, %d suiveurs" 98msgstr "%d abonnements, %d personnes abonnées"
99 99
100#: html.c:890 100#: html.c:890
101msgid "RSS" 101msgid "RSS"
@@ -127,7 +127,7 @@ msgid ""
127"#tag" 127"#tag"
128msgstr "" 128msgstr ""
129"Chercher les messages par URL ou contenu (expression régulière), comptes " 129"Chercher les messages par URL ou contenu (expression régulière), comptes "
130"@utilisateur@hôte, ou #tag" 130"@pseudo@hôte, ou #tag"
131 131
132#: html.c:946 132#: html.c:946
133msgid "Content search" 133msgid "Content search"
@@ -159,11 +159,11 @@ msgstr "Suivre"
159 159
160#: html.c:1189 160#: html.c:1189
161msgid "(by URL or user@host)" 161msgid "(by URL or user@host)"
162msgstr "(par URL ou utilisateur@hôte)" 162msgstr "(par URL ou pseudo@hôte)"
163 163
164#: html.c:1204 html.c:1764 html.c:4527 164#: html.c:1204 html.c:1764 html.c:4527
165msgid "Boost" 165msgid "Boost"
166msgstr "repartager" 166msgstr "Repartager"
167 167
168#: html.c:1206 html.c:1223 168#: html.c:1206 html.c:1223
169msgid "(by URL)" 169msgid "(by URL)"
@@ -175,7 +175,7 @@ msgstr "Aime"
175 175
176#: html.c:1347 176#: html.c:1347
177msgid "User Settings..." 177msgid "User Settings..."
178msgstr "Réglages utilisateur…" 178msgstr "Paramètre du compte…"
179 179
180#: html.c:1356 180#: html.c:1356
181msgid "Display name:" 181msgid "Display name:"
@@ -203,7 +203,7 @@ msgstr "Supprimer l'image d'entête actuelle"
203 203
204#: html.c:1384 204#: html.c:1384
205msgid "Bio:" 205msgid "Bio:"
206msgstr "CV :" 206msgstr "Description :"
207 207
208#: html.c:1390 208#: html.c:1390
209msgid "Write about yourself here..." 209msgid "Write about yourself here..."
@@ -256,7 +256,7 @@ msgstr "Les demande de suivi doivent être approuvées"
256 256
257#: html.c:1506 257#: html.c:1506
258msgid "Publish follower and following metrics" 258msgid "Publish follower and following metrics"
259msgstr "Publier les suiveurs et les statistiques de suivis" 259msgstr "Publier les statistiques d'abonnements"
260 260
261#: html.c:1508 261#: html.c:1508
262msgid "Current location:" 262msgid "Current location:"
@@ -280,7 +280,7 @@ msgstr "Répétez le nouveau mot de passe :"
280 280
281#: html.c:1560 281#: html.c:1560
282msgid "Update user info" 282msgid "Update user info"
283msgstr "Mettre à jour les infos utilisateur" 283msgstr "Mettre à jour les infos du compte"
284 284
285#: html.c:1571 285#: html.c:1571
286msgid "Followed hashtags..." 286msgid "Followed hashtags..."
@@ -324,7 +324,7 @@ msgstr "Épingler ce message en haut de votre chronologie"
324 324
325#: html.c:1764 325#: html.c:1764
326msgid "Announce this post to your followers" 326msgid "Announce this post to your followers"
327msgstr "Annoncer ce message à vos suiveurs" 327msgstr "Annoncer ce message aux personnes abonnées"
328 328
329#: html.c:1769 html.c:4544 329#: html.c:1769 html.c:4544
330msgid "Unboost" 330msgid "Unboost"
@@ -356,11 +356,11 @@ msgstr "Ne plus suivre"
356 356
357#: html.c:1784 html.c:3180 357#: html.c:1784 html.c:3180
358msgid "Stop following this user's activity" 358msgid "Stop following this user's activity"
359msgstr "Arrêter de suivre les activités de cet utilisateur" 359msgstr "Arrêter de suivre les activités de cette personne"
360 360
361#: html.c:1788 html.c:3194 361#: html.c:1788 html.c:3194
362msgid "Start following this user's activity" 362msgid "Start following this user's activity"
363msgstr "Commencer à suivre les activité de cet utilisateur" 363msgstr "Commencer à suivre les activité de cette personne"
364 364
365#: html.c:1794 html.c:4621 365#: html.c:1794 html.c:4621
366msgid "Unfollow Group" 366msgid "Unfollow Group"
@@ -380,11 +380,11 @@ msgstr "Commencer à suivre ce groupe ou canal"
380 380
381#: html.c:1805 html.c:3216 html.c:4552 381#: html.c:1805 html.c:3216 html.c:4552
382msgid "MUTE" 382msgid "MUTE"
383msgstr "TAIRE" 383msgstr "SOURDINE"
384 384
385#: html.c:1806 385#: html.c:1806
386msgid "Block any activity from this user forever" 386msgid "Block any activity from this user forever"
387msgstr "Bloquer toute activité de cet utilisateur à jamais" 387msgstr "Bloquer toute activité de cette personne à jamais"
388 388
389#: html.c:1811 html.c:3198 html.c:4638 389#: html.c:1811 html.c:3198 html.c:4638
390msgid "Delete" 390msgid "Delete"
@@ -404,7 +404,7 @@ msgstr "Cacher ce message et ses réponses"
404 404
405#: html.c:1845 405#: html.c:1845
406msgid "Edit..." 406msgid "Edit..."
407msgstr "Éditer…" 407msgstr "Modifier…"
408 408
409#: html.c:1865 409#: html.c:1865
410msgid "Reply..." 410msgid "Reply..."
@@ -424,7 +424,7 @@ msgstr "Épinglé"
424 424
425#: html.c:1996 425#: html.c:1996
426msgid "Bookmarked" 426msgid "Bookmarked"
427msgstr "Ajouté au signets" 427msgstr "Ajouté aux signets"
428 428
429#: html.c:2004 429#: html.c:2004
430msgid "Poll" 430msgid "Poll"
@@ -472,7 +472,7 @@ msgstr "Audio"
472 472
473#: html.c:2484 473#: html.c:2484
474msgid "Attachment" 474msgid "Attachment"
475msgstr "Attachement" 475msgstr "Pièce jointe"
476 476
477#: html.c:2498 477#: html.c:2498
478msgid "Alt..." 478msgid "Alt..."
@@ -541,7 +541,7 @@ msgstr "Illimité"
541 541
542#: html.c:3185 542#: html.c:3185
543msgid "Allow announces (boosts) from this user" 543msgid "Allow announces (boosts) from this user"
544msgstr "Permettre les annonces (repartages) par cet utilisateur" 544msgstr "Permettre les annonces (repartages) par cette personne"
545 545
546#: html.c:3188 html.c:4570 546#: html.c:3188 html.c:4570
547msgid "Limit" 547msgid "Limit"
@@ -549,11 +549,11 @@ msgstr "Limite"
549 549
550#: html.c:3189 550#: html.c:3189
551msgid "Block announces (boosts) from this user" 551msgid "Block announces (boosts) from this user"
552msgstr "Bloquer les annonces (repartages) par cet utilisateur" 552msgstr "Bloquer les annonces (repartages) par cette personne"
553 553
554#: html.c:3198 554#: html.c:3198
555msgid "Delete this user" 555msgid "Delete this user"
556msgstr "Supprimer cet utilisateur" 556msgstr "Supprimer cette personne"
557 557
558#: html.c:3203 html.c:4688 558#: html.c:3203 html.c:4688
559msgid "Approve" 559msgid "Approve"
@@ -561,7 +561,7 @@ msgstr "Approuver"
561 561
562#: html.c:3204 562#: html.c:3204
563msgid "Approve this follow request" 563msgid "Approve this follow request"
564msgstr "Approuver cette demande de suivit" 564msgstr "Approuver cette demande de suivi"
565 565
566#: html.c:3207 html.c:4712 566#: html.c:3207 html.c:4712
567msgid "Discard" 567msgid "Discard"
@@ -577,11 +577,11 @@ msgstr "Ne plus taire"
577 577
578#: html.c:3213 578#: html.c:3213
579msgid "Stop blocking activities from this user" 579msgid "Stop blocking activities from this user"
580msgstr "Arrêter de bloquer les activités de cet utilisateur" 580msgstr "Arrêter de bloquer les activités de cette personne"
581 581
582#: html.c:3217 582#: html.c:3217
583msgid "Block any activity from this user" 583msgid "Block any activity from this user"
584msgstr "Bloque toutes les activités de cet utilisateur" 584msgstr "Bloque toutes les activités de cette personne"
585 585
586#: html.c:3225 586#: html.c:3225
587msgid "Direct Message..." 587msgid "Direct Message..."
@@ -589,7 +589,7 @@ msgstr "Message direct…"
589 589
590#: html.c:3260 590#: html.c:3260
591msgid "Pending follow confirmations" 591msgid "Pending follow confirmations"
592msgstr "Confirmation de suivit en attente" 592msgstr "Confirmation de suivi en attente"
593 593
594#: html.c:3264 594#: html.c:3264
595msgid "People you follow" 595msgid "People you follow"
@@ -613,7 +613,7 @@ msgstr "Sondage terminé"
613 613
614#: html.c:3379 614#: html.c:3379
615msgid "Follow Request" 615msgid "Follow Request"
616msgstr "Requête de suivit" 616msgstr "Requête de suivi"
617 617
618#: html.c:3462 618#: html.c:3462
619msgid "Context" 619msgid "Context"
@@ -634,7 +634,7 @@ msgstr "Aucun"
634#: html.c:3769 634#: html.c:3769
635#, c-format 635#, c-format
636msgid "Search results for account %s" 636msgid "Search results for account %s"
637msgstr "Résultats de recher pour le compte %s" 637msgstr "Résultats de recherche pour le compte %s"
638 638
639#: html.c:3776 639#: html.c:3776
640#, c-format 640#, c-format
@@ -654,7 +654,7 @@ msgstr "Rien n'a été trouvé pour le tag %s"
654#: html.c:3823 654#: html.c:3823
655#, c-format 655#, c-format
656msgid "Search results for '%s' (may be more)" 656msgid "Search results for '%s' (may be more)"
657msgstr "Résultats de recherche pour '%s' (il pourrait y en avoir d'avantage)" 657msgstr "Résultats de recherche pour '%s' (il pourrait y en avoir davantage)"
658 658
659#: html.c:3826 659#: html.c:3826
660#, c-format 660#, c-format
@@ -664,7 +664,7 @@ msgstr "Résultats de recherche pour '%s'"
664#: html.c:3829 664#: html.c:3829
665#, c-format 665#, c-format
666msgid "No more matches for '%s'" 666msgid "No more matches for '%s'"
667msgstr "Pas d'avantage de résultats pour '%s'" 667msgstr "Pas davantage de résultats pour '%s'"
668 668
669#: html.c:3831 669#: html.c:3831
670#, c-format 670#, c-format
@@ -687,7 +687,7 @@ msgstr "Résultats de recherche pour le tag #%s"
687 687
688#: httpd.c:259 688#: httpd.c:259
689msgid "Recent posts by users in this instance" 689msgid "Recent posts by users in this instance"
690msgstr "Messages récents des utilisateurs de cette instance" 690msgstr "Messages récents des internautes de cette instance"
691 691
692#: html.c:1603 692#: html.c:1603
693msgid "Blocked hashtags..." 693msgid "Blocked hashtags..."
@@ -695,7 +695,7 @@ msgstr "Hashtags bloqués…"
695 695
696#: html.c:432 696#: html.c:432
697msgid "Optional URL to reply to" 697msgid "Optional URL to reply to"
698msgstr "" 698msgstr "URL optionnelle pour répondre à"
699 699
700#: html.c:575 700#: html.c:575
701msgid "" 701msgid ""
@@ -704,55 +704,59 @@ msgid ""
704"Option 3...\n" 704"Option 3...\n"
705"..." 705"..."
706msgstr "" 706msgstr ""
707"Option 1…\n"
708"Option 2…\n"
709"Option 3…\n"
710"…"
707 711
708#: html.c:1415 712#: html.c:1415
709msgid "Bot API key" 713msgid "Bot API key"
710msgstr "" 714msgstr "Clé API de bot"
711 715
712#: html.c:1421 716#: html.c:1421
713msgid "Chat id" 717msgid "Chat id"
714msgstr "" 718msgstr "Identifiant du salon"
715 719
716#: html.c:1429 720#: html.c:1429
717msgid "ntfy server - full URL (example: https://ntfy.sh/YourTopic)" 721msgid "ntfy server - full URL (example: https://ntfy.sh/YourTopic)"
718msgstr "" 722msgstr "serveur ntfy – adresse complète (ex : https://ntfy.sh/VotreSujet)"
719 723
720#: html.c:1435 724#: html.c:1435
721msgid "ntfy token - if needed" 725msgid "ntfy token - if needed"
722msgstr "" 726msgstr "jeton ntfy – si nécessaire"
723 727
724#: html.c:2892 728#: html.c:2892
725msgid "pinned" 729msgid "pinned"
726msgstr "" 730msgstr "épinglé"
727 731
728#: html.c:2904 732#: html.c:2904
729msgid "bookmarks" 733msgid "bookmarks"
730msgstr "" 734msgstr "signets"
731 735
732#: html.c:2916 736#: html.c:2916
733msgid "drafts" 737msgid "drafts"
734msgstr "" 738msgstr "brouillons"
735 739
736#: html.c:464 740#: html.c:464
737msgid "Scheduled post..." 741msgid "Scheduled post..."
738msgstr "" 742msgstr "Publication planifiée…"
739 743
740msgid "Post date and time:" 744msgid "Post date and time:"
741msgstr "" 745msgstr "Date et heure de publication :"
742 746
743#: html.c:2927 html.c:3989 747#: html.c:2927 html.c:3989
744msgid "Scheduled posts" 748msgid "Scheduled posts"
745msgstr "" 749msgstr "Publications planifiées"
746 750
747#: html.c:2928 751#: html.c:2928
748msgid "scheduled posts" 752msgid "scheduled posts"
749msgstr "" 753msgstr "publications planifiées"
750 754
751#: html.c:458 755#: html.c:458
752#, c-format 756#, c-format
753msgid "Post date and time (timezone: %s):" 757msgid "Post date and time (timezone: %s):"
754msgstr "" 758msgstr "Date et heure de publication (fuseau horaire : %s) :"
755 759
756#: html.c:1538 760#: html.c:1538
757msgid "Time zone:" 761msgid "Time zone:"
758msgstr "" 762msgstr "Fuseau horaire :"
diff --git a/po/pt_BR.po b/po/pt_BR.po
index ca48946..555d82d 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -4,10 +4,13 @@
4msgid "" 4msgid ""
5msgstr "" 5msgstr ""
6"Project-Id-Version: snac\n" 6"Project-Id-Version: snac\n"
7"Last-Translator: Daltux <https://daltux.net>\n" 7"PO-Revision-Date: 2025-04-18\n"
8"Last-Translator: Daltux <@daltux@snac.daltux.net>\n"
8"Language: pt_BR\n" 9"Language: pt_BR\n"
10"MIME-Version: 1.0\n"
9"Content-Type: text/plain; charset=UTF-8\n" 11"Content-Type: text/plain; charset=UTF-8\n"
10"X-Generator: Poedit 3.5\n" 12"Content-Transfer-Encoding: 8bit\n"
13"X-Generator: Poedit 3.6\n"
11 14
12#: html.c:384 15#: html.c:384
13msgid "Sensitive content: " 16msgid "Sensitive content: "
@@ -739,24 +742,24 @@ msgstr "rascunhos"
739 742
740#: html.c:464 743#: html.c:464
741msgid "Scheduled post..." 744msgid "Scheduled post..."
742msgstr "" 745msgstr "Publicação agendada..."
743 746
744msgid "Post date and time:" 747msgid "Post date and time:"
745msgstr "" 748msgstr "Data e horário da publicação:"
746 749
747#: html.c:2927 html.c:3989 750#: html.c:2927 html.c:3989
748msgid "Scheduled posts" 751msgid "Scheduled posts"
749msgstr "" 752msgstr "Publicações agendadas"
750 753
751#: html.c:2928 754#: html.c:2928
752msgid "scheduled posts" 755msgid "scheduled posts"
753msgstr "" 756msgstr "publicações agendadas"
754 757
755#: html.c:458 758#: html.c:458
756#, c-format 759#, c-format
757msgid "Post date and time (timezone: %s):" 760msgid "Post date and time (timezone: %s):"
758msgstr "" 761msgstr "Data e hora da publicação (fuso horário: %s):"
759 762
760#: html.c:1538 763#: html.c:1538
761msgid "Time zone:" 764msgid "Time zone:"
762msgstr "" 765msgstr "Fuso horário:"
diff --git a/po/ru.po b/po/ru.po
index df53ebe..4d5d9b4 100644
--- a/po/ru.po
+++ b/po/ru.po
@@ -745,24 +745,24 @@ msgstr "черновики"
745 745
746#: html.c:464 746#: html.c:464
747msgid "Scheduled post..." 747msgid "Scheduled post..."
748msgstr "" 748msgstr "Запланировать..."
749 749
750msgid "Post date and time:" 750msgid "Post date and time:"
751msgstr "" 751msgstr "Время поста:"
752 752
753#: html.c:2927 html.c:3989 753#: html.c:2927 html.c:3989
754msgid "Scheduled posts" 754msgid "Scheduled posts"
755msgstr "" 755msgstr "Запланированные посты"
756 756
757#: html.c:2928 757#: html.c:2928
758msgid "scheduled posts" 758msgid "scheduled posts"
759msgstr "" 759msgstr "запланированные посты"
760 760
761#: html.c:458 761#: html.c:458
762#, c-format 762#, c-format
763msgid "Post date and time (timezone: %s):" 763msgid "Post date and time (timezone: %s):"
764msgstr "" 764msgstr "Время поста (Часовой пояс: %s):"
765 765
766#: html.c:1538 766#: html.c:1538
767msgid "Time zone:" 767msgid "Time zone:"
768msgstr "" 768msgstr "Часовой пояс:"