21x9.org | System Administration | Home Automation | Smart Home
15.07.2023

IFTTT im Selbstbau

Ich kann mich an wenige Webdienste erinnern, die so verheißungsvoll gestartet sind und am Ende so enttäuschend waren, wie IFTTT1 (If This Than That). Die Idee war brilliant. Mit einfachsten "Wenn" => "Dann" Verknüpfungen kann man unterschiedlichste Geräte und Dienste miteinander Verknüpfen. Und das funktioniert auch heute noch wunderbar. Allerdings ist die Webseite irgendwie in die Jahre gekommen und der ganze Workflow fühlt sich an vielen Stellen etwas hakelig an. Und mit den Jahren sind auch irgendwie die Ideen gewachsen, was man mit einem solchen Tool alles machen könnte. Leider ist IFTTT da aber nie so richtig mitgewachsen. Ja, es sind etwas komplexere Möglichkeiten der Verknüpfung hinzugekommen, aber das konnte dennoch nicht so wirklich mit den Ideen mithalten. Die Anzahl der verfügbaren Hardware- und Webserviceintegration ist zwar über die Jahre immer weiter gewachsen, aber vieles davon gibt es nur auf dem US-Markt und/oder orientiert sich sehr an kommerziellen Produkten.

Das kostenlose Paket wurde mit der Zeit immer weiter eingedampft, bis man irgendwann kaum noch etwas sinnvolles damit tun konnte, dafür wurden die Preise für die kostenpflichtigen Pakete erhöht. Und auch wenn diese unterm Strich noch in Ordnung sind, habe ich mich irgendwann nach Alternativen umgeschaut und bin auf Huginn2 gestoßen.

Vom Userinterface her mag Huginn zunächst wie ein Rückschritt wirken, aber es ist auch ungleich mächtiger. Die Dokumentation könnte an einigen Stellen etwas besser sein, aber mit ein wenig herumprobieren, kommt man schnell zu brauchbaren Ergebnissen. Ich nutze Huginn hauptsächlich um RSS Feeds zu verabeiten, bzw. um Webseiten die keinen RSS-Feed anbieten auszulesen und mir daraus einen RSS-Feed selbst zu erzeugen. Man kann aber über MQTT auch mit seinen SmartHome-Geräten interagieren.

Die Bereitstellung von Huginn kann schnell und einfach mittels Docker erfolgen:

version: '2'

services:
  mysqldata:
    image: mysql:5.7
    command: /bin/true

  mysql:
    image: mysql:5.7
    restart: always
    env_file:
      - ../mysql.env
    volumes_from:
      - mysqldata

  web:
    image: ghcr.io/huginn/huginn-single-process
    restart: always
    ports:
      - "3000:3000"
    env_file:
      - ../mysql.env
      - ../secrets.env
    depends_on:
      - mysql

  threaded:
    image: ghcr.io/huginn/huginn-single-process
    command: /scripts/init bin/threaded.rb
    restart: always
    env_file:
      - ../mysql.env
      - ../secrets.env
    depends_on:
      - mysql
      - web

Vor dem Start erstellt man noch die Dateien mysql.env und secrets.env:

MYSQL_PORT_3306_TCP_ADDR=mysql
MYSQL_ROOT_PASSWORD=64zeichenrandom
HUGINN_DATABASE_PASSWORD=64zeichenrandom
HUGINN_DATABASE_USERNAME=root
HUGINN_DATABASE_NAME=huginn
APP_SECRET_TOKEN=128zeichenrandom

Nach einem docker-compose up sollte uns die Huginn Weboberfläche unter http://localhost:3000 zur Verfügung stehen (der Port sollte ggf. angepasst werden, falls wir auf demselben Server Grafana betreiben möchten). Der erste Start dauert eine Weile, die zahlreichen Fehlermeldungen während der Startvorgangs kann man ignorieren.

Wie schon erwähnt, kann man mit Huginn prima RSS-Feeds für Seiten erzeugen, die keinen eigenen Feed anbieten. Wer RSS3 nicht kennt, damit kann man Webseiten quasi als Newsticker nutzen. Sobald ein neuer Artikel veröffentlicht wird, werden dessen Daten (oft nur als Zusammenfassung, manchmal aber auch der ganze Artikel) auch im RSS-Feed veröffentlicht. Ein spezieller RSS-Reader4 fragt diesen Feed in regelmäßigen Abständen ab und sagt Bescheid, wenn es etwas neues gibt. Es ist mir sehr unerklärlich, warum RSS ein wenig aus der mode gekommen ist. aber zum Glück bieten viele seiten es immer noch an. Ich konsumiere fast alle meine Nachrichten darüber. aber zurück zum Thema.

