Skip to content

Instantly share code, notes, and snippets.

@alifeee
Last active November 21, 2024 00:22
Show Gist options
  • Save alifeee/da157fcb92605d4fcdf3932aabcf56fe to your computer and use it in GitHub Desktop.
Save alifeee/da157fcb92605d4fcdf3932aabcf56fe to your computer and use it in GitHub Desktop.
CGI script to grab random RSS item from OPML export, hosted on https://server.alifeee.co.uk/do/randomrss.cgi
#!/bin/bash
# https://gist.github.com/alifeee/da157fcb92605d4fcdf3932aabcf56fe
# a CGI script to get a random RSS feed from an OPML file
# must have installed xmlstarlet
# $ sudo apt install xmlstarlet
echo "Content-type: text/html"
echo ""
cat <<HTML
<!DOCTYPE html>
<html>
<head>
<style>
.err {
color: red;
}
body {
font-family: monospace;
}
.feed {
font-size: 1rem;
}
pre {
padding: 0.25rem;
background: lightgray;
}
</style>
</head>
<body>
<header>
<h1>random feed picker</h1>
<p>by <a href="https://alifeee.co.uk/">alifeee</a>, <a href="https://gist.github.com/alifeee/da157fcb92605d4fcdf3932aabcf56fe">source</a></p>
</header>
<main>
<h2>upload an OPML file</h2>
<p>most (all!?) feed readers offer an "export" feature which will give you an OPML file containing links to all your feeds.</p>
<form enctype="multipart/form-data" method="post">
<input type="file" id="opmlfile" name="opml file">
<br>
<br>
<button type="submit">get random feed!</button>
</form>
</body>
</html>
HTML
# wait for POST request
if [[ "${REQUEST_METHOD}" != "POST" ]]; then
exit 0
fi
# else we deal with the post request
echo '<h2>random picker results</h2>'
# get in_file from multipart/form-data
in_file=$(cat /dev/stdin)
length=$(echo "${in_file}" | wc -l)
# remove top 3 and bottom 2 lines, form-data makes files look like this
opml=$(echo "${in_file}" | awk 'NR>4 && NR<'"$(( $length - 1 ))"'')
repl_html='s/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&#39;/g'
echo '<p>...got OPML file...</p>'
echo "<details><summary>...show file...</summary>"
echo '<pre>'
echo "${opml}" | sed "${repl_html}"
echo '</pre>'
echo "</details>"
# select many different things for the many formats of RSS feeds
# to find new ones, use `xmlstarlet el -a file.xml` to list XPATHs
declare -a XPATHS
# atom
# rss
XPATHS=( \
"//entry/link/@href" \
"//item/link" \
)
IFS="|"; XPATH="${XPATHS[*]}"
{ echo "${opml}" | xmlstarlet val - > /dev/null; } || { echo "<p class=err>opml feed was invalid :(</p>"; exit 1; }
feed_urls=$(echo "${opml}" | xmlstarlet sel -t -v '//@xmlUrl')
echo "<p>...found" $(echo "${feed_urls}" | wc -l) "urls...</p>"
echo "<details><summary>...show feed urls...</summary>"
echo '<pre>'
echo "${feed_urls}" | sed "${repl_html}"
echo '</pre>'
echo "</details>"
random_url=$(echo "${feed_urls}" | sort --random-sort | head -n1)
# for testing
#random_url="https://sometimes.digital/feed.xml"
echo "<p>your random feed is <a href='${random_url}'>${random_url}</a>!</p>"
# -L to follow redirects
feed_xml=$(curl -s -L "${random_url}")
echo '<p>...got feed file...</p>'
echo "<details><summary>...show file...</summary>"
echo '<pre>'
echo "${feed_xml}" | sed "${repl_html}"
echo '</pre>'
echo "</details>"
if [ -z "${feed_xml}" ]; then
echo "<p class=err>feed was empty :(</p>"
exit 1
fi
# validate XML
{ echo "${feed_xml}" | xmlstarlet val - > /dev/null; } || { echo "<p class=err>feed was invalid :(</p>"; exit 1; }
feed_items=$(\
echo "${feed_xml}" | sed -E 's/ xmlns[^"=]*="[^"]*"//g' \
| xmlstarlet sel -t -v "${XPATH}" \
| sort | uniq
)
echo "<p>...in the feed I find" $(echo "${feed_items}" | wc -l) "items...</p>"
echo "<details><summary>...show item urls...</summary>"
echo '<ul>'
echo "${feed_items}" | sed "${repl_html}" | awk '{printf "<li><a href=\"%s\">%s</a></li>", $0, $0}'
echo '</ul>'
echo "</details>"
echo "<p>your random item is...</p>"
random=$(echo "${feed_items}" | sort --random-sort | head -n1)
echo "<p class=feed><a href=${random}>${random}</a></p>"
cat <<HTML
<p>to get another feed, refresh the page! (hopefully you will be asked to resubmit the form)</p>
<p>or return to <a href="./randomrss.cgi">form</a></p>
</main>
</body>
</html>
HTML
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment