Feb
13

Twitter Timeline und Twitter Search-Api-Component

Sehr schöne Anwendung der Javascript Timeline Bibliothek zur Darstellung von CakePHP Nachrichten die auf Twitter erscheinen.

Eine Cake Componente für die Twitter Search API habe ich vorkurzem zusammengeschrieben.

//  Twitter Search API (Json)
//
    App::import('Core', array('HttpSocket'));
    /**
     * Twitter Search
     * Documentation can be found on:
     * http://apiwiki.twitter.com/
     * http://apiwiki.twitter.com/Search+API+Documentation
     */
    class TwitterSearchComponent extends Object {
        var $Http = null;
        var $apiUrl = 'http://search.twitter.com/search.json';
 
        function __construct() {
            $this->Http =& new HttpSocket();
        }
 
        function query($url = null, $rpp = 25)
        {
            if (!empty($url)) {
                $params = array(
                    'q'=>$url,
                    'rpp'=>$rpp
                    );
                $response = $this->Http->get($this->apiUrl, $params);
                if (function_exists('json_deconde')) {
                    return json_decode($response);
                } else if (App::import('Vendor', 'json', array('file'=>'JSON.php'))) {
                    $json = new Services_JSON();
                    return $json->decode($response);
                }
                return false;
            }
        }
    }
Feb
11

21tweets.com jetzt online

In den letzten zwei Tagen hab ich mit Hilfe der Twitter-Komponente und ein paar Cake Shell-Skripten die Seite 21tweets online gebracht. Die Idee dahinter war eine Möglichkeit zu bauen, mit der man via Twitter neue Gewohnheiten in 21 Tagen - daher 21tweets - erlernen kann.

Die Anwendung sammelt also Tweets auf Twitter ein, die an den Account @21T gehen und eine Gewohnheit mit einem Hashtag # markieren. Damit aber nicht jeder Tweet aufgenommen wird, muss zunächst die neue Gewohnheit bei 21tweets angemeldet werden. Ab dann kann gesammelt und angezeigt werden. Nur Tweets die an aufeinander folgenden Tagen getwittert werden, werden zur Statistik für die Gewohnheit gezählt. Falls man einen Tag auslässt, dann verfallen alle vorher gesammelten Tweets für die Zählung der 21-Tage-Statistik. Klar ?

Ein paar Problemchen und wie ich die umschifft habe, will ich hier kurz erklären:

  • Ich wollte kein Login und keine Anmeldung
  • Erkennung von neuen Anwendern durch die Prüfung, ob der Twitterati dem Account von 21tweets @21T folgt.
  • Ausschließen von Twitter-Bots mit Hilfe eines Validate-Links, wie es bei normalen Accounts gemacht wird.
  • Abfrage von direct-messages zur Erkennung der Gewohnheit, die in den nächsten 21 Tagen praktiziert werden soll. Einfach via #hashtag in der DirectMessage

Eines der Probleme war die Erkennung von neuen Twitterati, die dem Account folgen. Mittlerweile hatte aber Twitter zwei neue Funktionen, die ich in einem der letzten Posts schon beschrieben habe, bereitgestellt. Damit lassen sich die neuen Follower recht einfach ermitteln, indem man einfach einen array_diff() auf die beiden Listen anwendet.

Es laufen insgesamt drei Backgroundjobs, die über crontab angestossen werden und dafür verantwortlich sind:

  1. Die neuen Follower einzutragen und ebenfalls als Follower aufnehmen
  2. Neue Habits aus den Direct Messages zu filtern
  3. Neue Tweets aus den normalen Twitter-Meldungen zu filter, die an @21T gesendet werden und die praktizierte Gewohnheit mit einem Hashtag enthalten.

Jetzt fehlt eigentlich nur noch ein wenig Statistik und Auswertungen, damit das Ganze rund wird. Bis dahin

happy baking

Feb
09

Twitter API Neuigkeiten

Hängt jetzt zwar nicht direkt mit CakePHP zusammen, aber da ich im Augenblick gerade eine kleine Twitter-App schreibe, hab ich gerade festgestellt, dass die Twitter-API mittlerweile zwei Funktionen unterstützt, die unter dem Stichwort Social Graph Methods laufen. Dabei lassen sich damit die ids der Friends und der Follower abholen. Die Funktion ist in der Twitter-Componente bisher wohl nicht drin gewesen.

