Pidgin Pounce (Linux-Magazin, Februar 2010)

Mit etwas Perl ordnet Pidgin, der Tausendsassa unter den Instant-Messaging-Clients, verschiedenen Kommunikationspartnern unterschiedliche Geräusche zu, und spielt sie zu eintreffenden Nachrichten ab.

Wer während der Arbeitszeit Instant Messaging zur Kommunikation nutzt, sieht sich häufig mit dem Problem konfrontiert, dass plötzlich eintrudelnde Nachrichten die Konzentration stören. Vieles könnte man auch liegen lassen und später bearbeiten. Doch falls ein als Gschaftlhuber bekannter Manager eine Nachricht schickt, ist schnelle Reaktion gefragt. Wie filtern?

Pidgin, ein Messaging-Client [2], der alle gängigen Kommunikationsprotokolle wie Yahoo Messenger, AOL AIM oder auch IRC fährt, bietet sogenannte ``Buddy Pounces'' an, die eintreffenden Ereignissen entsprechende Aktionen zuordnen. Der dafür zuständige Dialog (Abbildung 1), erreichbar über den Menüpunkt ``Tools->Buddy Pounces'', erlaubt es zum Beispiel, bei den Ereignissen ``Sends a message'' (schickt eine IM-Nachricht) oder ``Signs on'' (loggt sich ein) eines bestimmten Kommunikationspartners eine vordefinierte Sound-Datei abzuspielen oder ein externes Programm auszuführen. Dazu ist in dem Formular in Abbildung 1 das Protokoll des genutzten IM-Service, der eigene Account und der Name des IM-Partners anzugeben, bei dessen Aktivität das Ereignis ausgelöst wird. Wichtig ist es, die Checkbox recurring zu aktivieren, sonst wirft Pidgin den Handler genau einmal an und löscht danach dessen Definition.

Abbildung 1: Die "Pounce"-Funktion in Pidgin führt das Skript ~/bin/pounce-sound aus, falls ein IM-Buddy ein Ereignis auslöst.

Sounds zum Schlagerpreis

Nun habe ich zufälligerweise vor ein paar Wochen im Kaufrauf bei Amazon.com 500 Soundeffekte als runterladbare MP3-Dateien zum Schlagerpreis von $2.59 erstanden ([3], mittlerweile ist der Preis aber wieder auf $9.49 hochgeschnellt). Was liegt näher, als sie meinen liebsten IM-Freunden zuzuordnen? Ist das Pidgin-Chatfenster anschließend verdeckt, rüttelt mich nur bei ausgewählten Partnern ein eindeutig zu identifizierendes Geräusch aus der Konzentration. Alle anderen müssen warten. Der Gschaftlhuber bekommt eine Militärtrompete (bugle.mp3) zugeordnet, mein tanzversessener Arbeitskollege eine Samba-Rassel (cabassa.mp3) und ein Bekannter auf AIM, der immer den neuesten Tratsch parat hat, ein kurzes Soundschmankerl mit Geschnatter von fliegenden Wildgänsen (goose.mp3).

Allerdings hatte ich naturgemäß keine Lust, alles einzeln von Hand in Pidgins Dialogfelder einzutragen und dies bei jeder weiteren Pidgin-Instanz, auf dem Netbook und zuhause, zu wiederholen. Zum Glück legt Pidgin die Pounce-Definitionen aber in einer leicht lesbaren XML-Datei unter ~/.purple/pounces.xml ab.

Abbildung 2: Pidgin legt die konfigurierten Pounces in einer XML-Datei unter dem Verzeichnis .purple im Home-Verzeichnis ab.

Das Perlskript pounce-yml-to-xml nimmt eine kompakt formatierte YAML-Datei wie in Abbildung 3 und wandelt sie ohne viel Federlesens in das viel umständlichere aber von Pidgin favorisierte XML-Format um. Die YAML-Datei pounces.yml liegt unter einem neu angelegten Verzeichnis ~/.pidgin-pounces und dort trägt der Benutzer für jeden seiner Online-Buddies eine Sounddatei ein.

Tanz auf allen Hochzeiten

