Twitter-Tweets zu Identi.ca übertragen…

Zur Zeit wird ja viel über Wikileaks geredet und irgendwie macht sich derzeit so ziemlich jedes Unternehmen unbeliebt, weil es irgendein Konto von Wikileaks oder einem seiner Verbündeten sperrt. Es gibt sogar eine Hackerfront, die die Webseite jedes Widersachers mit DDOS-Attacken zuschüttet... einige sprechen schon von einem Cyberwar.

Auch Twitter ist in Ungnade gefallen, sodass nun einige Leute darüber nachdenken, zum Opensource-Konkurrenten Identi.ca zu wechseln. Ich persönlich - und einige andere auch - möchten diesen Schritt noch nicht gehen, sich jedoch trotzdem langsam von Twitter als Plattform lösen. Eine einfache Möglichkeit, dies zu tun, ist, erstmal einfach alle Tweets auch auf Identi.ca zu veröffentlichen. Hierfür gibt es mehrere Möglichkeiten:

  • Zum einen kann man Identi.ca als primäre Plattform nutzen, sie mit Twitter verknüpfen und die integrierte Synchronisation von Identi.ca verwenden. Dadurch werden alle "Dents" (die Nachrichten bei Identi.ca) auch bei Twitter veröffentlicht. Leider ist das Angebot an Twitter-Clients viel besser als das Angebot an Identi.ca-Clients.
  • Eine zweite Möglichkeit ist, einen Client zu verwenden, der sowohl Twitter als auch Identi.ca beherrscht. Mit diesem kann man dann beide Plattformen gleichzeitig nutzen und ist nicht darauf beschränkt, dass die Nachrichten der einen Plattform eine exakte Kopie der anderen Plattform sind. Für mich persönlich gibt es hierfür jedoch keine guten Clients.
  • Die dritte Möglichkeit ist, einen externen Dienst zu nutzen, der die Tweets von Twitter automatisch bei Identi.ca veröffentlicht. Dadurch kann man seinen gewohnten Twitter-Client weiternutzen und muss sich keinen suchen, der auch Identi.ca unterstützt.

Da mir - wie gesagt - die Clients mit Identi.ca-Unterstützung nicht wirklich gefallen haben, habe ich geguckt, welche Möglichkeit es gibt, meine Tweets mit Identi.ca zu synchronisieren. Dabei bin ich auf das Angebot von Twitterfeed.com gestoßen, das ich bis heute auch genutzt habe. Leider hat das Tool einen großen Haken: pro 30 Minuten werden maximal 5 Tweets synchronisiert. Dadurch gehen massenweise Tweets einfach verloren. 🙁

Glücklicherweise ist mir heute durch @rka (dem Landesvorstandsvorsitzenden der Piratenpartei Berlin) ein Stück PHP-Code in die Finger gefallen, der die Synchronisation zwischen Twitter und Identi.ca übernehmen konnte. Leider entsprach der Code überhaupt nicht meinen Ansprüchen. Wie es aussieht, wurde er ursprünglich von @cdevroe geschrieben und hieß damals noch rss2twitter. Nix mit Identi.ca und so. Diese Anpassungen wurden anscheinend durch @einfachben und @hermes42 geleistet. Wenn man sich den Original-Quelltext ansieht, kann man sich vorstellen, wie das Stück Code aussah, das heute bei mir gelandet war.

