From 6aa64d77e0f8ef2e42bdb90bc5ed3f6557e3ed81 Mon Sep 17 00:00:00 2001 From: Case Duckworth Date: Sun, 17 Jul 2022 23:10:22 -0500 Subject: Initial commit --- .gitignore | 1 + mars-eyes.png | Bin 0 -> 4907 bytes runsfeed | 77 ++++++++++++++++++ scripts/get-feed.sh | 59 ++++++++++++++ sfeed_html.sh | 148 ++++++++++++++++++++++++++++++++++ sfeed_update_xargs | 45 +++++++++++ sfeedrc | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 176 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 731 insertions(+) create mode 100644 .gitignore create mode 100644 mars-eyes.png create mode 100755 runsfeed create mode 100755 scripts/get-feed.sh create mode 100755 sfeed_html.sh create mode 100755 sfeed_update_xargs create mode 100644 sfeedrc create mode 100644 style.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..865369a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +feeds/ \ No newline at end of file diff --git a/mars-eyes.png b/mars-eyes.png new file mode 100644 index 0000000..2e62c21 Binary files /dev/null and b/mars-eyes.png differ diff --git a/runsfeed b/runsfeed new file mode 100755 index 0000000..f987a92 --- /dev/null +++ b/runsfeed @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Run sfeed + +# set -euo pipefail + +main() { + export SFEED_CONFIG="$HOME/.sfeed" + # SFEED_CONFIG="${XDG_CONFIG_HOME:-$HOME/.config}/sfeed" + export SFEED_DATA="$HOME/.sfeed" + # SFEED_DATA="${XDG_DATA_HOME:-$HOME/.local/share}/sfeed" + export SFEED_OUTPUT="$HOME/.sfeed" + # SFEED_OUTPUT=/var/www/sfeed + export sfeedrc="$SFEED_CONFIG/sfeedrc" + export sfeedpath="$SFEED_DATA/feeds" + test -d "$(dirname "$sfeedrc")" || mkdir -p "$(dirname "$sfeedrc")" + test -d "$sfeedpath" || mkdir -p "$sfeedpath" + + log Updating feeds... + update "$sfeedrc" + log Generating HTML... + html "$sfeedpath"/* >"$SFEED_OUTPUT/index.html" + log + LIMIT=0 html "$sfeedpath"/* >"$SFEED_OUTPUT/feeds.html" + log + log Generating RSS... + atom "$sfeedpath" >"$SFEED_OUTPUT/feeds.xml" + atom "$sfeedpath" 7 >"$SFEED_OUTPUT/feeds-short.xml" + log Generating OPML... + opml "$sfeedrc" >"$SFEED_OUTPUT/feeds.opml" + log Done. +} + +log() { + printf '%s\n' "$*" >&2 +} + +update() { + cmd="$(command -v sfeed_update_xargs || echo ./sfeed_update_xargs)" + "$cmd" "$@" +} + +opml() { + sfeed_opml_export "$@" +} + +html() { + converter="$(command -v sfeed_html.sh || echo ./sfeed_html.sh)" + "$converter" "$@" +} + +atom() ( # atom DIRECTORY [DAYS] + curd="$PWD" + cd "$1" || return 1 + if [ $# -eq 2 ]; then + old=$(($(date +%s) - ($2 * 24 * 3600))) + else + old=0 + fi + awk -F $'\t' -v old="$old" \ + 'BEGIN{OFS="\t"} int($1)>=old{$2="["FILENAME"] "$2;print}' \ + * | + sort -k1,1rn | + sfeed_atom +) + +sfeed_archive() ( # sfeed_archive FEED ... + for feed; do + awk -v old="$(($(date +%s) - (15 * 24 * 3600)))" \ + -F '\t' 'int($1) > old' <"$feed" >"$feed.new" + mv "$feed" "$feed.old" + mv "$feed.new" "$feed" + done +) + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/scripts/get-feed.sh b/scripts/get-feed.sh new file mode 100755 index 0000000..f000fee --- /dev/null +++ b/scripts/get-feed.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +main() { + # get-feed.sh URL(str) => DIRECTIVE(feed_directive) + url="$1" + wp="$(mktemp /tmp/get-feed.XXXXXX)" + curl -sL "$url" >"$wp" + case "$url" in + *html) # We know it's a webpage + type=html + ;; + *xml) # We know it's a feed + type=xml + ;; + *) # Not sure + type="$(head -n1 "$wp")" + ;; + esac + case "$type" in + *xml*) # a feed + title="$(get_title_xml <"$wp")" + output_feed "$title" "$url" + ;; + *html*) # a webpage + cat "$wp" | sfeed_web | cut -f1 | + while read u; do + title="$(curl -sL "$u" | get_title_xml)" + output_feed "$title" "$u" + done + ;; + *) + echo >&2 "Don't know type \"$type\"." + exit 1 + ;; + esac +} + +output_feed() { + ## output_feed TITLE(str) URL(str) => FEED_DIRECTIVE(str) + printf "feed \"%s\" '%s'\n" "$1" "$2" +} + +get_title_xml() { + ## get_title_xml < FILE => TITLE(str) + awk ' +// { channel = 1; }//{ channel = 0; } +channel && $0 ~ // { title = 1; } +title { + if (match($0,/<\/title>/)) title = 0; + gsub(/<\/?title>/,""); + sub(/^[ \t]*/,""); + sub(/[ \t]*$/,""); + print; +} +channel && $0 ~ /<\/title>/ { title = 0; } +' +} + +main "$@" diff --git a/sfeed_html.sh b/sfeed_html.sh new file mode 100755 index 0000000..58fce30 --- /dev/null +++ b/sfeed_html.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash + +echo() { printf '%s\n' "$*"; } + +html() { + : "${LIMIT:=1}" + aside="$(mktemp /tmp/sfeed_html_aside.XXXXXX)" + cat <<EOF +$(html_head) +<body> +<header> +<h1> +<a href="index.html"><img src="mars-eyes.png" + title="$(fortune)" + width="40" height="39" + alt="mars, but with eyes" /></a> +Planet ACDW</h1> +<p class="last-updated">last updated at <time>$(date -R)</time></p> +</header> +<nav> +<a href="feeds.html">all feeds</a> +// +<a href="feeds.xml">rss (full)</a> +// +<a href="feeds-short.xml">rss (short)</a> +// +<a href="feeds.opml">opml</a> +</nav> +<main> +$(html_main "$@") +<aside><ul>$(cat "$aside")</ul></aside> +</main> +</body> +</html> +EOF + rm "$aside" +} + +html_head() { + cat <<EOF +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<meta http-equiv="X-UA-Compatible" content="IE=edge"> +<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>Planet ACDW + + + + + + +EOF +} + +html_main() { + cat < +$(for file in "$@"; do html_feed "$file"; done) + +EOF +} + +html_feed() { # html_feed FEED(file) => HTML + filename="$(basename "$1")" + now="$(date +%s)" + fresh_days=7 + fresh_secs="$((fresh_days * 24 * 60 * 60))" + + ## ENTRIES + entries="$(awk -v NOW="$now" -v FRESH_SECS="$fresh_secs" \ + -v NAME="$filename" -v ASIDE="$aside" -v limit="$LIMIT" \ + 'BEGIN { FS="\t"; fresh_feed = 0; FRESH = (NOW - FRESH_SECS); } + function unescape(t) { + t = html_escape(t); + gsub(/\\\t/,"\t",t); + gsub(/\\\n/,"\n",t); + gsub(/\\\\/,"\\",t); + return t + } + function html_escape(t) { + gsub(//,"\\>",t); + gsub(/&/,"\\&",t); + return t + } + { + timestamp=$1; + title=html_escape($2); + link=$3; + content=unescape($4); + content_type=$5; + id=$6; + author=$7; + enclosure=$8; + category=$9; + + if (limit && (timestamp < (NOW - (FRESH_SECS * 3)))) next; + show_in_sidebar = 1; + #print timestamp, title, link > "/dev/stderr"; + + date_cmd = "date -d \"@" timestamp "\" +\"%F %R\"" + if (timestamp) { + date_cmd | getline ts; + close(date_cmd); + } + + fresh = (timestamp >= FRESH) + if (fresh) fresh_feed = 1; + + print "" + print "" ts "" + printf "%s", "" + if (enclosure) { + stamp = "@" + printf "%s", "" stamp "" + } + if ((link != id) && (id != enclosure) && (id ~ /^https?:/)) { + stamp = "#" + printf "%s", "" stamp "" + } + print "" + print "" title "" + print "" + } + END { + if (show_in_sidebar) { + printf "", (fresh_feed?" class=\"fresh\"":"") >> ASIDE + printf "%s\n", NAME, NAME >> ASIDE + } + printf "%s", (stamp ? stamp : ".") > "/dev/stderr" + }' "$1")" + if [ -z "$entries" ]; then return 1; fi + echo "
" + # TODO: Include a link back to the website + printf '

