Aus Linux-Magazin 02/2014

IRC plus Dokumentation

© Charles Taylor, 123RF.com

Warum sollten Chaträume nur für Menschen da sein? Dominik Honnef zeigt, wie sich spezialisierte Bots in Wissensdatenbanken oder Ticketsysteme einbinden lassen und so die Arbeit erleichtern.

IRC, der Internet Relay Chat, existiert bereits seit 20 Jahren, kommt aber noch immer als beliebter Kommunikationskanal gleichermaßen in Open-Source-Projekten und Unternehmen zum Einsatz. Fast ebenso lange existieren Bots, die im Channel warten und auf die Kommandos der Nutzer reagieren. Der Artikel zeigt, wie die Bots sich nützlich machen, indem sie beim Verwalten von Tickets und Erstellen von Dokumentationen helfen.

Qual der Wahl

Wer einen IRC-Bot entwickeln möchte, muss nicht mehr Tcl lernen, um den Bot-Urahn Eggdrop [1] zu aktivieren: Frameworks für Bots gibt es in nahezu jeder Sprache (siehe Tabelle 1), häufig existiert sogar mehr als eines. Auch wenn sich der Artikel auf Cinch [2], ein in Ruby geschriebenes Framework unter MIT-Lizenz (Abbildung 1), konzentriert, lassen sich die hier vorgestellten Codebeispiele auf ähnliche Weise in anderen Sprachen umsetzen.

Tabelle 1

IRC-Bots (Auswahl)

Name des Bots

Programmiersprache

Lizenz

Webseite

Autumn

Ruby

Freeware

https://github.com/RISCfuture/autumn

Willie

Python

EFL

http://willie.dftba.net

PHP IRC-Bot

PHP

CC-BY-3.0

http://wildphp.com

Jsircbot

Javascript

GPLv2

http://code.google.com/p/jsircbot/

Java IRC-Bot

Java

GPLv2

http://sourceforge.net/projects/jircb/

Abbildung 1: Das Schema zeigt vereinfacht den Aufbau des Ruby-Bots Cinch.

Abbildung 1: Das Schema zeigt vereinfacht den Aufbau des Ruby-Bots Cinch.

Cinch bringt ein objektorientiertes API und ein modulares Plugin-System mit. Ein einzelner Bot kann dank vieler voneinander unabhängiger Plugins allerlei Aufgaben erfüllen. Einem IRC-Server erscheint Cinch, wie bei IRC-Bots üblich, als ein gewöhnlicher Client. Es spielt daher keine Rolle, auf welchem System der Bot läuft, ob auf demselben wie der IRC-Server oder fernab auf einer Entwicklermaschine. Fast egal ist auch, welche Serversoftware zum Einsatz kommt, wenn es sich um eine konforme Implementierung von IRC handelt.

Bot an Bord

Ausgehend von einer vorhandenen Ruby-Installation lässt sich Cinch über

gem install cinch

einspielen. Einen einfachen Bot namens »hallobot.rb« (Abbildung 2), der auf Begrüßungen der Form »!hallo« reagiert, stellt Listing 1 vor.

Listing 1

hallobot.rb

01 # -*- coding: utf-8 -*-
02 require "cinch"
03
04 class Greeter
05     include Cinch::Plugin
06
07     match /hallo$/, method: :greet
08     def greet(m)
09         m.reply "Sei gegrüßt"
10     end
11 end
12
13 bot = Cinch::Bot.new do
14     configure do |c|
15         c.nick = "UnserBot"
16         c.server = "Adresse des IRC-Servers"
17         c.channels = ["#Ein_Channel", "#Ein_anderer_Channel"]
18         c.plugins.plugins = [Greeter]
19     end
20 end
21
22 bot.start
23
Abbildung 2: Und ewig grüßt der Bot, hier in seiner einfachsten Fassung.

Abbildung 2: Und ewig grüßt der Bot, hier in seiner einfachsten Fassung.

