Zimmer mit Aussicht (Linux-Magazin, Dezember 2013)

Die Micro-Blogplattform Tumblr nimmt Textbeiträge von Browsernutzern entgegen, akzeptiert über die API aber auch maschinell erstellte Artikel. Aus regelmäßig veröffentlichten Schnappschüssen einer Überwachungskamera entsteht so ein Blog der besonderen Art.

Durch mein Wohnzimmerfenster lugt seit einiger Zeit eine schwenkbare Kamera hinaus auf die Skyline San Franciscos. Sie lässt sich über das Internet fernsteuern und ihren Videostream kann ich aufs Handy holen, auch wenn ich gerade am anderen Ende der Welt weile. Das mit $60 relativ preiswerte Modell FI8910W mit Wifi-Anschluss von der Firma Foscam tut hier gute Dienste, denn es ist immer wieder beruhigend, auf Reisen übers Internet einen Blick aus dem Fenster daheim zu werfen und damit sicher zu stellen, dass die Wohnung noch nicht abgebrannt ist oder ausgeplündert wurde.

Abbildung 1: Die schwenkbare Foscam-Kamera im Wohnzimmer des Perlmeisters mit Blick auf Downtown San Francisco.

Nicht ohne Macken

Wer jetzt aber gleich zu Amazon hinüberschielt und mit einem Impulskauf liebäugelt, der sei jedoch gewarnt: Die preiswerte Foscam-Kamera hat eine nervige Macke: Sie schaltet die eingebaute Infrarotbeleuchtung ein, falls das Gerät sich nachts aufhängt und frisch bootet. Das kommt leider hin und wieder aus unerfindlichen Gründen vor, und falls es in der Nacht passiert, schaltet die Kamera mangels Umgebungslicht automatisch einen Ring von Infrarot-LEDs an. Direkt vor einem spiegelnden Fenster ist das freilich eine ausgesprochen dämliche Idee, denn ab dann besteht das Kamerabild aus imposanten Lichtmustern, und der zu überwachende Außenraum bleibt solange unüberwacht, bis jemand den Fehler bemerkt und den Infrarotring ausschaltet. Leider lässt sich diese Macke nicht abstellen und auf einen entsprechenden Firmware-Update haben die Kunden bislang vergebens gewartet. "You get what you pay for", sagt man dazu in Amerika.

Abbildung 2: Das altbackene Webinterface der Foscam-Kamera.

Auch sieht das Web-Interface der Kamera aus wie ein Überbleibsel aus dem letzten Jahrhundert (Abbildung 2). Der User kann den Video-Stream mit ein paar Frames pro Sekunde verfolgen, und mit den Steuerknöpfen links oben im Bild den Kamerakopf um etwa 180 Grad seitwärts und auch etwa 90 Grad nach oben schwenken. Dass Foscam den HTTP-Zugriff mit dem Passwort im Klartext ausführt (kein HTTPS), ist freilich ein weiterer extremer Faux-Pas, und das sollten vernunftbegabte User auf dem offenen Internet nur über ein VPN machen. Steht die Kamera daheim und möchte man von außen darauf zugreifen, muss die Firewall des Routers mittels Port-Forwarding eingehende Requests weiterleiten. Da die Kamera mit einem dynamischen DNS-Provider zusammen arbeitet, erscheint das Web-Interface statisch unter einer URL wie http://xxx.myfoscam.org:5148, auch wenn der Internet-Provider die WAN-IP ändert.

Bloggen auf Kommando

Statt sich hin und wieder übers Internet in die Kamera einzuwählen und nach dem Rechten zu sehen, bietet es sich auch an, in regelmäßigen Zeitabständen Schnappschüsse von der Kamera abzuziehen und ins Internet zu stellen. Die Standbilder kann ein Skript dann auf einer Webseite aufreihen, und ein Blick genügt, um den Tagesverlauf zu kontrollieren. Neulich fiel mir auf, dass die vor etwa einem halben Jahr von meinem Arbeitgeber Yahoo geschluckte Micro-Blogplattform Tumblr eine programmierbare API anbietet, und ein Blick aufs CPAN offenbarte, dass ein eifriger Open-Source-Programmierer mit WWW::Tumblr bereits ein entsprechendes Perl-Modul veröffentlicht hatte.

Abbildung 3: Bei jedem Aufruf des Skripts tumblr-post erscheint im Tumblr-Blog ein Standbild der Kamera.