Da ich die Idee, das Synchronisieren selber vornehmen zu können, trotzdem so großartig fand, habe ich mich dran gesetzt und den Code einmal komplett neu geschrieben - lediglich die Ideen aus dem ursprünglichen Code blieben erhalten. Da der ursprüngliche Code unter der GPL-Lizenz steht (und ich nicht sagen kann, wieviele Prozent des Originalcodes in meiner Version noch enthalten sind), steht meine Variante ebenfalls unter der GPL:

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
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<?php
  // TCtoICsync 0.1b - Post Twitter.com tweets as Identi.ca dents
  // Copyright (C) 2010 Kevin Niehage (@weizenspreu)
  //
  // This program is free software: you can redistribute it and/or modify
  // it under the terms of the GNU General Public License as published by
  // the Free Software Foundation, either version 3 of the License, or
  // (at your option) any later version.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License
  // along with this program. If not, see <http://www.gnu.org/licenses/>.
  //
  // This tool is based on the work of @cdevroe, @einfachben and @hermes42.
  //
  // HowTo:
  // ======
  //
  // In order to use this Twitter.Com to Identi.Ca synchronization
  // tool you have to specify the following values:
  //
  // * $EXECUTE_PASSWORD:  Will protect you from malicious callers.
  //                       Only the people knowing this password
  //                       can call the synchronization process.
  //                       This password has to be provided as the
  //                       GET parameter "pwd" when calling this script.
  // * $IDENTICA_NICK:     This is your identi.ca nickname. It is
  //                       needed for using the identi.ca API.
  // * $IDENTICA_PASSWORD: This is your identi.ca password. It is
  //                       needed for using the identi.ca API.
  // * $TWITTER_NICK:      This is your twitter nickname. It is
  //                       needed for formating the messages that
  //                       are read from the twitter RSS feed.
  // * $AUTO_REFRESH:      This feature enables a small JavaScript
  //                       auto-refresh feature. This value defines
  //                       the number of seconds the page waits before
  //                       it refreshes itself. A value of zero or less
  //                       deactivates the refresh feature.
  //
  // This tool is not useful if you are not able to call it frequently.
  // A good way to do so is to employ a CRON job. Alternatively you can
  // activate the $AUTO_REFRESH feature.
  //
  // You can use this script for more than one twitter/identi.ca account.
  // To do so you have to read $IDENTICA_NICK, $IDENTICA_PASSSWORD and
  // $TWITTER_NICK from some other place (like a database).
  //
  // If you do not want others to see the status of your synchronization
  // you can protect the "*.date" and "*.lock" files with a small ".htaccess"
  // file that contains the following code (remove the leading ">"):
  // > RewriteEngine on
  // > Options +FollowSymLinks
  // >
  // > RewriteBase /
  // >
  // > RewriteRule ^(.*)\.date$ - [L,R=404]
  // > RewriteRule ^(.*)\.lock$ - [L,R=404]

  /* THESE SETTINGS ARE FREE TO EDIT */

  $EXECUTE_PASSWORD = "[EXECUTE_PASSWORD]";

  $AUTO_REFRESH      = 0;
  $IDENTICA_NICK     = "[IDENTICA_NICK]";
  $IDENTICA_PASSWORD = "[IDENTICA_PASSWORD]";
  $TWITTER_NICK      = "[TWITTER_NICK]";

  /* STOP EDITING HERE IF YOU DO NOT KNOW WHAT YOU ARE DOING */

  $DATE_FILE    = dirname(__FILE__) . "/" . $TWITTER_NICK . ".date";
  $IDENTICA_API = "https://identi.ca/api/statuses/update.xml";
  $LOCK_FILE    = dirname(__FILE__) . "/" . $TWITTER_NICK . ".lock";
  $TWITTER_RSS  = "http://twitter.com/statuses/user_timeline/" . $TWITTER_NICK . ".rss";
  $USER_AGENT   = "TCtoICsync 0.1b";

  /* STOP EDITING HERE */

  // we are going to output some UTF-8
  header("Content-Type: text/html; charset=utf-8");

  // only allow authorized access
  if (isset($_GET["pwd"]) && ($_GET["pwd"] == $EXECUTE_PASSWORD)) {
    // stop other executions from making trouble
    $locked = "0";
    if (file_exists($LOCK_FILE)) {
      $locked = file_get_contents($LOCK_FILE);
    }

    if ($locked == "0") {
      try {
        // disallow next execution
        file_put_contents($LOCK_FILE, "1");

        // retrieve Twitter RSS feed of user
        $options = array(
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_SSL_VERIFYHOST => false,
          CURLOPT_SSL_VERIFYPEER => false,
          CURLOPT_USERAGENT      => $USER_AGENT,
          CURLOPT_VERBOSE        => 1
        );
        $curl = curl_init($TWITTER_RSS);
        curl_setopt_array($curl, $options);
        $xml    = curl_exec($curl);
        $header = curl_getinfo($curl);
        curl_close($curl);

        // only proceed if Twitter RSS feed could be retrieved
        if ($header["http_code"] == 200) {
          print("Twitter RSS feed retrieved: " . $TWITTER_RSS . "<br />\n");

          // load last synchronization date
          $previousDate = null;
          if (file_exists($DATE_FILE)) {
            $previousDate = file_get_contents($DATE_FILE);
          }

          // parse feed
          $xml = new SimpleXMLElement($xml);

          // convert SimpleXMLElement to array
          $index      = 0;
          $latestDate = $previousDate;
          $messages   = null;
          foreach ($xml->channel[0]->item as $item) {
            if (($item->title != null) && ($item->pubDate != null)) {
              $currentDate = strtotime($item->pubDate);

              // only synchronize new tweets
              if (($previousDate == null) || ($previousDate < $currentDate)) {
                $temp = html_entity_decode($item->title, ENT_QUOTES, "UTF-8");

                // check if tweet belongs to selected twitter user
                if (stripos($temp, $TWITTER_NICK) === 0) {
                  $messages[$index++] = substr($temp, strlen($TWITTER_NICK)+2);

                  if ($latestDate == null) {
                    $latestDate = $currentDate;
                  } else {
                    if ($latestDate < $currentDate) {
                      $latestDate = $currentDate;
                    }
                  }
                }
              }
            }
          }

          if (($messages != null) && (count($messages) > 0)) {
            // reverse messages
            $messages = array_reverse($messages);
            ksort($messages);

            // handle Twitter messages
            foreach ($messages as $message) {
              // call identi.ca API
              $options = array(
                CURLOPT_HTTPAUTH       => CURLAUTH_BASIC,
                CURLOPT_POST           => true,
                CURLOPT_POSTFIELDS     => "status=" . urlencode($message) . "&source=" . urlencode($USER_AGENT),
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_SSL_VERIFYHOST => false,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_USERAGENT      => $USER_AGENT,
                CURLOPT_USERPWD        => $IDENTICA_NICK . ":" . $IDENTICA_PASSWORD,
                CURLOPT_VERBOSE        => 1
              );
              $curl = curl_init($IDENTICA_API);
              curl_setopt_array($curl, $options);
              $output = curl_exec($curl);
              $header = curl_getinfo($curl);
              curl_close($curl);

              if ($header["http_code"] == 200) {
                print("Tweet synchronized: " . $message . "<br />\n");
              } else {
                print("Tweet could not be synchronized: " . $message . "<br />\n" . $output . "<br />\n");
              }
            }
          }

          // save last synchronization date
          if ($latestDate != null) {
            file_put_contents($DATE_FILE, $latestDate);
          }
        } else {
          print("Twitter RSS feed could not be retrieved: " . $TWITTER_RSS . "<br />\n");
        }
      } catch (Exception $e) {
        print("An exception has occured. Execution has been aborted.<br />\n");
      }

      // allow next execution
      file_put_contents($LOCK_FILE, "0");
    } else {
      print("The synchronization is already running.<br />\n");
    }
  } else {
    print("You are not authorized to execute this action.<br />\n");
  }
  print("DONE!<br />\n");

  if ($AUTO_REFRESH > 0) {
    print("<br />\nWill auto-refresh in " . $AUTO_REFRESH . " seconds.<br />\n");
    print("<script tyle="text/JavaScript">\n");
    print("<!--\n");
    print("  setTimeout("location.reload(true);", " . $AUTO_REFRESH*1000 . ");\n");
    print("//-->\n");
    print("</script>\n");
  }