# %s

\n' "$filename" "$filename" + printf '%s
' "[back to top]" + echo "" + echo "$entries" + echo "
" + echo "
" +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + html "$@" +fi diff --git a/sfeed_update_xargs b/sfeed_update_xargs new file mode 100755 index 0000000..f8ee8e7 --- /dev/null +++ b/sfeed_update_xargs @@ -0,0 +1,45 @@ +#!/bin/sh +# -*- sh -*- +# update feeds, merge with old feeds using xargs in parallel mode (non-POSIX). + +# include script and reuse its functions, but do not start main(). +SFEED_UPDATE_INCLUDE="1" . sfeed_update +# load config file, sets $config. +loadconfig "$1" + +# process a single feed. +# args are: config, tmpdir, name, feedurl, basesiteurl, encoding +if [ "${SFEED_UPDATE_CHILD}" = "1" ]; then + sfeedtmpdir="$2" + _feed "$3" "$4" "$5" "$6" + exit $? +fi + +# ...else parent mode: + +# feed(name, feedurl, basesiteurl, encoding) +feed() { + # workaround: *BSD xargs doesn't handle empty fields in the middle. + name="${1:-$$}" + feedurl="${2:-http://}" + basesiteurl="${3:-${feedurl}}" + encoding="$4" + + printf '%s\0%s\0%s\0%s\0%s\0%s\0' "${config}" "${sfeedtmpdir}" \ + "${name}" "${feedurl}" "${basesiteurl}" "${encoding}" +} + +# fetch feeds and store in temporary directory. +sfeedtmpdir="$(mktemp -d '/tmp/sfeed_XXXXXX')" +mkdir -p "${sfeedtmpdir}/feeds" +touch "${sfeedtmpdir}/ok" +# make sure path exists. +mkdir -p "${sfeedpath}" +# print feeds for parallel processing with xargs. +feeds | SFEED_UPDATE_CHILD="1" xargs -r -0 -P "${maxjobs}" -L 6 "$(readlink -f "$0")" +status=$? +# check error exit status indicator for parallel jobs. +test -f "${sfeedtmpdir}/ok" || status=1 +# cleanup temporary files etc. +cleanup +exit ${status} diff --git a/sfeedrc b/sfeedrc new file mode 100644 index 0000000..c5e3001 --- /dev/null +++ b/sfeedrc @@ -0,0 +1,225 @@ +# -*- sh -*- + +# SFEED="$HOME/.sfeed" +USER_AGENT='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0' +# sfeedpath="$SFEED/feeds" +maxjobs="$(nproc)" + +### Feeds ####################################################### + +feeds() { + feeds_planets + feeds_youtube + feeds_podcasts + feeds_friends + feeds_news + feeds_smolweb + feeds_comics + feeds_misc + feeds_me +} + +feeds_planets() { + feed "Planet Emacs" "https://planet.emacslife.com/atom.xml" + feed "Planet Lisp" "https://planet.lisp.org/rss20.xml" + feed "Planet Scheme" "https://planet.scheme.org/atom.xml" +} + +feeds_youtube() { + feed "3Blue1Brown" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCYO_jab_esuFRV4b17AJtAw' + feed "AB - Ancienne Belgique" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCUVAw2kdxJlcfCdEcdgXv5A' + feed "Abraham Moller" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCMfIwe2KHD2XoBO2lWqeFXg' + feed "Adam Ragusea" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC9_p50tH3WmMslWRWKnM7dQ' + feed "Babish Culinary Universe" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCJHA_jMfCvEnv-3kRjTCQXw' + feed "Baggers" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCMV8p6Lb-bd6UZtTc_QD4zA' + feed "Case Duckworth" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC92gRJdnUYklVu4pvj9n0Lw' + feed "Claire Saffitz x Dessert Person" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCvw6Y1kr_8bp6B5m1dqNyiw' + feed "Computerphile" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC9-y-6csu5WGm29I7JiwpnA' + feed "EBRPL Career Center" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCIvntuaxP7PyaJDeHE_9E8Q' + feed "EmacsConf and Emacs hangouts" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCwuyodzTl_KdEKNuJmeo99A' + feed "freeCodeCamp.org" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC8butISFwT-Wl7EV0hUK0BQ' + feed "Gavin Freeborn" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCJetJ7nDNLlEzDLXv7KIo0w' + feed "Henry Homesweet" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCZqjwc1Wy5t1rsviYYsJiYg' + feed "Howard Abrams" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCVHICXXtKG7rZgtC5xonNdQ' + feed "Ignite Talks" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCZotK8ZPTUNLMeW5Q6T0cKg' + feed "Jake B" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCBMMB7Yi0eyFuY95Qn2o0Yg' + feed "James Tomasino" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCbTp1BYjpuhDRG5OmgIT8iw' + feed "jan Misali" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCJOh5FKisc0hUlEeWFBlD-w' + feed "J Duckworth Animations" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCtAEaNVrNxAUy2VSRPD_PYQ' + feed "Jelle's Marble Runs" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCYJdpnjuSWVOLgGT9fIzL0g' + feed "John Kitchin" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCQp2VLAOlvq142YN3JO3y8w' + feed "karthik" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCbh_g91w0T6OYp40xFrtnhA' + feed "Ken Forkish" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCvVvFZd0e86bLbd5FdgYiUg' + feed "Lex Fridman" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCSHZKyawb77ixDdsGog4iWA' + feed "LockPickingLawyer" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCm9K6rby98W8JigLoZOh6FQ' + feed "Maangchi" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC8gFadPgK2r1ndqLI04Xvvw' + feed "Mike Zamansky" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCxkMDXQ5qzYOgXPRnOBrp1w' + feed "MIT OpenCourseWare" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCEBb1b_L6zDS3xTUrIALZOw' + feed "My Analog Journal" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC8TZwtZ17WKFJSmwTZQpBTA' + feed "Nat's What I Reckon" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCEFW1E8QzP-hKxjO2Rj68wg' + feed "Now You See It" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCWTFGPpNQ0Ms6afXhaWDiRw' + feed "Numberphile" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCoxcjq-8xIDTYp3uz647V5A' + feed "Philosophy Tube" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC2PA-AKmVpU6NKCGtZq_rKQ' + feed "PronunciationManual" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCqDSLtXeZsGc3dtVb5MW13g' + feed "Protesilaos Stavrou" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC0uTPqBCFIpZxlz_Lv1tk_g' + feed "RailCowGirl" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCj-Xm8j6WBgKY8OG7s9r2vQ' + feed "Simone Giertz" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC3KEoMzNz8eYnwBC34RaKCQ' + feed "Steve Yegge" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC2RCcnTltR3HMQOYVqwmweA' + feed "System Crafters" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCAiiOTio8Yu69c3XnR7nQBQ' + feed "Tasting History with Max Miller" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCsaGKqPZnGp_7N80hcHySGQ' + feed "Technology Connections" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCy0tKL1T7wFoYcxCe0xjN6Q' + feed "Too Many Zooz" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCtjXVqMVzBIgU0SO8AV0vPg' + feed "Townsends" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCxr2d4As312LulcajAkKJYw' + feed "Unitarian Church of Baton Rouge" 'https://www.youtube.com/feeds/videos.xml?channel_id=UClrqHvbiFM-1hn931ZmAPFw' + feed "Vulf" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCtWuB1D_E3mcyYThA9iKggQ' + feed "WFTDA: Women's Flat Track Derby Association" 'https://www.youtube.com/feeds/videos.xml?channel_id=UC7eMWpvytqd3gYAqxTl9w7g' + feed "Zach Anner" 'https://www.youtube.com/feeds/videos.xml?channel_id=UCPTVYxUoYWhNa8J7GzIGnyQ' +} + +feeds_podcasts() { + feed "Tilde Whirl Tildeverse Podcast" 'https://tilde.town/~dozens/podcast/rss.xml' + feed "trash cat tech cat" 'https://podcast.librepunk.club/tctc/ogg.xml' + feed "Hacker Public Radio" 'https://hackerpublicradio.org/hpr_ogg_rss.php' +} + +feeds_friends() { + # nihilazo + feed "lipu pi jan Niko" 'https://tilde.town/~nihilazo/index.xml' + # dozens + feed "chrismanbrown.gitlab.io (dozens)" 'https://chrismanbrown.gitlab.io/rss.xml' + feed "Dozens and Dragons" 'https://dozensanddragons.neocities.org/rss.xml' + feed "dozens: Society For Putting Things On Top Of Other Things" 'https://society.neocities.org/rss.xml' + feed "dozens: vgnfdblg" 'https://supervegan.neocities.org/feed.xml' + feed "dozens: backgammon" 'http://tilde.town/~dozens/backgammon/rss.xml' + feed "dozens: It's Pro Toad and Superb Owl" 'https://git.tilde.town/dozens/protoadandsuperbowl/raw/branch/master/feed.xml' + feed "dozens dreams" 'https://tilde.team/~dozens/dreams/rss.xml' + feed "dozens: write.as" 'https://write.tildeverse.org/dozens/feed/' + feed "dozens css art" 'http://tilde.town/~dozens/cssart/feed.xml' + ### + feed "Benjamin Wil" 'https://benjaminwil.info/feed.xml' + feed "(lambda (x) (create x))" 'http://lambdacreate.com/static/feed.rss' + feed "m455.casa" 'https://m455.casa/feed.rss' + feed "Oatmeal" 'https://eli.li/feed.rss' + feed "RSRSSS" 'https://envs.net/~lucidiot/rsrsss/feed.xml' + feed "Tomasino Blog" 'https://blog.tomasino.org/index.xml' + feed "Tomasino Labs" 'https://labs.tomasino.org/index.xml' + feed "Will's Blog" 'https://wflewis.com/feed.xml' + feed "Rick Carlino's Blog" 'https://rickcarlino.com/rss/feed.rss' + feed "Causal Agency" 'https://text.causal.agency/feed.atom' + feed "Benoit Joly" 'https://blog.benoitj.ca/posts/index.xml' + feed "p1k3::feed" 'https://p1k3.com/feed' + feed "linkbudz" 'https://linkbudz.m455.casa/feed.rss' + feed "Alex Schroeder" "https://alexschroeder.ch/wiki/feed/full/" + feed "Björn Wärmedal" "https://warmedal.se/~bjorn/atom.xml" + feed "a rickety bridge of impossible crossing" "https://bluelander.bearblog.dev/feed/" +} + +feeds_comics() { + feed "Cat and Girl" 'https://catandgirl.com/feed/' + feed "Dinosaur Comics!" 'https://qwantz.com/rssfeed.php' + feed "False Knees" 'https://falseknees.tumblr.com/rss' + feed "Saturday Morning Breakfast Cereal" 'https://www.smbc-comics.com/comic/rss' + feed "xkcd" 'https://xkcd.com/atom.xml' +} + +feeds_news() { + feed "tilde news: Private feed for acdw" \ + 'https://tilde.news/rss?token=FvdFj8rQkhrBy9j1yON1t6RYKDdcuG1MoUlyvRICmbgDGCf2JTWAEObDhdgt' + feed "Tildes Atom feed" 'https://tildes.net/topics.atom' + feed "NPR" "https://feeds.npr.org/1001/rss.xml" +} + +feeds_me() { + : +} + +feeds_smolweb() { + feed "~town friday postcard" 'https://tilde.town/~lucidiot/fridaypostcard.xml' + feed "Cosmic Voyage" 'https://cosmic.voyage/rss.xml' + feed "plan.cat" 'https://plan.cat/rss' +} + +feeds_misc() { + feed "Crystalverse" 'https://crystalverse.com/feed/' + feed "Hetzner" 'https://status.hetzner.com/en.atom' + feed "LOW-TECH MAGAZINE" 'https://feeds2.feedburner.com/typepad/krisdedecker/lowtechmagazineenglish' +} + +### Filter ###################################################### + +filter() { + case "$1" in + # Filter items based on feed name. + *NPR*) + sed 's@www\.npr\.org@text.npr.org@' + ;; + *) cat ;; + esac | + filter_add_empties | + filter_html_entities | + filter_embed_youtube | + filter_filter_links +} + +filter_add_empties() { + awk 'BEGIN{FS="\t";OFS=FS;} + { $2 = $2 ? $2 : "[empty]" } + { print $1,$2,$3,$4,$5,$6,$7,$8,$9; } +' +} + +filter_embed_youtube() { + # replace youtube links with embed links + sed 's@www.youtube.com/watch?v=@www.youtube.com/embed/@g' +} + +filter_filter_links() { + # shorten feedburner links and strip tracking parameters and pixels + awk -F '\t' 'BEGIN { OFS = "\t"; } + function filterlink(s) { + # protocol must start with http, https or gopher. + if (match(s, /^(http|https|gopher):\/\//) == 0) { + return ""; + } + # shorten feedburner links. + if (match(s, /^(http|https):\/\/[^\/]+\/~r\/.*\/~3\/[^\/]+\//)) { + s = substr($3, RSTART, RLENGTH); + } + # strip tracking parameters + # urchin, facebook, piwik, webtrekk and generic. + gsub(/\?(ad|campaign|fbclid|pk|tm|utm|wt)_([^&]+)/, "?", s); + gsub(/&(ad|campaign|fbclid|pk|tm|utm|wt)_([^&]+)/, "", s); + gsub(/\?&/, "?", s); + gsub(/[\?&]+$/, "", s); + return s + } + { + $3 = filterlink($3); # link + $8 = filterlink($8); # enclosure + # try to remove tracking pixels: tags with 1px width or height. + gsub("]*(width|height)[[:space:]]*=[[:space:]]*[\"'"'"' ]?1[\"'"'"' ]?[^0-9>]+[^>]*>", "", $4); + print $0; + }' +} + +filter_html_entities() { + # convert HTML entities into dumb counterparts + awk '{ + gsub(/"/,"\""); gsub(/"/,"\""); + gsub(/'/,"'\''"); gsub(/'/,"'\''"); + gsub(/&/,"\\&"); # MUST BE LAST!; + print +}' +} + +# Fetch ######################################################### + +fetch() { # fetch(name, url, feedfile) + # return + curl -s -L \ + --max-redirs 3 \ + --header "'User-Agent: $USER_AGENT'" \ + --fail \ + --max-time 15 \ + "$2" +} diff --git a/style.css b/style.css new file mode 100644 index 0000000..0fa9d6b --- /dev/null +++ b/style.css @@ -0,0 +1,176 @@ +body { + background: #888; +} + +main { + position: relative; + display: flex; + flex: column nowrap; +} + +#items { + max-width: 70ch; +} + +.entries tr { + vertical-align: baseline; +} + +.fresh { + font-weight: bold; +} + +.entry-timestamp { + font-family: monospace; + padding-right: 8px; +} + +#list { + padding: 2ch; +} + +aside { + flex-grow: 1; +} + +aside li { + list-style: none; + text-align: right; +} + +aside li:nth-child(even), +tr:nth-child(even) +{ + background: inherit; +} + +aside li:nth-child(odd), +tr:nth-child(odd) +{ + background: #aaa; +} + +a { display: block; } + +a:link { + text-decoration: none; + color: black; +} +a:visited { + font-style: italic; + color: inherit; +} +aside a:visited { font-style: normal; } +a:hover { + background: yellow; +} +a:active{ + background: cyan; +} + +header { + display: flex; + flex: row wrap; + margin: 1ch 0; + align-items: baseline; + justify-content: space-between; +} +header h2, header h1 { + margin: 0; +} +header h2 a { display: inline; } +header .top { + font-size: 80%; + text-align: right; + flex-grow: 1; +} + +body>nav { + border-bottom: 1px solid black; + margin-top: 0; + text-align: right; +} +body>nav a { display: inline; } + +.last-updated { + font-style: italic; + font-size: 80%; + text-align: right; +} + +.entries { border-collapse: collapse; } + +.entries, .entry-title { width: 100%; } +.entry-extra a { color: blue; display: inline; } + +header a { display: inline; } + +@media screen and (max-width: 720px) { + main, header { + flex-flow: row wrap; + padding:0; margin: 0; + } + * { border: none; } + .entries, .entry-title { width: 100%; } + html, body, #list, #items { padding: 0; margin: 0; max-width: 100%; } + header h2, header p { margin: 4px; } + aside a { display: inline; } + aside { + padding: 2ch; + order: 1; + border-bottom: 1px solid black; + } + aside ul { margin: 0; padding: 0; } + #list { order: 2; } + aside li { + display: inline; + background: inherit !important; + } + aside li::after { + background: inherit; + font-weight: normal; + content: " //"; + } + aside li:last-child::after { + content: ""; + } + .entry-timestamp { + display: none; + } +} + +@media (prefers-color-scheme: light) { + body { background: white; color: black; } + aside li:nth-child(even), + tr:nth-child(even) { background: white; } + aside li:nth-child(odd), + tr:nth-child(odd) { background: #eee; } + a:link { color: black; } + a:hover { background: yellow; } + a:active { background: cyan; } + .entry-extra a { color: blue; } + body>nav { border-bottom: 1px solid black; } +} + +@media (prefers-color-scheme: dark) { + body { background: black; color: white; } + aside li:nth-child(even), + tr:nth-child(even) { background: black; } + aside li:nth-child(odd), + tr:nth-child(odd) { background: #222; } + a:link { color: white; } + a:hover { color: yellow; background: inherit; } + a:active { color: cyan; background: inherit; } + .entry-extra a { color: cyan; } + body>nav { border-bottom: 1px solid white; } +} + +@media screen and (max-width: 720px) and (prefers-color-scheme: dark) { + aside { border-bottom: 1px solid white; } + aside li { background: black !important; } +} + +@media screen and (max-width: 720px) and (prefers-color-scheme: light) { + aside { border-bottom: 1px solid black; } + aside li { background: white !important; } +} -- cgit 1.4.1-21-gabe81