Ein einfaches »ruby hallobot.rb« erweckt den Bot zum Leben, woraufhin er sich mit dem IRC-Server »Adresse des IRC-Servers« verbindet und die Kanäle »#Ein_Channel« und »#Ein_anderer_Channel« betritt. Tippt ein IRC-Nutzer ein »!hallo« in einem dieser Kanäle, reagiert der Bot mit einem eher indifferenten »Sei gegrüßt« .

Der Beispielcode besteht aus zwei Teilen: Den Hauptteil bildet die Klasse »Greeter« (Zeilen 04 bis 11). Jede Klasse repräsentiert ein einzelnes Plugin, das auf einen oder mehrere vom User abgesetzte Befehle reagiert, wobei ein solches Kommando mit einem Ausrufezeichen beginnen muss. Zum Matchen der Befehle greift der Bot-Entwickler zu regulären Ausdrücken. Die bestehen aus keiner, einer oder mehreren Capture Groups, deren Werte die angegebene Methode in Form von Argumenten entgegennimmt. So stellt zum Beispiel »/(\d+)/« ein einzelnes Argument bestehend aus einer oder mehreren Ziffern dar.

Der zweite Teil (Zeilen 13 bis 20) kümmert sich um die Konfiguration des Bots. Er erhält hier einen Namen und eine Adresse, über die er sich anmelden kann. Zeile 18 erklärt zudem, welche Plugins er verwenden soll. Da sich die Konfiguration der Datei im weiteren Verlauf des Artikels nicht ändert, folgen nun noch die Plugin-Klassen selbst. Ein Entwickler erweitert das Array der Plugins dann einfach um neue Exemplare, weitere Informationen zum API liefert die Dokumentation [3].

Github sucht Anschluss

Entwickler verbringen viel Zeit damit, Tickets zu bearbeiten. Kein Wunder also, dass sich auch ihre Konversationen häufig um diese drehen. Dieser Artikel macht es sich zur Aufgabe, das Ticketsystem von Github über einen Bot steuerbar zu machen. Ein Plugin soll das Öffnen, Schließen und Suchen von Tickets ermöglichen. Zugleich soll der Bot auf Verweise der Art »Repository/gh-Ticketnummer« in Nachrichten reagieren, indem er den Titel und Status des Tickets im Channel anzeigt.

Githubs API [4] basiert auf HTTP und Json und erlaubt sowohl lesenden als auch schreibenden Zugriff auf große Teile von Github – unter anderem auch auf Tickets. Zugriffe lassen sich mit »curl« auf der Konsole ausprobieren, Listing 2 fragt zum Beispiel das Ticket mit der Nummer »13069« aus dem Rails-Projekt ab.

Listing 2

Ticketabfrage mit curl

01 $ curl https://api.github.com/repos/rails/rails/issues/13069
02 {
03     "title": "Requires JSON gem version 1.7.7 or above as it contains an important security fix.",
04     "user": {
05         "login": "chancancode",
06         [...]
07     },
08     "labels": [],
09     "state": "closed",
10     "created_at": "2013-11-27T06:20:30Z",
11     "updated_at": "2013-11-27T10:07:54Z",
12     "closed_at": "2013-11-27T10:07:54Z",
13     "body": "See [here](https://github.com/flori/json/blob/master/CHANGES).",
14     [...]
15 }

Das (gekürzte) Beispiel zeigt, wie Github seine Informationen strukturiert, die auch der Bot benötigt, um Tickets anzuzeigen. Der Entwickler kann sowohl auf öffentliche Informationen (etwa auf Tickets von Open-Source-Projekten) als auch auf lokale Tickets zugreifen [5]. Im zweiten Fall muss er sich jedoch authentifizieren. Das API erlaubt dies wahlweise über O-Auth oder eine klassische HTTP-Authentifizierung, wobei diese für einen IRC-Bot meist ausreicht. Bei firmeninternen Installationen von Github gelingt der Zugriff auf das API über »http://IP-Adresse/api/v3/« .

Sag’s dem API

Im Kern kommuniziert das Plugin mit dem Github-API. Es ruft die Daten mittels HTTP ab und übersetzt Json in Ruby-Strukturen. Da die Standardbibliotheken von Ruby alles Nötige erledigen, fällt das Implementieren leicht.

Die Zeilen 7 bis 10 in Listing 3 übernehmen die Konfiguration des Plugins. »BaseURL« steht für die Basisadresse des Github-API und verlangt in firmeninternen Installationen nach einer Anpassung. Es folgen dann der »Benutzername« und das »Passwort« des Bots bei Github. Der sollte das Recht besitzen, auf die Tickets von Repositories zuzugreifen, aber nicht mehr Rechte als notwendig.

Listing 3

issues.rb

01 require "json"
02 require "net/http"
03
04 class GithubIssues
05     include Cinch::Plugin
06
07     BaseURL      = "api.github.com"
08     Organization = "Organisation"
09     Username     = "Benutzername"
10     Password     = "Passwort"
11
12     private
13     def request(uri, method, data = nil)
14         uri = URI("https://#{BaseURL}#{uri}")
15         Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
16             req = method.new(uri.request_uri)
17             req.basic_auth(Username, Password)
18             req.body = data
19             resp = http.request(req)
20             return JSON.parse(resp.body)
21         end
22     end
23 end

Um die Interaktion mit dem Bot zu vereinfachen, gilt die Annahme, dass alle Repositories bei Github zu einer einzelnen »Organisation« gehören. Die Methode »request()« in Zeile 13 wird von allen Funktionen des Plugins verwendet, um Anfragen an das API zu stellen. Sie setzt eine HTTP-Anfrage ab und konvertiert die Antwort, die in Json formatiert ist, in eine Struktur, die Ruby versteht. Das Argument »method« dient dazu, das eingesetzte HTTP-Kommando anzugeben. Github verwendet zum Beispiel »GET« , um Daten abzufragen, und »PATCH« , um sie zu bearbeiten.

Die Funktionen des Plugins erschöpfen sich darin, diese Methode zusammen mit den richtigen Argumenten aufzurufen. Der Entwickler ergänzt mit den Methoden aus den Listings 4 bis 6 in der Datei »issues.rb« die Klasse »GithubIssues« . Die ersten beiden Funktionen, sie öffnen und schließen Tickets, bestehen im Wesentlichen aus dem korrekten Aufruf der »request()« -Methode.

Listing 4

Ergänzung (1) zu issues.rb

01 # !gh issue open Repository Ticketnummer
02 match(/gh issue open ([^ ]+) (\d+)$/, method: :open_issue)
03 def open_issue(m, repo, id)
04     uri = "/repos/#{Organization}/#{repo}/issues/#{id}"
05     request(uri, Net::HTTP::Patch, '{"state": "open"}')
06     m.reply "Opened issue %s/gh-%d" % [repo, id]
07 end
08
09 # !gh issue close Repository Ticketnummer
10 match(/gh issue close ([^ ]+) (\d+)$/, method: :close_issue)
11 def close_issue(m, repo, id)
12     uri = "/repos/#{Organization}/#{repo}/issues/#{id}"
13     request(uri, Net::HTTP::Patch, '{"state": "closed"}')
14     m.reply "Closed issue %s/gh-%d" % [repo, id]
15 end

Um nach Tickets zu suchen (Abbildung 3), muss der Entwickler etwas mehr Aufwand betreiben. Zwar besteht die eigentliche Suche erneut aus einem einfachen Aufruf der »request()« -Methode, doch der Code sollte das Suchergebnis auch entsprechend präsentieren. Er soll nicht sämtliche Ergebnisse anzeigen, da dies bis zu 100 sein könnten, und er soll pro Ticket nur die wirklich relevanten Informationen anzeigen, zum Beispiel die Ticketnummer, den Titel, den Link zum Ticket und den Status (geöffnet oder geschlossen).

Abbildung 3: Der Bot durchsucht das Ticketsystem von Github gezielt nach bestimmten Tickets.

Abbildung 3: Der Bot durchsucht das Ticketsystem von Github gezielt nach bestimmten Tickets.

