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).
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.
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: |
|---|
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: |
|---|
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.
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.
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] |












