Ein Lausch-Bot in einem IRC-Kanal springt bei bestimmten Schlüsselwörtern an und benachrichtigt den User über Instant Messaging.
Open-Source-Projekte wie Catalyst bieten ihren Support über IRC-Kanäle an, auf denen hochkarätige Fachleute auf Nutzeranfragen warten und dann sofort mit Rat und Tat zu Hilfe eilen. Allerdings beeinträchtigt andauerndes IRC-Gedudel die Konzentration arbeitender Geistesmenschen. Bei vollem Kanal steht die Konversation selten still und oft geht es um völlig unnütze Dinge. Der heute vorgestellte Perl-Bot lauscht auf einem IRC-Kanal und benachrichtigt seinen Herrn und Meister, falls bestimmte Schlüsselwörter, wie zum Beispiel der Name desselben, fallen.
Abbildung 1: Der Bot ymbot verhält sich still, da niemand ein Schlüsselwort erwähnt hat. |
Der erste Teil der Aufgabe, die Erstellung eines IRC-Bots, geht sehr einfach von der Hand, denn das schon einmal in [2] vorgestellte CPAN-Modul Bot::BasicBot stellt ein einfach erweiterbares Framework für IRC-Bots aller Art bereit. Doch wie erregt der Bot die Aufmerksamkeit seines in tiefe Gedanken versunkenen Users? Instant Messaging mit aufpoppenden Dialogfenstern bietet sich an, und der Allround-Client Pidgin bietet die gängige Protokolle wie Yahoo Messenger, Google Talk, AIM oder MSN an.
Vor einiger Zeit öffnete Yahoo seinen Messenger-Service über eine Web-API,
auf der sich der User zunächst einloggt und dann mittels HTTP-Requests
Nachrichten mit anderen
Yahoo-Messenger-Nutzern austauschen kann. Das vorgestellte
Bot-Script irc2ym
klinkt sich in einen IRC-Kanal ein, wartet zunächst
einmal still und lauscht. Erwähnt einer der Chat-Teilnehmer ein Schlüsselwort
aus der Datei ~/.irc-keywords (Abbildung 4), wirft der Bot das Skript
ymsend
an, das sich auf der Messenger-Web-API einloggt und die
aufgeschnappte Textnachricht an einen voreingestellten
Messenger-Account weiterleitet. Dies alarmiert den in Gedanken versunkenen
User, der seine Arbeit unterbricht, sich dann dem IRC-Kanal zuwendet und
dort sein Fachwissen beisteuert, um ahnungslosen Neulingen auf die Sprünge
zu helfen.
Abbildung 2: Der IRC-Teilnehmer 'hubbelquadrat' erwähnt das Schlüsselwort "cpan" und der Flüsterer benachrichtigt den User über Y!Messenger. |
Abbildung 3: Der Bot hat die Nachricht an den Y!Messenger-User weiter geleitet. |
Abbildung 4: Die Liste der Schlüsselwörter, auf die der Flüsterer anspringt. |
Listing 1 leitet eine Klasse YMBot von der Basisklasse Bot::BasicBot
ab und überlädt deren
Methode said()
, die der Bot immer dann aufruft, wenn ein User in einem
IRC-Kanal etwas zum Besten gibt. Als zweiten Parameter reicht der Bot
eine Datenstruktur hinein, die unter dem Schlüssel who
den Benutzernamen
des Users und unter body
den Text der Nachricht führt.
In diesem Callback ruft der Bot in Zeile 24 die weiter unten
definierte Funktion keyword_match()
auf, die den Nachrichtentext
mit einer Liste vorher eingelesener Schlüsselworte aus der Datei
~/.irc2ym-keywords
vergleicht. Das Skript liest deren Zeilen
zu Beginn in den globalen Array @KEYWORD_LIST
ein.
Passt einer der im Array @KEYWORD_LIST
gespeicherten regulären Ausdrücke, triggert Zeile 25 das im selben
Verzeichnis liegende Skript ymsend
. Dieses nimmt den Nachrichtentext
auf der Kommandozeile entgegen, loggt sich auf der Web-API ein, führt
einige Autorisierungsschritte nach dem OAuth-Protokoll aus und schickt
schließlich den Nachrichtentext an den in Zeile 10 in der Variablen
$recipient
hartkodierten User.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use local::lib; 04 05 ########################################### 06 package YMBot; 07 ########################################### 08 use base qw( Bot::BasicBot ); 09 use FindBin qw($Bin); 10 11 my $ymsend = "$Bin/ymsend"; 12 my($home) = glob "~"; 13 my $KEYWORD_LIST_FILE = 14 "$home/.irc2ym-keywords"; 15 my @KEYWORD_LIST = (); 16 17 keyword_list_read(); 18 19 ########################################### 20 sub said { 21 ########################################### 22 my($self, $data) = @_; 23 24 if( keyword_match( $data->{body} ) ) { 25 my $rc = system($ymsend, 26 "$data->{who} said: '$data->{body}'"); 27 warn "$ymsend failed: $!" if $rc; 28 } 29 30 return $data; 31 } 32 33 ########################################### 34 sub keyword_list_read { 35 ########################################### 36 if( !open FILE, "<$KEYWORD_LIST_FILE" ) { 37 warn "$KEYWORD_LIST_FILE not found"; 38 return; 39 } 40 41 while(<FILE>) { 42 chomp; 43 s/#.*//; 44 next if /^\s*$/; 45 push @KEYWORD_LIST, $_; 46 } 47 close FILE; 48 } 49 50 ########################################### 51 sub keyword_match { 52 ########################################### 53 my($said) = @_; 54 55 for my $regex (@KEYWORD_LIST) { 56 return 1 if $said =~ /$regex/i; 57 } 58 return 0; 59 } 60 61 ########################################### 62 package main; 63 ########################################### 64 use Bot::BasicBot; 65 66 my $bot = YMBot->new( 67 server => "irc.freenode.com", 68 channels => ["#ymtest"], 69 nick => "ymbot", 70 name => "Relay to Y!M", 71 charset => "utf-8", 72 ); 73 74 $bot->run();
Die Interaktion mit der Yahoo-WebAPI erfordert vom Skript einige Bocksprünge mit dem voreingestellten Messenger-User, dessen Passwort, sowie einem auf developer.yahoo.com einzuholenden API-Key und einem "Shared Secret" für die Applikation.
Mit OAuth [4] gibt ein authentifizierter User einen Token an eine
Applikation weiter, die dann in seinem Namen für eine bestimmte Zeit
Aktionen ausführen kann. Im Fall des Messengers berechtigt der Token
die Applikation (also das Skript) dazu, eine Stunde lang Nachrichten ins
IM-Netzwerk zu senden und Antworten zu empfangen. Da das Skript aber
selten anspringt und sich nach dem Abschicken der Nachricht gleich
wieder beendet, brächte die Speicherung des Tokens kaum Vorteile. So
authentifiziert es sich auf Yahoos Login-Seite jedesmal kurzerhand neu
mit Username und Passwort (Hartkodiert als $user
und $password
in
ymsend
), holt sich einen neuen Access Token ab und führt damit
den Sendebefehl aus auf der Web-API aus.
In Zeile 34 loggt das Skript den User mit $user und $passwd auf der
in $login_url gespeicherten URL ein, von wo Yahoo im Body der
Antwort einen Request-Token zurückschickt. Diesen leitet das Skript dann
zusammen mit dem API-Key und einem dazugehörigen Geheimstring $secret
and die nächste URL, $auth_token_url, weiter, die daraus einen
Access-Token oauth_token
samt einem oauth_token_secret
fabriziert.
Die Antwort des Webservers kommt im Format field=value&field=value...
,
die das Skript in Zeile 64 einfach als query
in ein URI-Objekt einspeist
und die Methode query_form
dann parsen lässt -- das funktioniert,
da die Daten exakt wie in einem URL mit Formparametern vorliegen.
OAuth ermöglicht es dem User, sich auf der normalen Login-Seite eines
Anbieters anzumelden.
Die dort erstellte
Kombo aus Token und Secret identifiziert die Applikation als vom
User autorisiert gegenüber dem Webservice, der auf einer völlig anderen
Webseite liegen kann. Das Skript ymsend
leitet Token und Secret
an den Messenger-Webservice
unter der $session_url
weiter, die eine neue Messenger-Session
einläutet und den User $user
im Yahoo-Messenger-Netzwerk anmeldet.
Geschieht das, sehen andere IM-Nutzer den User in ihren Buddy-Listen
auftauchen und das Skript schickt ab Zeile 108 mit der POST-Methode
die vorher per Kommandozeile hereingereichte Nachricht an den in
$recipient
definierten (und hoffentlich eingeloggten) Messenger-Nutzer
ab.
Dieser letzte Schritt verlangt eine Kodierung des Requests im JSON-Format nach
{ message : "die nachricht" }
was, falls der Nachrichtentext ebenfalls Anführungszeichen enthält,
eine saubere Kodierung dieser Sonderzeichen erfordert. Die
Funktion qquote
aus dem CPAN-Modul erledigt das schnell und
unkompliziert.
001 #!/usr/local/bin/perl -w 002 use strict; 003 use LWP::UserAgent; 004 use Sysadm::Install qw(qquote); 005 use URI; 006 use JSON; 007 008 my $user = "zangzongzing"; 009 my $passwd = "*********"; 010 my $recipient = "mikeschi1li"; 011 012 my $api_key = "******************"; 013 my $secret = "*************"; 014 015 my $login_url = "https://login.yahoo.com/WSLogin/V1/get_auth_token"; 016 my $auth_token_url = "https://api.login.yahoo.com/oauth/v2/get_token"; 017 my $session_url = "http://developer.messenger.yahooapis.com/v1/session"; 018 my $message_url = "http://developer.messenger.yahooapis.com/v1/message/yahoo/$recipient"; 019 020 my($msg) = join ' ', @ARGV; 021 022 die "usage: $0 message" unless 023 length $msg; 024 025 my $ua = LWP::UserAgent->new(); 026 027 my $url = URI->new( $login_url ); 028 029 $url->query_form( 030 login => $user, 031 passwd => $passwd, 032 oauth_consumer_key => $api_key ); 033 034 my $resp = $ua->get( $url ); 035 036 if( $resp->is_error() ) { 037 die "Can't get request token: ", 038 $resp->message(), " ", $resp->content(); 039 } 040 041 my($request_token) = 042 ($resp->content() =~ /RequestToken=(.*)/); 043 044 $url = URI->new($auth_token_url); 045 046 $url->query_form( 047 oauth_consumer_key => $api_key, 048 oauth_nonce => int( rand 10000000 ), 049 oauth_signature => "$secret&", 050 oauth_signature_method => "PLAINTEXT", 051 oauth_timestamp => time(), 052 oauth_token => $request_token, 053 oauth_version => "1.0" 054 ); 055 056 $resp = $ua->get( $url ); 057 058 if( $resp->is_error() ) { 059 die "Can't get access token: ", 060 $resp->message(), " ", $resp->content(); 061 } 062 063 my $u = URI->new(); 064 $u->query( $resp->content() ); 065 my %form = $u->query_form; 066 067 $session_url = URI->new( $session_url ); 068 069 $session_url->query_form( 070 oauth_consumer_key => $api_key, 071 oauth_nonce => int( rand 10000000 ), 072 oauth_signature => 073 "$secret&$form{oauth_token_secret}", 074 oauth_signature_method => "PLAINTEXT", 075 oauth_timestamp => time(), 076 oauth_token => $form{oauth_token}, 077 oauth_version => "1.0" 078 ); 079 080 $resp = $ua->post( $session_url, 081 Content_Type => 082 'application/json; charset=utf-8', 083 Content => 084 q[ {"presenceState" : 0, 085 "presenceMessage" : "I'm alive!" }] ); 086 087 if( $resp->is_error() ) { 088 die "Can't get session: ", 089 $resp->message(), " ", $resp->content(); 090 } 091 092 my $data = from_json( $resp->content() ); 093 094 $message_url = URI->new( $message_url ); 095 096 $message_url->query_form( 097 oauth_consumer_key => $api_key, 098 oauth_nonce => int( rand 10000000 ), 099 oauth_signature => 100 "$secret&$form{oauth_token_secret}", 101 oauth_signature_method => "PLAINTEXT", 102 oauth_timestamp => time(), 103 oauth_token => $form{oauth_token}, 104 oauth_version => "1.0", 105 sid => $data->{sessionId}, 106 ); 107 108 $resp = $ua->post( $message_url, 109 Content_Type => 110 'application/json; charset=utf-8', 111 Content => 112 '{"message" : ' . qquote($msg) . ' }' 113 ); 114 115 if( $resp->is_error() ) { 116 die "Can't send message: ", 117 $resp->message(), " ", $resp->content(); 118 }
Um einen Consumer Key mit Secret für die neugeschaffene
Applikation, also das Skript ymsend
,
zu erzeugen, klickt der API-Entwickler auf
developer.yahoo.com durch "My Projects" und "New Project"
(Yahoo-Account erforderlich), woraufhin
die Pop-Up-Box in Abbildung 5 erscheint. Da es sich nicht um eine
Webapplikation im Browser handelt, sondern um einen Desktop-Client,
ist hier "Or an application using these APIs: BOSS, Contacts, Mail ..."
zu wählen.
Abbildung 5: Der Entwickler fordert einen Consumer Key für eine Desktop-Client-Applikation an. |
Im daraufhin erscheinenden Formular verpasst der Entwickler der Applikation ein Namenskürzel (z.B. "irc2ymessenger") und fügt als "Description" ein paar erläuternde Worte ein. Die Auswahlbox "Kind of Application" ist auf "Client/Desktop" (nicht "Web-based) zu stellen. Unter "Access Scopes" ist dann "This app requires access to private user data." zu wählen und unter dem Wust der aufklappenden Unterpunkte die Option "Read/Write" unter "Yahoo! Messenger" (Abbildung 7).
Abbildung 6: Der Entwickler fordert einen Auth-Token für eine Desktop-Client-Applikation an. |
Abbildung 7: Die Applikation verlangt Schreib/Lesezugriff auf Daten des Yahoo! Messenger. |
Nach dem Abnicken der Nutzungsbedingungen erscheinen dann unmittelbar die zum
Basteln des neuen Messenger-Clients notwendigen Keys. Diese werden dann
mittels cut-und-paste in die Strings der Zeilen 12 und 13 des Skripts ymsend
eingefügt. In Zeile 9 ist weiterhin das Passwort des Messenger-Accounts
(im Beispiel der User 'zangzongzing') anzugeben, der die Nachricht initiiert.
Ist noch keiner vorhanden, führt yahoo.com über den Link "Sign Up" zur
Account-Registrierung.
Abbildung 8: Die fertigen API-Keys zum Basteln des Y!-Messenger-Clients. |
Dann ist nur noch eine Liste mit Schlüsselwörtern in ~/.irc-keywords
anzulegen und der Bot irc2ym
zu starten. Es kann bis zu 20 Sekunden
dauern, bis er sich auf einem vielgenutzten IRC-Server in den eingestellten
Kanal eingewählt hat, aber dann erscheint der Bot unter dem Namen
ymbot
in der Anwesenheitsliste. Gängige IRC-Clients sind irssi
(im Terminalfenster) oder der Allrounder Pidgin.
Fällt ein Schlüsselwort, springt
ymsend
an und die Nachricht sollte über das Messenger-Protokoll beim
voreingestellten und zu diesem Zeitpunkt hoffentlich eingeloggten
IM-Nutzer $recipient in einem Dialogfenster eintreffen.
Zeit, den Newbies auszuhelfen, was macht man nicht alles!
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2011/01/Perl
Michael Schilli, "Der Protokollant" http://www.linux-magazin.de/Heft-Abo/Ausgaben/2009/11/Der-Protokollant
Yahoo! Messenger IM API http://developer.yahoo.com/messenger/guide/ch02.html
Dokumentation zum Erzeugen eines Auth-Tokens http://developer.yahoo.com/messenger/guide/chapterintrotomessengersdk.html
OAuth-Artikel auf Wikipedia: http://en.wikipedia.org/wiki/Oauth
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |