Aus Linux-Magazin 01/2011

Perl-Skript analysiert Konversationsinhalte beim Chat

© Paul Georg Meister, Pixelio.de

Ein Lausch-Bot in einem IRC-Kanal springt bei bestimmten Schlüsselwörtern an und benachrichtigt einen definierten User via Instant Messaging über das Gehörte.

Open-Source-Projekte wie Catalyst bieten Support über IRC-Kanäle an, in denen Fachleute auf Nutzeranfragen warten. Allerdings stört andauerndes IRC-Gedudel die Konzentration der Helfer, wenn sie nebenbei arbeiten. Oft dreht sich die Diskussion auch um nebensächliche Dinge. Der heute vorgestellte Perl-Bot lauscht auf einem IRC-Kanal und benachrichtigt seinen Herrn und Meister, falls bestimmte Schlüsselwörter fallen.

Der erste Teil der Aufgabe, die Erstellung eines IRC-Bot, 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?

Chat über Web-API

Instant Messaging mit aufpoppenden Dialogfenstern bietet sich an, und der Allround-Client Pidgin offeriert die gängigen Protokolle wie Yahoo Messenger, Google Talk, AIM oder MSN.

Vor einiger Zeit öffnete Yahoo seinen Messenger-Service über ein Web-API [3], auf dem sich der User zunächst einloggt und mittels HTTP-Requests Nachrichten mit anderen Yahoo-Messenger-Nutzern austauscht. Das vorgestellte Bot-Skript »irc2ym« klinkt sich in einen IRC-Kanal ein, wartet zunächst und lauscht, meldet und leitet weiter (Abbildungen 1 bis 3).

Abbildung 1: Der Bot »ymbot« verhält sich still, da niemand ein Schlüsselwort erwähnt hat.

Abbildung 1: Der Bot »ymbot« verhält sich still, da niemand ein Schlüsselwort erwähnt hat.

Abbildung 2: Ein IRC-Teilnehmer erwähnt »cpan« und der Flüsterer benachrichtigt den User.

Abbildung 2: Ein IRC-Teilnehmer erwähnt »cpan« und der Flüsterer benachrichtigt den User.

Abbildung 3: Der Bot hat die Nachricht an den Yahoo-Messenger-User weitergeleitet.

Abbildung 3: Der Bot hat die Nachricht an den Yahoo-Messenger-User weitergeleitet.

Erwähnt einer der Chat-Teilnehmer ein Schlüsselwort aus der Datei »~/.irc2ym-keywords« (Abbildung 4), wirft der Bot das Skript »ymsend« an, das sich auf dem 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 dem IRC-Kanal zuwendet und dort sein Fachwissen zur allgemeinen Verfügung stellt.

Abbildung 4: Die Liste der Schlüsselwörter, auf die der Flüsterer anspringt.

Abbildung 4: Die Liste der Schlüsselwörter, auf die der Flüsterer anspringt.

Nachrichten ausschnüffeln

Listing 1 leitet eine Klasse »YMBot« von der Basisklasse »Bot::BasicBot« ab und überlädt deren Methode »said()«, die der Bot immer aufruft, wenn ein User in einem IRC-Kanal etwas zum Besten gibt. Der zweite Parameter ist eine Datenstruktur, die unter dem Schlüssel »who« den Benutzernamen des Users und unter »body« den Text der Nachricht führt.

Listing 1:
»irc2ym«

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();

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üsselwörter 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 dem Web-API ein, führt einige Autorisierungsschritte nach dem Oauth-Protokoll aus und schickt schließlich den Nachrichtentext an den in der Variablen »$recipient« kodierten User.

Die Interaktion mit dem Yahoo-Web-API 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.

Oauth-Dschungel

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 dazu, eine Stunde lang Nachrichten ins IM-Netzwerk zu senden und Antworten zu empfangen [5]. Da das Skript aber selten anspringt und sich nach dem Abschicken der Nachricht gleich wieder beendet, brächte die Speicherung des Token kaum Vorteile. So authentifiziert es sich auf Yahoos Login-Seite jedes Mal kurzerhand neu mit Username und Passwort (hart kodiert als »$user« und »$password« in Listing 2), holt sich einen neuen Access-Token ab und führt damit den Sendebefehl auf dem Web-API aus.

Listing 2:
»ymsend«

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 }

In Zeile 34 von Listing 2 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 zugehörigen Geheimstring »secret« an die nächste URL, also »$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 einer URL mit Formparametern vorliegen.

Die Kombination aus Token und Secret identifiziert die Applikation als vom User autorisiert gegenüber dem Webservice. Das Skript »ymsend« leitet sie dann 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 die Kodierung des Request im Json-Format nach:

{ message : "Die  Nachricht" }

Was, falls der Nachrichtentext ebenfalls Anführungszeichen enthält, eine saubere Kodierung dieser Sonderzeichen erfordert, »qquote()« erledigt das.

Um einen Auth-Token mit Secret für die neu geschaffene Applikation, also das Skript »ymsend«, zu erzeugen, klickt sich 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.

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, etwa »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«.

Nach dem Abnicken der Nutzungsbedingungen erscheinen unmittelbar die zum Basteln des neuen Messenger-Clients notwendigen Keys (Abbildung 6). Der Benutzer fügt sie dann mittels Copy&Paste in die Strings der Zeilen 12 und 13 des Skripts »ymsend« ein. In Zeile 9 platziert er zusätzlich noch das Passwort des Messenger-Accounts, der die Nachricht initiiert.

Abbildung 6: Die fertigen API-Keys zum Basteln des Y!-Messenger-Clients.

Abbildung 6: Die fertigen API-Keys zum Basteln des Y!-Messenger-Clients.

Dann muss der Anwender nur noch eine Liste mit Schlüsselwörtern in »~/.irc2ym-keywords« anlegen und den Bot »irc2ym« starten. Es kann bis zu 20 Sekunden dauern, bis er sich auf einem vielgenutzten IRC-Server in den eingestellten Kanal eingewählt hat. Fällt ein Schlüsselwort im Chat, springt »ymsend« an und die Nachricht sollte über das Messenger-Protokoll bei dem voreingestellten und hoffentlich zu diesem Zeitpunkt auch eingeloggten Nutzer »$recipient« eintreffen. (jcb)

Infos

[1] Listings: [ftp://www.linux-magazin.de/pub/listings/magazin/2011/01/Perl]

[2] Michael Schilli, “Der Protokollant”:[https://www.linux-magazin.de/Heft-Abo/Ausgaben/2009/11/Der-Protokollant]

[3] Yahoo Messenger, IM-API: [http://developer.yahoo.com/messenger/guide/ch02.html]

[4] Oauth: [http://en.wikipedia.org/wiki/Oauth]

[5] Dokumentation zum Auth-Token:[http://developer.yahoo.com/messenger/guide/chapterintrotomessengersdk.html]

Der Autor


Michael Schilli arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. Er hat die Bücher “Goto Perl 5” (auf Deutsch) und “Perl Power” (auf Englisch) für Addison-Wesley geschrieben und ist unter [mschilli@perlmeister.com] zu erreichen.

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