Im abschließendem Feature (Listing 6) werden alle Verweise der Art »Repository/gh-Ticketnummer« in Chat-Nachrichten aufgegriffen, um Titel und Status der referenzierten Tickets auszugeben. Rubys »scan()« -Methode greift alle Verweise einer Nachricht auf, sodass eine einzelne Nachricht auch mehrere Tickets erwähnen darf (Abbildung 4). Die Spezialoption »use_prefix: false« sorgt dafür, dass der Bot nicht nach einem Ausrufezeichen am Anfang des Befehls schaut.

Listing 6

Ergänzung (3) zu issues.rb

01 # Repository/gh-Ticketnummer
02 match(/[^ ]+\/gh-\d+/, method: :display_issue, use_prefix: false)
03 def display_issue(m)
04     m.message.scan(/([^ ]+)\/gh-(\d+)/) do |repo, id|
05         uri = "/repos/#{Organization}/#{repo}/issues/#{id}"
06         issue = request(uri, Net::HTTP::Get)
07         m.reply "[%s/gh-%d] %s (%s)" % [repo, id, issue["title"], issue["state"]]
08     end
09 end
Abbildung 4: Bei Eingabe von Ticketnummern spuckt der Bot die zugehörigen Titel aus.

Abbildung 4: Bei Eingabe von Ticketnummern spuckt der Bot die zugehörigen Titel aus.

Caveats

Das entwickelte Plugin erleichtert Entwicklern und Admins also die Arbeit über IRC-Chaträume, indem es gezielt Informationen zu bestimmten Tickets heraussucht. Allerdings bleibt Luft für Verbesserungen. So geht das Plugin davon aus, dass die Repositories und Tickets existieren und Githubs API nie Probleme hat. Kurz, es fehlt eine Fehlerbehandlung. Zudem ist die Anfragenzahl an Githubs API limitiert, nämlich auf 5000 pro Stunde. Sehr aktive Projekte und große Firmen müssten Anfragen cachen oder anderweitig die Limits einhalten.

Was weiß der Bot

Natürlich gibt es Logs der Konversationen im IRC, auf die sich bei Fragen verweisen lässt. Es ist aber auch möglich, im IRC selbst Wissen als FAQs zu sammeln und bei Bedarf abzurufen. Bots können solche FAQs verwalten. Inspiriert vom Infobot [6] kombinieren die elektronischen Helfer Textschnipsel – etwa Links oder Hinweise – mit abrufbaren Schlagwörtern. Speichert der Entwickler diese Fragen und Antworten in einer kompakten SQlite-Datenbank, können auch andere Tools auf die Daten zugreifen.

Die Implementierung der Wissensdatenbank funktioniert ähnlich wie die Github-Anbindung. Das Grundgerüst bildet der Aufbau einer Datenbankverbindung, dann folgen die eigentlichen Funktionen. Die Datenbank lässt sich zunächst über

gem install sqlite3

installieren. Listing 7 baut eine Verbindung zur SQlite-Datenbank auf, die sich im Homeverzeichnis des Benutzers befindet. Zusätzlich legt das Plugin des Bot-Entwicklers eine Tabelle mit drei Spalten für die ID, das Schlagwort und die assoziierte Information an, so diese noch nicht existiert. Dann kann er das Plugin ohne manuelle Intervention verwenden.

Listing 7

infobot.rb

