Börsenwecker (Linux-Magazin, September 2010)

Ein in Perl geschriebener Pidgin-Plugin ignoriert dröge Börsentage, an denen die Kurse stillstehen, aber schlägt per Instant Message Alarm, falls die Aktien anfangen, Achterbahn zu fahren.

Der flexible Instant-Messenger-Client Pidgin [1] läuft nicht nur auf grundsätzlich verschiedenen Plattformen with Linux und Windows, sondern unterstützt auch eine Vielzahl unterschiedener Instant-Messaging-(IM-)Protokolle. Ob Yahoo oder MSN, ob Google Talk oder IRC, Pidgin bringt alle unter einen Hut und vereinfacht die Kommunikation mit Online-Freunden, die unterschiedliche Angebote nutzen. Pidgins Plugin-Architektur trägt maßgeblich dazu bei, dass der Tausendsassa mit plötzlich geänderten Protokollen mithält und seinen Abdeckungsradius stetig erweitert.

Dabei ist es nicht einmal notwendig, Plugins in Pidgins Muttersprache C zu schreiben und als Shared Libs bereitzustellen. Auch in Hochsprachen wie Perl geschriebene Skripts bindet er klaglos ein, und falls sie mit Pidgins Glib-basierter Event-Loop zusammenarbeiten, dürfen sie sogar langwierige Operationen wie das Einholen von Webseiten ausführen, ohne dass Pidgins graphische Oberfläche zu ruckeln anfängt. Auf den ersten Blick erscheint es zwar verrückt, dass ein C-Programm zusammen mit einem Perlskript im Sprungseiltakt in derselben Eventschleife hüpft, es wird aber klarer, wenn man sich vergegenwärtigt, dass Perl letztlich auch nur ein Abstraktionslayer über einer C-Schicht ist und deshalb genausogut Events einhängen und empfangen kann.

Abbildung 1: Der Aktienmarktwecker alarmiert den User, weil die Google-Aktie um mehr als 2% gestiegen ist.

Abbildung 2: In der YAML-Datei setzt der User bestimmte Aktien auf eine Beobachtungsliste und gibt bei Bedarf Prozentpunkte vor.

Alarm bei Achterbahnfahrt

Der heute vorgestellte Pidgin-Plugin zeigt sich zunächst nicht, nimmt aber in regelmäßigen Abständen Kontakt mit einem Stockticker-Service auf und untersucht, ob einer der in einer Konfigurationsdatei abgelegten Aktienwerte anfängt, wild nach oben zu streben oder kräftig an Wert zu verlieren. Tritt dies ein, öffnet der Plugin ein Kommunikationsfenster zum eingeloggten User nach Abbildung 1 und zeigt die Gesamtliste aller überwachten Aktien an, samt den einzelnen prozentualen Tagesänderungen. Der User darf in der Konfigurationsdatei nach Abbildung 2 vorgeben, welche Aktienkürzel ihn interessieren und ab welcher prozentualen Änderung ein Weckruf erfolgen soll. Bei Zeilen, die lediglich das Aktienkürzel und keine Prozentzahl vorgeben, nimmt der Plugin automatisch 2% an.

Simpler Plugin

Der Plugin, den Pidgin lädt, falls das entsprechende Perl-Skript mit .pl-Endung ausführbar im richtigen Verzeichnis liegt und der User ihn anschließend im Plugin-Menü konfiguriert (siehe Abschnitt Installation), gestaltet sich mit 74 Zeilen relativ simpel, wie Listing 1 zeigt.

Er muss die von der Pidgin-API vorgeschriebenen Callbacks plugin_init() und plugin_load() definieren. Erstere springt Pidgin an, um Informationen über den Plugin einzuholen, um diese später im Menü Tools->Plugins anzuzeigen und den User per Mausklick entscheiden zu lassen, ob er den Plugin aktivieren möchte. Passiert dies, kommt der Callback plugin_load ab Zeile 43 zum Zug, der die Aufgaben des Plugins definiert. Deaktiviert der User den Plugin wieder, erledigt dieser in plugin_unload() eventuell noch letzte Aufräumarbeiten, bevor Pidgin ihn aus dem Speicher wirft.

