]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - lib/twitterapi.php
cosmetic fixes.
[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         $entry = array();
131
132         # We trim() to avoid extraneous whitespace in the output
133
134         $entry['content'] = common_xml_safe_str(trim($notice->rendered));
135         $entry['title'] = $profile->nickname . ': ' . common_xml_safe_str(trim($notice->content));
136         $entry['link'] = common_local_url('shownotice', array('notice' => $notice->id));
137         $entry['published'] = common_date_iso8601($notice->created);
138
139         $taguribase = common_config('integration', 'taguri');
140         $entry['id'] = "tag:$taguribase:$entry[link]";
141
142         $entry['updated'] = $entry['published'];
143         $entry['author'] = $profile->getBestName();
144
145         # RSS Item specific
146         $entry['description'] = $entry['content'];
147         $entry['pubDate'] = common_date_rfc2822($notice->created);
148         $entry['guid'] = $entry['link'];
149
150         return $entry;
151     }
152
153     function twitter_rss_dmsg_array($message)
154     {
155
156         $entry = array();
157
158         $entry['title'] = sprintf('Message from %s to %s',
159             $message->getFrom()->nickname, $message->getTo()->nickname);
160
161         $entry['content'] = common_xml_safe_str(trim($message->content));
162         $entry['link'] = common_local_url('showmessage', array('message' => $message->id));
163         $entry['published'] = common_date_iso8601($message->created);
164
165         $taguribase = common_config('integration', 'taguri');
166
167         $entry['id'] = "tag:$taguribase,:$entry[link]";
168         $entry['updated'] = $entry['published'];
169         $entry['author'] = $message->getFrom()->getBestName();
170
171         # RSS Item specific
172         $entry['description'] = $entry['content'];
173         $entry['pubDate'] = common_date_rfc2822($message->created);
174         $entry['guid'] = $entry['link'];
175
176         return $entry;
177     }
178
179     function twitter_dmsg_array($message)
180     {
181         $twitter_dm = array();
182
183         $from_profile = $message->getFrom();
184         $to_profile = $message->getTo();
185
186         $twitter_dm['id'] = $message->id;
187         $twitter_dm['sender_id'] = $message->from_profile;
188         $twitter_dm['text'] = trim($message->content);
189         $twitter_dm['recipient_id'] = $message->to_profile;
190         $twitter_dm['created_at'] = $this->date_twitter($message->created);
191         $twitter_dm['sender_screen_name'] = $from_profile->nickname;
192         $twitter_dm['recipient_screen_name'] = $to_profile->nickname;
193         $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false);
194         $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false);
195
196         return $twitter_dm;
197     }
198
199     function show_twitter_xml_status($twitter_status)
200     {
201         $this->elementStart('status');
202         foreach($twitter_status as $element => $value) {
203             switch ($element) {
204             case 'user':
205                 $this->show_twitter_xml_user($twitter_status['user']);
206                 break;
207             case 'text':
208                 $this->element($element, null, common_xml_safe_str($value));
209                 break;
210             default:
211                 $this->element($element, null, $value);
212             }
213         }
214         $this->elementEnd('status');
215     }
216
217     function show_twitter_xml_user($twitter_user, $role='user')
218     {
219         $this->elementStart($role);
220         foreach($twitter_user as $element => $value) {
221             if ($element == 'status') {
222                 $this->show_twitter_xml_status($twitter_user['status']);
223             } else {
224                 $this->element($element, null, $value);
225             }
226         }
227         $this->elementEnd($role);
228     }
229
230     function show_twitter_rss_item($entry)
231     {
232         $this->elementStart('item');
233         $this->element('title', null, $entry['title']);
234         $this->element('description', null, $entry['description']);
235         $this->element('pubDate', null, $entry['pubDate']);
236         $this->element('guid', null, $entry['guid']);
237         $this->element('link', null, $entry['link']);
238         $this->elementEnd('item');
239     }
240
241     function show_json_objects($objects)
242     {
243         print(json_encode($objects));
244     }
245
246     function show_single_xml_status($notice)
247     {
248         $this->init_document('xml');
249         $twitter_status = $this->twitter_status_array($notice);
250         $this->show_twitter_xml_status($twitter_status);
251         $this->end_document('xml');
252     }
253
254     function show_single_json_status($notice)
255     {
256         $this->init_document('json');
257         $status = $this->twitter_status_array($notice);
258         $this->show_json_objects($status);
259         $this->end_document('json');
260     }
261
262     function show_single_xml_dmsg($message)
263     {
264         $this->init_document('xml');
265         $dmsg = $this->twitter_dmsg_array($message);
266         $this->show_twitter_xml_dmsg($dmsg);
267         $this->end_document('xml');
268     }
269
270     function show_single_json_dmsg($message)
271     {
272         $this->init_document('json');
273         $dmsg = $this->twitter_dmsg_array($message);
274         $this->show_json_objects($dmsg);
275         $this->end_document('json');
276     }
277
278     function show_twitter_xml_dmsg($twitter_dm)
279     {
280         $this->elementStart('direct_message');
281         foreach($twitter_dm as $element => $value) {
282             switch ($element) {
283             case 'sender':
284             case 'recipient':
285                 $this->show_twitter_xml_user($value, $element);
286                 break;
287             case 'text':
288                 $this->element($element, null, common_xml_safe_str($value));
289                 break;
290             default:
291                 $this->element($element, null, $value);
292             }
293         }
294         $this->elementEnd('direct_message');
295     }
296
297     function show_xml_timeline($notice)
298     {
299
300         $this->init_document('xml');
301         $this->elementStart('statuses', array('type' => 'array'));
302
303         if (is_array($notice)) {
304             foreach ($notice as $n) {
305                 $twitter_status = $this->twitter_status_array($n);
306                 $this->show_twitter_xml_status($twitter_status);
307             }
308         } else {
309             while ($notice->fetch()) {
310                 $twitter_status = $this->twitter_status_array($notice);
311                 $this->show_twitter_xml_status($twitter_status);
312             }
313         }
314
315         $this->elementEnd('statuses');
316         $this->end_document('xml');
317     }
318
319     function show_rss_timeline($notice, $title, $link, $subtitle, $suplink=null)
320     {
321
322         $this->init_document('rss');
323
324         $this->elementStart('channel');
325         $this->element('title', null, $title);
326         $this->element('link', null, $link);
327         if (!is_null($suplink)) {
328             # For FriendFeed's SUP protocol
329             $this->element('link', array('xmlns' => 'http://www.w3.org/2005/Atom',
330                                          'rel' => 'http://api.friendfeed.com/2008/03#sup',
331                                          'href' => $suplink,
332                                          'type' => 'application/json'));
333         }
334         $this->element('description', null, $subtitle);
335         $this->element('language', null, 'en-us');
336         $this->element('ttl', null, '40');
337
338         if (is_array($notice)) {
339             foreach ($notice as $n) {
340                 $entry = $this->twitter_rss_entry_array($n);
341                 $this->show_twitter_rss_item($entry);
342             }
343         } else {
344             while ($notice->fetch()) {
345                 $entry = $this->twitter_rss_entry_array($notice);
346                 $this->show_twitter_rss_item($entry);
347             }
348         }
349
350         $this->elementEnd('channel');
351         $this->end_twitter_rss();
352     }
353
354     function show_atom_timeline($notice, $title, $id, $link, $subtitle=null, $suplink=null, $selfuri=null)
355     {
356
357         $this->init_document('atom');
358
359         $this->element('title', null, $title);
360         $this->element('id', null, $id);
361         $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null);
362
363         if (!is_null($suplink)) {
364             # For FriendFeed's SUP protocol
365             $this->element('link', array('rel' => 'http://api.friendfeed.com/2008/03#sup',
366                                          'href' => $suplink,
367                                          'type' => 'application/json'));
368         }
369
370         if (!is_null($selfuri)) {
371             $this->element('link', array('href' => $selfuri,
372                 'rel' => 'self', 'type' => 'application/atom+xml'), null);
373         }
374
375         $this->element('updated', null, common_date_iso8601('now'));
376         $this->element('subtitle', null, $subtitle);
377
378         if (is_array($notice)) {
379             foreach ($notice as $n) {
380                 $this->raw($n->asAtomEntry());
381             }
382         } else {
383             while ($notice->fetch()) {
384                 $this->raw($notice->asAtomEntry());
385             }
386         }
387
388         $this->end_document('atom');
389
390     }
391
392     function show_json_timeline($notice)
393     {
394
395         $this->init_document('json');
396
397         $statuses = array();
398
399         if (is_array($notice)) {
400             foreach ($notice as $n) {
401                 $twitter_status = $this->twitter_status_array($n);
402                 array_push($statuses, $twitter_status);
403             }
404         } else {
405             while ($notice->fetch()) {
406                 $twitter_status = $this->twitter_status_array($notice);
407                 array_push($statuses, $twitter_status);
408             }
409         }
410
411         $this->show_json_objects($statuses);
412
413         $this->end_document('json');
414     }
415
416     // Anyone know what date format this is?
417     // Twitter's dates look like this: "Mon Jul 14 23:52:38 +0000 2008" -- Zach
418     function date_twitter($dt)
419     {
420         $t = strtotime($dt);
421         return date("D M d G:i:s O Y", $t);
422     }
423
424     // XXX: Candidate for a general utility method somewhere?
425     function count_subscriptions($profile)
426     {
427
428         $count = 0;
429         $sub = new Subscription();
430         $sub->subscribed = $profile->id;
431
432         $count = $sub->find();
433
434         if ($count > 0) {
435             return $count - 1;
436         } else {
437             return 0;
438         }
439     }
440
441     function init_document($type='xml')
442     {
443         switch ($type) {
444          case 'xml':
445             header('Content-Type: application/xml; charset=utf-8');
446             $this->startXML();
447             break;
448          case 'json':
449             header('Content-Type: application/json; charset=utf-8');
450
451             // Check for JSONP callback
452             $callback = $this->arg('callback');
453             if ($callback) {
454                 print $callback . '(';
455             }
456             break;
457          case 'rss':
458             header("Content-Type: application/rss+xml; charset=utf-8");
459             $this->init_twitter_rss();
460             break;
461          case 'atom':
462             header('Content-Type: application/atom+xml; charset=utf-8');
463             $this->init_twitter_atom();
464             break;
465          default:
466             $this->client_error(_('Not a supported data format.'));
467             break;
468         }
469
470         return;
471     }
472
473     function end_document($type='xml')
474     {
475         switch ($type) {
476          case 'xml':
477             $this->endXML();
478             break;
479          case 'json':
480
481             // Check for JSONP callback
482             $callback = $this->arg('callback');
483             if ($callback) {
484                 print ')';
485             }
486             break;
487          case 'rss':
488             $this->end_twitter_rss();
489             break;
490          case 'atom':
491             $this->end_twitter_rss();
492             break;
493          default:
494             $this->client_error(_('Not a supported data format.'));
495             break;
496         }
497         return;
498     }
499
500     function client_error($msg, $code = 400, $content_type = 'json')
501     {
502
503         static $status = array(400 => 'Bad Request',
504                                401 => 'Unauthorized',
505                                402 => 'Payment Required',
506                                403 => 'Forbidden',
507                                404 => 'Not Found',
508                                405 => 'Method Not Allowed',
509                                406 => 'Not Acceptable',
510                                407 => 'Proxy Authentication Required',
511                                408 => 'Request Timeout',
512                                409 => 'Conflict',
513                                410 => 'Gone',
514                                411 => 'Length Required',
515                                412 => 'Precondition Failed',
516                                413 => 'Request Entity Too Large',
517                                414 => 'Request-URI Too Long',
518                                415 => 'Unsupported Media Type',
519                                416 => 'Requested Range Not Satisfiable',
520                                417 => 'Expectation Failed');
521
522         $action = $this->trimmed('action');
523
524         common_debug("User error '$code' on '$action': $msg", __FILE__);
525
526         if (!array_key_exists($code, $status)) {
527             $code = 400;
528         }
529
530         $status_string = $status[$code];
531         header('HTTP/1.1 '.$code.' '.$status_string);
532
533         if ($content_type == 'xml') {
534             $this->init_document('xml');
535             $this->elementStart('hash');
536             $this->element('error', null, $msg);
537             $this->element('request', null, $_SERVER['REQUEST_URI']);
538             $this->elementEnd('hash');
539             $this->end_document('xml');
540         } else {
541             $this->init_document('json');
542             $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']);
543             print(json_encode($error_array));
544             $this->end_document('json');
545         }
546
547     }
548
549     function init_twitter_rss()
550     {
551         $this->startXML();
552         $this->elementStart('rss', array('version' => '2.0'));
553     }
554
555     function end_twitter_rss()
556     {
557         $this->elementEnd('rss');
558         $this->endXML();
559     }
560
561     function init_twitter_atom()
562     {
563         $this->startXML();
564         // FIXME: don't hardcode the language here!
565         $this->elementStart('feed', array('xmlns' => 'http://www.w3.org/2005/Atom',
566                                           'xml:lang' => 'en-US',
567                                           'xmlns:thr' => 'http://purl.org/syndication/thread/1.0'));
568     }
569
570     function end_twitter_atom()
571     {
572         $this->elementEnd('feed');
573         $this->endXML();
574     }
575
576     function show_profile($profile, $content_type='xml', $notice=null)
577     {
578         $profile_array = $this->twitter_user_array($profile, true);
579         switch ($content_type) {
580          case 'xml':
581             $this->show_twitter_xml_user($profile_array);
582             break;
583          case 'json':
584             $this->show_json_objects($profile_array);
585             break;
586          default:
587             $this->client_error(_('Not a supported data format.'));
588             return;
589         }
590         return;
591     }
592
593     function get_user($id, $apidata=null)
594     {
595         if (!$id) {
596             return $apidata['user'];
597         } else if (is_numeric($id)) {
598             return User::staticGet($id);
599         } else {
600             $nickname = common_canonical_nickname($id);
601             return User::staticGet('nickname', $nickname);
602         }
603     }
604
605     function get_profile($id)
606     {
607         if (is_numeric($id)) {
608             return Profile::staticGet($id);
609         } else {
610             $user = User::staticGet('nickname', $id);
611             if ($user) {
612                 return $user->getProfile();
613             } else {
614                 return null;
615             }
616         }
617     }
618
619     function source_link($source)
620     {
621         $source_name = _($source);
622         switch ($source) {
623          case 'web':
624          case 'xmpp':
625          case 'mail':
626          case 'omb':
627          case 'api':
628             break;
629          default:
630             $ns = Notice_source::staticGet($source);
631             if ($ns) {
632                 $source_name = '<a href="' . $ns->url . '">' . $ns->name . '</a>';
633             }
634             break;
635         }
636         return $source_name;
637     }
638
639 }