Ein paar seiten bieten eben leider keine RSS-Feeds an. Aber mit Huginn kann man über den sog. Website Agent sehr einfach Webseiten laden und in ihre Bestandteile zerlegen. Die Konfiguration eines solchen Agents sieht dann z.B. so aus:

{
  "url": "https://what-if.xkcd.com",
  "mode": "on_change",
  "expected_update_period_in_days": "365",
  "extract": {
    "question": {
      "css": "#question",
      "value": "string(.)"
    },
    "attribute": {
      "css": "#attribute",
      "value": "string(.)"
    },
    "answer": {
      "xpath": "//*[@id=\"entry\"]/p[3]",
      "value": "string(.)"
    },
    "img": {
      "xpath": "//*[@id=\"entry\"]/img[1]",
      "value": "@src"
    }
  }
}

Im ersten Moment mag das sehr kryptisch aussehen, aber schauen wir uns das einfach mal im Einzelnen an:

  • Die url gibt die Seite an, die abgefragt werden soll. in diesem fall also https://what-if.xkcd.com/
  • Der mode gibt an, wann der Agent tätig werden soll. Hier on_change, also immer dann, wenn er eine Veränderung der seite feststellt
  • Die expected_update_period_in_days gibt an, wie oft wir mind. mit einer Aktivität des Agents rechnen (in diesem Fall also einer Änderung der überwachten Seite), löst der Agent länger keinen Event aus, dann wird er in der Übersicht von Huginn als defekt markiert
  • Im extract Abschnitt lesen wir die Teile der seite aus, die uns Interessieren. das geht ziemlich einfach, entweder über die ID5 eines HTML-Elements, die CSS-Klasse bzw. den CSS-Selector6 oder über den XPath7 unter 8 findet sich ein gutes Tutorial bezüglich XPath.

Der XPath sieht im ersten Moment manchmal etwas wild aus, aber man kann ihn meist einfach aus der Developerkonsole seines Browsers herauskopieren. Über die Developerkonsole gelangt man auch leicht an des CSS-Selector der gewünschten Elemente. Am einfachsten ist es natürlich auf Elemente über ihre ID zuzugreifen, dafür sind diese schließlich da. Leider setzen nicht alle Seiten diese Sinnvoll ein.

Aber schauen wir uns das einmal ganz Praktisch an: Wir wollen zunächst das Element mit dem CSS-Selector question finden und dessen value in Form eines Strings auslesen. string(.) heißt hier, dass man den Inhalt des HTML Tags mit dem CSS-Selector question ausliest:

    "question": {
      "css": "#question",
      "value": "string(.)"
    },

Im zweiten Schritt lesen wir das Element mit dem CSS-Selektor attribute aus. Ebenfalls ein String:

    "attribute": {
      "css": "#attribute",
      "value": "string(.)"
    },

Nun wollen wir einen Teil der Seite auslesen, den wir answer nennen. Dieser hat keinen CSS-Selektor, daher greifen wir hier auf den XPath zurück.

    "answer": {
      "xpath": "//*[@id=\"entry\"]/p[3]",
      "value": "string(.)"
    },

//*[@id="entry"]/p[3] sagt, dass es ein Element mit der ID entry gibt, dieses enthält mehrere <p> Tags, wir möchten den Inhalt des 3. <p> innerhalb des Elements entry auslesen, erneut ein string.

Außerdem wollen wir ein Bild aus dem Artikel extrahieren. Auch dieses befindet sich im Element entry und wir möchten diesmal das Erste img. Da ein <img>Ttag in html aber keinen Inhalt hat, müssen wir ein Attribut des Tags auslesen, nämlich src, daher geben wir hier bei value nicht string(.) an, sondern @src:

    "img": {
      "xpath": "//*[@id=\"entry\"]/img[1]",
      "value": "@src"
    }

Der Agent liest also bei jeder Veränderung der Seite die question, den Fragesteller (attribute), den Beginn der answer und das Erste img im Artikel aus. Diese Infos können wir jetzt in Huginn an den sog. DataOutputAgent weiterreichen. Dieser sieht wie folgt aus:

{
  "expected_receive_period_in_days": 365,
  "template": {
    "title": "What if?",
    "description": "serious answers to absurd questions and absurd advice for common concerns from xkcd's Randall Munroe",
    "item": {
      "title": "{{question}}",
      "description": "{{answer}}",
      "link": "{{img}}"
    }
  },
  "ns_media": "true",
  "secrets": [
    "whatif"
  ]
}

