blog · git · desktop · images · contact & privacy · gopher


Feed-To-E-Mail auf Basis von Newsbeuter

Als ich noch hauptsächlich an einem Rechner unterwegs war, habe ich newsbeuter benutzt, um Feeds zu lesen. Das hat (fast) immer wunderbar funktioniert. Der größte Haken an dem Programm ist, dass es etwas ungemütlich wird, wenn man häufig den Rechner wechselt, was heute bei mir der Fall ist. Dann sind hier die Feeds schon als gelesen markiert, drüben aber noch nicht, am dritten Rechner wurde noch gar kein Refresh gemacht und so weiter.

Aus diesem Grund hatte ich mir vor langer Zeit ein Python-Skript unter Verwendung von feedparser geschrieben, um meine Feeds zu beobachten. Existierende Lösungen wie rss2email hatten mir damals nicht so zugesagt. Neue Items hat es mir per E-Mail zugeschickt, denn für E-Mails habe ich die notwendige Infrastruktur, um sie komfortabel auf mehreren Rechnern synchron zu halten. Außerdem hat dieser Ansatz den zusätzlichen Vorteil, dass ich auf den Clients kein extra Programm für Feeds mehr brauche: Es landet einfach alles in Mutt. Von der Feed-To-E-Mail-Methode will ich auch nicht mehr weg, denn das hat sich bewährt. Schließlich benutze ich E-Mails für alles mögliche (Geburtstagserinnerung, TODO-Reminder, allgemeine Notifications, …), da fügt sich das einfach sehr gut ein. Deswegen will auch nicht soetwas wie Tiny Tiny RSS verwenden (was auch gar nicht so „tiny“ ist, wie ich finde).

Eigentlich lief mein Skript ziemlich lange fehlerlos. Ich musste „lediglich“ von Zeit zu Zeit einen weiteren Sonderfall berücksichtigen oder noch einen Workaround für einen kaputten Feed einbauen. Vorhin ist dann wieder soetwas passiert: Bei zwei neuen Feeds hat sich das Skript am Encoding verschluckt. Selbstverständlich sind die Feeds nicht valide und werden auch vom W3C-Validator bemängelt, aber das hilft mir nunmal nicht weiter.

Das hat mich schon deutlich genervt. Ich hatte eigentlich keine Lust, das zu fixen. Schon gar nicht, wenn ich weiß, dass es andere Programme gibt – zum Beispiel Newsbeuter –, die für all diese Sonderfälle bereits gewappnet sind. Ich hatte also die Wahl: Entweder ich baue mein Skript ein weiteres Mal aus, bis es irgendwann die Komplexität von Newsbeuter erreicht, oder ich gehe zurück zu Newsbeuter. Von dem weiß ich, dass er funktioniert. Irgendwas musste ich mir nur noch überlegen, um nicht die schöne Feed-To-E-Mail-Funktionalität zu verlieren.

Der neue Ansatz sieht jetzt so aus, dass Newsbeuter bei mir auf dem Server die Feeds überwacht. Interaktiv starte ich das Programm aber nie. Stattdessen habe ich folgendes kleines Skript drumherum gebaut, was einmal pro Stunde von Cron aufgerufen wird:

#!/bin/bash

myfeedfrom='blabla@domain.tld'
myfeedto='blublu@domain.tld'
urls="$HOME"/.newsbeuter/urls
config="$HOME"/.newsbeuter/config
db="$HOME"/.newsbeuter/cache.db

[[ -f ~/.newsbeuter/to-mail.rc ]] && . ~/.newsbeuter/to-mail.rc

newsbeuter -u "$urls" -c "$db" -C "$config" -x reload