Da Pidgin verschiedenste IM-Protokolle fährt und ein Nutzer meist unter mehreren Accounts auf mehreren Servern gleichzeitig eingeloggt ist, steht unter dem Eintrag sound_map in der YAML-Datei zuerst ein Schlüssel, der den jeweiligen IM-Anbieter und den dort verwendeten Nutzernamen durch einen Doppelpunkt getrennt auflistet. Unter dieser Hierarchiestufe folgen die Zuordnungen von Online-Partnern und Sounddateien, die Pidgin abspielen soll, falls der Partner sich einloggt oder dem Nutzer eine Nachricht schickt. Nach Abbildung 3 ist also mein Screenname auf Yahoos Messenger-Service ``mikeschilli'', und falls mir dort ``elcaramba'' eine Nachricht schickt, rasselt Pidgin mit ``cabassa.mp3'' einen heißen südamerikanischen Rhythmus.

Abbildung 3: Aus den Daten dieser YAML-Datei generiert das Skript pounce-yml-to-xml die XML-Datei in Abbildung 1.

Filter gegen Trommelfeuer

Neben der Zuordnung der Buddies zu den Geräuschen unter dem Eintrag ``sound_map'' setzt die YAML-Datei auch noch den Parameter interval, damit das System nicht bei jeder eintrudelnden Nachricht ein Geräusch abspielt. Steht interval auf 60, ist die Mindestzeitspanne zwischen zwei Geräuscheinspielungen 60 Sekunden. Kommen in diesem Zeitraum mehrere Nachrichten an, unterdrückt das System das abzuspielende Geräusch, denn schließlich soll der arme User kein akustisches Trommelfeuer ertragen sondern arbeiten.

YAML-Daten lassen sich 1:1 in Perl importieren, die Funktion LoadFile() aus dem YAML-Modul wandelt sie in die in Abbildung 4 gezeigte Struktur um. Im nächsten Schritt steht nun die Umwandlung der Daten in aufgeblähtes XML an.

Abbildung 4: Mit LoadFile() eingelesen, ergeben die YAML-Daten Abbildung 3 diese Datenstruktur in Perl.

Von YAML nach XML

Das von Pidgin verwendete XML-Format (Abbildung 2) definiert für jeden ``Pounce'' ein Konstrukt, das in <pounce>-Tags eingeschlossen ist. Die Gesamtheit aller dieser Tags steht innerhalb eines Sammeltags mit dem Namen <pounces> (Plural). Dies ist einfach mittels Reverse-Engineering herauszufinden, in dem man mehrere Pounces mit Pidgins grafischer Oberfläche definiert und anschließend die automatisch neu geschriebene XML-Datei inspiziert.

Das Skript pounce-yml-to-xml wandelt nun die kompakte YAML-Datei in Abbildung 3 in ausführliche XML-Instruktionen für Pidgin um, die es anschließend in ~/.purple/pounces.xml speichert. Die for-Schleife ab Zeile 17 iteriert über die Einträge im sound_map-Hash der YAML-Datei, die allesamt im Format protocol:account vorliegen. Darunter wiederum liegt ein weiterer Hash, der den Online-Partnern (Buddies) Geräusche zuordnet. Die keys()-Funktion gibt eine Liste dieser Partner zurück, und Zeile 26 ruft für jeden die ab Zeile 51 definierte Funktion mk_pounce() auf. Als Parameter erhält sie den Namen des Buddies, den Namen des Accounts (recv genannt, weil dieser später Nachrichten von den Buddies empfängt) und das verwendete Protokoll (``yahoo'', ``aim'', oder dergleichen). Die Funktion gibt jeweils einen XML-String zurück, der einem Eintrag in Pidgins Konfigurationsdatei pounces.xml entspricht.

