]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/twitterapi.php
Merge branch '0.8.x' of git@gitorious.org:laconica/dev into 0.8.x
[quix0rs-gnu-social.git] / lib / twitterapi.php
1 <?php
2 /*
3  * Laconica - a distributed open-source microblogging tool
4  * Copyright (C) 2008, Controlez-Vous, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 if (!defined('LACONICA')) { exit(1); }
21
22 class TwitterapiAction extends Action
23 {
24
25     var $auth_user;
26
27     /**
28      * Initialization.
29      *
30      * @param array $args Web and URL arguments
31      *
32      * @return boolean false if user doesn't exist
33      */
34
35     function prepare($args)
36     {
37         parent::prepare($args);
38         return true;
39     }
40
41     /**
42      * Handle a request
43      *
44      * @param array $args Arguments from $_REQUEST
45      *
46      * @return void
47      */
48
49     function handle($args)
50     {
51         parent::handle($args);
52     }
53
54     /**
55      * Overrides XMLOutputter::element to write booleans as strings (true|false).
56      * See that method's documentation for more info.
57      * 
58      * @param string $tag     Element type or tagname
59      * @param array  $attrs   Array of element attributes, as
60      *                        key-value pairs
61      * @param string $content string content of the element
62      *
63      * @return void
64      */
65     function element($tag, $attrs=null, $content=null)
66     {
67         if (is_bool($content)) {
68             $content = ($content ? 'true' : 'false');
69         }
70
71         return parent::element($tag, $attrs, $content);
72     }
73     
74     function twitter_user_array($profile, $get_notice=false)
75     {
76
77         $twitter_user = array();
78
79         $twitter_user['name'] = $profile->getBestName();
80         $twitter_user['followers_count'] = $this->count_subscriptions($profile);
81         $twitter_user['screen_name'] = $profile->nickname;
82         $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
83         $twitter_user['location'] = ($profile->location) ? $profile->location : null;
84         $twitter_user['id'] = intval($profile->id);
85
86         $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
87
88         $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE);
89         $twitter_user['protected'] = false; # not supported by Laconica yet
90         $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
91
92         if ($get_notice) {
93             $notice = $profile->getCurrentNotice();
94             if ($notice) {
95                 # don't get user!
96                 $twitter_user['status'] = $this->twitter_status_array($notice, false);
97             }
98         }
99
100         return $twitter_user;
101     }
102
103     function twitter_status_array($notice, $include_user=true)
104     {
105         $profile = $notice->getProfile();
106
107         $twitter_status = array();
108         $twitter_status['text'] = $notice->content;
109         $twitter_status['truncated'] = false; # Not possible on Laconica
110         $twitter_status['created_at'] = $this->date_twitter($notice->created);
111         $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ?
112             intval($notice->reply_to) : null;
113         $twitter_status['source'] = $this->source_link($notice->source);
114         $twitter_status['id'] = intval($notice->id);
115
116         $replier_profile = null;
117
118         if ($notice->reply_to) {
119             $reply = Notice::staticGet(intval($notice->reply_to));
120             if ($reply) {
121                 $replier_profile = $reply->getProfile();
122             }
123         }
124
125         $twitter_status['in_reply_to_user_id'] =
126             ($replier_profile) ? intval($replier_profile->id) : null;
127         $twitter_status['in_reply_to_screen_name'] =
128             ($replier_profile) ? $replier_profile->nickname : null;
129
130         if (isset($this->auth_user)) {
131             $twitter_status['favorited'] = $this->auth_user->hasFave($notice);
132         } else {
133             $twitter_status['favorited'] = false;
134         }
135
136         if ($include_user) {
137             # Don't get notice (recursive!)
138             $twitter_user = $this->twitter_user_array($profile, false);
139             $twitter_status['user'] = $twitter_user;
140         }
141
142         return $twitter_status;
143     }
144
145     function twitter_rss_entry_array($notice)
146     {
147
148         $profile = $notice->getProfile();
149         $entry = array();
150
151         # We trim() to avoid extraneous whitespace in the output
152
153         $entry['content'] = common_xml_safe_str(trim($notice->rendered));
154         $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
155         $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
156         $entry['published'] = common_date_iso8601($notice->created);
157
158         $taguribase = common_config('integration', 'taguri');
159         $entry['id'] = "tag:$taguribase:$entry[link]";
160
161         $entry['updated'] = $entry['published'];
162         $entry['author'] = $profile->getBestName();
163
164         # RSS Item specific
165         $entry['description'] = $entry['content'];
166         $entry['pubDate'] = common_date_rfc2822($notice->created);
167         $entry['guid'] = $entry['link'];
168
169         return $entry;
170     }
171
172     function twitter_rss_dmsg_array($message)
173     {
174
175         $entry = array();
176
177         $entry['title'] = sprintf('Message from %s to %s',
178             $message->getFrom()->nickname, $message->getTo()->nickname);
179
180         $entry['content'] = common_xml_safe_str(trim($message->content));
181         $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
182         $entry['published'] = common_date_iso8601($message->created);
183
184         $taguribase = common_config('integration', 'taguri');
185
186         $entry['id'] = "tag:$taguribase,:$entry[link]";
187         $entry['updated'] = $entry['published'];
188         $entry['author'] = $message->getFrom()->getBestName();
189
190         # RSS Item specific
191         $entry['description'] = $entry['content'];
192         $entry['pubDate'] = common_date_rfc2822($message->created);
193         $entry['guid'] = $entry['link'];
194
195         return $entry;
196     }
197
198     function twitter_dmsg_array($message)
199     {
200         $twitter_dm = array();
201
202         $from_profile = $message->getFrom();
203         $to_profile = $message->getTo();
204
205         $twitter_dm['id'] = $message->id;
206         $twitter_dm['sender_id'] = $message->from_profile;
207         $twitter_dm['text'] = trim($message->content);
208         $twitter_dm['recipient_id'] = $message->to_profile;
209         $twitter_dm['created_at'] = $this->date_twitter($message->created);
210         $twitter_dm['sender_screen_name'] = $from_profile->nickname;
211         $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
212         $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
213         $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
214
215         return $twitter_dm;
216     }
217
218     function show_twitter_xml_status($twitter_status)
219     {
220         $this->elementStart('status');
221         foreach($twitter_status as $element => $value) {
222             switch ($element) {
223             case 'user':
224                 $this->show_twitter_xml_user($twitter_status['user']);
225                 break;
226             case 'text':
227                 $this->element($element, null, common_xml_safe_str($value));
228                 break;
229             default:
230                 $this->element($element, null, $value);
231             }
232         }
233         $this->elementEnd('status');
234     }
235
236     function show_twitter_xml_user($twitter_user, $role='user')
237     {
238         $this->elementStart($role);
239         foreach($twitter_user as $element => $value) {
240             if ($element == 'status') {
241                 $this->show_twitter_xml_status($twitter_user['status']);
242             } else {
243                 $this->element($element, null, $value);
244             }
245         }
246         $this->elementEnd($role);
247     }
248
249     function show_twitter_rss_item($entry)
250     {
251         $this->elementStart('item');
252         $this->element('title', null, $entry['title']);
253         $this->element('description', null, $entry['description']);
254         $this->element('pubDate', null, $entry['pubDate']);
255         $this->element('guid', null, $entry['guid']);
256         $this->element('link', null, $entry['link']);
257         $this->elementEnd('item');
258     }
259
260     function show_json_objects($objects)
261     {
262         print(json_encode($objects));
263     }
264
265     function show_single_xml_status($notice)
266     {
267         $this->init_document('xml');
268         $twitter_status = $this->twitter_status_array($notice);
269         $this->show_twitter_xml_status($twitter_status);
270         $this->end_document('xml');
271     }
272
273     function show_single_json_status($notice)
274     {
275         $this->init_document('json');
276         $status = $this->twitter_status_array($notice);
277         $this->show_json_objects($status);
278         $this->end_document('json');
279     }
280
281     function show_single_xml_dmsg($message)
282     {
283         $this->init_document('xml');
284         $dmsg = $this->twitter_dmsg_array($message);
285         $this->show_twitter_xml_dmsg($dmsg);
286         $this->end_document('xml');
287     }
288
289     function show_single_json_dmsg($message)
290     {
291         $this->init_document('json');
292         $dmsg = $this->twitter_dmsg_array($message);
293         $this->show_json_objects($dmsg);
294         $this->end_document('json');
295     }
296
297     function show_twitter_xml_dmsg($twitter_dm)
298     {
299         $this->elementStart('direct_message');
300         foreach($twitter_dm as $element => $value) {
301             switch ($element) {
302             case 'sender':
303             case 'recipient':
304                 $this->show_twitter_xml_user($value, $element);
305                 break;
306             case 'text':
307                 $this->element($element, null, common_xml_safe_str($value));
308                 break;
309             default:
310                 $this->element($element, null, $value);
311             }
312         }
313         $this->elementEnd('direct_message');
314     }
315
316     function show_xml_timeline($notice)
317     {
318
319         $this->init_document('xml');
320         $this->elementStart('statuses', array('type' => 'array'));
321
322         if (is_array($notice)) {
323             foreach ($notice as $n) {
324                 $twitter_status = $this->twitter_status_array($n);
325                 $this->show_twitter_xml_status($twitter_status);
326             }
327         } else {
328             while ($notice->fetch()) {
329                 $twitter_status = $this->twitter_status_array($notice);
330                 $this->show_twitter_xml_status($twitter_status);
331             }
332         }
333
334         $this->elementEnd('statuses');
335         $this->end_document('xml');
336     }
337
338     function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null)
339     {
340
341         $this->init_document('rss');
342
343         $this->elementStart('channel');
344         $this->element('title', null, $title);
345         $this->element('link', null, $link);
346         if (!is_null($suplink)) {
347             # For FriendFeed's SUP protocol
348             $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
349                                          'rel' => 'http://api.friendfeed.com/2008/03#sup',
350                                          'href' => $suplink,
351                                          'type' => 'application/json'));
352         }
353         $this->element('description', null, $subtitle);
354         $this->element('language', null, 'en-us');
355         $this->element('ttl', null, '40');
356
357         if (is_array($notice)) {
358             foreach ($notice as $n) {
359                 $entry = $this->twitter_rss_entry_array($n);
360                 $this->show_twitter_rss_item($entry);
361             }
362         } else {
363             while ($notice->fetch()) {
364                 $entry = $this->twitter_rss_entry_array($notice);
365                 $this->show_twitter_rss_item($entry);
366             }
367         }
368
369         $this->elementEnd('channel');
370         $this->end_twitter_rss();
371     }
372
373     function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
374     {
375
376         $this->init_document('atom');
377
378         $this->element('title', null, $title);
379         $this->element('id', null, $id);
380         $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
381
382         if (!is_null($suplink)) {
383             # For FriendFeed's SUP protocol
384             $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
385                                          'href' => $suplink,
386                                          'type' => 'application/json'));
387         }
388
389         if (!is_null($selfuri)) {
390             $this->element('link', array('href' => $selfuri,
391                 'rel' => 'self', 'type' => 'application/atom+xml'), null);
392         }
393
394         $this->element('updated', null, common_date_iso8601('now'));
395         $this->element('subtitle', null, $subtitle);
396
397         if (is_array($notice)) {
398             foreach ($notice as $n) {
399                 $this->raw($n->asAtomEntry());
400             }
401         } else {
402             while ($notice->fetch()) {
403                 $this->raw($notice->asAtomEntry());
404             }
405         }
406
407         $this->end_document('atom');
408
409     }
410
411     function show_json_timeline($notice)
412     {
413
414         $this->init_document('json');
415
416         $statuses = array();
417
418         if (is_array($notice)) {
419             foreach ($notice as $n) {
420                 $twitter_status = $this->twitter_status_array($n);
421                 array_push($statuses, $twitter_status);
422             }
423         } else {
424             while ($notice->fetch()) {
425                 $twitter_status = $this->twitter_status_array($notice);
426                 array_push($statuses, $twitter_status);
427             }
428         }
429
430         $this->show_json_objects($statuses);
431
432         $this->end_document('json');
433     }
434
435     // Anyone know what date format this is?
436     // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
437     function date_twitter($dt)
438     {
439         $t = strtotime($dt);
440         return date("D M d H:i:s O Y", $t);
441     }
442
443     // XXX: Candidate for a general utility method somewhere?
444     function count_subscriptions($profile)
445     {
446
447         $count = 0;
448         $sub = new Subscription();
449         $sub->subscribed = $profile->id;
450
451         $count = $sub->find();
452
453         if ($count > 0) {
454             return $count - 1;
455         } else {
456             return 0;
457         }
458     }
459
460     function init_document($type='xml')
461     {
462         switch ($type) {
463          case 'xml':
464             header('Content-Type: application/xml; charset=utf-8');
465             $this->startXML();
466             break;
467          case 'json':
468             header('Content-Type: application/json; charset=utf-8');
469
470             // Check for JSONP callback
471             $callback = $this->arg('callback');
472             if ($callback) {
473                 print $callback . '(';
474             }
475             break;
476          case 'rss':
477             header("Content-Type: application/rss+xml; charset=utf-8");
478             $this->init_twitter_rss();
479             break;
480          case 'atom':
481             header('Content-Type: application/atom+xml; charset=utf-8');
482             $this->init_twitter_atom();
483             break;
484          default:
485             $this->client_error(_('Not a supported data format.'));
486             break;
487         }
488
489         return;
490     }
491
492     function end_document($type='xml')
493     {
494         switch ($type) {
495          case 'xml':
496             $this->endXML();
497             break;
498          case 'json':
499
500             // Check for JSONP callback
501             $callback = $this->arg('callback');
502             if ($callback) {
503                 print ')';
504             }
505             break;
506          case 'rss':
507             $this->end_twitter_rss();
508             break;
509          case 'atom':
510             $this->end_twitter_rss();
511             break;
512          default:
513             $this->client_error(_('Not a supported data format.'));
514             break;
515         }
516         return;
517     }
518
519     function client_error($msg, $code = 400, $content_type = 'json')
520     {
521
522         static $status = array(400 => 'Bad Request',
523                                401 => 'Unauthorized',
524                                402 => 'Payment Required',
525                                403 => 'Forbidden',
526                                404 => 'Not Found',
527                                405 => 'Method Not Allowed',
528                                406 => 'Not Acceptable',
529                                407 => 'Proxy Authentication Required',
530                                408 => 'Request Timeout',
531                                409 => 'Conflict',
532                                410 => 'Gone',
533                                411 => 'Length Required',
534                                412 => 'Precondition Failed',
535                                413 => 'Request Entity Too Large',
536                                414 => 'Request-URI Too Long',
537                                415 => 'Unsupported Media Type',
538                                416 => 'Requested Range Not Satisfiable',
539                                417 => 'Expectation Failed');
540
541         $action = $this->trimmed('action');
542
543         common_debug("User error '$code' on '$action': $msg", __FILE__);
544
545         if (!array_key_exists($code, $status)) {
546             $code = 400;
547         }
548
549         $status_string = $status[$code];
550         header('HTTP/1.1 '.$code.' '.$status_string);
551
552         if ($content_type == 'xml') {
553             $this->init_document('xml');
554             $this->elementStart('hash');
555             $this->element('error', null, $msg);
556             $this->element('request', null, $_SERVER['REQUEST_URI']);
557             $this->elementEnd('hash');
558             $this->end_document('xml');
559         } else {
560             $this->init_document('json');
561             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
562             print(json_encode($error_array));
563             $this->end_document('json');
564         }
565
566     }
567
568     function init_twitter_rss()
569     {
570         $this->startXML();
571         $this->elementStart('rss', array('version' => '2.0'));
572     }
573
574     function end_twitter_rss()
575     {
576         $this->elementEnd('rss');
577         $this->endXML();
578     }
579
580     function init_twitter_atom()
581     {
582         $this->startXML();
583         // FIXME: don't hardcode the language here!
584         $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
585                                           'xml:lang' => 'en-US',
586                                           'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
587     }
588
589     function end_twitter_atom()
590     {
591         $this->elementEnd('feed');
592         $this->endXML();
593     }
594
595     function show_profile($profile, $content_type='xml', $notice=null)
596     {
597         $profile_array = $this->twitter_user_array($profile, true);
598         switch ($content_type) {
599          case 'xml':
600             $this->show_twitter_xml_user($profile_array);
601             break;
602          case 'json':
603             $this->show_json_objects($profile_array);
604             break;
605          default:
606             $this->client_error(_('Not a supported data format.'));
607             return;
608         }
609         return;
610     }
611
612     function get_user($id, $apidata=null)
613     {
614         if (!$id) {
615             return $apidata['user'];
616         } else if (is_numeric($id)) {
617             return User::staticGet($id);
618         } else {
619             $nickname = common_canonical_nickname($id);
620             return User::staticGet('nickname', $nickname);
621         }
622     }
623
624     function get_profile($id)
625     {
626         if (is_numeric($id)) {
627             return Profile::staticGet($id);
628         } else {
629             $user = User::staticGet('nickname', $id);
630             if ($user) {
631                 return $user->getProfile();
632             } else {
633                 return null;
634             }
635         }
636     }
637
638     function source_link($source)
639     {
640         $source_name = _($source);
641         switch ($source) {
642          case 'web':
643          case 'xmpp':
644          case 'mail':
645          case 'omb':
646          case 'api':
647             break;
648          default:
649             $ns = Notice_source::staticGet($source);
650             if ($ns) {
651                 $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
652             }
653             break;
654         }
655         return $source_name;
656     }
657
658 }