]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/twitterapi.php
Added the new pinghandler to the stopdaemons script and improved the behaviour and...
[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     function twitter_user_array($profile, $get_notice=false)
55     {
56
57         $twitter_user = array();
58
59         $twitter_user['name'] = $profile->getBestName();
60         $twitter_user['followers_count'] = $this->count_subscriptions($profile);
61         $twitter_user['screen_name'] = $profile->nickname;
62         $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;
63         $twitter_user['location'] = ($profile->location) ? $profile->location : null;
64         $twitter_user['id'] = intval($profile->id);
65
66         $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
67
68         $twitter_user['profile_image_url'] = ($avatar) ? $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE);
69         $twitter_user['protected'] = 'false'; # not supported by Laconica yet
70         $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
71
72         if ($get_notice) {
73             $notice = $profile->getCurrentNotice();
74             if ($notice) {
75                 # don't get user!
76                 $twitter_user['status'] = $this->twitter_status_array($notice, false);
77             }
78         }
79
80         return $twitter_user;
81     }
82
83     function twitter_status_array($notice, $include_user=true)
84     {
85         $profile = $notice->getProfile();
86
87         $twitter_status = array();
88         $twitter_status['text'] = $notice->content;
89         $twitter_status['truncated'] = 'false'; # Not possible on Laconica
90         $twitter_status['created_at'] = $this->date_twitter($notice->created);
91         $twitter_status['in_reply_to_status_id'] = ($notice->reply_to) ?
92             intval($notice->reply_to) : null;
93         $twitter_status['source'] = $this->source_link($notice->source);
94         $twitter_status['id'] = intval($notice->id);
95
96         $replier_profile = null;
97
98         if ($notice->reply_to) {
99             $reply = Notice::staticGet(intval($notice->reply_to));
100             if ($reply) {
101                 $replier_profile = $reply->getProfile();
102             }
103         }
104
105         $twitter_status['in_reply_to_user_id'] =
106             ($replier_profile) ? intval($replier_profile->id) : null;
107         $twitter_status['in_reply_to_screen_name'] =
108             ($replier_profile) ? $replier_profile->nickname : null;
109
110         if (isset($this->auth_user)) {
111             $twitter_status['favorited'] =
112                 ($this->auth_user->hasFave($notice)) ? 'true' : 'false';
113         } else {
114             $twitter_status['favorited'] = 'false';
115         }
116
117         if ($include_user) {
118             # Don't get notice (recursive!)
119             $twitter_user = $this->twitter_user_array($profile, false);
120             $twitter_status['user'] = $twitter_user;
121         }
122
123         return $twitter_status;
124     }
125
126     function twitter_rss_entry_array($notice)
127     {
128
129         $profile = $notice->getProfile();
130
131         $server = common_config('site', 'server');
132         $entry = array();
133
134         # We trim() to avoid extraneous whitespace in the output
135
136         $entry['content'] = common_xml_safe_str(trim($notice->rendered));
137         $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
138         $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
139         $entry['published'] = common_date_iso8601($notice->created);
140         $entry['id'] = "tag:$server,2008:$entry[link]";
141         $entry['updated'] = $entry['published'];
142
143         # RSS Item specific
144         $entry['description'] = $entry['content'];
145         $entry['pubDate'] = common_date_rfc2822($notice->created);
146         $entry['guid'] = $entry['link'];
147
148         return $entry;
149     }
150
151     function twitter_rss_dmsg_array($message)
152     {
153
154         $server = common_config('site', 'server');
155         $entry = array();
156
157         $entry['title'] = sprintf('Message from %s to %s',
158             $message->getFrom()->nickname, $message->getTo()->nickname);
159
160         $entry['content'] = common_xml_safe_str(trim($message->content));
161         $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
162         $entry['published'] = common_date_iso8601($message->created);
163         $entry['id'] = "tag:$server,2008:$entry[link]";
164         $entry['updated'] = $entry['published'];
165
166         # RSS Item specific
167         $entry['description'] = $entry['content'];
168         $entry['pubDate'] = common_date_rfc2822($message->created);
169         $entry['guid'] = $entry['link'];
170
171         return $entry;
172     }
173
174     function twitter_dmsg_array($message)
175     {
176         $twitter_dm = array();
177
178         $from_profile = $message->getFrom();
179         $to_profile = $message->getTo();
180
181         $twitter_dm['id'] = $message->id;
182         $twitter_dm['sender_id'] = $message->from_profile;
183         $twitter_dm['text'] = trim($message->content);
184         $twitter_dm['recipient_id'] = $message->to_profile;
185         $twitter_dm['created_at'] = $this->date_twitter($message->created);
186         $twitter_dm['sender_screen_name'] = $from_profile->nickname;
187         $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
188         $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
189         $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
190
191         return $twitter_dm;
192     }
193
194     function show_twitter_xml_status($twitter_status)
195     {
196         $this->elementStart('status');
197         foreach($twitter_status as $element => $value) {
198             switch ($element) {
199             case 'user':
200                 $this->show_twitter_xml_user($twitter_status['user']);
201                 break;
202             case 'text':
203                 $this->element($element, null, common_xml_safe_str($value));
204                 break;
205             default:
206                 $this->element($element, null, $value);
207             }
208         }
209         $this->elementEnd('status');
210     }
211
212     function show_twitter_xml_user($twitter_user, $role='user')
213     {
214         $this->elementStart($role);
215         foreach($twitter_user as $element => $value) {
216             if ($element == 'status') {
217                 $this->show_twitter_xml_status($twitter_user['status']);
218             } else {
219                 $this->element($element, null, $value);
220             }
221         }
222         $this->elementEnd($role);
223     }
224
225     function show_twitter_rss_item($entry)
226     {
227         $this->elementStart('item');
228         $this->element('title', null, $entry['title']);
229         $this->element('description', null, $entry['description']);
230         $this->element('pubDate', null, $entry['pubDate']);
231         $this->element('guid', null, $entry['guid']);
232         $this->element('link', null, $entry['link']);
233         $this->elementEnd('item');
234     }
235
236     function show_twitter_atom_entry($entry)
237     {
238         $this->elementStart('entry');
239         $this->element('title', null, $entry['title']);
240         $this->element('content', array('type' => 'html'), $entry['content']);
241         $this->element('id', null, $entry['id']);
242         $this->element('published', null, $entry['published']);
243         $this->element('updated', null, $entry['updated']);
244         $this->element('link', array('href' => $entry['link'], 'rel' => 'alternate', 'type' => 'text/html'), null);
245         $this->elementEnd('entry');
246     }
247
248     function show_json_objects($objects)
249     {
250         print(json_encode($objects));
251     }
252
253     function show_single_xml_status($notice)
254     {
255         $this->init_document('xml');
256         $twitter_status = $this->twitter_status_array($notice);
257         $this->show_twitter_xml_status($twitter_status);
258         $this->end_document('xml');
259     }
260
261     function show_single_json_status($notice)
262     {
263         $this->init_document('json');
264         $status = $this->twitter_status_array($notice);
265         $this->show_json_objects($status);
266         $this->end_document('json');
267     }
268
269     function show_single_xml_dmsg($message)
270     {
271         $this->init_document('xml');
272         $dmsg = $this->twitter_dmsg_array($message);
273         $this->show_twitter_xml_dmsg($dmsg);
274         $this->end_document('xml');
275     }
276
277     function show_single_json_dmsg($message)
278     {
279         $this->init_document('json');
280         $dmsg = $this->twitter_dmsg_array($message);
281         $this->show_json_objects($dmsg);
282         $this->end_document('json');
283     }
284
285     function show_twitter_xml_dmsg($twitter_dm)
286     {
287         $this->elementStart('direct_message');
288         foreach($twitter_dm as $element => $value) {
289             switch ($element) {
290             case 'sender':
291             case 'recipient':
292                 $this->show_twitter_xml_user($value, $element);
293                 break;
294             case 'text':
295                 $this->element($element, null, common_xml_safe_str($value));
296                 break;
297             default:
298                 $this->element($element, null, $value);
299             }
300         }
301         $this->elementEnd('direct_message');
302     }
303
304     function show_xml_timeline($notice)
305     {
306
307         $this->init_document('xml');
308         $this->elementStart('statuses', array('type' => 'array'));
309
310         if (is_array($notice)) {
311             foreach ($notice as $n) {
312                 $twitter_status = $this->twitter_status_array($n);
313                 $this->show_twitter_xml_status($twitter_status);
314             }
315         } else {
316             while ($notice->fetch()) {
317                 $twitter_status = $this->twitter_status_array($notice);
318                 $this->show_twitter_xml_status($twitter_status);
319             }
320         }
321
322         $this->elementEnd('statuses');
323         $this->end_document('xml');
324     }
325
326     function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null)
327     {
328
329         $this->init_document('rss');
330
331         $this->elementStart('channel');
332         $this->element('title', null, $title);
333         $this->element('link', null, $link);
334         if (!is_null($suplink)) {
335             # For FriendFeed's SUP protocol
336             $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
337                                          'rel' => 'http://api.friendfeed.com/2008/03#sup',
338                                          'href' => $suplink,
339                                          'type' => 'application/json'));
340         }
341         $this->element('description', null, $subtitle);
342         $this->element('language', null, 'en-us');
343         $this->element('ttl', null, '40');
344
345         if (is_array($notice)) {
346             foreach ($notice as $n) {
347                 $entry = $this->twitter_rss_entry_array($n);
348                 $this->show_twitter_rss_item($entry);
349             }
350         } else {
351             while ($notice->fetch()) {
352                 $entry = $this->twitter_rss_entry_array($notice);
353                 $this->show_twitter_rss_item($entry);
354             }
355         }
356
357         $this->elementEnd('channel');
358         $this->end_twitter_rss();
359     }
360
361     function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null)
362     {
363
364         $this->init_document('atom');
365
366         $this->element('title', null, $title);
367         $this->element('id', null, $id);
368         $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
369         if (!is_null($suplink)) {
370             # For FriendFeed's SUP protocol
371             $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
372                                          'href' => $suplink,
373                                          'type' => 'application/json'));
374         }
375         $this->element('subtitle', null, $subtitle);
376
377         if (is_array($notice)) {
378             foreach ($notice as $n) {
379                 $entry = $this->twitter_rss_entry_array($n);
380                 $this->show_twitter_atom_entry($entry);
381             }
382         } else {
383             while ($notice->fetch()) {
384                 $entry = $this->twitter_rss_entry_array($notice);
385                 $this->show_twitter_atom_entry($entry);
386             }
387         }
388
389         $this->end_document('atom');
390
391     }
392
393     function show_json_timeline($notice)
394     {
395
396         $this->init_document('json');
397
398         $statuses = array();
399
400         if (is_array($notice)) {
401             foreach ($notice as $n) {
402                 $twitter_status = $this->twitter_status_array($n);
403                 array_push($statuses, $twitter_status);
404             }
405         } else {
406             while ($notice->fetch()) {
407                 $twitter_status = $this->twitter_status_array($notice);
408                 array_push($statuses, $twitter_status);
409             }
410         }
411
412         $this->show_json_objects($statuses);
413
414         $this->end_document('json');
415     }
416
417     // Anyone know what date format this is?
418     // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
419     function date_twitter($dt)
420     {
421         $t = strtotime($dt);
422         return date("D M d G:i:s O Y", $t);
423     }
424
425     // XXX: Candidate for a general utility method somewhere?
426     function count_subscriptions($profile)
427     {
428
429         $count = 0;
430         $sub = new Subscription();
431         $sub->subscribed = $profile->id;
432
433         $count = $sub->find();
434
435         if ($count > 0) {
436             return $count - 1;
437         } else {
438             return 0;
439         }
440     }
441
442     function init_document($type='xml')
443     {
444         switch ($type) {
445          case 'xml':
446             header('Content-Type: application/xml; charset=utf-8');
447             $this->startXML();
448             break;
449          case 'json':
450             header('Content-Type: application/json; charset=utf-8');
451
452             // Check for JSONP callback
453             $callback = $this->arg('callback');
454             if ($callback) {
455                 print $callback . '(';
456             }
457             break;
458          case 'rss':
459             header("Content-Type: application/rss+xml; charset=utf-8");
460             $this->init_twitter_rss();
461             break;
462          case 'atom':
463             header('Content-Type: application/atom+xml; charset=utf-8');
464             $this->init_twitter_atom();
465             break;
466          default:
467             $this->client_error(_('Not a supported data format.'));
468             break;
469         }
470
471         return;
472     }
473
474     function end_document($type='xml')
475     {
476         switch ($type) {
477          case 'xml':
478             $this->endXML();
479             break;
480          case 'json':
481
482             // Check for JSONP callback
483             $callback = $this->arg('callback');
484             if ($callback) {
485                 print ')';
486             }
487             break;
488          case 'rss':
489             $this->end_twitter_rss();
490             break;
491          case 'atom':
492             $this->end_twitter_rss();
493             break;
494          default:
495             $this->client_error(_('Not a supported data format.'));
496             break;
497         }
498         return;
499     }
500
501     function client_error($msg, $code = 400, $content_type = 'json')
502     {
503
504         static $status = array(400 => 'Bad Request',
505                                401 => 'Unauthorized',
506                                402 => 'Payment Required',
507                                403 => 'Forbidden',
508                                404 => 'Not Found',
509                                405 => 'Method Not Allowed',
510                                406 => 'Not Acceptable',
511                                407 => 'Proxy Authentication Required',
512                                408 => 'Request Timeout',
513                                409 => 'Conflict',
514                                410 => 'Gone',
515                                411 => 'Length Required',
516                                412 => 'Precondition Failed',
517                                413 => 'Request Entity Too Large',
518                                414 => 'Request-URI Too Long',
519                                415 => 'Unsupported Media Type',
520                                416 => 'Requested Range Not Satisfiable',
521                                417 => 'Expectation Failed');
522
523         $action = $this->trimmed('action');
524
525         common_debug("User error '$code' on '$action': $msg", __FILE__);
526
527         if (!array_key_exists($code, $status)) {
528             $code = 400;
529         }
530
531         $status_string = $status[$code];
532         header('HTTP/1.1 '.$code.' '.$status_string);
533
534         if ($content_type == 'xml') {
535             $this->init_document('xml');
536             $this->elementStart('hash');
537             $this->element('error', null, $msg);
538             $this->element('request', null, $_SERVER['REQUEST_URI']);
539             $this->elementEnd('hash');
540             $this->end_document('xml');
541         } else {
542             $this->init_document('json');
543             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
544             print(json_encode($error_array));
545             $this->end_document('json');
546         }
547
548     }
549
550     function init_twitter_rss()
551     {
552         $this->startXML();
553         $this->elementStart('rss', array('version' => '2.0'));
554     }
555
556     function end_twitter_rss()
557     {
558         $this->elementEnd('rss');
559         $this->endXML();
560     }
561
562     function init_twitter_atom()
563     {
564         $this->startXML();
565         $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom', 'xml:lang' => 'en-US'));
566     }
567
568     function end_twitter_atom()
569     {
570         $this->endXML();
571         $this->elementEnd('feed');
572     }
573
574     function show_profile($profile, $content_type='xml', $notice=null)
575     {
576         $profile_array = $this->twitter_user_array($profile, true);
577         switch ($content_type) {
578          case 'xml':
579             $this->show_twitter_xml_user($profile_array);
580             break;
581          case 'json':
582             $this->show_json_objects($profile_array);
583             break;
584          default:
585             $this->client_error(_('Not a supported data format.'));
586             return;
587         }
588         return;
589     }
590
591     function get_user($id, $apidata=null)
592     {
593         if (!$id) {
594             return $apidata['user'];
595         } else if (is_numeric($id)) {
596             return User::staticGet($id);
597         } else {
598             $nickname = common_canonical_nickname($id);
599             return User::staticGet('nickname', $nickname);
600         }
601     }
602
603     function get_profile($id)
604     {
605         if (is_numeric($id)) {
606             return Profile::staticGet($id);
607         } else {
608             $user = User::staticGet('nickname', $id);
609             if ($user) {
610                 return $user->getProfile();
611             } else {
612                 return null;
613             }
614         }
615     }
616
617     function source_link($source)
618     {
619         $source_name = _($source);
620         switch ($source) {
621          case 'web':
622          case 'xmpp':
623          case 'mail':
624          case 'omb':
625          case 'api':
626             break;
627          default:
628             $ns = Notice_source::staticGet($source);
629             if ($ns) {
630                 $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
631             }
632             break;
633         }
634         return $source_name;
635     }
636
637     function show_extended_profile($user, $apidata)
638     {
639
640         $this->auth_user = $apidata['user'];
641
642         $profile = $user->getProfile();
643
644         if (!$profile) {
645             common_server_error(_('User has no profile.'));
646             return;
647         }
648
649         $twitter_user = $this->twitter_user_array($profile, true);
650
651         // Add in extended user fields offered up by this method
652         $twitter_user['created_at'] = $this->date_twitter($profile->created);
653
654         $subbed = DB_DataObject::factory('subscription');
655         $subbed->subscriber = $profile->id;
656         $subbed_count = (int) $subbed->count() - 1;
657
658         $notices = DB_DataObject::factory('notice');
659         $notices->profile_id = $profile->id;
660         $notice_count = (int) $notices->count();
661
662         $twitter_user['friends_count'] = (is_int($subbed_count)) ? $subbed_count : 0;
663         $twitter_user['statuses_count'] = (is_int($notice_count)) ? $notice_count : 0;
664
665         // Other fields Twitter sends...
666         $twitter_user['profile_background_color'] = '';
667         $twitter_user['profile_text_color'] = '';
668         $twitter_user['profile_link_color'] = '';
669         $twitter_user['profile_sidebar_fill_color'] = '';
670
671         $faves = DB_DataObject::factory('fave');
672         $faves->user_id = $user->id;
673         $faves_count = (int) $faves->count();
674         $twitter_user['favourites_count'] = $faves_count;
675
676         $timezone = 'UTC';
677
678         if ($user->timezone) {
679             $timezone = $user->timezone;
680         }
681
682         $t = new DateTime;
683         $t->setTimezone(new DateTimeZone($timezone));
684         $twitter_user['utc_offset'] = $t->format('Z');
685         $twitter_user['time_zone'] = $timezone;
686
687         $following = 'false';
688
689         if (isset($this->auth_user)) {
690             if ($this->auth_user->isSubscribed($profile)) {
691                 $following = 'true';
692             }
693
694             // Not implemented yet
695             $twitter_user['notifications'] = 'false';
696         }
697
698         $twitter_user['following'] = $following;
699
700         if ($apidata['content-type'] == 'xml') {
701             $this->init_document('xml');
702             $this->show_twitter_xml_user($twitter_user);
703             $this->end_document('xml');
704         } elseif ($apidata['content-type'] == 'json') {
705             $this->init_document('json');
706             $this->show_json_objects($twitter_user);
707             $this->end_document('json');
708         }
709
710     }
711
712 }