Listing 1: pounce-yml-to-xml

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use Sysadm::Install qw(:all);
    04 
    05 use YAML qw(LoadFile);
    06 use Template;
    07 
    08 my($home) = glob "~";
    09 my $path = "$home/.pidgin-pounce";
    10 my $yaml = LoadFile( "$path/pounce.yml" );
    11 
    12 my $xml_file = "$home/.purple/pounces.xml";
    13 my $SOUND_CMD = "~/bin/pounce-sound";
    14 
    15 my @pounces = ();
    16 
    17 for my $account (
    18         keys %{ $yaml->{sound_map} }) {
    19 
    20   my($proto, $recv) = split /:/, $account;
    21 
    22   for my $buddy (keys %{ 
    23       $yaml->{sound_map}->{$account} }) {
    24 
    25     push @pounces,
    26       mk_pounce($buddy, $recv, $proto);
    27   }
    28 }
    29 
    30 binmode STDOUT, ":utf8";
    31 
    32 my $xml = q{
    33 <?xml version='1.0' encoding='UTF-8' ?>
    34 <pounces version='1.0'>
    35 [% FOR pounce IN pounces %]
    36   [% pounce %]
    37 [% END %]
    38 </pounces>
    39 };
    40 
    41 my $tmpl = Template->new();
    42 
    43 mv $xml_file, "$xml_file.old" if
    44    -f $xml_file;
    45 
    46 $tmpl->process( \$xml, 
    47   { pounces => \@pounces }, 
    48   $xml_file ) or die $tmpl->error;
    49 
    50 ###########################################
    51 sub mk_pounce {
    52 ###########################################
    53     my($buddy, $recv, $prot) = @_;
    54 
    55     return qq{
    56 <pounce ui='gtk-gaim'>
    57   <account 
    58  protocol='prpl-$prot'>$recv</account>
    59   <pouncee>$buddy</pouncee>
    60   <options/>
    61   <events>
    62     <event type='sign-on'/>
    63     <event type='message-received'/>
    64   </events>
    65   <actions>
    66     <action type='execute-command'>
    67       <param name='command'
    68      >$SOUND_CMD $prot:$recv $buddy</param>
    69     </action>
    70   </actions>
    71   <save/>
    72 </pounce>
    73     }
    74 }

Empfindlicher Pidgin

Zum Schreiben des XML-Formats der Gesamtdatei kommt das Modul Template vom CPAN zum Einsatz, ein Template-Prozessor, der normalerweise beim dynamischen Aufbau von Webseiten hilft. Im Skript pounce-yml-to-xml expandiert es nur Makros im Format [% variable %] und das praktische FOR-Konstrukt der Template-Sprache hilft in Zeile 35, die unästetische Vermengung von Perl- und XML-Code zu vermeiden. Zuerst habe ich zu diesem Zweck ein wenig mit XML::Simple experimentiert, aber damit XML zu erzeugen, das genauso aussieht wie die Vorgabe, ist praktisch unmöglich. Und Pidgin ist nicht gerade robust, was die Abfolge der Daten in dieser XML-Datei betrifft. Wer einen gnadenlos abkrachenden Pidgin beobachten will, braucht nur den pouncee-Eintrag vom Anfang ans Ende der pounce-Struktur zu schieben.

Die Funktion mv aus dem Modul Sysadm::Install schiebt höflicherweise eine eventuell schon von Pidgin verwaltete Datei pounces.xml zur Seite und benennt sie in pounces.xml.old um.

Die process-Methode in Zeile 46 nimmt einen Array von Pounce-XML-Texten entgegen, über den die FOR-Schleife im Template-Code iteriert, das parametrisierte XML an die richtigen Stellen einpflanzt, und anschließend Pidgins Datei pounces.xml mit den neuen Daten überschreibt. Das Ergebnis, sichtbar in Abbildung 5, zeigt einen Wust von XML für jede Kombination aus Protokoll, Account und Kommunikationspartner. Alle Instanzen dieses XML-Snippets rufen das externe Programm pounce-sound mit ebendiesen Parametern auf.

Abbildung 5: Das von pounce-yml-to-xml generierte XML, das Pidgin dazu veranlasst, bei eintreffenden Nachrichten von bigboss an mikeschilli das externe Skript pounce-sound aufzurufen.

Sound zurechtgeschnitten

Die den Online-Buddies zugeordneten Geräuschdateien werden allesamt im Verzeichnis sounds unterhalb des .pidgin-pounces-Directories installiert. Ihre Länge sollte jeweils ein paar Sekunden nicht überschreiten. Liegt eine längere Sounddatei vor, eignet sich zum Kürzen, wie in Abbildung 6 gezeigt, das Tool audacity, mit dem man das Geräusch außerdem einfach mit dem Effekten ``Fade in'' und ``Fade out'' sanft ein- und ausblenden kann.

Abbildung 6: Das Tool Audacity schneidet das Geräusch auf die korrekte Länge zurecht und lässt es mit dem Effekt "Fade Out" sanft ausklingen.

Das von Pidgin im Ereignisfall aufgerufene Skript pounce-sound nimmt auf der Kommandozeile als Parameter die Kombination aus Protokoll und Account, sowie das Kürzel des sendenden Users entgegen. Es liest die YAML-Datei ein, und findet heraus, und ob ein Spezialsound für den User vorliegt. Falls ja, spielt es diesen mit der Utility play über die Soundkarte ab. Erhält Pidgin zum Beispiel über das Yahoo-Protokoll mit dem eingeloggten User 'mikeschilli' eine Nachricht von 'bigboss', ruft es

   pounce-sound yahoo:mikeschilli bigboss

