about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xsfeed_html.awk341
-rwxr-xr-xsfeed_html.sh223
2 files changed, 341 insertions, 223 deletions
diff --git a/sfeed_html.awk b/sfeed_html.awk new file mode 100755 index 0000000..8477677 --- /dev/null +++ b/sfeed_html.awk
@@ -0,0 +1,341 @@
1#!/bin/awk -f
2# Convert sfeed(1) formatted files into an HTML webpage.
3# Usage: sfeed_html.awk -- FILES...
4
5## Set up variables
6BEGIN {
7 # Sidebar
8 if (! ASIDE) {
9 ASIDE = envor("SFEED_HTML_ASIDE", "/tmp/sfeed-aside.html")
10 }
11 print > ASIDE # clear out ASIDE
12 in_aside = 0 # Don't show the feed in the sidebar
13 # Formatting
14 if (! DATEFMT) {
15 DATEFMT = envor("SFEED_HTML_DATEFMT", "<span class=date>%F</span> <span class=time>%RZ</span>")
16 }
17 if (! TITLE) {
18 TITLE = envor("SFEED_HTML_TITLE", "Planet ACDW")
19 }
20 if (! SEPARATOR) {
21 SEPARATOR = envor("SFEED_HTML_SEPARATOR", "//")
22 }
23 if (! ALT_LINK_STAMP) {
24 ALT_LINK_STAMP = envor("SFEED_ALT_LINK_STAMP", "&")
25 }
26 if (! ENCLOSURE_STAMP) {
27 ENCLOSURE_STAMP = envor("SFEED_ENCLOSURE_STAMP", "@")
28 }
29 if (! SILO_STAMP) {
30 SILO_STAMP = envor("SFEED_SILO_STAMP", "%")
31 }
32 # Limiting posts...
33 ## by time
34 if (! NOW) {
35 datecmd = "date +%s"
36 datecmd | getline NOW
37 close(datecmd)
38 }
39 if (! FRESHDAYS) {
40 FRESHDAYS = envor("SFEED_FRESHDAYS", 1.5)
41 }
42 if (! STALEDAYS) {
43 STALEDAYS = envor("SFEED_STALEDAYS", 4)
44 }
45 fresh_secs = FRESHDAYS * 24 * 60 * 60
46 stale_secs = STALEDAYS * 24 * 60 * 60
47 fresh_age = (NOW - fresh_secs)
48 stale_age = (NOW - stale_secs)
49 ## by number
50 if (! LIMIT) {
51 # If LIMIT == -1, ignore time limit as well.
52 LIMIT = envor("SFEED_LIMIT", 20)
53 }
54 # Alternate URLs for siloed content
55 if (! YOUTUBE_ALT_URL) {
56 YOUTUBE_ALT_URL = envor("SFEED_YOUTUBE_ALT_URL", "https://piped.kavin.rocks")
57 }
58 if (! TWITTER_ALT_URL) {
59 TWITTER_ALT_URL = envor("SFEED_TWITTER_ALT_URL", "https://nitter.net")
60 }
61 if (! REDDIT_ALT_URL) {
62 REDDIT_ALT_URL = envor("SFEED_REDDIT_ALT_URL", "https://libreddit.spike.codes")
63 }
64 # Awk and convenience constants
65 FS = "\t"
66 STDERR = "/dev/stderr"
67}
68
69## Print HTML header
70BEGIN {
71 fortunecmd = "fortune"
72 fortunecmd | getline LOGO_TITLE
73 close(fortunecmd)
74 sub(/"/, "", LOGO_TITLE)
75 datecmd = "date -u +'" DATEFMT "'"
76 datecmd | getline UPDATE_TIME
77 close(datecmd)
78 print "<!DOCTYPE html>"
79 print "<html>"
80 # <head>
81 print "<head>"
82 print "<meta charset=\"utf-8\">"
83 print "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">"
84 print "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\">"
85 print "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
86 print "<title>" TITLE "</title>"
87 print "<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">"
88 print "<link rel=\"shortcut icon\" type=\"image/png\" href=\"mars-eyes.png\">"
89 print "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"rss (full)\" href=\"feeds.xml\">"
90 print "<link rel=\"alternate\" type=\"application/atom+xml\" title=\"rss (short)\" href=\"feeds-short.xml\">"
91 print "<link rel=\"alternate\" type=\"application/xml\" title=\"opml\" href=\"feeds.opml\">"
92 print "<link rel=\"alternate\" type=\"text/plain\" title=\"twtxt\" href=\"feeds.txt\">"
93 print "</head>"
94 print "<body>"
95 # <header>
96 print "<header id=\"page-header\">"
97 print "<h1>"
98 printf "<a id=\"logo\" href=\"index.html\"><img src=\"mars-eyes.png\""
99 printf " title=\"%s\" ", LOGO_TITLE
100 printf "width=\"40\" height=\"39\""
101 print "alt=\"mars, but with eyes\" /></a>"
102 print TITLE "</h1>"
103 print "<p id=\"last-updated\">last updated at <time>" UPDATE_TIME "</time></p>"
104 print "</header>"
105 # <nav>
106 print "<nav id=\"page-nav\">"
107 print "<a href=\"#\" id=\"stalebutton\">hide stale</a>", SEPARATOR
108 print "<a href=\"feeds-short.xml\">rss</a>", SEPARATOR
109 print "<a href=\"feeds.html\">all</a>", "(<a href=\"feeds.xml\">rss</a>)", SEPARATOR
110 print "<a href=\"feeds.txt\">twtxt</a>", SEPARATOR
111 print "<a href=\"feeds.opml\">opml</a>"
112 print "</nav>"
113 # first part of <main>
114 print "<main>"
115 print "<section id=\"list\">"
116}
117
118FNR == 1 {
119 if (file_count) {
120 # End the previous file before beginning the current one
121 end_file()
122 }
123 # Filename
124 name = FILENAME
125 sub(/.*\//, "", name)
126 safe_name = name
127 sub(/\//, "_", safe_name)
128 dir = FILENAME
129 sub(/[^\/]*$/, "", dir)
130 if (name ~ /\[yt\]$/) {
131 sub(/ \[yt\]$/, "", name)
132 yt = " class=\"yt\""
133 } else {
134 yt = ""
135 }
136 URLS = envor("SFEED_DATA", ENVIRON["HOME"] "/.sfeed") "/urls/" safe_name
137 # State variables
138 stamp = "."
139 buf = ""
140 ni = 0
141 stale_count = 0
142 fresh_count = 0
143 item_count = 0
144 file_count++
145 siloed = 0
146}
147
148{
149 # Skip if we have too many items
150 if ((LIMIT > 0) && (++ni > LIMIT)) {
151 next
152 }
153 # Collect fields
154 timestamp = $1
155 title = $2
156 silo = $3
157 link = silo_link(silo)
158 content = unescape($4)
159 content_type = $5
160 id = $6
161 author = $7
162 enclosure = $8
163 category = $9
164 # Skip if the item is too old
165 if ((LIMIT >= 0) && (timestamp < stale_age)) {
166 next
167 }
168 # Otherwise, we're showing it
169 stale_count++
170 # Is this item fresh?
171 if (timestamp >= fresh_age) {
172 is_fresh = 1
173 stamp = "!"
174 fresh_count++
175 } else {
176 is_fresh = 0
177 }
178 # Print!
179 bufprint("<tr class=\"entry " (is_fresh ? "fresh" : "stale") "\">")
180 # Timestamp
181 datecmd = "date -d \"@" timestamp "\" +'" DATEFMT "'"
182 if (timestamp) {
183 datecmd | getline timestamp
184 close(datecmd)
185 }
186 bufprint("<td class=\"entry-timestamp\"><time>" timestamp "</time></td>")
187 # Extra links
188 bufprint("<td class=\"entry-extra\">")
189 if (siloed) {
190 # "siloed" links like youtube, facebook, etc. --- I convert them
191 # to more privacy-friendly links, but sometimes those don't
192 # work. TODO: also try to circumvent paywalls.
193 stamp = SILO_STAMP
194 print_link(silo, "Silo: " silo, stamp)
195 }
196 if (enclosure) {
197 # enclosures --- podcast files, etc.
198 stamp = ENCLOSURE_STAMP
199 print_link(enclosure, "Enclosure: " enclosure, stamp)
200 }
201 if ((link != id) && (id != enclosure) && (id ~ /^https?:/)) {
202 # alternate links (comments, etc.)
203 stamp = ALT_LINK_STAMP
204 print_link(id, "alternate link", stamp)
205 }
206 bufprint("</td>")
207 # Title
208 bufprint("<td class=\"entry-title" silo_class(silo) "\">")
209 print_link(link, "", title)
210 bufprint("</td>")
211 # End row
212 bufprint("</tr>")
213 item_count++
214}
215
216END {
217 # End the last file and the #list section
218 end_file()
219 print "</section>"
220 # Sidebar
221 print "<aside><ul id=\"feedlist\">"
222 system("cat " ASIDE)
223 close(ASIDE)
224 print "</ul></aside>"
225 # End of <main>
226 print "</main>"
227 # footer
228 printf "<footer>"
229 print "Generated by <a href=\"https://codemadness.org/sfeed-simple-feed-parser.html\">sfeed</a>"
230 print "using <a href=\"https://git.acdw.net/sfeed/\">this configuration</a>."
231 print "<a href=\"mailto:planet@me.acdw.net\">suggest a feed!</a></footer>"
232 # end of HTML
233 print "<script src=\"script.js\"> </script>"
234 print "</body>"
235 print "</html>"
236 printf(SEPARATOR) > STDERR
237}
238
239
240function bufprint(text, sep)
241{
242 buf = buf text (sep ? sep : ((sep == 0) ? "" : "\n"))
243}
244
245function end_file()
246{
247 if (! item_count) {
248 return 1
249 }
250 # Header
251 printf "<section id=\"%s\" class=\"%s\">\n", name, (fresh_count ? "fresh" : "stale")
252 printf "<header><h2%s><a class=\"anchor\" href=\"#%s\">#</a> %s</h2>\n", yt, name, name
253 # Feed links
254 printf "<nav class=\"flinks\">"
255 feed_url = ""
256 site_url = ""
257 getline feed_url < URLS
258 getline site_url < URLS
259 if (site_url) {
260 printf "<a class=\"site-url\" href=\"%s\">%s</a>\n", site_url, (yt ? "chan" : "site")
261 }
262 if (feed_url) {
263 printf "<a class=\"feed-url\" href=\"%s\">feed</a>\n", feed_url
264 }
265 # Top link
266 printf "<a class=\"top\" href=\"#\">top</a>"
267 printf "</nav>"
268 printf "</header>\n"
269 # Feed entries
270 printf "<table class=\"entries\">\n"
271 printf "%s", buf
272 printf "</table>\n</section>\n"
273 # Sidebar
274 if (stale_count) {
275 printf("<li class=\"%s\">", (fresh_count ? "fresh" : "stale")) >> ASIDE
276 printf("<a href=\"#%s\"%s>%s</a></li>\n", name, (yt ? " class=\"yt\"" : ""), name) >> ASIDE
277 }
278 # Log
279 printf("%s", stamp) > STDERR
280}
281
282function envor(var, def)
283{
284 return (ENVIRON[var] ? ENVIRON[var] : def)
285}
286
287function html_escape(t)
288{
289 gsub(/</, "\\&lt;", t)
290 gsub(/>/, "\\&gt;", t)
291 gsub(/&/, "\\&amp;", t)
292 return t
293}
294
295function print_link(href, title, text)
296{
297 bufprint("<a href=\"" href "\" title=\"" title "\" target=\"_blank\">" text "</a>", 0)
298}
299
300function silo_class(link)
301{
302 if (link ~ /youtube\.com/) {
303 return " youtube"
304 }
305 if (link ~ /facebook\.com/) {
306 return " facebook"
307 }
308 if (link ~ /twitter\.com/) {
309 return " twitter"
310 }
311 if (link ~ /reddit\.com/) {
312 return " reddit"
313 }
314 return ""
315}
316
317function silo_link(link)
318{
319 ret = link
320 http = "https?://[^\\.]*\\.?"
321 siloed = 1
322 if (ret ~ (http "youtube\\.com")) {
323 sub(http "youtube\\.com", YOUTUBE_ALT_URL, ret)
324 } else if (ret ~ (http "reddit\\.com")) {
325 sub(http "reddit\\.com", REDDIT_ALT_URL, ret)
326 } else if (ret ~ (http "twitter\\.com")) {
327 sub(http "twitter\\.com", TWITTER_ALT_URL, ret)
328 } else {
329 siloed = 0
330 }
331 return ret
332}
333
334function unescape(t)
335{
336 t = html_escape(t)
337 gsub(/\\\t/, "\t", t)
338 gsub(/\\\n/, "\n", t)
339 gsub(/\\\\/, "\\", t)
340 return t
341}
diff --git a/sfeed_html.sh b/sfeed_html.sh deleted file mode 100755 index 3965a87..0000000 --- a/sfeed_html.sh +++ /dev/null
@@ -1,223 +0,0 @@
1#!/usr/bin/env bash
2
3echo() { printf '%s\n' "$*"; }
4
5html() {
6 : "${LIMIT:=12}"
7 : "${FRESH_DAYS:=1}"
8 : "${STALE_DAYS:=4}"
9 aside="$(mktemp /tmp/sfeed_html_aside.XXXXXX)"
10 cat <<EOF
11$(html_head)
12<body>
13<header>
14<h1>
15<a href="index.html"><img src="mars-eyes.png"
16 title="$(fortune | tr -d '"')"
17 width="40" height="39"
18 alt="mars, but with eyes" /></a>
19Planet ACDW</h1>
20<p class="last-updated">last updated at <time>$(date -R)</time></p>
21</header>
22<nav>
23<a href="#" id="stalebutton">hide stale</a>
24//
25<a href="feeds-short.xml">rss</a>
26//
27<a href="feeds.html">all</a>
28(<a href="feeds.xml">rss</a>)
29//
30<a href="feeds.txt">twtxt</a>
31//
32<a href="feeds.opml">opml</a>
33</nav>
34<main>
35$(html_main "$@")
36<aside><ul id="feedlist">$(cat "$aside")</ul></aside>
37</main>
38<footer>
39Generated by <a href="https://codemadness.org/sfeed-simple-feed-parser.html">sfeed</a>
40using <a href="https://git.acdw.net/sfeed/">this configuration</a>.
41<a href="mailto:planet@me.acdw.net">suggest a feed!</a>
42</footer>
43<script>
44var staleHidden = false;
45var staleItems = new Set(document.getElementsByClassName("stale"));
46var staleFeeds = new Set(document.getElementsByClassName("stale_feed"));
47var feedlistFeeds = new Set(document.getElementById("feedlist").children);
48function hideShowStale(){
49 var display;
50 if (staleHidden) {
51 display = null; staleHidden = false;
52 flBG = null; flBB = null;
53 } else {
54 display = 'none'; staleHidden = true;
55 flBG = 'inherit'; flBB = '1px solid';
56 }
57 staleItems.forEach(e => { e.style.display = display; });
58 staleFeeds.forEach(e => { e.style.display = display; });
59 if (window.innerWidth > 720) {
60 feedlistFeeds.forEach(e => { e.style.background = flBG; e.style.borderBottom = flBB; });
61 }
62}
63var button = document.getElementById("stalebutton");
64button.addEventListener("click", function() {
65 hideShowStale();
66 if (staleHidden) { button.textContent = "show stale"; }
67 else { button.textContent = "hide stale"; }
68});
69</script>
70</body>
71</html>
72EOF
73 rm "$aside"
74}
75
76html_head() {
77 cat <<EOF
78<!DOCTYPE html>
79<html>
80<head>
81<meta charset="utf-8">
82<meta http-equiv="X-UA-Compatible" content="IE=edge">
83<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
84<meta name="viewport" content="width=device-width, initial-scale=1">
85<title>Planet ACDW</title>
86<link rel="stylesheet" type="text/css" href="style.css">
87<link rel="shortcut icon" type="image/png" href="mars-eyes.png">
88<link rel="alternate" type="application/atom+xml" title="rss (full)" href="feeds.xml">
89<link rel="alternate" type="application/atom+xml" title="rss (short)" href="feeds-short.xml">
90<link rel="alternate" type="application/xml" title="opml" href="feeds.opml">
91<link rel="alternate" type="text/plain" title="twtxt" href="feeds.txt">
92</head>
93EOF
94}
95
96html_main() {
97 cat <<EOF
98<section id="list">
99$(for file in "$@"; do html_feed "$file"; done)
100</section>
101EOF
102}
103
104filter_invidious() {
105 # Convert youtube links to invidious links
106 sed -e "s@https://www\.youtube\.com@https://piped.kavin.rocks@" \
107 -e 's@https://i\.reddit\.com@https://libreddit.spike.codes@' \
108 -e 's@https://twitter\.com@https://nitter.net@'
109}
110
111html_feed() { # html_feed FEED(file) => HTML
112 filename="$(basename "$1")"
113 now="$(date +%s)"
114 fresh_secs="$((FRESH_DAYS * 24 * 60 * 60))"
115 stale_secs="$((STALE_DAYS * 24 * 60 * 60))"
116
117 ## ENTRIES
118 entries="$(awk -v NOW="$now" \
119 -v FRESH_SECS="$fresh_secs" -v STALE_SECS="$stale_secs" \
120 -v NAME="$filename" -v FNAME="$filename" \
121 -v ASIDE="$aside" -v limit="$LIMIT" \
122 'BEGIN { FS="\t"; fresh_feed = 0; FRESH = (NOW - FRESH_SECS); }
123 FNR == 1 { nitem = 0; }
124 NAME ~ /\[yt\]$/ { sub(/ \[yt\]$/, "", NAME); yt = 1; }
125 function unescape(t) {
126 t = html_escape(t);
127 gsub(/\\\t/,"\t",t);
128 gsub(/\\\n/,"\n",t);
129 gsub(/\\\\/,"\\",t);
130 return t
131 }
132 function html_escape(t) {
133 gsub(/</,"\\&lt;",t);
134 gsub(/>/,"\\&gt;",t);
135 gsub(/&/,"\\&amp;",t);
136 return t
137 }
138 {
139 if (limit && (++nitem > limit)) next;
140 timestamp=$1;
141 title=$2;
142 link=$3;
143 content=unescape($4);
144 content_type=$5;
145 id=$6;
146 author=$7;
147 enclosure=$8;
148 category=$9;
149
150 if (limit && (timestamp < (NOW - STALE_SECS))) next;
151 show_in_sidebar = 1;
152 #print timestamp, title, link > "/dev/stderr";
153
154 date_cmd = "date -d \"@" timestamp "\" +\"%F&nbsp;%R\""
155 if (timestamp) {
156 date_cmd | getline ts;
157 close(date_cmd);
158 }
159
160 fresh = (timestamp >= FRESH)
161 if (fresh) fresh_feed = 1;
162
163 print "<tr class=\"entry " (fresh ? "fresh" : "stale") "\">"
164 print "<td class=\"entry-timestamp\">" ts "</td>"
165 printf "%s", "<td class=\"entry-extra\">"
166 if (enclosure) {
167 stamp = "@"
168 extra_title = " title=\"enclosure\""
169 printf "%s", "<a href=\"" enclosure "\"" extra_title " target=\"_blank\">" stamp "</a>"
170 }
171 if ((link != id) && (id != enclosure) && (id ~ /^https?:/)) {
172 stamp = "&"
173 extra_title = " title=\"alternate link\""
174 printf "%s", "<a href=\"" id "\"" extra_title " target=\"_blank\">" stamp "</a>"
175 }
176 print "</td>"
177 printf "<td class=\"entry-title%s", silo_links(link)
178 print "\"><a href=\"" link "\" target=\"_blank\">" title "</a></td>"
179 print "</tr>"
180 }
181 END {
182 if (show_in_sidebar) {
183 printf "<li class=\"%s\">", (fresh_feed?"fresh":"stale") >> ASIDE
184 printf "<a%s href=\"#%s\">%s</a></li>\n", (yt ? " class=\"yt\"" : ""), FNAME, NAME >> ASIDE
185 }
186 printf "%s", (stamp ? stamp : ".") > "/dev/stderr"
187 }
188function silo_links(link) {
189 if (link ~ /youtube\.com/) return " youtube"
190 if (link ~ /facebook\.com/) return " facebook"
191 if (link ~ /twitter\.com/) return " twitter"
192 if (link ~ /reddit\.com/) return " reddit"
193 return ""
194}
195' "$1" | filter_invidious)"
196 if [ -z "$entries" ]; then return 1; fi
197 fresh_feed='stale_feed'
198 if echo "$entries" | grep -q 'class="[^"]*fresh"'; then fresh_feed='fresh_feed'; fi
199 printf '<section id="%s" class="%s">\n' "$filename" "$fresh_feed"
200 case "$filename" in
201 *\[yt\]) yt=" class=\"yt\"" ;;
202 *) yt="" ;;
203 esac
204 fn="$(echo "$filename" | sed 's@ \[yt\]$@@')"
205 printf '<header><h2%s><a href="#%s">#</a> %s</h2>\n' "$yt" "$filename" "$fn"
206 echo "<span class=\"flinks\">"
207 if [ -f "$SFEED_DATA/urls/$filename" ]; then
208 feed_url="$(sed -n '1p;1q' "$SFEED_DATA/urls/$filename")"
209 site_url="$(sed -n '2p;2q' "$SFEED_DATA/urls/$filename")"
210 [ -n "$site_url" ] && printf '<a class="site-url" href="%s">%s</a>\n//\n' "$site_url" site
211 [ -n "$feed_url" ] && printf '<a class="feed-url" href="%s">%s</a>\n//\n' "$feed_url" feed
212 fi
213 printf '<a class="top" href="#">%s</a>' top
214 echo "</span></header>"
215 echo "<table class=\"entries\">"
216 echo "$entries"
217 echo "</table>"
218 echo "</section>"
219}
220
221if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
222 html "$@"
223fi