Ich hab die zwei Methoden einfach mal für die Komponente erstellt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
        /* social graph methods */
        /**
         * Returns an array of numeric IDs for every user the specified user is following.
         *
         */
        function friendsids() {
            $url = "http://twitter.com/friends/ids.xml";
 
            return $this->__process($this->Http->get($url, null, $this->__getAuthHeader()));
        }
        /**
         * Returns an array of numeric IDs for every user the specified user is followed by.
         *
         * @param string id optional.  The ID of follower from whom to get the followers.
         */
        function followerids($id = null) {
            if (is_null($id)) {
                $url = "http://twitter.com/followers/ids.xml";
            } else {
                $url = "http://twitter.com/followers/ids/{$id}.xml";
            }
 
            return $this->__process($this->Http->get($url, null, $this->__getAuthHeader()));
        }

Viel Spass beim Twittern mit CakePHP

Jan
27

Wer hat denn den Datensatz erzeugt oder geändert

CakePHP hat ja eine sehr praktischen Automatismus und speichert in die Datenbankfelder created und modified entsprechend das aktuelle Datum ab, wenn die Felder vorhanden und NULL sind. Modified wird bei jedem Update automatisch neu gesetzt. Das klappt bei jedem Model::save(), aber nicht bei Model::updateAll() hab ich übrigens festgestellt, da bei updateAll() intern SQL-Queries erzeugt werden in der Form:

1
  UPDATE modelname SET feld1 = 12 WHERE feld2 = Wert

Wäre es nicht praktisch, wenn man unterschiedliche Benutzer hat, dass auch die Information, welcher Benutzer den Datensatz erzeugt bzw. geändert hat, gespeichert wäre.
Die AuthComponent stellt ja in CakePHP eine recht handliche Möglichkeit bereit den Zugang zu einer Anwendung zu regeln. Dabei hat man dann eine Tabelle mit den Usern die unter anderem eben auch die UserId enthält.
Auth ist auch so nett und speichert in der aktuellen Session die Informationen über den eingeloggten Benutzer. Allerdings kommt man nicht so leicht an diese Informationen, wenn man versucht im Model::beforeSave() den Benutzer in den Datensatz einzutragen.

1
2
3
4
5
6
7
8
9
10
CREATE TABLE articles (
  id int UNSIGNED NOT NULL AUTO_INCREMENT,
  title varchar(255) NOT NULL,
  article varchar(255) NOT NULL,
  created datetime,
  creator_id int UNSIGNED,   # soll über LoadsysAuth gesetzt werden
  modified datetime,
  modifier_id int UNSIGNED,   # soll über LoadsysAuth gesetzt werden
  PRIMARY KEY(id)
);

Vor einiger Zeit habe ich die LoadsysAuth Komponente gefunden, die genau an dieser Stelle hilft. Denn sie kann im Model auf die Session-Informationen zugreifen und den Benutzer im Datensatz eintragen.
Das sieht dann in etwa so aus wenn man es gleich in das beforeSave() im app_model.php einträgt, kann man seine Tabelle um die Felder creator_id und modifier_id erweitern und im beforeSave() werden die Werte dann von der LoadsysAuth gesetzt.

1
2
3
4
5
6
7
8
9
10
11
12
class AppModel extends Model {
        function beforeSave() {
                $exists = $this->exists();
                if ( !$exists && $this->hasField('creator_id') && empty($this->data[$this->alias]['creator_id']) ) {
                        $this->data[$this->alias]['creator_id'] = LoadsysAuth::getUserId();
                }
                if ( $this->hasField('modifier_id') && empty($this->data[$this->alias]['modifier_id']) ) {
                        $this->data[$this->alias]['modifier_id'] = LoadsysAuth::getUserId();
                }
                return true;
        }
}

Wenn man die LoadsysAuth Komponente einsetzt, dann sollte man allerdings darauf achten, dass sämtlich Auth Aufrufe durch LoadsysAuth ersetzt werden. Die Komponente ist nämlich direkt von der Auth Komponente abgeleitet und stellt alle Funktionen von Auth zur Verfügung. Zusätzlich kann man allerdings mit Funktionen wie

1
2
LoadsysAuth::getUser(); // liefert den kompletten User Datensatz
LoadsysAuth::getUserId(); // liefert nur die Id des Users

auch im Model auf die entsprechenden Benutzerdaten zugreifen. Bei meinem letzten Projekt hat mir das enorm geholfen und einiges an extra Arbeit erspart. Dabei sind die Funktionen in LoadsysAuth als static-Funktionen implementiert und können recht einfach erweitert bzw. angepasst werden.

Hier gehts zur LoadsysAuth Komponente, wo sie auch heruntergeladen werden kann.

Jan
24

SMS-Gateway als CakePHP Componente