Der Rest war Formsache. Ein Cronjob ruft das später in Listing 2 vorgestellte Skript mehrmals am Tag auf und holt einen aktuellen Schnappschuss von der Kamera ab. Das Bild schickt das Skript dann per API an das Tumblr-Blog, das die Meldungen chronologisch darstellt und allerhand sozialen Plattformschnickschnack wie "Like"-Knöpfe, oder eine "Reblog"-Funktion anbietet (Abbildung 3). Da User auf Tumblr üblicherweise mehrere Blogs verfolgen, bietet es sich an, den Bilderreigen in die Reihe interessierender Blogs aufzunehmen und neben anderen Postings zu konsumieren (Abbildung 4).

Abbildung 4: Nachdem das "Blog" in die Folgeliste aufgenommen wurde, erscheinen seine Postings zwischen anderen interessierenden Publikationen.

OAuth gewährt Zugang

Damit das später vorgestellte Perl-Skript tumblr-post automatisch schreibend auf das Blog eines Users zugreifen darf, muss Tumblr es als Applikation registriert haben und der User muss ihr entsprechende Rechte eingeräumt haben. Auf [2] stehen die durchzuführenden Schritte aufgelistet. Abbildung 5 zeigt die Registrierung der Testapplikation "Perlsnapshot" und da es sich um ein Skript und nicht um eine App fürs iPhone oder ein Android-Gerät handelt, habe ich die Felder "App Store URL" und "Google Play Store URL" leer gelassen.

Abbildung 5: Nach der Registrierung der Applikation ...

Abbildung 6: ... mit Angabe der Callback-URL ...

Als "Default Callback URL", also die Web-Adresse, die Tumblr nach der Autorisierung seitens des Users mit den generierten Access-Tokens aufruft, ist "http://localhost:8082" eingetragen. Als Applikations-Icon habe ich einfach ein 128x128 Pixel großes Selbstportrait hochgeladen. Das später aufgerufene Skript in Listing 1 zum Einholen der Genemigung des Users und dem Einsammeln der Zugriffsschlüssel startet nämlich einen Webserver auf dem heimischen Linux-Rechner. Nach einem Klick auf "Register" unter dem ausgefüllten Formular zeigt Tumblr einen "Consumer Key" und einen "Secret Key" an (Abbildung 7). Diese beiden Hex-Strings identifizieren eine registrierte Tumblr-Applikation, die nun bei Usern um Zugriffsrechte buhlen darf.

Abbildung 7: ... generiert die Tumblr-API-Seite den Token.

Alleskönner Mojo

Wie schon in früheren Perlsnapshots mit anderen OAuth-kontrollierten Applikationen wie Google Drive ([3]) kommt auch diesmal zum Einholen der Erlaubnis zum Publizieren von Blog-Einträgen ein Perl-Skript mit dem Mojolicious-Modul vom CPAN zum Einsatz (Listing 1).

Es fährt beim Start ensprechend der Meldung

    $ ./tumblr-oauth
    [Sat Oct 12 09:56:27 2013] [info] 
    Listening at "http://localhost:8082";.
    Server available at http://localhost:8082.

auf localhost und auf Port 8082 einen Web-Server hoch, der, falls ein Browser den URL http://localhost:8082 anfragt, nur einen Link mit dem Text "Login on Tumblr" anzeigt. Klickt der User darauf, verzweigt der Browser auf Tumblrs Login-Seite, wo der User sich mit Email und Passwort identifizieren kann, falls er nicht schon eingeloggt ist.

Abbildung 8: Der User autorisiert das Skript zum Posten von neuen Tumblr-Artikeln.

