]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/twitapistatuses.php
if-else instead of ?:
[quix0rs-gnu-social.git] / actions / twitapistatuses.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 require_once(INSTALLDIR.'/lib/twitterapi.php');
23
24 /* XXX: Please don't freak out about all the ugly comments in this file.
25  * They are mostly in here for reference while I work on the
26  * API. I'll fix things up later to make them look better later. -- Zach 
27  */
28 class TwitapistatusesAction extends TwitterapiAction {
29         
30         function is_readonly() {
31                 
32                 static $write_methods = array(  'update',
33                                                                                 'destroy');
34                 
35                 $cmdtext = explode('.', $this->arg('method'));          
36                 
37                 if (in_array($cmdtext[0], $write_methods)) {                    
38                         return false;
39                 }
40                                 
41                 return true;
42         }
43         
44         function public_timeline($args, $apidata) {
45                 parent::handle($args);
46                 
47                 $sitename = common_config('site', 'name');
48                 $siteserver = common_config('site', 'server'); 
49                 $title = sprintf(_("%s public timeline"), $sitename);
50                 $id = "tag:$siteserver:Statuses";
51                 $link = common_root_url();
52                 $subtitle = sprintf(_("%s updates from everyone!"), $sitename);
53
54                 // Number of public statuses to return by default -- Twitter sends 20
55                 $MAX_PUBSTATUSES = 20;
56
57                 $notice = new Notice();
58
59                 // FIXME: To really live up to the spec we need to build a list
60                 // of notices by users who have custom avatars, so fix this SQL -- Zach
61
62                 # XXX: sub-optimal performance
63                 
64                 $notice->is_local = 1;
65                 $notice->orderBy('created DESC, notice.id DESC');
66                 $notice->limit($MAX_PUBSTATUSES);
67                 $cnt = $notice->find();
68                 
69                 if ($cnt > 0) {
70                         
71                         switch($apidata['content-type']) {
72                                 case 'xml': 
73                                         $this->show_xml_timeline($notice);
74                                         break;
75                                 case 'rss':
76                                         $this->show_rss_timeline($notice, $title, $id, $link, $subtitle);
77                                         break;
78                                 case 'atom': 
79                                         $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
80                                         break;
81                                 case 'json':
82                                         $this->show_json_timeline($notice);
83                                         break;
84                                 default:
85                                         common_user_error("API method not found!", $code = 404);
86                                         break;
87                         }
88                         
89                 } else {
90                         common_server_error('Couldn\'t find any statuses.', $code = 503);
91                 }
92  
93                 exit();
94         }       
95         
96         function show_xml_timeline($notice) {
97
98                 $this->init_document('xml');
99                 common_element_start('statuses', array('type' => 'array'));
100
101                 if (is_array($notice)) {
102                         foreach ($notice as $n) {
103                                 $twitter_status = $this->twitter_status_array($n);                                              
104                                 $this->show_twitter_xml_status($twitter_status);        
105                         }
106                 } else {
107                         while ($notice->fetch()) {
108                                 $twitter_status = $this->twitter_status_array($notice);                                         
109                                 $this->show_twitter_xml_status($twitter_status);
110                         }
111                 }
112                 
113                 common_element_end('statuses');
114                 $this->end_document('xml');
115         }       
116         
117         function show_rss_timeline($notice, $title, $id, $link, $subtitle) {
118                 
119                 $this->init_document('rss');
120                 
121                 common_element_start('channel');
122                 common_element('title', NULL, $title);
123                 common_element('link', NULL, $link);
124                 common_element('description', NULL, $subtitle);
125                 common_element('language', NULL, 'en-us');
126                 common_element('ttl', NULL, '40');
127         
128         
129                 if (is_array($notice)) {
130                         foreach ($notice as $n) {
131                                 $entry = $this->twitter_rss_entry_array($n);                                            
132                                 $this->show_twitter_rss_item($entry);
133                         } 
134                 } else {
135                         while ($notice->fetch()) {
136                                 $entry = $this->twitter_rss_entry_array($notice);                                               
137                                 $this->show_twitter_rss_item($entry);
138                         }
139                 }
140
141                 common_element_end('channel');                  
142                 $this->end_twitter_rss();               
143         }
144
145         function show_atom_timeline($notice, $title, $id, $link, $subtitle=NULL) {
146                 
147                 $this->init_document('atom');
148
149                 common_element('title', NULL, $title);
150                 common_element('id', NULL, $id);
151                 common_element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), NULL);
152                 common_element('subtitle', NULL, $subtitle);
153
154                 if (is_array($notice)) {
155                         foreach ($notice as $n) {
156                                 $entry = $this->twitter_rss_entry_array($n);                                            
157                                 $this->show_twitter_atom_entry($entry);
158                         } 
159                 } else {
160                         while ($notice->fetch()) {
161                                 $entry = $this->twitter_rss_entry_array($notice);                                               
162                                 $this->show_twitter_atom_entry($entry);
163                         }
164                 }
165                 
166                 $this->end_document('atom');
167                 
168         }
169
170         function show_json_timeline($notice) {
171                 
172                 $this->init_document('json');
173                 
174                 $statuses = array();
175                 
176                 if (is_array($notice)) {
177                         foreach ($notice as $n) {
178                                 $twitter_status = $this->twitter_status_array($n);
179                                 array_push($statuses, $twitter_status);
180                         } 
181                 } else {
182                         while ($notice->fetch()) {
183                                 $twitter_status = $this->twitter_status_array($notice);
184                                 array_push($statuses, $twitter_status);
185                         }
186                 }                       
187                 
188                 $this->show_twitter_json_statuses($statuses);                   
189                 
190                 $this->end_document('json');
191         }
192                 
193         /*
194         Returns the 20 most recent statuses posted by the authenticating user and that user's friends. 
195         This is the equivalent of /home on the Web. 
196         
197         URL: http://server/api/statuses/friends_timeline.format
198         
199         Parameters:
200
201             * since.  Optional.  Narrows the returned results to just those statuses created after the specified 
202                         HTTP-formatted date.  The same behavior is available by setting an If-Modified-Since header in 
203                         your HTTP request.  
204                         Ex: http://server/api/statuses/friends_timeline.rss?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
205             * since_id.  Optional.  Returns only statuses with an ID greater than (that is, more recent than) 
206                         the specified ID.  Ex: http://server/api/statuses/friends_timeline.xml?since_id=12345
207             * count.  Optional.  Specifies the number of statuses to retrieve. May not be greater than 200.
208                         Ex: http://server/api/statuses/friends_timeline.xml?count=5 
209             * page. Optional. Ex: http://server/api/statuses/friends_timeline.rss?page=3
210         
211         Formats: xml, json, rss, atom
212         */
213         function friends_timeline($args, $apidata) {
214                 parent::handle($args);
215
216                 $since = $this->arg('since');
217                 $since_id = $this->arg('since_id');
218                 $count = $this->arg('count');
219                 $page = $this->arg('page');
220                 
221                 if (!$page) {
222                         $page = 1;
223                 }
224
225                 if (!$count) {
226                         $count = 20;
227                 }
228
229                 $user = $this->get_user($id, $apidata);
230                 $profile = $user->getProfile();
231                 
232                 $sitename = common_config('site', 'name');
233                 $siteserver = common_config('site', 'server'); 
234                 
235                 $title = sprintf(_("%s and friends"), $user->nickname);
236                 $id = "tag:$siteserver:friends:".$user->id;
237                 $link = common_local_url('all', array('nickname' => $user->nickname));
238                 $subtitle = sprintf(_("Updates from %s and friends on %s!"), $user->nickname, $sitename);
239
240                 $notice = $user->noticesWithFriends(($page-1)*20, $count);
241                 
242                 switch($apidata['content-type']) {
243                  case 'xml': 
244                         $this->show_xml_timeline($notice);
245                         break;
246                  case 'rss':
247                         $this->show_rss_timeline($notice, $title, $id, $link, $subtitle);
248                         break;
249                  case 'atom': 
250                         $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
251                         break;
252                  case 'json':
253                         $this->show_json_timeline($notice);
254                         break;
255                  default:
256                         common_user_error("API method not found!", $code = 404);
257                 }
258                 
259                 exit();
260         }
261
262         /*
263                 Returns the 20 most recent statuses posted from the authenticating user. It's also possible to
264         request another user's timeline via the id parameter below. This is the equivalent of the Web
265         /archive page for your own user, or the profile page for a third party.
266
267                 URL: http://server/api/statuses/user_timeline.format
268
269                 Formats: xml, json, rss, atom
270
271                 Parameters:
272
273                     * id. Optional. Specifies the ID or screen name of the user for whom to return the
274             friends_timeline. Ex: http://server/api/statuses/user_timeline/12345.xml or
275             http://server/api/statuses/user_timeline/bob.json. 
276                         * count. Optional. Specifies the number of
277             statuses to retrieve. May not be greater than 200. Ex:
278             http://server/api/statuses/user_timeline.xml?count=5 
279                         * since. Optional. Narrows the returned
280             results to just those statuses created after the specified HTTP-formatted date. The same
281             behavior is available by setting an If-Modified-Since header in your HTTP request. Ex:
282             http://server/api/statuses/user_timeline.rss?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT 
283                         * since_id. Optional. Returns only statuses with an ID greater than (that is, more recent than)
284             the specified ID. Ex: http://server/api/statuses/user_timeline.xml?since_id=12345 * page.
285             Optional. Ex: http://server/api/statuses/friends_timeline.rss?page=3
286         */
287         function user_timeline($args, $apidata) {
288                 parent::handle($args);
289                 
290                 $user = null;
291                 
292                 // function was called with an argument /statuses/user_timeline/api_arg.format
293                 if (isset($apidata['api_arg'])) {
294                 
295                         if (is_numeric($apidata['api_arg'])) {
296                                 $user = User::staticGet($apidata['api_arg']);
297                         } else {
298                                 $nickname = common_canonical_nickname($apidata['api_arg']);
299                                 $user = User::staticGet('nickname', $nickname);
300                         } 
301                 } else {
302                         
303                         // if no user was specified, then we'll use the authenticated user
304                         $user = $apidata['user'];
305                 }
306
307                 if (!$user) {
308                         // Set the user to be the auth user if asked-for can't be found
309                         // honestly! This is what Twitter does, I swear --Zach
310                         $user = $apidata['user'];
311                 }
312
313                 $profile = $user->getProfile();
314
315                 if (!$profile) {
316                         common_server_error(_('User has no profile.'));
317                         exit();
318                 }
319                                 
320                 $count = $this->arg('count');
321                 $since = $this->arg('since');
322                 $since_id = $this->arg('since_id');
323                                 
324                 if (!$page) {
325                         $page = 1;
326                 }
327
328                 if (!$count) {
329                         $count = 20;
330                 }
331                                 
332                 $sitename = common_config('site', 'name');
333                 $siteserver = common_config('site', 'server'); 
334                 
335                 $title = sprintf(_("%s timeline"), $user->nickname);
336                 $id = "tag:$siteserver:user:".$user->id;
337                 $link = common_local_url('showstream', array('nickname' => $user->nickname));
338                 $subtitle = sprintf(_("Updates from %s on %s!"), $user->nickname, $sitename);
339
340                 $notice = new Notice();
341
342                 $notice->profile_id = $user->id;
343                 
344                 # XXX: since
345                 # XXX: since_id
346                 
347                 $notice->orderBy('created DESC, notice.id DESC');
348
349                 $notice->limit((($page-1)*20), $count);
350
351                 $cnt = $notice->find();
352                 
353                 switch($apidata['content-type']) {
354                  case 'xml': 
355                         $this->show_xml_timeline($notice);
356                         break;
357                  case 'rss':
358                         $this->show_rss_timeline($notice, $title, $id, $link, $subtitle);
359                         break;
360                  case 'atom': 
361                         $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
362                         break;
363                  case 'json':
364                         $this->show_json_timeline($notice);
365                         break;
366                  default:
367                         common_user_error("API method not found!", $code = 404);
368                 }
369                 
370                 exit();
371         }
372                 
373         function update($args, $apidata) {
374                 parent::handle($args);
375                 
376                 $user = $apidata['user'];
377                                 
378                 $this->is_readonly();
379                 
380                                 
381                 $notice = DB_DataObject::factory('notice');             
382                 
383                 $notice->profile_id = $user->id; # user id *is* profile id
384                 $notice->created = DB_DataObject_Cast::dateTime();      
385                 $notice->content = $this->trimmed('status');
386
387                 if (!$notice->content) {
388                         
389                         // XXX: Note: In this case, Twitter simply returns '200 OK'
390                         // No error is given, but the status is not posted to the 
391                         // user's timeline.  Seems bad.  Shouldn't we throw an 
392                         // errror? -- Zach
393                         exit();
394                         
395                 } else if (strlen($notice->content) > 140) {
396
397                         // XXX: Twitter truncates anything over 140, flags the status 
398                     // as "truncated."  Sending this error may screw up some clients
399                     // that assume Twitter will truncate for them.  Should we just
400                     // truncate too? -- Zach
401                         header('HTTP/1.1 406 Not Acceptable');                  
402                         print "That's too long. Max notice size is 140 chars.\n";
403                         exit();
404                 }
405
406                 $notice->rendered = common_render_content($notice->content, $notice);
407                 $notice->is_local = 1;
408                 
409                 $id = $notice->insert();
410
411                 if (!$id) {
412                         common_server_error('Could not update status!', 500);
413                         exit();
414                 }
415
416                 $orig = clone($notice);
417                 $notice->uri = common_notice_uri($notice);
418
419                 if (!$notice->update($orig)) {
420                         common_server_error('Could not save status!', 500);
421                         exit();
422                 }
423
424         common_save_replies($notice);
425                 common_broadcast_notice($notice);
426
427                 // FIXME: Bad Hack 
428                 // I should be able to just sent this notice off for display,
429                 // but $notice->created does not contain a string at this
430                 // point and I don't know how to convert it to one here. So
431                 // I'm forced to have DBObject pull the notice back out of the
432                 // DB before printing. --Zach
433                 $apidata['api_arg'] = $id;
434                 $this->show($args, $apidata);
435
436                 exit();
437         }
438         
439         /*
440                 Returns the 20 most recent @replies (status updates prefixed with @username) for the authenticating user.
441                 URL: http://server/api/statuses/replies.format
442                 
443                 Formats: xml, json, rss, atom
444
445                 Parameters:
446
447                 * page. Optional. Retrieves the 20 next most recent replies. Ex: http://server/api/statuses/replies.xml?page=3 
448                 * since. Optional. Narrows the returned results to just those replies created after the specified HTTP-formatted date. The
449         same behavior is available by setting an If-Modified-Since header in your HTTP request. Ex:
450         http://server/api/statuses/replies.xml?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
451                 * since_id. Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified
452                 ID. Ex: http://server/api/statuses/replies.xml?since_id=12345
453         */
454         function replies($args, $apidata) {
455
456                 parent::handle($args);
457
458                 $since = $this->arg('since');
459
460                 $count = $this->arg('count');
461                 $page = $this->arg('page');
462
463                 $user = $apidata['user'];
464                 $profile = $user->getProfile();
465
466                 $sitename = common_config('site', 'name');
467                 $siteserver = common_config('site', 'server'); 
468
469                 $title = sprintf(_("%s / Updates replying to %s"), $sitename, $user->nickname);
470                 $id = "tag:$siteserver:replies:".$user->id;
471                 $link = common_local_url('replies', array('nickname' => $user->nickname));
472                 $subtitle = "gar";
473                 $subtitle = sprintf(_("%s updates that reply to updates from %s / %s."), $sitename, $user->nickname, $profile->getBestName());
474
475                 if (!$page) {
476                         $page = 1;
477                 }
478
479                 if (!$count) {
480                         $count = 20;
481                 }
482
483                 $reply = new Reply();
484
485                 $reply->profile_id = $user->id;
486
487                 $reply->orderBy('modified DESC');
488
489                 $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
490
491                 $reply->limit((($page-1)*20), $count);
492
493                 $cnt = $reply->find();
494
495                 $notices = array();
496         
497                 if ($cnt) {
498                         while ($reply->fetch()) {
499                                 $notice = new Notice();
500                                 $notice->id = $reply->notice_id;
501                                 $result = $notice->find(true);
502                                 if (!$result) {
503                                         continue;
504                                 }
505                                 $notices[] = clone($notice);
506                         }
507                 }
508
509                 switch($apidata['content-type']) {
510                  case 'xml': 
511                         $this->show_xml_timeline($notices);
512                         break;
513                  case 'rss':
514                         $this->show_rss_timeline($notices, $title, $id, $link, $subtitle);
515                         break;
516                  case 'atom': 
517                         $this->show_atom_timeline($notices, $title, $id, $link, $subtitle);
518                         break;
519                  case 'json':
520                         $this->show_json_timeline($notices);
521                         break;
522                  default:
523                         common_user_error("API method not found!", $code = 404);
524                 }
525
526
527                 exit();
528
529
530         }
531
532         
533         
534         /*
535                 Destroys the status specified by the required ID parameter. The authenticating user must be
536         the author of the specified status.
537                 
538                  URL: http://server/api/statuses/destroy/id.format
539                 
540                  Formats: xml, json
541                 
542                  Parameters:
543                 
544                  * id. Required. The ID of the status to destroy. Ex:
545                 http://server/api/statuses/destroy/12345.json or
546                 http://server/api/statuses/destroy/23456.xml
547         
548         */
549         function destroy($args, $apidata) {
550                 parent::handle($args);
551                 common_server_error("API method under construction.", $code=501);
552         }
553         
554         # User Methods
555         
556         /*
557                 Returns up to 100 of the authenticating user's friends who have most recently updated, each with current status inline.
558         It's also possible to request another user's recent friends list via the id parameter below.
559                 
560                  URL: http://server/api/statuses/friends.format
561                 
562                  Formats: xml, json
563                 
564                  Parameters:
565                 
566                  * id. Optional. The ID or screen name of the user for whom to request a list of friends. Ex:
567                 http://server/api/statuses/friends/12345.json 
568                         or 
569                         http://server/api/statuses/friends/bob.xml
570                  * page. Optional. Retrieves the next 100 friends. Ex: http://server/api/statuses/friends.xml?page=2
571                  * lite. Optional. Prevents the inline inclusion of current status. Must be set to a value of true. Ex:
572                 http://server/api/statuses/friends.xml?lite=true
573                  * since. Optional. Narrows the returned results to just those friendships created after the specified
574                         HTTP-formatted date. The same behavior is available by setting an If-Modified-Since header in your HTTP
575                         request. Ex: http://server/api/statuses/friends.xml?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
576         */
577         function friends($args, $apidata) {
578                 parent::handle($args);
579                 return $this->subscriptions($apidata, 'subscribed', 'subscriber');
580         }
581         
582         /*
583                 Returns the authenticating user's followers, each with current status inline. They are ordered by the
584                 order in which they joined Twitter (this is going to be changed).
585                 
586                 URL: http://server/api/statuses/followers.format
587                 Formats: xml, json
588
589                 Parameters: 
590
591                     * id. Optional. The ID or screen name of the user for whom to request a list of followers. Ex:
592                 http://server/api/statuses/followers/12345.json 
593                                 or 
594                                 http://server/api/statuses/followers/bob.xml
595                     * page. Optional. Retrieves the next 100 followers. Ex: http://server/api/statuses/followers.xml?page=2   
596                     * lite. Optional. Prevents the inline inclusion of current status. Must be set to a value of true.
597                                 Ex: http://server/api/statuses/followers.xml?lite=true
598         */
599         function followers($args, $apidata) {
600                 parent::handle($args);
601
602                 return $this->subscriptions($apidata, 'subscriber', 'subscribed');
603         }
604
605         function subscriptions($apidata, $other_attr, $user_attr) {
606                 
607                 $user = $this->get_subs_user($apidata);
608                 
609                 # XXX: id
610                 # XXX: lite
611                 
612                 $page = $this->trimmed('page');
613                 
614                 if (!$page || !is_numeric($page)) {
615                         $page = 1;
616                 }
617                 
618                 $profile = $user->getProfile();
619                 
620                 if (!$profile) {
621                         common_server_error(_('User has no profile.'));
622                         return;
623                 }
624                                 
625                 $sub = new Subscription();
626                 $sub->$user_attr = $profile->id;
627                 $sub->orderBy('created DESC');
628                 $sub->limit(($page-1)*100, 100);
629                 
630                 $others = array();
631
632                 if ($sub->find()) {
633                         while ($sub->fetch()) {
634                                 $others[] = Profile::staticGet($sub->$other_attr);
635                         }
636                 } else {
637                         // user has no followers
638                 }
639                 
640                 $type = $apidata['content-type'];
641                 
642                 $this->init_document($type);
643                 $this->show_profiles($others, $type);
644                 $this->end_document($type);
645                 exit();
646         }
647
648         function get_subs_user($apidata) {
649                 
650                 // function was called with an argument /statuses/user_timeline/api_arg.format
651                 if (isset($apidata['api_arg'])) {
652                 
653                         if (is_numeric($apidata['api_arg'])) {
654                                 $user = User::staticGet($apidata['api_arg']);
655                         } else {
656                                 $nickname = common_canonical_nickname($apidata['api_arg']);
657                                 $user = User::staticGet('nickname', $nickname);
658                         } 
659                 } else {
660                         
661                         // if no user was specified, then we'll use the authenticated user
662                         $user = $apidata['user'];
663                 }
664
665                 if (!$user) {
666                         // Set the user to be the auth user if asked-for can't be found
667                         // honestly! This is what Twitter does, I swear --Zach
668                         $user = $apidata['user'];
669                 }
670                 
671                 return $user;
672         }
673         
674         function show_profiles($profiles, $type) {
675                 switch ($type) {
676                  case 'xml':
677                         common_element_start('users', array('type' => 'array'));
678                         foreach ($profiles as $profile) {
679                                 $this->show_profile($profile);
680                         }
681                         common_element_end('users');
682                         break;
683                  case 'json':
684                         $arrays = array();
685                         foreach ($profiles as $profile) {
686                                 $arrays[] = $this->twitter_user_array($profile, true);
687                         }
688                         print json_encode($arrays);
689                         break;
690                  default:
691                         $this->client_error(_('unsupported file type'));
692                         exit();
693                 }
694         }
695         
696         /*
697         Returns a list of the users currently featured on the site with their current statuses inline. 
698         URL: http://server/api/statuses/featured.format 
699
700         Formats: xml, json
701         */
702         function featured($args, $apidata) {
703                 parent::handle($args);
704                 common_server_error("API method under construction.", $code=501);
705         }
706
707         function get_user($id, $apidata) {
708                 if (!$id) {
709                         return $apidata['user'];
710                 } else if (is_numeric($id)) {
711                         return User::staticGet($id);
712                 } else {
713                         return User::staticGet('nickname', $id);
714                 }
715         }
716 }
717
718