Das weiter unten beschriebene Modul WatchQuotes liest die Konfigurationsdatei, sobald Zeile 50 im Plugin dessen init()-Methode aufruft. Zeile 51 startet anschließend den kontinuierlich arbeitenden Überwachungsprozess mit dem Aufruf der Methode watch(). Ihr überreicht das Plugin-Skript eine Referenz auf den ab Zeile 55 definierten Callback quotes_update, der eine ihm übergebene Nachricht in eine IM-Konversation verpackt und dem verblüfften User schickt.

Listing 1: pidgin-stockwatch.pl

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 
    04 use Pidgin;
    05 use local::lib;
    06 use Glib;
    07 use WatchQuotes;
    08 
    09 our %PLUGIN_INFO = (
    10   perl_api_version => 2,
    11   name        => "Pidgin Stockwatch",
    12   summary     => "Stock Alert via IM",
    13   version     => "1.0",
    14   author      => "Mike Schilli " .
    15                  "<m\@perlmeister.com>",
    16   load        => "plugin_load",
    17   unload      => "plugin_unload",
    18 );
    19 
    20 our $USER     = "mikeschilli";
    21 our $PROTOCOL = "prpl-yahoo";
    22 
    23 our $WATCH_QUOTES = WatchQuotes->new();
    24 
    25 ###########################################
    26 sub plugin_init {
    27 ###########################################
    28   return %PLUGIN_INFO;
    29 }
    30 
    31 ###########################################
    32 sub plugin_unload {
    33 ###########################################
    34   my($plugin) = @_;
    35 
    36   Purple::Debug::info("stockwatch", 
    37       "Plugin unloaded.\n");
    38 
    39   1;
    40 }
    41 
    42 ###########################################
    43 sub plugin_load {
    44 ###########################################
    45   my($plugin) = @_;
    46 
    47   Purple::Debug::info("stockwatch", 
    48       "Plugin loaded.\n");
    49 
    50   $WATCH_QUOTES->init();
    51   $WATCH_QUOTES->watch( \&quotes_update );
    52 }
    53 
    54 ###########################################
    55 sub quotes_update {
    56 ###########################################
    57     my($msg) = @_;
    58 
    59     Purple::Debug::info("stockwatch", 
    60         "Updating Quotes.\n");
    61 
    62     my $account = Purple::Accounts::find(
    63         $USER, $PROTOCOL);
    64 
    65     my $conv = Purple::Conversation->new(
    66         1, $account, $USER);
    67 
    68       # user not online?
    69     return unless defined $conv; 
    70 
    71     $conv->get_im_data->write( 
    72         $PLUGIN_INFO{name}, $msg, 
    73         0, time );
    74 }

Dazu findet die Methode find() eines Purple::Account-Objektes in Zeile 62 den hoffentlich eingeloggten User, und Zeile 65 baut zu diesem eine Konversation auf, die sich in einem Objekt der Klasse Purple::Conversation manifestiert. Ging dabei etwas schief, zum Beispiel weil der festgelegte User gar nicht auf dem angegebenen Service eingeloggt war, bricht Zeile 69 ab und die Nachricht kommt nie ans Licht.

Geht alles gut, schickt die in Zeile 71 aufgerufene write()-Methode die Nachricht ab und egt als Sender willkürlich den Namen des Plugins fest. Der letzte Parameter setzt mit der Perl-Funktion time() noch die aktuelle Uhrzeit, die Pidgin neben jeder Nachricht anzeigt.

Alarm auf den Schirm

Die Variablen $USER und $PROTOCOL in den Zeilen 20 und 21 definieren den im Alarmfall zu benachrichtigende Usernamen und den dabei verwendeten IM-Service, im vorgegebenen Fall "prpl-yahoo", also Yahoos Messenger-Protokoll.

Was genau treibt also das Modul WatchQuotes.pm? Bei seiner Implementierung ist zu beachten, dass es mit Pidgins-Eventschleife zusammenarbeiten muss, damit auch während langlaufender Operationen wie dem Einholen einer Webseite der Glib-Kern Mauseingaben des Users verarbeitet und Pidgin nicht etwa regungslos dahindümpelt.