markread=no
for i in $(sqlite3 "$db" 'SELECT id FROM rss_item WHERE unread = "1";')
do
    title=$(sqlite3 "$db" "SELECT title FROM rss_item WHERE id = \"$i\";")
    content=$(sqlite3 "$db" "SELECT content FROM rss_item WHERE id = \"$i\";")
    itemurl=$(sqlite3 "$db" "SELECT url FROM rss_item WHERE id = \"$i\";")
    enclurl=$(sqlite3 "$db" "SELECT enclosure_url FROM rss_item WHERE id = \"$i\";")
    feedurl=$(sqlite3 "$db" "SELECT feedurl FROM rss_item WHERE id = \"$i\";")
    feedtitle=$(sqlite3 "$db" "SELECT rss_feed.title
        FROM rss_item
        LEFT OUTER JOIN rss_feed ON rss_item.feedurl = rss_feed.rssurl
        WHERE rss_item.id = \"$i\";" | sed 's/[^[:alnum:]]/ /g')

    {
        echo "* $itemurl"
        [[ -n "$enclurl" ]] && echo "* $enclurl"
        echo
        echo "$content" | elinks -dump -dump-width 72 -dump-charset utf-8 \
            -force-html -localhost 1 -no-connect 1 -no-home 1
        echo
        echo '--'
        echo "$feedurl"
    } | mail -r "$feedtitle <$myfeedfrom>" -s "$title" "$myfeedto"

    markread=yes
done

if [[ "$markread" == "yes" ]]
then
    sqlite3 "$db" 'UPDATE rss_item SET unread = "0" WHERE unread = "1";'
fi

Was da passiert:

– Nachtrag 25.09.2012: Das ursprüngliche Skript hatte einen schlimmen Bug: Es hat in $feedtitle auch nicht-alphanumerische Zeichen zugelassen. Dieser Fehler ist mir peinlich spät aufgefallen. De facto habe ich dadurch ziemlich viele Elemente meiner Feeds verpasst.

Dinge wie Doppelpunkte und Kommata sind nämlich gar nicht so gesund an dieser Stelle:

$ date | mail -r 'foo: <foo@bar.de>' -s test void
exim: bad -f address "foo: <foo@bar.de>": missing or malformed local part (expected word or "<")

$ date | mail -r 'foo, bar <foo@bar.de>' -s test void
A Sender: field is required with multiple addresses in From: field.
No such file or directory
"/home/void/dead.letter" 1/30
. . . message not sent.

Ärgerlich! Vielleicht ist das aber auch ein Zeichen dafür, dass ich mal meine Feeds ausmisten sollte.

Nun denn, weiter im Text.

Man kann hier diskutieren, ob ein Shellskript ein geeignetes Werkzeug ist, um mit der SQLite-Datenbank zu interagieren. Einen Schönheitspreis gewinnt das sicherlich nicht. Ich bin aber zu dem Schluss gekommen, dass es keine Rolle spielt, ob ich hier ein Shellskript schreibe oder Python, Ruby oder sonstwas verwende. Der Unterschied wäre rein kosmetischer Natur, die Performance ist in meinem Fall nicht so wichtig. Die eigentliche Hässlichkeit an meiner Lösung ist nämlich, dass ich direkt auf die Newsbeuter-Datenbank zugreife. Wenn sich dort etwas an den Tabellen ändert, fliegt mein Skript auseinander. Dieses Problem hätte ich aber in jeder Sprache und es ließe sich nur lösen, wenn der Newsbeuter selbst eine passende API für meinen Zweck anböte.

Folglich kann ich auch ein Shellskript nehmen. Im Endeffekt hat das Skript auch so gut wie keine eigene Logik und bedient im Wesentlichen andere Programme – und genau dafür sind Shellskripte da.

Einen echten Mehrwert gibt es für mich sogar: Ich sehe jetzt auch Enclosures. Die hatte ich bei meinem eigenen Feedreader ignoriert, weil es auch hier wieder sehr viele verschiedene Fälle gibt, wie etwas in einem Feed als Enclosure markiert werden kann. Das übernimmt nun Newsbeuter alles für mich.