auf. Das Skript wiederum sieht in der YAML-Datei nach, findet, dass für diesen Fall die MP3-Datei ``bugle.mp3'' vorgesehen ist, und spielt sie ab.

Das einzig Bemerkenswerte an pounce-sound ist der Aufruf von Data::Throttler, der verhindert, dass zu häufiges Abspielen der Sounds den Zorn des Benutzers erregt. Data::Throttler legt unter ~/.pidgin-pounce/throttler.yml eine YAML-Datei an und merkt sich dort, wann und wie oft das Programm zuletzt aufgerufen wurde. Die Methode try_push() merkt sich unter einem Schlüssel aus Protokoll, dem Accountnamen und dem Namen des sendenden Buddies die Aktivitäten und liefert einen unwahren Wert zurück, falls der Buddy während der Zeitspanne der letzten interval Sekunden schon aktiv war. Andere Kommunikationspartner hingegen lässt es gewähren, bis auch diese ihr Quota überschritten haben.

Listing 2: pounce-sound

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use YAML qw(LoadFile);
    04 use lib '/home/mike/perl-modules/lib/perl5';
    05 use Data::Throttler;
    06 
    07 my($proto, $buddy) = @ARGV;
    08 
    09 die "usage: $0 proto:recv buddy" 
    10   if !defined $buddy;
    11 
    12 my($home) = glob "~";
    13 my $path = "$home/.pidgin-pounce";
    14 
    15 my $yaml = LoadFile( "$path/pounce.yml" );
    16 
    17 my $throttler = Data::Throttler->new(
    18     max_items => 1,
    19     interval  => $yaml->{interval},
    20     backend   => "YAML",
    21     backend_options => {
    22         db_file => "$path/throttle.yml",
    23     },
    24 );
    25 
    26 if(! $throttler->try_push(
    27       key => "$proto:$buddy")) {
    28     # rate limit reached, skip it
    29     exit 0;
    30 }
    31 
    32 if(exists 
    33  $yaml->{sound_map}->{$proto}->{$buddy}) {
    34   my $sound = 
    35     $yaml->{sound_map}->{$proto}->{$buddy};
    36   system("play $path/sounds/$sound");
    37 }

Installation

Das von Pidgin im Ereignisfall aufgerufene Skript pounce-sound wandert ins bin-Verzeichnis unterhalb des Home-Verzeichnisses und wird mit chmod +x ausführbar gemacht.

Die CPAN-Module YAML, Template, Sysadm::Install liegen vielen Distributionen bei und lassen sich mit deren Package-Manager installieren. Das Modul Data::Throttler wird über eine CPAN-Shell mit perl -MCPAN -e'install Data::Throttler' vom CPAN geholt und installiert. Die Utility play, mit der das Skript pounce-sound die .mp3-Datei abspielt, ist Bestandteil des Pakets sox, das der erfahrende Ubuntu-Administrator mit sudo apt-get install sox installiert.

Die eventuell noch gekürzten und weich ein- und ausklingenden Sounddateien wandern ins Unterverzeichnis sounds. Danach gilt es, die YAML-Datei in Abbildung 2 an die lokalen Verhältnisse anzupassen, den eventuell laufenden Pidgin-Prozess zu stoppen und das Skript pounce-yml-to-xml aufzurufen. Wer den eingestellten Sound nur bei eingehenden Nachrichten hören möchte und nicht wenn der Buddy sich einloggt, löscht den sign-on-Event im XML in Zeile 66 von pounce-yml-to-xml.

Abbildung 7: Pidgin hat sich die XML-Datei geschnappt und zeigt nun im Pounce-Dialog die auto-generierten Einstellungen an.

Nach einem Neustart schnappt Pidgin sich dann die autogenerierte XML-Datei und zeigt die neue Konfiguration auf Abfrage, wie in Abbildung 7 gezeigt, l im Pounce-Dialog an. Passiert ein reaktionswürdiges Ereignis, führt Pidgin den dazu definierten Pounce aus ruft das Soundskript auf, worauf die Militärtrompete erschallt, die den Anwender aus dem Büroschlaf schreckt. Jaja, Boss, bin praktisch fertig!

Infos

[1]

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

[2]

``Pidgin, the universal chat client'', http://www.pidgin.im/

[3]

``500+ Sound Effects'' als MP3 bei Amazon, http://www.amazon.com/gp/product/B002OVD5FK

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.