Relevant ist vor allem der Abschnitt template. Hier geben wir zunächst den title und die description für unseren RSS-Feed an. Dieser bleibt immer gleich. Im Abschnitt item werden dann die vom Website Agent gesammelten Informationen übergeben.

Der etwas irreführend benannte Abschnitt secrets gibt den Namen der RSS-Datei an. Die Adresse des Feeds wird in der Detailansicht des Data Output Agents angezeigt und kann dann einfach an den gewünschten RSS-Reader verfüttert werden. Neben dem normalen Feed in Form einer XML Datei, exportiert Huginn auch gleich noch eine JSON Entsprechung.

Der fertige RSS-Feed sieht dann in etwa so aus:

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
 <title>What if?</title>
 <description>serious answers to absurd questions and absurd advice for common concerns from xkcd's Randall Munroe</description>
 <link>https://localhost:3000</link>
 <lastBuildDate>Tue, 27 Sep 2022 14:30:25 -0700</lastBuildDate>
 <pubDate>Tue, 27 Sep 2022 14:30:25 -0700</pubDate>
 <ttl>60</ttl>
 <item>
  <title>My daughter recently received her driver's permit in the US, and aspires to visit mainland Europe someday. She has learned enough about the rules of the road to know never to drive into the ocean; however, she jokingly suggested that given a sufficient quantity of rental cars, she could eventually get to Europe by driving east repeatedly. The question is, how many vehicles would it take to build a car-bridge across the Atlantic?</title>
  <description>After extensive research, I can conclusively state that this would be a violation of your rental car agreement.</description>
  <link>https://what-if.xkcd.com/imgs/a/160/rental.png</link>
  <guid isPermaLink="false">2414</guid>
  <pubDate>Tue, 27 Sep 2022 14:30:10 -0700</pubDate>
 </item>
</channel>
</rss>

Der title ist so nicht ganz optimal, weil sehr lang, man könnte hier noch eine Anpassung am Data Output Agent vornehmen und statt:

  "template": {
    "title": "What if?",
    "description": "serious answers to absurd questions and absurd advice for common concerns from xkcd's Randall Munroe",
    "item": {
      "title": "{{question}}",
      "description": "{{answer}}",
      "link": "{{img}}"
    }
  },

folgende Angabe nutzen:

  "template": {
    "title": "What if?",
    "description": "serious answers to absurd questions and absurd advice for common concerns from xkcd's Randall Munroe",
    "item": {
      "title": "{{question | truncate: 64}}",
      "description": "{{question}} - {{answer}} - https://what-if.xkcd.com",
      "link": "{{img}}"
    }
  },

Hier würde durch | truncate: 64 der Titel auf max. 64 Zeichen gekürzt. Damit wir die Frage aber dennoch vollständig lesen können, packen wir sie zusammen mit dem Beginn der Antwort in die description. Außerdem fügen wir noch einen Link zur Webseite hinzu, denn der Link im Feed zeigt ja auf das erste extrahierte Bild. In der Praxis würde man hier eher den Link zur Seite selbst eintragen. ich wollte das hier nur im Beispiel nutzen, um noch einen Fall zu haben, bei dem ein Attribut eines HTML-Tags ausgelesen wird.


  1. https://www.ifttt.com ↩

  2. https://github.com/huginn/huginn ↩

  3. https://de.wikipedia.org/wiki/RSS_(Web-Feed) ↩

  4. https://de.ryte.com/wiki/RSS-Reader ↩

  5. https://www.w3schools.com/html/html_id.asp ↩

  6. https://www.w3schools.com/cssref/sel_class.php ↩

  7. https://www.w3schools.com/xml/xpath_syntax.asp ↩

  8. https://www.w3schools.com/xml/xpath_intro.asp ↩

Tags: docker huginn ifttt rss xpath css

Mehr

  • Serverüberwachung mit checkmk
  • Auf ins Fediverse mit Firefish
  • Lokaler DNS Server mit Pi-Hole
  • Docker
  • ZFS

Tags

docker huginn ifttt rss xpath css

Archiv

  • Mar 2025 (2)
  • May 2024 (2)
  • Oct 2023 (1)
  • Aug 2023 (5)
  • Jul 2023 (31)

  • Ältere Einträge (95)

Feeds

Atom 1.0 RSS JSON
  • Datenschutz
  • Impressum
  • Archiv