diff options
Diffstat (limited to 'rss.c')
| -rw-r--r-- | rss.c | 105 |
1 files changed, 105 insertions, 0 deletions
| @@ -0,0 +1,105 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | ||
| 2 | /* copyright (c) 2025 grunfink et al. / MIT license */ | ||
| 3 | |||
| 4 | #include "xs.h" | ||
| 5 | #include "xs_html.h" | ||
| 6 | #include "xs_regex.h" | ||
| 7 | #include "xs_time.h" | ||
| 8 | |||
| 9 | #include "snac.h" | ||
| 10 | |||
| 11 | xs_str *rss_from_timeline(snac *user, const xs_list *timeline, | ||
| 12 | const char *title, const char *link, const char *desc) | ||
| 13 | /* converts a timeline to rss */ | ||
| 14 | { | ||
| 15 | xs_html *rss = xs_html_tag("rss", | ||
| 16 | xs_html_attr("xmlns:content", "http:/" "/purl.org/rss/1.0/modules/content/"), | ||
| 17 | xs_html_attr("version", "2.0"), | ||
| 18 | xs_html_attr("xmlns:atom", "http:/" "/www.w3.org/2005/Atom")); | ||
| 19 | |||
| 20 | xs_html *channel = xs_html_tag("channel", | ||
| 21 | xs_html_tag("title", | ||
| 22 | xs_html_text(title)), | ||
| 23 | xs_html_tag("language", | ||
| 24 | xs_html_text("en")), | ||
| 25 | xs_html_tag("link", | ||
| 26 | xs_html_text(link)), | ||
| 27 | xs_html_sctag("atom:link", | ||
| 28 | xs_html_attr("href", link), | ||
| 29 | xs_html_attr("rel", "self"), | ||
| 30 | xs_html_attr("type", "application/rss+xml")), | ||
| 31 | xs_html_tag("generator", | ||
| 32 | xs_html_text(USER_AGENT)), | ||
| 33 | xs_html_tag("description", | ||
| 34 | xs_html_text(desc))); | ||
| 35 | |||
| 36 | xs_html_add(rss, channel); | ||
| 37 | |||
| 38 | int cnt = 0; | ||
| 39 | const char *v; | ||
| 40 | |||
| 41 | xs_list_foreach(timeline, v) { | ||
| 42 | xs *msg = NULL; | ||
| 43 | |||
| 44 | if (user) { | ||
| 45 | if (!valid_status(timeline_get_by_md5(user, v, &msg))) | ||
| 46 | continue; | ||
| 47 | } | ||
| 48 | else { | ||
| 49 | if (!valid_status(object_get_by_md5(v, &msg))) | ||
| 50 | continue; | ||
| 51 | } | ||
| 52 | |||
| 53 | const char *id = xs_dict_get(msg, "id"); | ||
| 54 | const char *content = xs_dict_get(msg, "content"); | ||
| 55 | const char *published = xs_dict_get(msg, "published"); | ||
| 56 | |||
| 57 | if (user && !xs_startswith(id, user->actor)) | ||
| 58 | continue; | ||
| 59 | |||
| 60 | if (!id || !content || !published) | ||
| 61 | continue; | ||
| 62 | |||
| 63 | /* create a title with the first line of the content */ | ||
| 64 | xs *title = xs_replace(content, "<br>", "\n"); | ||
| 65 | title = xs_regex_replace_i(title, "<[^>]+>", " "); | ||
| 66 | title = xs_regex_replace_i(title, "&[^;]+;", " "); | ||
| 67 | int i; | ||
| 68 | |||
| 69 | for (i = 0; title[i] && title[i] != '\n' && i < 50; i++); | ||
| 70 | |||
| 71 | if (title[i] != '\0') { | ||
| 72 | title[i] = '\0'; | ||
| 73 | title = xs_str_cat(title, "..."); | ||
| 74 | } | ||
| 75 | |||
| 76 | title = xs_strip_i(title); | ||
| 77 | |||
| 78 | /* convert the date */ | ||
| 79 | time_t t = xs_parse_iso_date(published, 0); | ||
| 80 | xs *rss_date = xs_str_utctime(t, "%a, %d %b %Y %T +0000"); | ||
| 81 | |||
| 82 | /* if it's the first one, add it to the header */ | ||
| 83 | if (cnt == 0) | ||
| 84 | xs_html_add(channel, | ||
| 85 | xs_html_tag("lastBuildDate", | ||
| 86 | xs_html_text(rss_date))); | ||
| 87 | |||
| 88 | xs_html_add(channel, | ||
| 89 | xs_html_tag("item", | ||
| 90 | xs_html_tag("title", | ||
| 91 | xs_html_text(title)), | ||
| 92 | xs_html_tag("link", | ||
| 93 | xs_html_text(id)), | ||
| 94 | xs_html_tag("guid", | ||
| 95 | xs_html_text(id)), | ||
| 96 | xs_html_tag("pubDate", | ||
| 97 | xs_html_text(rss_date)), | ||
| 98 | xs_html_tag("description", | ||
| 99 | xs_html_text(content)))); | ||
| 100 | |||
| 101 | cnt++; | ||
| 102 | } | ||
| 103 | |||
| 104 | return xs_html_render_s(rss, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | ||
| 105 | } | ||