?>

Ja, es ist ein Batzen Code, deswegen hier ein kurze Erklärung: Ganz am Anfang in dem elendig langen Kommentar steht das ganze GPL-Geraffel, gefolgt von einem kleinen Howto. Dort wird beschrieben, welche Werte vom Benutzer geändert werden müssen, was sie bedeuten und wofür sie benutzt werden. Es gibt noch ein paar weitere Werte, die verändert werden können - das sollte allerdings nur von Leuten getan werden, die auch wirklich wissen, was sie da tun!

Zu Beginn des eigentlichen Codes wird ein Passwort überprüft. Das soll verhindern, dass fremde Leute mit dem Code rumspielen und das Script nach Belieben aufrufen können. Zudem wird sichergestellt, dass das Script nicht mehrfach parallel ausgeführt werden kann. Hierdurch könnten Tweets mehrfach bei Identi.ca landen. Auf die Implementierung der Sperre sollte man sich jedoch nicht zu 100% verlassen, da sie relativ einfach gestrickt ist. Die gröbsten Probleme sollte sie jedoch auffangen können.

Die eigentliche Funktionalität sieht nun so aus: Das Script ruft den RSS-Feed des Twitter-Nutzers ab. In diesem befinden sich die letzten 20 Tweets, die man verfasst hat. Wichtig: Die Tweets müssen für alle zugänglich sein, ansonsten kann das Script die Tweets nicht auslesen. Wenn die Tweets ausgelesen worden sind, wird überprüft, ob sie zum richtigen Benutzer gehören und ob sie nicht bereits bei Identi.ca eingespielt wurden (wird anhand des Datums geprüft). Anschließend werden die neuen Tweets mit Hilfe der Identi.ca-API auf der Plattform eingespielt.