Tumblr zeigt dann den Dialog in Abbildung 8 an, um vom User die Erlaubnis abzuholen, dass die Applikation "Perlsnapshot" sowohl lesend als auch schreibend auf dessen Blog zugreifen darf. Das vorher von mir zu Testzwecken eingerichtete Blog "Schtonk's Rants" unter schtonk.tumblr.com soll später die Kamerabilder darstellen. Klickt der User auf "Allow", verweist Tumblr den Browser zurück auf die vorher eingestellte Callback-URL (http://localhost:8082) und schiebt dem Server dort mit oauth_token und oauth_verifier zwei weitere Zugriffstokens unter. Mit diesen darf der Empfänger entsprechend den gewährten Rechten beliebig auf dem Blog des Users herumorgeln, beinahe so, als wäre er selbst dort eingeloggt. Allerdings musste der User der Applikation kein Passwort mitteilen, sondern nur einen Token, den er jederzeit widerrufen kann.

Listing 1: tumblr-oauth

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use Storable;
    04 use WWW::Tumblr::Authentication::OAuth;
    05 use Mojolicious::Lite;
    06 use YAML qw( DumpFile );
    07 use Sysadm::Install qw( :all );
    08 
    09 my( $home ) = glob "~";
    10 my $cfg_file     = "$home/.tumblr.yml";
    11 my $local_url    = "http://localhost:8082";
    12 my $consumer_key = "XXX";
    13 my $secret_key   = "YYY";
    14 
    15 my $tumblr_oauth = 
    16    WWW::Tumblr::Authentication::OAuth->new(
    17   consumer_key => $consumer_key,
    18   secret_key   => $secret_key,
    19   callback     => "$local_url/callback",
    20 )->oauth_tools;
    21 
    22 @ARGV = (qw(daemon --listen), $local_url);
    23 
    24 ###########################################
    25 get '/' => sub {
    26 ###########################################
    27   my( $self ) = @_;
    28 
    29   $self->stash->{login_url} =
    30     $tumblr_oauth->authorize_url();
    31 
    32 } => 'index';
    33 
    34 ###########################################
    35 get '/callback' => sub {
    36 ###########################################
    37   my ( $self ) = @_;
    38 
    39   my $oauth_token    = 
    40     $self->param( "oauth_token" );
    41   my $oauth_verifier = 
    42     $self->param( "oauth_verifier" );
    43 
    44   $tumblr_oauth->oauth_token( 
    45       $oauth_token );
    46   $tumblr_oauth->oauth_verifier( 
    47       $oauth_verifier );
    48 
    49   DumpFile( $cfg_file, {
    50     oauth_token    => $oauth_token,
    51     oauth_verifier => $oauth_verifier,
    52     consumer_key   => $consumer_key,
    53     secret_key     => $secret_key,
    54     token => $tumblr_oauth->token(),
    55     token_secret => 
    56       $tumblr_oauth->token_secret(),
    57   } );
    58 
    59   $self->render( 
    60     text => "Tokens saved in $cfg_file.",
    61             layout => 'default' );
    62 };
    63 
    64 app->start();
    65 
    66 __DATA__
    67 ###########################################
    68 @@ index.html.ep
    69 % layout 'default';
    70 <a href="<%= $login_url %>"
    71 >Login on Tumblr</a>
    72 
    73 @@ layouts/default.html.ep
    74 <!doctype html><html>
    75   <head><title>Token Fetcher</title></head>
    76     <body>
    77       <pre>
    78       <%== content %>
    79       </pre>
    80     </body>
    81 </html>

Ein lokaler Webserver

Listing 1 zieht hierzu das CPAN-Modul WWW::Tumblr::Authentication::OAuth herein, das die notwendigen URLs zum Abholen der Tokens bereits enthält. In den Zeilen 12 und 13 stehen in den Variablen $consumer_key und $secret_key die in Abbildung 7 beim Registrieren der Applikation erhaltenen Werte. Damit das Modul Mojolicious::Lite den Webserver startet, erwartet es einige Optionen auf der Kommandozeile. Entgegen allen Erwartungen startet die Option daemon den Webserver im Vordergrund, und der Parameter --listen bekommt den in Zeile 11 gesetzten URL. Damit tumblr-oauth ohne Kommandozeilenparameter auskommt, stopft Zeile 22 die Optionen kurzerhand in den Array @ARGV, und gaukelt sie dem Mojolicious-Modul so vor.

Browseranfragen auf Port 8082 ohne Pfadangabe landen im Code in Listing 1 in Zeile 25, die die Stash-Variable login_url setzt und dann damit das Template im DATA-Bereich des Skripts ab Zeile 68 in eine HTML-Seite mit einem Login-Link umwandelt und den Browser darstellen lässt. Verzweigt Tumblr nach dem Einholen der Erlaubnis seitens des Users zurück zum Mojolicious-Server, geschieht dies unter dem Pfad /callback und der Code ab Zeile 35 kommt zur Ausführung.

Zeile 49 speichert mit DumpFile aus dem CPAN-Modul YAML alle vier soweit erhaltenen Tokens sowie zwei neue mit token() bzw. token_secret() erzeugte Access-Tokens im lesbaren YAML-Format in der Datei .tumblr.yml im Home-Verzeichnis ab. Später greifen Applikationsskripts auf diese Datei zu, lesen die Tokens aus, und erhalten dadurch Zugang zum Blog des Users.

Bilder aufs Blog

Listing 2: tumblr-post

    01 #!/usr/local/bin/perl -w
    02 use strict;
    03 use Sysadm::Install qw(:all);
    04 use YAML qw( LoadFile );
    05 use WWW::Tumblr;
    06 use WWW::Mechanize;
    07 
    08 my( $home )  = glob "~";
    09 my $cfg_file = "$home/.tumblr.yml";
    10 
    11 my $ua = WWW::Mechanize->new();
    12 my $picfile = "snapshot.jpg";
    13 my $url = "http://XXX.myfoscam.org" .
    14   ":5148/snapshot.cgi";
    15 my $site = "schtonk.tumblr.com";
    16 my $cam_user = "XXX";
    17 my $cam_pass = "YYY";
    18 
    19 $ua->credentials( $cam_user, $cam_pass );
    20 
    21 my $resp = $ua->get( $url );
    22 blurt $resp->content(), $picfile;
    23 
    24 my $cfg = LoadFile( $cfg_file );
    25 
    26 my $t = WWW::Tumblr->new(
    27   consumer_key => $cfg->{ consumer_key },
    28   secret_key   => $cfg->{ secret_key },
    29   token        => $cfg->{ token },
    30   token_secret => $cfg->{ token_secret },
    31 );
    32  
    33 my $blog = $t->blog( $site );
    34 
    35 my $post = $blog->post(
    36   type => 'photo', data => [$picfile] ,
    37 );
    38 
    39 if( $post ) {
    40    print "I have published post id: " .
    41       $post->{id}, "\n"; 
    42 } else {
    43    die $blog->error;
    44 }

Listing 2 zeigt die Anwendung, die die Fotos von der Überwachungskamera abholt. Der URL in Zeile 13 ist an den Wert für die tatsächlich genutzte Webadresse der Kamera anzupassen, sowie der Username und das Passwort für die Weboberfläche der Kamera in den Zeilen 16 und 17 einzutragen. Die Methode credentials des Objekts vom Typ WWW::Mechanize speichert diese Information und wird sie bei Webanfragen automatisch beifügen, falls der Kamera-Webserver nach einem Passwort fragt. Eigentlich genügte zum Einholen von Daten auf einem Webserver das Perl-Modul LWP::UserAgent, aber die Superklasse WWW::Mechanize bietet eine bequemere credential()-Methode an, die weniger Parameter erfordert. Die Foscom-Kamera liefert unter dem Pfad /snapshot.cgi einen aktuellen Schnappschuss ihres Blickfelds und die Funktion blurt() aus dem CPAN-Modul Sysadm::Install schreibt die Bilddaten in Zeile 22 in eine lokale Datei namens snapshot.jpg. Nun kommen die Tumblr-Funktionen zum Zug, Zeile 24 liest die YAML-Datei mit den Tokens ein und Zeile 26 erzeugt ein neues Objekt vom Typ WWW::Tumblr.

Mittels der Tumblr-API holt Zeile 33 dann Informationen zum vorher definierten Testblog auf schtonk.tumblr.com ein und die Methode post() sendet den Inhalt der Bilddatei unter Angabe des Typs "photo" an den Tumblr-Server. Im Erfolgsfall steht in $post ein wahrer Wert, und das Skript druckt die ID des neu angelegten Blogeintrags aus:

    $ ./tumblr-post 
    I have published post id: 63791478637

Leider bietet die API noch keine Möglichkeit, zum Bild einen Text beizufügen, dies scheint zur Zeit nur durch das Browserinterface zu funktionieren, das richtige HTML-Seiten mit eingebauten Fotos erlaubt. Theoretisch könnte das Skript jedoch Texteinträge hochladen und die Bilder von einer anderen Seite referenzieren, doch für die Fotos der Überwachungskamera ist es ist praktischer, wenn Tumblr die Bilder selbst speichert. Eine weitere Möglichkeit wäre Beschriftung im Bild selbst, wie letzten Monat im Snapshot mittels des CPAN-Moduls Imager vorgestellt. So lassen sich kurze Texte wie zum Beispiel die aktuell gemessene Außentemperatur ins Bild einpassen.

Infos

[1]

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

[2]

Tumblr API, http://www.tumblr.com/docs/en/api/v2

[3]

"Onlinebücherei", Michael Schilli, http://www.linux-magazin.de/Ausgaben/2013/02/Perl-Snapshot

Michael Schilli

arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.