Hierfür bieten sich mehrere Frameworks an. POE wurde hier im Snapshot schon oft besprochen, also widmen wir uns heute mal einer neueren Entwicklung, die die coolen Kids der Perlszene ganz aufgeregt schnattern lässt: Das Framework AnyEvent bindet sich nicht direkt an eine bestimmte Eventschleife, sondern arbeitet mit einem halbem Dutzend unterschiedlicher Implementierungen zusammen.

Von Vorteil ist, dass sich so ein Modul wie WatchQuotes.pm ganz generisch implementieren lässt und später ohne Änderung auf allen denkbaren Eventschleifen und Plattformen läuft. Einzige Voraussetzung ist, dass das Hauptprogramm eine Referenz auf die verwendeten AnyEvent-Objekte behält, was durch das Speichern der Objektreferenz in der globalen Variablen $WATCH_QUOTES geschieht. Dies ist notwendig, da Pidgin die definierten Plugin-Callbacks nur kurz aufruft und deren lokalen Variablen demnach verschwinden, sobald der Programmfluss wieder in Pidgins Eingeweide zurückkehrt.

Aktienwecker in Aktion

Ein weiterer Vorteil der lockeren Bindung durch AnyEvent zeigt sich in der Testphase des Projekts: Das Modul WatchQuotes.pm lässt sich völlig unabhängig von Pidgin testen, zum Beispiel mit einem Skript nach Listing 2.

Listing 2: watch-quotes

    01 #!/usr/bin/perl -w
    02 use strict;
    03 use local::lib;
    04 use AnyEvent;
    05 use WatchQuotes;
    06 
    07 my $watcher = WatchQuotes->new();
    08 $watcher->init();
    09 $watcher->watch( \&callback );
    10 
    11 my $quit_program = AnyEvent->condvar;
    12 $quit_program->recv;
    13 
    14 ###########################################
    15 sub callback {
    16 ###########################################
    17     print "$_[0]\n";
    18 }

Es erzeugt eine Instanz der Klasse WatchQuotes und ruft die Methode init() auf, mit der WatchQuotes die vom User definierte Konfigurationsdatei ~/.pidgin-stockwatch.yml einliest und sie intern in eine Perl-Datenstruktur umformt (Abbildung 3).

Abbildung 3: Die Perl-Repräsentation der Daten in der YAML-Datei.

Als Beispiel einer Eventschleife bringt AnyEvent eine in Perl implementierte mit, die im Testskript zur Ausführung kommt. Letzteres definiert mit condvar() eine Bedingungsvariable, der der AnyEvent-Kernel Nachrichten schicken kann. Zeile 12 wartet mit recv() (=receive) auf diese Nachricht (die aber nie kommt) und lässt derweil den AnyEvent-Kernel Events von Modulen wie WatchQuotes bearbeiten. Das aufgerufene Testskript holt also in regelmäßigen Zeitabständen Börsenkurse ein und ruft, falls sich der Kurs einer der überwachten Aktien über die in der Konfigurationsdatei festgelegten Grenzwerte hinausbewegt, den ab Zeile 15 definierten Callback mit einer Tabelle formatierter Börsenkurse auf. Die Funktion print in Zeile 17 gibt die Nachricht jeweils auf STDOUT aus. Das Skript läuft weiter, bis der User es mit CTRL-C abbricht.

So lassen sich etwaige Fehler in Ruhe ausmerzen bevor das Skript in der feindlichen Pidgin-Umgebung läuft, in der Debuggen nur sehr eingeschränkt möglich ist, besonders, wenn Pidgin den Plugin aus irgendwelchen Gründen gar nicht korrekt lädt.

Das Modul WatchQuotes.pm selbst definiert zunächst Perl-typisch einen Konstruktor new(), der lediglich das Home-Directory des Users herausfindet und im Objekthash einige Standardwerte wie den Namen der Konfigurationsdatei festlegt.