01 require "sqlite3"
02
03 class Infobot
04     include Cinch::Plugin
05
06     DB = ENV["HOME"] + "/infobot.db"
07
08     def initialize(*args)
09         @db = SQLite3::Database.new(DB)
10         @db.execute("
11             CREATE TABLE IF NOT EXISTS infobot(
12             id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
13             term TEXT NOT NULL,
14             value TEXT NOT NULL
15         )")
16         super
17     end
18 end

Listing 8 implementiert zwei Methoden zum Erstellen und Abrufen von Informationen. Die Methode »remember()« kommt stets dann zum Einsatz, wenn jemand den Bot über »!rem Schlagwort = Die Information« mit einem neuen Info-Häppchen füttert oder er eine bestehende Information überschreibt, etwa um einen Fehler zu korrigieren.

Listing 8

Ergänzung zu infobot.rb

01 # !rem Schlagwort = Die Information
02 match(/rem ([^ ]+) = (.+)$/, method: :remember)
03 def remember(m, term, val)
04     @db.execute("INSERT OR REPLACE INTO infobot (term, value) VALUES (?, ?)", term, val)
05     m.reply("Okay, #{term} bedeutet von nun an #{val}")
06 rescue => e
07     exception(e)
08     m.reply("Fehler beim Merken von '#{term}'")
09 end
10
11 # !Schlagwort
12 match(/([^ ]+)$/, method: :lookup)
13 def lookup(m, term)
14     res = @db.get_first_value("SELECT value FROM infobot WHERE term = ?", term)
15     if res
16         m.reply(res)
17     end
18 end

Die zweite Methode, »lookup()« , aktiviert den Bot, sobald er eine Nachricht der Form »!Schlagwort« sieht. Kennt er »Schlagwort« , gibt er dessen Bedeutung aus, sonst schweigt er. Das ist klug, denn es könnte sich ja auch um den Befehl für ein anderes Plugin handeln. Mit nur zwei Methoden lassen sich Informationen also reproduzierbar speichern.

Eine typische Interaktion mit dem Bot sieht etwa wie in Listing 9 aus, das demonstriert, wie die IRC-Nutzer Informationen speichern und abrufen. Da die Daten zudem in einer schematisch simplen SQlite-Datenbank stecken, beschränkt sich der Zugang zum Wissen nicht auf den Bot. Ein Konsolen-Client könnte die Schlagwörter auflisten, Daten ließen sich aus bestehenden Quellen importieren, etwa aus Wikis. Aber: Ganze Paragraphen stören im IRC eher. Infobots zeichnet aus, dass sie kurze Sätze und keine Romane von sich geben.

Listing 9

Knowledge Base im IRC

01 <Max> !rem deploy = Beim Deployen muss unbedingt auf X und Y geachtet werden
02 <Susanne> !rem github_api = http://developer.github.com/v3/
03 [einige Tage später]
04 <Max> Hmm, wo war noch gleich die GitHub API Dokumentation...
05 <Max> !github_api
06 <Bot> http://developer.github.com/v3/

Fazit

Bereits 150 Zeilen Code reichen aus, um einen Chatraum in ein hilfreiches Werkzeug für Entwicklung und Dokumentation zu verwandeln. Es gibt weitere Möglichkeiten, über IRC-Bots die Arbeit zu erleichtern. Egal ob eine Trac-Anbindung, das Verwalten von Memos oder das Einchecken für Home-Office-Arbeiter – der Fantasie sind kaum Grenzen gesetzt. Da es zudem Bibliotheken für nahezu jede Sprache gibt, müssen die Entwickler nicht einmal umlernen, um einen Bot zu programmieren.

Listing 5

Ergänzung (2) zu issues.rb

01 # !gh issue search Repository Suchstring
02 match(/gh issue search ([^ ]+) (.+)/, method: :search_issue)
03 def search_issue(m, repo, query)
04     uri = "/search/issues?q=%s+repo:%s+user:%s" %
05         [URI.escape(query), repo, Organization]
06
07     res = request(uri, Net::HTTP::Get)
08     n = 3
09     total = res["total_count"]
10     if n > total
11         n = total
12     end
13
14     m.reply "Zeige %d von %d Tickets für '%s'" % [n, total, query]
15     res["items"][0...n].each_with_index do |issue, i|
16         # [123] Der Titel des Tickets https://github.com/... (open)
17         m.reply "[%d] %s <%s> (%s)" %
18             [issue["number"], issue["title"], issue["html_url"], issue["state"]]
19     end
20 end

Der Autor

Dominik Honnef ist seit zehn Jahren im IRC aktiv. Vor drei Jahren entwickelte er Cinch und hilft seitdem Leuten dabei, eigene Plugins zu entwerfen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben