summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELEASE_NOTES.md4
-rw-r--r--TODO.md14
-rw-r--r--activitypub.c19
-rw-r--r--data.c6
-rw-r--r--html.c83
-rw-r--r--httpd.c6
-rw-r--r--snac.h7
7 files changed, 95 insertions, 44 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index b8ee714..72e1d77 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -2,12 +2,16 @@
2 2
3## 2.48 3## 2.48
4 4
5New instance page, that show all posts by users in the same instance (like the public instance timeline, but interactive). This will help building communities.
6
5Follower-only replies to unknown users are not shown in timelines. 7Follower-only replies to unknown users are not shown in timelines.
6 8
7Added verification of metadata links: if the linked page contains a link back to the snac user with a rel="me" attribute, it's marked as verified. 9Added verification of metadata links: if the linked page contains a link back to the snac user with a rel="me" attribute, it's marked as verified.
8 10
9Added a profile-page relation to links in webfinger responses (contributed by khm). 11Added a profile-page relation to links in webfinger responses (contributed by khm).
10 12
13Fixed some regressions and a crash.
14
11## 2.47 15## 2.47
12 16
13Added pagination to the notification page. 17Added pagination to the notification page.
diff --git a/TODO.md b/TODO.md
index 51faf7c..3193974 100644
--- a/TODO.md
+++ b/TODO.md
@@ -14,14 +14,16 @@ Important: deleting a follower should do more that just delete the object, see h
14 14
15## Wishlist 15## Wishlist
16 16
17Add support for rel="me" links, see https://codeberg.org/grunfink/snac2/issues/124 and https://streetpass.social
18
19Hide followers-only replies to unknown accounts, see https://codeberg.org/grunfink/snac2/issues/123
20
21Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93 17Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93
22 18
23Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104 19Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104
24 20
21Consider implementing the rejection of activities from recently-created accounts to mitigate spam, see https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/activity_pub/mrf/reject_newly_created_account_note_policy.ex
22
23Consider discarding posts by content using string or regex to mitigate spam.
24
25Consider adding milter-like support to reject posts to mitigate spam.
26
25Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` field instead of `inReplyTo`). 27Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` field instead of `inReplyTo`).
26 28
27Add more CSS classes according to https://comam.es/snac/grunfink/p/1705598619.090050 29Add more CSS classes according to https://comam.es/snac/grunfink/p/1705598619.090050
@@ -301,3 +303,7 @@ Add a flag to make accounts private, i.e., they don't expose any content from th
301Fix duplicate mentions, see https://codeberg.org/grunfink/snac2/issues/115 (2024-02-14T09:51:01+0100). 303Fix duplicate mentions, see https://codeberg.org/grunfink/snac2/issues/115 (2024-02-14T09:51:01+0100).
302 304
303Change HTML metadata information to the post info instead of user info, see https://codeberg.org/grunfink/snac2/issues/116 (2024-02-14T09:51:22+0100). 305Change HTML metadata information to the post info instead of user info, see https://codeberg.org/grunfink/snac2/issues/116 (2024-02-14T09:51:22+0100).
306
307Add support for rel="me" links, see https://codeberg.org/grunfink/snac2/issues/124 and https://streetpass.social (2024-02-22T12:40:58+0100).
308
309Hide followers-only replies to unknown accounts, see https://codeberg.org/grunfink/snac2/issues/123 (2024-02-22T12:40:58+0100).
diff --git a/activitypub.c b/activitypub.c
index e389915..d8f748e 100644
--- a/activitypub.c
+++ b/activitypub.c
@@ -1955,9 +1955,12 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1955 if (xs_type(object) == XSTYPE_DICT) 1955 if (xs_type(object) == XSTYPE_DICT)
1956 object = xs_dict_get(object, "id"); 1956 object = xs_dict_get(object, "id");
1957 1957
1958 timeline_admire(snac, object, actor, 1); 1958 if (timeline_admire(snac, object, actor, 1) == 201) {
1959 snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object)); 1959 snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object));
1960 do_notify = 1; 1960 do_notify = 1;
1961 }
1962 else
1963 snac_log(snac, xs_fmt("repeated 'Like' from %s to %s", actor, object));
1961 } 1964 }
1962 else 1965 else
1963 if (strcmp(type, "Announce") == 0) { /** **/ 1966 if (strcmp(type, "Announce") == 0) { /** **/
@@ -1983,9 +1986,13 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req)
1983 xs *who_o = NULL; 1986 xs *who_o = NULL;
1984 1987
1985 if (valid_status(actor_request(snac, who, &who_o))) { 1988 if (valid_status(actor_request(snac, who, &who_o))) {
1986 timeline_admire(snac, object, actor, 0); 1989 if (timeline_admire(snac, object, actor, 0) == 201) {
1987 snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); 1990 snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object));
1988 do_notify = 1; 1991 do_notify = 1;
1992 }
1993 else
1994 snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s",
1995 actor, object));
1989 } 1996 }
1990 else 1997 else
1991 snac_debug(snac, 1, xs_fmt("dropped 'Announce' on actor request error %s", who)); 1998 snac_debug(snac, 1, xs_fmt("dropped 'Announce' on actor request error %s", who));
diff --git a/data.c b/data.c
index 4c2a4a8..1312790 100644
--- a/data.c
+++ b/data.c
@@ -1107,7 +1107,7 @@ int timeline_add(snac *snac, const char *id, const xs_dict *o_msg)
1107} 1107}
1108 1108
1109 1109
1110void timeline_admire(snac *snac, const char *id, const char *admirer, int like) 1110int timeline_admire(snac *snac, const char *id, const char *admirer, int like)
1111/* updates a timeline entry with a new admiration */ 1111/* updates a timeline entry with a new admiration */
1112{ 1112{
1113 /* if we are admiring this, add to both timelines */ 1113 /* if we are admiring this, add to both timelines */
@@ -1116,10 +1116,12 @@ void timeline_admire(snac *snac, const char *id, const char *admirer, int like)
1116 object_user_cache_add(snac, id, "private"); 1116 object_user_cache_add(snac, id, "private");
1117 } 1117 }
1118 1118
1119 object_admire(id, admirer, like); 1119 int ret = object_admire(id, admirer, like);
1120 1120
1121 snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s", 1121 snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s",
1122 like ? "Like" : "Announce", id, admirer)); 1122 like ? "Like" : "Announce", id, admirer));
1123
1124 return ret;
1123} 1125}
1124 1126
1125 1127
diff --git a/html.c b/html.c
index 5407e22..e1df7e9 100644
--- a/html.c
+++ b/html.c
@@ -646,7 +646,7 @@ xs_html *html_user_head(snac *user, char *desc)
646} 646}
647 647
648 648
649static xs_html *html_user_body(snac *user, int local) 649static xs_html *html_user_body(snac *user, int read_only)
650{ 650{
651 xs_html *body = xs_html_tag("body", NULL); 651 xs_html *body = xs_html_tag("body", NULL);
652 652
@@ -667,7 +667,7 @@ static xs_html *html_user_body(snac *user, int local)
667 xs_html_attr("class", "snac-avatar"), 667 xs_html_attr("class", "snac-avatar"),
668 xs_html_attr("alt", ""))); 668 xs_html_attr("alt", "")));
669 669
670 if (local) { 670 if (read_only) {
671 xs *rss_url = xs_fmt("%s.rss", user->actor); 671 xs *rss_url = xs_fmt("%s.rss", user->actor);
672 xs *admin_url = xs_fmt("%s/admin", user->actor); 672 xs *admin_url = xs_fmt("%s/admin", user->actor);
673 673
@@ -698,6 +698,8 @@ static xs_html *html_user_body(snac *user, int local)
698 xs *admin_url = xs_fmt("%s/admin", user->actor); 698 xs *admin_url = xs_fmt("%s/admin", user->actor);
699 xs *notify_url = xs_fmt("%s/notifications", user->actor); 699 xs *notify_url = xs_fmt("%s/notifications", user->actor);
700 xs *people_url = xs_fmt("%s/people", user->actor); 700 xs *people_url = xs_fmt("%s/people", user->actor);
701 xs *instance_url = xs_fmt("%s/instance", user->actor);
702
701 xs_html_add(top_nav, 703 xs_html_add(top_nav,
702 xs_html_tag("a", 704 xs_html_tag("a",
703 xs_html_attr("href", user->actor), 705 xs_html_attr("href", user->actor),
@@ -714,7 +716,11 @@ static xs_html *html_user_body(snac *user, int local)
714 xs_html_text(" - "), 716 xs_html_text(" - "),
715 xs_html_tag("a", 717 xs_html_tag("a",
716 xs_html_attr("href", people_url), 718 xs_html_attr("href", people_url),
717 xs_html_text(L("people")))); 719 xs_html_text(L("people"))),
720 xs_html_text(" - "),
721 xs_html_tag("a",
722 xs_html_attr("href", instance_url),
723 xs_html_text(L("instance"))));
718 } 724 }
719 725
720 xs_html_add(body, 726 xs_html_add(body,
@@ -724,7 +730,7 @@ static xs_html *html_user_body(snac *user, int local)
724 xs_html *top_user = xs_html_tag("div", 730 xs_html *top_user = xs_html_tag("div",
725 xs_html_attr("class", "h-card snac-top-user")); 731 xs_html_attr("class", "h-card snac-top-user"));
726 732
727 if (local) { 733 if (read_only) {
728 char *header = xs_dict_get(user->config, "header"); 734 char *header = xs_dict_get(user->config, "header");
729 if (header && *header) { 735 if (header && *header) {
730 xs_html_add(top_user, 736 xs_html_add(top_user,
@@ -749,7 +755,7 @@ static xs_html *html_user_body(snac *user, int local)
749 xs_html_attr("class", "snac-top-user-id"), 755 xs_html_attr("class", "snac-top-user-id"),
750 xs_html_text(handle))); 756 xs_html_text(handle)));
751 757
752 if (local) { 758 if (read_only) {
753 xs *es1 = encode_html(xs_dict_get(user->config, "bio")); 759 xs *es1 = encode_html(xs_dict_get(user->config, "bio"));
754 xs *bio1 = not_really_markdown(es1, NULL); 760 xs *bio1 = not_really_markdown(es1, NULL);
755 xs *tags = xs_list_new(); 761 xs *tags = xs_list_new();
@@ -1306,7 +1312,7 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const
1306} 1312}
1307 1313
1308 1314
1309xs_html *html_entry(snac *user, xs_dict *msg, int local, 1315xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1310 int level, char *md5, int hide_children) 1316 int level, char *md5, int hide_children)
1311{ 1317{
1312 char *id = xs_dict_get(msg, "id"); 1318 char *id = xs_dict_get(msg, "id");
@@ -1315,7 +1321,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local,
1315 char *v; 1321 char *v;
1316 1322
1317 /* do not show non-public messages in the public timeline */ 1323 /* do not show non-public messages in the public timeline */
1318 if ((local || !user) && !is_msg_public(msg)) 1324 if ((read_only || !user) && !is_msg_public(msg))
1319 return NULL; 1325 return NULL;
1320 1326
1321 /* hidden? do nothing more for this conversation */ 1327 /* hidden? do nothing more for this conversation */
@@ -1334,7 +1340,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local,
1334 xs_html_tag("div", 1340 xs_html_tag("div",
1335 xs_html_attr("class", "snac-origin"), 1341 xs_html_attr("class", "snac-origin"),
1336 xs_html_text(L("follows you"))), 1342 xs_html_text(L("follows you"))),
1337 html_msg_icon(local ? NULL : user, xs_dict_get(msg, "actor"), msg))); 1343 html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg)));
1338 } 1344 }
1339 else 1345 else
1340 if (!xs_match(type, "Note|Question|Page|Article|Video")) { 1346 if (!xs_match(type, "Note|Question|Page|Article|Video")) {
@@ -1446,7 +1452,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local,
1446 if (!xs_is_null(name)) { 1452 if (!xs_is_null(name)) {
1447 xs *href = NULL; 1453 xs *href = NULL;
1448 1454
1449 if (!local && user != NULL) 1455 if (!read_only && user != NULL)
1450 href = xs_fmt("%s/people#%s", user->actor, p); 1456 href = xs_fmt("%s/people#%s", user->actor, p);
1451 else 1457 else
1452 href = xs_dup(xs_dict_get(actor_r, "id")); 1458 href = xs_dup(xs_dict_get(actor_r, "id"));
@@ -1482,7 +1488,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local,
1482 } 1488 }
1483 1489
1484 xs_html_add(post_header, 1490 xs_html_add(post_header,
1485 html_msg_icon(local ? NULL : user, actor, msg)); 1491 html_msg_icon(read_only ? NULL : user, actor, msg));
1486 1492
1487 /** post content **/ 1493 /** post content **/
1488 1494
@@ -1510,7 +1516,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local,
1510 1516
1511 /* only show it when not in the public timeline and the config setting is "open" */ 1517 /* only show it when not in the public timeline and the config setting is "open" */
1512 char *cw = xs_dict_get(user->config, "cw"); 1518 char *cw = xs_dict_get(user->config, "cw");
1513 if (xs_is_null(cw) || local) 1519 if (xs_is_null(cw) || read_only)
1514 cw = ""; 1520 cw = "";
1515 1521
1516 snac_content = xs_html_tag("details", 1522 snac_content = xs_html_tag("details",
@@ -1574,7 +1580,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local,
1574 1580
1575 xs_html *poll = xs_html_tag("div", NULL); 1581 xs_html *poll = xs_html_tag("div", NULL);
1576 1582
1577 if (local) 1583 if (read_only)
1578 closed = 1; /* non-identified page; show as closed */ 1584 closed = 1; /* non-identified page; show as closed */
1579 else 1585 else
1580 if (xs_dict_get(msg, "closed")) 1586 if (xs_dict_get(msg, "closed"))
@@ -1795,7 +1801,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local,
1795 1801
1796 /** controls **/ 1802 /** controls **/
1797 1803
1798 if (!local && user) { 1804 if (!read_only && user) {
1799 xs_html_add(entry, 1805 xs_html_add(entry,
1800 html_entry_controls(user, actor, msg, md5)); 1806 html_entry_controls(user, actor, msg, md5));
1801 } 1807 }
@@ -1839,7 +1845,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local,
1839 object_get_by_md5(cmd5, &chd); 1845 object_get_by_md5(cmd5, &chd);
1840 1846
1841 if (chd != NULL && xs_is_null(xs_dict_get(chd, "name"))) { 1847 if (chd != NULL && xs_is_null(xs_dict_get(chd, "name"))) {
1842 xs_html *che = html_entry(user, chd, local, level + 1, cmd5, hide_children); 1848 xs_html *che = html_entry(user, chd, read_only, level + 1, cmd5, hide_children);
1843 1849
1844 if (che != NULL) { 1850 if (che != NULL) {
1845 if (left > 3) 1851 if (left > 3)
@@ -1879,8 +1885,9 @@ xs_html *html_footer(void)
1879} 1885}
1880 1886
1881 1887
1882xs_str *html_timeline(snac *user, const xs_list *list, int local, 1888xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
1883 int skip, int show, int show_more, char *tag) 1889 int skip, int show, int show_more,
1890 char *tag, char *page, int utl)
1884/* returns the HTML for the timeline */ 1891/* returns the HTML for the timeline */
1885{ 1892{
1886 xs_list *p = (xs_list *)list; 1893 xs_list *p = (xs_list *)list;
@@ -1903,7 +1910,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local,
1903 1910
1904 if (user) { 1911 if (user) {
1905 head = html_user_head(user, desc); 1912 head = html_user_head(user, desc);
1906 body = html_user_body(user, local); 1913 body = html_user_body(user, read_only);
1907 } 1914 }
1908 else { 1915 else {
1909 head = html_instance_head(); 1916 head = html_instance_head();
@@ -1914,7 +1921,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local,
1914 head, 1921 head,
1915 body); 1922 body);
1916 1923
1917 if (user && !local) 1924 if (user && !read_only)
1918 xs_html_add(body, 1925 xs_html_add(body,
1919 html_top_controls(user)); 1926 html_top_controls(user));
1920 1927
@@ -1932,7 +1939,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local,
1932 xs *msg = NULL; 1939 xs *msg = NULL;
1933 int status; 1940 int status;
1934 1941
1935 if (user && !is_pinned_by_md5(user, v)) 1942 if (utl && user && !is_pinned_by_md5(user, v))
1936 status = timeline_get_by_md5(user, v, &msg); 1943 status = timeline_get_by_md5(user, v, &msg);
1937 else 1944 else
1938 status = object_get_by_md5(v, &msg); 1945 status = object_get_by_md5(v, &msg);
@@ -1954,14 +1961,15 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local,
1954 } 1961 }
1955 } 1962 }
1956 1963
1957 xs_html *entry = html_entry(user, msg, local, 0, v, user ? 0 : 1); 1964 xs_html *entry = html_entry(user, msg, read_only, 0, v, user ? 0 : 1);
1958 1965
1959 if (entry != NULL) 1966 if (entry != NULL)
1960 xs_html_add(posts, 1967 xs_html_add(posts,
1961 entry); 1968 entry);
1962 } 1969 }
1963 1970
1964 if (list && user && local) { 1971 if (list && user && read_only) {
1972 /** history **/
1965 if (xs_type(xs_dict_get(srv_config, "disable_history")) != XSTYPE_TRUE) { 1973 if (xs_type(xs_dict_get(srv_config, "disable_history")) != XSTYPE_TRUE) {
1966 xs_html *ul = xs_html_tag("ul", NULL); 1974 xs_html *ul = xs_html_tag("ul", NULL);
1967 1975
@@ -2003,12 +2011,15 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local,
2003 xs *m = NULL; 2011 xs *m = NULL;
2004 xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show); 2012 xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show);
2005 2013
2014 xs *url = page == NULL || user == NULL ?
2015 xs_dup(srv_baseurl) : xs_fmt("%s%s", user->actor, page);
2016
2006 if (tag) { 2017 if (tag) {
2007 t = xs_fmt("%s?t=%s", srv_baseurl, tag); 2018 t = xs_fmt("%s?t=%s", url, tag);
2008 m = xs_fmt("%s&%s", t, ss); 2019 m = xs_fmt("%s&%s", t, ss);
2009 } 2020 }
2010 else { 2021 else {
2011 t = xs_fmt("%s%s", user ? user->actor : srv_baseurl, local ? "" : "/admin"); 2022 t = xs_dup(url);
2012 m = xs_fmt("%s?%s", t, ss); 2023 m = xs_fmt("%s?%s", t, ss);
2013 } 2024 }
2014 2025
@@ -2401,7 +2412,8 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2401 xs *h = xs_str_localtime(0, "%Y-%m.html"); 2412 xs *h = xs_str_localtime(0, "%Y-%m.html");
2402 2413
2403 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) { 2414 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) {
2404 *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL); 2415 /** empty public timeline for private users **/
2416 *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL, "", 1);
2405 *b_size = strlen(*body); 2417 *b_size = strlen(*body);
2406 status = 200; 2418 status = 200;
2407 } 2419 }
@@ -2419,7 +2431,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2419 xs *pins = pinned_list(&snac); 2431 xs *pins = pinned_list(&snac);
2420 pins = xs_list_cat(pins, list); 2432 pins = xs_list_cat(pins, list);
2421 2433
2422 *body = html_timeline(&snac, pins, 1, skip, show, xs_list_len(next), NULL); 2434 *body = html_timeline(&snac, pins, 1, skip, show, xs_list_len(next), NULL, "", 1);
2423 2435
2424 *b_size = strlen(*body); 2436 *b_size = strlen(*body);
2425 status = 200; 2437 status = 200;
@@ -2456,7 +2468,8 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2456 xs *pins = pinned_list(&snac); 2468 xs *pins = pinned_list(&snac);
2457 pins = xs_list_cat(pins, list); 2469 pins = xs_list_cat(pins, list);
2458 2470
2459 *body = html_timeline(&snac, pins, 0, skip, show, xs_list_len(next), NULL); 2471 *body = html_timeline(&snac, pins, 0, skip, show,
2472 xs_list_len(next), NULL, "/admin", 1);
2460 2473
2461 *b_size = strlen(*body); 2474 *b_size = strlen(*body);
2462 status = 200; 2475 status = 200;
@@ -2491,6 +2504,22 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2491 } 2504 }
2492 } 2505 }
2493 else 2506 else
2507 if (strcmp(p_path, "instance") == 0) { /** instance timeline **/
2508 if (!login(&snac, req)) {
2509 *body = xs_dup(uid);
2510 status = 401;
2511 }
2512 else {
2513 xs *list = timeline_instance_list(skip, show);
2514 xs *next = timeline_instance_list(skip + show, 1);
2515
2516 *body = html_timeline(&snac, list, 0, skip, show,
2517 xs_list_len(next), NULL, "/instance", 0);
2518 *b_size = strlen(*body);
2519 status = 200;
2520 }
2521 }
2522 else
2494 if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/ 2523 if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/
2495 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) 2524 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE)
2496 return 403; 2525 return 403;
@@ -2504,7 +2533,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2504 2533
2505 list = xs_list_append(list, md5); 2534 list = xs_list_append(list, md5);
2506 2535
2507 *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL); 2536 *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL, "", 1);
2508 *b_size = strlen(*body); 2537 *b_size = strlen(*body);
2509 status = 200; 2538 status = 200;
2510 } 2539 }
diff --git a/httpd.c b/httpd.c
index fde40cb..319a7b3 100644
--- a/httpd.c
+++ b/httpd.c
@@ -177,6 +177,7 @@ int server_get_handler(xs_dict *req, const char *q_path,
177 char *t = NULL; 177 char *t = NULL;
178 178
179 if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) { 179 if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) {
180 /** search by tag **/
180 int skip = 0; 181 int skip = 0;
181 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); 182 int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries"));
182 char *v; 183 char *v;
@@ -194,12 +195,13 @@ int server_get_handler(xs_dict *req, const char *q_path,
194 more = 1; 195 more = 1;
195 } 196 }
196 197
197 *body = html_timeline(NULL, tl, 0, skip, show, more, t); 198 *body = html_timeline(NULL, tl, 0, skip, show, more, t, NULL, 0);
198 } 199 }
199 else 200 else
200 if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) { 201 if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) {
202 /** instance timeline **/
201 xs *tl = timeline_instance_list(0, 30); 203 xs *tl = timeline_instance_list(0, 30);
202 *body = html_timeline(NULL, tl, 0, 0, 0, 0, NULL); 204 *body = html_timeline(NULL, tl, 0, 0, 0, 0, NULL, NULL, 0);
203 } 205 }
204 else 206 else
205 *body = greeting_html(); 207 *body = greeting_html();
diff --git a/snac.h b/snac.h
index 8de3a83..85f8b44 100644
--- a/snac.h
+++ b/snac.h
@@ -133,7 +133,7 @@ int timeline_del(snac *snac, char *id);
133xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show); 133xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show);
134xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show); 134xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show);
135int timeline_add(snac *snac, const char *id, const xs_dict *o_msg); 135int timeline_add(snac *snac, const char *id, const xs_dict *o_msg);
136void timeline_admire(snac *snac, const char *id, const char *admirer, int like); 136int timeline_admire(snac *snac, const char *id, const char *admirer, int like);
137 137
138xs_list *timeline_top_level(snac *snac, xs_list *list); 138xs_list *timeline_top_level(snac *snac, xs_list *list);
139xs_list *local_list(snac *snac, int max); 139xs_list *local_list(snac *snac, int max);
@@ -296,8 +296,9 @@ xs_str *not_really_markdown(const char *content, xs_list **attach);
296xs_str *sanitize(const char *content); 296xs_str *sanitize(const char *content);
297xs_str *encode_html(const char *str); 297xs_str *encode_html(const char *str);
298 298
299xs_str *html_timeline(snac *user, const xs_list *list, int local, 299xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
300 int skip, int show, int show_more, char *tag); 300 int skip, int show, int show_more,
301 char *tag, char *page, int utl);
301 302
302int html_get_handler(const xs_dict *req, const char *q_path, 303int html_get_handler(const xs_dict *req, const char *q_path,
303 char **body, int *b_size, char **ctype, xs_str **etag); 304 char **body, int *b_size, char **ctype, xs_str **etag);