Listing 3: WatchQuotes.pm

    001 ###########################################
    002 package WatchQuotes;
    003 # Mike Schilli, 2010 (m@perlmeister.com)
    004 ###########################################
    005 use strict;
    006 use warnings;
    007 use AnyEvent;
    008 use AnyEvent::HTTP;
    009 use YAML qw(LoadFile);
    010 
    011 ###########################################
    012 sub new {
    013 ###########################################
    014   my($class, %options) = @_;
    015 
    016   my ($home) = glob "~";
    017 
    018   my $self = { 
    019       watcher => undef,
    020       data    => {},
    021       refdata => {},
    022       conf_file => 
    023         "$home/.pidgin-stockwatch.yml",
    024       conf    => {},
    025       %options, 
    026   };
    027 
    028   bless $self, $class;
    029 }
    030 
    031 ###########################################
    032 sub init {
    033 ###########################################
    034   my($self) = @_;
    035 
    036   my $yml = LoadFile( $self->{conf_file} );
    037 
    038   for my $e ( @$yml ) {
    039     if( ref $e eq "HASH") {
    040       my($key, $val) = %$e;
    041       $val =~ s/%//g;
    042       $self->{conf}->{ $key } = $val;
    043     } else {
    044         # 2% by default
    045       $self->{conf}->{ $e } = 2;
    046     }
    047   }
    048 }
    049 
    050 ###########################################
    051 sub watch {
    052 ###########################################
    053   my($self, $cb) = @_;
    054 
    055   $self->{watcher} = AnyEvent->timer (
    056     after    => 10,
    057     interval => 300,
    058     cb    => sub {
    059       $self->fetch( $cb );
    060     },
    061   );
    062 }
    063 
    064 ###########################################
    065 sub fetch {
    066 ###########################################
    067   my($self, $cb) = @_;
    068 
    069   my $url = "http://" .
    070    "download.finance.yahoo.com/d/" .
    071    "quotes.csvr?e=.csv" .
    072    "&f=spl1p2&s=" . 
    073    join('+', sort keys %{ $self->{conf} });
    074 
    075   http_get($url, sub {
    076     $self->parse_csv( $_[0] );
    077     $self->check( $cb );
    078   });
    079 }
    080 
    081 ###########################################
    082 sub parse_csv {
    083 ###########################################
    084   my($self, $csv) = @_;
    085 
    086   for my $line ( split /\n/, $csv ) {
    087 
    088     my($symbol, $prev, $last, $change) = 
    089       map { s/[^\w\.-]//g; $_ } 
    090       split /,/, $line;
    091 
    092     next unless defined $symbol;
    093 
    094     $symbol = lc $symbol;
    095 
    096     $self->{data}->{$symbol} = 
    097       [ $prev, $last, $change ];
    098   }
    099 }
    100 
    101 ###########################################
    102 sub check {
    103 ###########################################
    104   my($self, $cb) = @_;
    105 
    106   if(!scalar keys %{ $self->{refdata} }) {
    107     $self->{refdata} = {%{$self->{data}}};
    108   }
    109 
    110   for my $stock (keys %{ $self->{data}} ) {
    111     if( $self->noteworthy( $stock ) ) {
    112       $self->{refdata} = {
    113           %{$self->{data}} };
    114 
    115         # reset 'prev'
    116       for my $s ( 
    117           keys %{$self->{refdata}} ) {
    118         $self->{refdata}->{$s}->[0] =
    119             $self->{data}->{$s}->[1];
    120       }
    121         
    122       $cb->( $self->message );
    123       last;
    124     }
    125   }
    126 }
    127 
    128 ###########################################
    129 sub message {
    130 ###########################################
    131   my($self) = @_;
    132 
    133   my $msg = "\n";
    134 
    135   for my $stock (keys %{ $self->{data} }) {
    136       my($prev, $last, $change) = 
    137          @{ $self->{data}->{$stock} };
    138       $msg .= "$stock: $last $change%\n";
    139   }
    140 
    141   return $msg;
    142 }
    143 
    144 ###########################################
    145 sub noteworthy {
    146 ###########################################
    147   my($self, $stock) = @_;
    148 
    149   my $price_ref = 
    150     $self->{refdata}->{$stock}->[0];
    151 
    152   my $price_now = 
    153     $self->{data}->{$stock}->[1];
    154   
    155   my $change_percent = abs( 
    156       ($price_now - $price_ref))/
    157       $price_ref*100;
    158   
    159   return($change_percent > 
    160          $self->{conf}->{$stock});
    161 }
    162 
    163 1;

YAML für Mensch und Maschine

Die ab Zeile 32 definierte Methode init() liest die Konfiguration mit der Funktion LoadFile ein, die aus dem YAML-Modul vom CPAN stammt. Das in Abbildung 2 sichtbare YAML-Format hat den Vorteil, dass es sowohl vom Menschen als auch Maschinen gleichermaßen leicht zu lesen ist. Die Applikation erlaubt sowohl einfache Array-Einträge (zum Beispiel "- amzn"), wie auch kleine Hashes (zum Beispiel "- goog: 2%"), die YAML als Referenz auf einen Hash ("goog" => "2%") speichert unter dem Array-Eintrag speichert. Zeile 39 prüft, ob Perls ref-Funktion das Wort "HASH" zurückliefert, was auf eine Hashreferenz hindeutet. Kommt statt dessen der Leerstring zurück, handelt es sich um einen einfachen Skalar und Zeile 45 setzt einen Standard-Schwellwert von 2% fest.

Die überwachten Aktienkürzel speichert das Modul unter dem Eintrag conf im Objekthash und ordnet ihnen die konfigurierten prozentualen Triggerwerte zu.

Einklinken ins Event-Framework

Die Methode watch ab Zeile 51 erzeugt einen periodisch unterbrochenen Timer. Der Parameter after mit dem Wert 10 gibt an, dass der Timer den unter cb definierten Callback genau 10 Sekunden nach dem Start aufruft. Grund für die gewollte Verzögerung ist, dass der Plugin eventuell schon startet, bevor der User im IM-Netzwerk ansprechbar ist. Eine verfrüht geschickte Nachricht käme aber nicht an. Der Parameter interval gibt mit dem Wert 300 an, dass der Timer den Callback nach dem ersten Aufruf alle 300 Sekunden (also alle 5 Minuten) abermals anspringt, um die neuesten Börsenkurse vom Yahoo-Server einzuholen und die Kurse auf Sprünge zu untersuchen.

Die der Methode watch() übergebene Referenz auf eine Callbackfunktion zeigt auf die Funktion quotes_update() des Hauptskripts und wird zur Methode fetch durchgereicht, die die Webseite mit den Kursen einholt. Dies kann bei stürmischem Internetwetter naturgemäß ein paar Sekunden dauern, doch die in Zeile 75 aufgerufene Funktion http_get stammt aus dem Fundus des Moduls AnyEvent::HTTP vom CPAN und arbeitet den Request asynchron ab. Sie nimmt den URL für den Stockservice und einen Callback entgegen, den sie anspringt, falls die Internetdaten vollständig eingetrudelt sind. Zu beachten ist dabei, dass Perl den Programmfluss sofort nach dem Aufruf von http_get() fortsetzt, ohne dass zu diesem Zeitpunkt die angeforderten HTTP-Daten vorliegen.

Wie die Dokumentation des CPAN-Moduls Finance-YahooQuote verrät, bietet Yahoo eine Fülle von Parametern an, von denen das Modul WatchQuotes.pm folgende auswählt:

    s  Symbol
    p  Previous Close
    l1 Last Trade (Price Only)
    p2 Change in Percent

Neben dem Tickersymbol liefert der Server also den Vortageskurs, den aktuellen Kurs, sowie die prozentuale Änderung. Zusammengesetzt verlangt WatchQuotes also "spl1p2" und falls der User Google und Yahoo-Aktien verlangt hat, hängt der Code "s=goog+yahoo" dahinter. Zurück kommen die Daten im CSV-Format, also zum Beispiel zwei Zeilen Text wie etwa

    "GOOG",467.49,475.83,"+1.78%"
    "YHOO",14.89,14.94,"+0.34%"

welche der aus einfachen regulären Ausdrücken aufgebaute Parser in der Methode parse_csv in eine Datenstruktur umformt. Im Hash-Eintrag data des WatchQuotes-Objektes steht dann eine Referenz auf einen Array, der jeweils den Vortageskurs, den letzten verfügbaren Kurs (üblicherweise um 20 Minuten verzögert) und die Änderung in Prozent enthält.

Ob die bislang erfolgten Schwankungen des Kurses an der Börse eine Benachrichtigung des Users rechtfertigen, prüft die Methode check ab Zeile 102. Auch sie erhält den Callback weitergereicht, der den User eventuell per IM-Message kontaktieren kann. Zeile 106 kopiert die aktuell eingelesenen Daten im Objekteintrag {data} in das Archiv unter {refdata}, damit der Code später eine Referenz zum Vergleich parat hat. Dabei reicht es nicht, die Referenz zu kopieren. Stattdessen kopiert

    { %{ $self->{data} } };

die hinter der Referenz $self->{data} stehenden Daten, formt daraus einen neuen Hash und gibt eine Referenz darauf zurück.

User wecken oder nicht?

Lagen schon vor dem Aufruf Archivdaten vor, passt die for-Schleife ab Zeile 116 die Einträge für den Vortageskurs auf den aktuellen Kurs an. Schließlich soll der Code bei einer gestiegenen Aktie nicht alle fünf Minuten einen neue Nachricht schicken, sondern nur, falls sich der Kurs weiter verschiebt und erneut den eingestellten Schwellenwert überschreitet. Die Methode noteworthy() prüft, ob ein Aktienkurs den eingestellten Schwellenwert gegenüber dem Vortageskurs (oder dem als Vortageskurs gesetzten Tageskurs, falls vorher schon ein Alarm ausgelöst wurde) überschritten hat. Die Methode message() ab Zeile 129 formatiert in diesem Fall die Daten aller überwachten Aktien zu einem Textstring und Zeile 122 ruft den Callback auf, der im Hauptprogramm den User per IM alarmiert.

Installation

Gängige Linux-Distributionen wie Ubuntu bieten Pidgin als Paket an, das auch die Perl-Schnittstelle schon enthält (pidgin und libpurple0 unter Ubuntu, beide in Version 2.7.0). Das Plugin-Skript pidgin-stockwatch.pl wandert dann ausführbar ins Verzeichnis ~/.purple/plugins unter dem Home-Verzeichnis des Users. Das Modul WatchQuotes.pm landet unter einem Pfad, den die lokale Perl-Installation findet. Notfalls weist der Skript-Code, wie in Zeile 7 von Listing 1, mit der Anweisung 'use lib' auf die entsprechende Stelle im Dateisystem hin.

Die Module AnyEvent und AnyEvent::HTTP haben sich noch nicht bei den bekannten Distros durchgesetzt und müssen deshalb vom CPAN installiert werden. Damit zusätzliche CPAN-Module nicht die sauberen Perl-Pakete des Linux-Package-Managers vollkleistern, installiert der ordnungsliebende User sie mittels des CPAN-Moduls local::lib unter dem Pfad "~/perl5" im Home-Verzeichnis. Nach

    perl Makefile.PL --bootstrap
    make test && make install

in der local::lib-Distribution hängt der ordnungsliebende Admin die Ausgabe von

    perl -I$HOME/perl5/lib/perl5 \
      -Mlocal::lib

an die lokale .bashrc-Datei an. Nach einem Neustart der Shell (oder dem Sourcen der .bashrc-Datei) setzt local::lib so eine Reihe von Environment-Variablen, die die CPAN-Shell anweisen, zusätzliche Module unter ~/.perl5 zu installieren. Die Variablen weisen auch aufgerufene Perl-Skripte auf zusätzliche Suchpfade hin, doch Pidgin weiß davon nichts und benötigt eine explizite 'use lib'-Anweisung.

Ätzend für Entwickler

Das Plugin-Skript in Listing 1 lässt sich übrigens nicht ohne Pidgin starten und wirft, falls man es doch versucht, mit wirren Fehlermeldungen um sich, die andeuten, dass es bestimmte Glib-Funktionen nicht in den entsprechenden shared Libraries findet. Laut Pidgin-Entwicklern ist das normal, doch aus Entwicklersicht natürlich ätzend. Abhilfe schafft sich der findige Hacker, indem er die Zeilen 'use Pidgin' und 'use Glib' auskommentiert und mit "perl -c pidgin-stockwatch.pl" wenigstens prüft, ob die Syntax stimmt und das Skript das Modul WatchQuotes.pm sowie die darin aufgerufenen CPAN-Module findet.

RTFM falls existent

Dokumentation zum Schreiben von Pidgin-Plugins ist rar. Zwar hat Pidgin-Chefentwicker Sean Egan ein Buch zum Thema "Building and Extending Gaim" geschrieben, doch nicht nur der Name des Projekts hat sich seit dem geändert (aus Gaim wurde Pidgin), sondern auch kaum ein Funktionsaufruf oder eine Datenstruktur sind konstant geblieben, sodass sich das sehr gut geschriebene Werk allenfalls zum Studium der Pidgin-Architektur eignet. Auch die Online-Dokumentation lässt in punkto Aktualität schwer zu wünschen übrig ([2], [3]), und die automatisch generierte Doxygen-Dokumentation ist, wie so oft, lieblos zusammengeschustert, unvollständig, und damit ebenfalls für die Katz. Die effektivste Methode, die in der aktuellen Pidgin-Version erforderlichen Parameter zu einem Funktionsaufruf zu finden, ist das Studium real existierenden Plugins neuerer Bauart, wie zum Beispiel [4].

Wird pidgin mit pidgin -d im Debug-Modus aufgerufen, erscheinen Debug-Schnipsel im Code wie

    Purple::Debug::info("stockwatch", 
      "Plugin loaded.\n");

auf dem Bildschirm und man kann zumindest sehen, was der Plugin treibt und ob Pidgin ihn überhaupt findet. Klappt dies, und er führt zumindest die plugin_init-Routine ab Zeile 26 aus, erscheint der Plugin im Pidgin-Menü Tools-Plugins unter dem in Zeile 11 angegebenen Namen Pidgin Stockwatch (Abbildung 4). Ein Mausklick auf die linksseitige Checkbox aktiviert den Plugin und führt dessen plugin_load-Routine aus. Testweise kann man den Plugin auch deaktivieren, worauf er plugin_unload (Zeile 32) ausführt.

Abbildung 4: Ein Mausklick aktiviert den neu installierten Plugin, den Pidgin im "Plugins"-Menü anzeigt.

Anschließend füllt der Aktienspekulant die Konfigurationsdatei ~/.pidgin-stockwatch.yml mit den Tickersymbolen der interessierenden Aktien und weist ihnen Prozentwerte zu oder akzeptiert den voreingestellten Trigger bei 2%. Nach einem Neustart arbeitet Pidgin mit den aktualisierten Werten.

Statt amerikanischer Aktien wie in Abbildung 1 lassen sich natürlich auch an deutschen Märkten gehandelte Werte angeben, wie zum Beispiel SIE1.de, das Symbol der Siemens-Aktie, die an der XETRA-Börse in Euro gehandelt wird. Obwohl die Chance, dass Siemens sich an einem Tag um 2% bewegt, natürlich gering ist, doch falls es tatsächlich passiert, weiß der User sofort Bescheid und kann entsprechende Panikverkäufe in Angriff nehmen.

Infos

[1]

Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2010/09/Perl

[2]

Pidgin, der vielseitige Instant-Messenger-Client: http://pidgin.im

[3]

Kurze Anleitung zum Schreiben von Pidgin-Plugins in Perl: http://developer.pidgin.im/doxygen/dev/html/perl-howto.html

[4]

Beispiel-Plugin in Perl: http://code.google.com/p/pidgin-knotifications/downloads/detail?name=knotifications.pl&can=2&q=

[5]

Weitere 3rd-Party Plugins: http://developer.pidgin.im/wiki/ThirdPartyPlugins#DevelopmentofThird-PartyPlugins

[6]

CPAN-Modul für Yahoo-Börsenservice: http://search.cpan.org/~edd/Finance-YahooQuote-0.24/YahooQuote.pm

Michael Schilli

arbeitet 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.