Für eine Anwendung, die ich gerade umsetze, brauche ich unter anderem auch die Möglichkeit SMS Meldungen zu versenden. Dazu geht man am Einfachsten her und sucht sich von den zahlreichen SMS-Providern einen aus, der einem zusagt. Die Provider bieten in der Regel unterschiedliche Schnittstellen an. E-Mail und HTTP sind so die üblichen Schnittstellen. Ich habe mich für Mobilant entschieden und dort für die HTTP-Schnittstelle. Im großen und Ganzen hat man ein einfaches REST-Interface, sprich alle Daten werden über einen HTTP-Aufruf an den Provider übermittelt.

Um nicht mit urlencoding und dem Zusammenbau der URL zu stolpern während der Programmierung des SMS-Versands, habe ich mir eine übersichtliche Komponente erstellt. Für die Kommunikation mit HTTP-Servern bietet CakePHP 1.2 von Haus aus eine HttpSocket Bibliothek an, die recht einfach zu nutzen ist. Für die Dokumenation zu HttpSocket steht im Augenblick nur die API zur Verfügung. Im Book findet sich dazu leider noch nichts. Wird aber sicherlich noch kommen.

HttpSocket wird zunächst mit App::import geladen und im Konstruktor initalisiert. Danach ist es eigentlich ein leichtes die send() zu Schreiben. Für einen GET Aufruf übergibt man an HttpSocket::send() die URL des Anbieters und übergibt im zweiten Parameter als Array die Parameter für den HTTP-Aufruf.

Bei Mobilant liegt die Schnittstelle auf:

Damit die Komponenten später auch einfach auf anderen SMS-Gateways angepasst werden kann, hab ich mir ein kleines Array $service_apis aufgebaut, das die URL enthält und die Beschreibung für die Parameter die erwartet werden. Bei Mobilant und auch bei den anderen Anbietern benötigt man in der Regel Zugangsinformationen oder eben einen Schlüssel (key) der dafür sorgt, dass das Absenden möglich wird und vorallem auch, damit der SMS-Provider mir eine Rechnung über die versandten SMS-Nachrichten stellen kann.

Die Anwendung der Komponente ist recht trivial. Im Controller wird die Komponente über

1
      $var components = array('SmsGateway');

eingebunden und dann kann durch einen Aufruf in der Art:

1
2
     $this->SmsGateway->send('01701234567', 
                 'Das ist eine SMS Nachricht mit CakePHP versandt');

genutzt werden.

Als dritten Parameter von send() könnte man noch einen anderen SMS-Provider auswählen, wenn die Daten entsprechend in dem Array $service_api hinterlegt wären.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
    App::import('Core', array('HttpSocket'));
    /**
     * SMS Gateway
     * Documentation can be found on:
     * http://www.mobilant.de/system/m_navInf.php?infName=documents
     * PDF-Document
     */
    class SmsGatewayComponent extends Object {
        var $Http = null;
        var $service_apis = array(
            'mobilant'=>array('url'=>'https://gateway2.mobilant.net/index.php',
                              'params'=>array('key'=>'xxxxxxxxxxxxxxxxxxx',
                                              'message'=>'%message%',  // external parameter
                                              'receiver'=>'%receiver%', // external parameter
                                              'service'=>'sms'
                                              ),
                              ),
            );
 
        /**
         * __construc -- constructor to initialize the HttpSocket Object to access the services
         *
         * @author: SHirsch
         * @created: 09.01.2009
         */
        function __construct() {
            $this->Http =& new HttpSocket();
        }
 
        /* function
        **
        ** @created: 05.01.2009 12:54:43
        ** @param string $message that should be send via Provider
        ** @param strng $receiver the phone number of the receiving cell phone
        ** @param string $service_api name of the url shortener to be used by create as in $this->service_apis
        ** @return the error from the HTTP request if an error was encountered
        */
        function send($message = null, $receiver = null, $service_api = 'mobilant')
        {
            if (!empty($message) && !empty($receiver)) {
                if (array_key_exists($service_api,  $this->service_apis)) {
                    $params = $this->service_apis[$service_api]['params'];
                    $params['message'] = utf8_encode($message);
                    $params['receiver'] = $receiver;
                    return $this->Http->get($this->service_apis[$service_api]['url'], $params);
                }
            }
            return false;
        }
 
        /* check_phone_number
        **
        ** @created: 15.01.2009 13:53:59
        ** @param type $var
        **
        */
        function check_phone_number($phone)
        {
            return $phone;
        }
    }

Ein paar Erweiterungen sind noch notwendig, wie beispielsweise die Prüfung der Telefonnummer mit Hilfe der Funktion check_phone_number() auf gültige Nummern im Inland oder ähnliches.

Oben