Das Script selber legt zwei Dateien an - eine "*.date"-Datei und eine "*.lock"-Datei. In die .date-Datei wird der Zeitstempel des letzten Tweets geschrieben, der nach Identi.ca synchronisiert wurde - dadurch kann beim nächsten Aufruf geprüft werden, welche Tweets neu sind. Die .lock-Datei ist dafür da, um sicherzustellen, dass das Script nicht doppelt ausgeführt werden kann.

Eigentlich hatte ich vor, das Script bei Hetzner mit einem einfachen CRON-Job jede Minute einmal laufen zu lassen. Leider ist es dort nur erlaubt, einen CRON-Job alle 2 Stunden laufen zu lassen - viel zu viel für solch eine Aufgabe! Deshalb habe ich eine Option eingebaut, mit der ein Stück JavaScript in die Ergebnisseite des Scripts geschrieben wird. Durch diesen JavaScript-Code kann man das regelmäßige Aufrufen mit jedem beliebigen Browser erledigen, der JavaScript beherrscht. Einfach die Seite einmal aufrufen und fertig. Bei mir läuft das Script nun z.B. im Browser meines Windows-Heimservers - der ist schließlich sowieso die ganze Zeit eingeschaltet. 😉

So, das war es zum Sync-Script; jetzt noch ein kleiner Tipp für die Leute, die gerne zweigleisig fahren wollen: Da man mit dieser Lösung weiterhin mit Twitter als primäre Plattform arbeitet, kriegt man hintenrum garnicht mit, dass ein Identi.ca-Nutzer einem eine Nachricht geschrieben hat. Hierfür bietet Identi.ca direkt eine Lösung an. Und zwar kann man seinen Identi.ca-Account mit seinem Twitter-Account verbinden und Identi.ca dann anweisen, @-Replies von Identi.ca nach Twitter zu schicken, damit man sie dort wie normale Tweets lesen kann:

Twitter-Optionen bei Identi.ca

Sooo... ich hoffe, dieser ziemlich lange Artikel hat euch gefallen. Ich würde mich freuen, dem ein oder anderen mit dieser Arbeit zu helfen, sich ein Stückchen von Twitter zu lösen. Das gezeigte Script dürfte hierfür ein gutes, schmerzloses Mittel sein. 🙂

Zum Schluss noch etwas Rechtliches:
Der Autor dieses Programms haftet nicht für Schäden an Soft- oder Hardware oder Vermögensschäden, die durch das Benutzen des Programms entstehen, es sei denn, diese beruhen auf einem grob fahrlässigen oder vorsätzlichen Handeln des Autors, seiner Erfüllungsgehilfen oder seiner gesetzlichen Vertreter.
Update:
Derzeit funktioniert dieses Script leider nicht, da die verwendete URL zum Abrufen der Tweets von Twitter nicht mehr angeboten wird. Es gibt jedoch die Möglichkeit, das Script auf die URL http://search.twitter.com/search.rss?q=from%3Ausername umzuschreiben. Diese Arbeit muss ich jedoch erst noch auf mich nehmen. 🙂
Twitdentende Grüße, Kenny

1 Kommentar » Schreibe einen Kommentar

  1. Pingback: Cross-posting auf Identi.ca und Twitter | PoBlog

Schreibe einen Kommentar

Um Ihnen beim weiteren Kommentieren auf dieser Webseite die erneute Eingabe Ihrer Daten zu ersparen, wird beim Absenden Ihres Kommentars ein Cookie an Ihren Browser gesendet und von diesem gespeichert. Mit dem Absenden eines Kommentars auf dieser Webseite stimmen Sie der Speicherung und Übertragung dieses Cookies explizit zu.

Pflichtfelder sind mit * markiert.