]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/twitapistatuses.php
remember to broadcast in api, and also use the right ID
[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                 
375                 parent::handle($args);
376                 
377                 $user = $apidata['user'];
378                 
379                 $status = $this->trimmed('status');
380                 $source = $this->trimmed('source');
381                 
382                 if (!$source) {
383                         $source = 'api';
384                 }
385                 
386                 if (!$status) {
387                         
388                         // XXX: Note: In this case, Twitter simply returns '200 OK'
389                         // No error is given, but the status is not posted to the 
390                         // user's timeline.  Seems bad.  Shouldn't we throw an 
391                         // errror? -- Zach
392                         exit();
393                         
394                 } else if (strlen($status) > 140) {
395
396                         // XXX: Twitter truncates anything over 140, flags the status 
397                     // as "truncated."  Sending this error may screw up some clients
398                     // that assume Twitter will truncate for them.  Should we just
399                     // truncate too? -- Zach
400                         header('HTTP/1.1 406 Not Acceptable');                  
401                         print "That's too long. Max notice size is 140 chars.\n";
402                         exit();
403                 }
404                 
405                 $notice = Notice::saveNew($user->id, $status, $source);
406
407                 if (is_string($notice)) {
408                         $this->server_error($notice);
409                         return;
410                 }
411
412                 common_broadcast_notice($notice);
413                 
414                 // FIXME: Bad Hack 
415                 // I should be able to just sent this notice off for display,
416                 // but $notice->created does not contain a string at this
417                 // point and I don't know how to convert it to one here. So
418                 // I'm forced to have DBObject pull the notice back out of the
419                 // DB before printing. --Zach
420                 $apidata['api_arg'] = $notice->id;
421                 $this->show($args, $apidata);
422
423                 exit();
424         }
425         
426         /*
427                 Returns the 20 most recent @replies (status updates prefixed with @username) for the authenticating user.
428                 URL: http://server/api/statuses/replies.format
429                 
430                 Formats: xml, json, rss, atom
431
432                 Parameters:
433
434                 * page. Optional. Retrieves the 20 next most recent replies. Ex: http://server/api/statuses/replies.xml?page=3 
435                 * since. Optional. Narrows the returned results to just those replies created after the specified HTTP-formatted date. The
436         same behavior is available by setting an If-Modified-Since header in your HTTP request. Ex:
437         http://server/api/statuses/replies.xml?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
438                 * since_id. Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified
439                 ID. Ex: http://server/api/statuses/replies.xml?since_id=12345
440         */
441         function replies($args, $apidata) {
442
443                 parent::handle($args);
444
445                 $since = $this->arg('since');
446
447                 $count = $this->arg('count');
448                 $page = $this->arg('page');
449
450                 $user = $apidata['user'];
451                 $profile = $user->getProfile();
452
453                 $sitename = common_config('site', 'name');
454                 $siteserver = common_config('site', 'server'); 
455
456                 $title = sprintf(_("%s / Updates replying to %s"), $sitename, $user->nickname);
457                 $id = "tag:$siteserver:replies:".$user->id;
458                 $link = common_local_url('replies', array('nickname' => $user->nickname));
459                 $subtitle = "gar";
460                 $subtitle = sprintf(_("%s updates that reply to updates from %s / %s."), $sitename, $user->nickname, $profile->getBestName());
461
462                 if (!$page) {
463                         $page = 1;
464                 }
465
466                 if (!$count) {
467                         $count = 20;
468                 }
469
470                 $reply = new Reply();
471
472                 $reply->profile_id = $user->id;
473
474                 $reply->orderBy('modified DESC');
475
476                 $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
477
478                 $reply->limit((($page-1)*20), $count);
479
480                 $cnt = $reply->find();
481
482                 $notices = array();
483         
484                 if ($cnt) {
485                         while ($reply->fetch()) {
486                                 $notice = new Notice();
487                                 $notice->id = $reply->notice_id;
488                                 $result = $notice->find(true);
489                                 if (!$result) {
490                                         continue;
491                                 }
492                                 $notices[] = clone($notice);
493                         }
494                 }
495
496                 switch($apidata['content-type']) {
497                  case 'xml': 
498                         $this->show_xml_timeline($notices);
499                         break;
500                  case 'rss':
501                         $this->show_rss_timeline($notices, $title, $id, $link, $subtitle);
502                         break;
503                  case 'atom': 
504                         $this->show_atom_timeline($notices, $title, $id, $link, $subtitle);
505                         break;
506                  case 'json':
507                         $this->show_json_timeline($notices);
508                         break;
509                  default:
510                         common_user_error("API method not found!", $code = 404);
511                 }
512
513
514                 exit();
515
516
517         }
518
519         
520         
521         /*
522                 Destroys the status specified by the required ID parameter. The authenticating user must be
523         the author of the specified status.
524                 
525                  URL: http://server/api/statuses/destroy/id.format
526                 
527                  Formats: xml, json
528                 
529                  Parameters:
530                 
531                  * id. Required. The ID of the status to destroy. Ex:
532                 http://server/api/statuses/destroy/12345.json or
533                 http://server/api/statuses/destroy/23456.xml
534         
535         */
536         function destroy($args, $apidata) {
537                 parent::handle($args);
538                 common_server_error("API method under construction.", $code=501);
539         }
540         
541         # User Methods
542         
543         /*
544                 Returns up to 100 of the authenticating user's friends who have most recently updated, each with current status inline.
545         It's also possible to request another user's recent friends list via the id parameter below.
546                 
547                  URL: http://server/api/statuses/friends.format
548                 
549                  Formats: xml, json
550                 
551                  Parameters:
552                 
553                  * id. Optional. The ID or screen name of the user for whom to request a list of friends. Ex:
554                 http://server/api/statuses/friends/12345.json 
555                         or 
556                         http://server/api/statuses/friends/bob.xml
557                  * page. Optional. Retrieves the next 100 friends. Ex: http://server/api/statuses/friends.xml?page=2
558                  * lite. Optional. Prevents the inline inclusion of current status. Must be set to a value of true. Ex:
559                 http://server/api/statuses/friends.xml?lite=true
560                  * since. Optional. Narrows the returned results to just those friendships created after the specified
561                         HTTP-formatted date. The same behavior is available by setting an If-Modified-Since header in your HTTP
562                         request. Ex: http://server/api/statuses/friends.xml?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
563         */
564         function friends($args, $apidata) {
565                 parent::handle($args);
566                 return $this->subscriptions($apidata, 'subscribed', 'subscriber');
567         }
568         
569         /*
570                 Returns the authenticating user's followers, each with current status inline. They are ordered by the
571                 order in which they joined Twitter (this is going to be changed).
572                 
573                 URL: http://server/api/statuses/followers.format
574                 Formats: xml, json
575
576                 Parameters: 
577
578                     * id. Optional. The ID or screen name of the user for whom to request a list of followers. Ex:
579                 http://server/api/statuses/followers/12345.json 
580                                 or 
581                                 http://server/api/statuses/followers/bob.xml
582                     * page. Optional. Retrieves the next 100 followers. Ex: http://server/api/statuses/followers.xml?page=2   
583                     * lite. Optional. Prevents the inline inclusion of current status. Must be set to a value of true.
584                                 Ex: http://server/api/statuses/followers.xml?lite=true
585         */
586         function followers($args, $apidata) {
587                 parent::handle($args);
588
589                 return $this->subscriptions($apidata, 'subscriber', 'subscribed');
590         }
591
592         function subscriptions($apidata, $other_attr, $user_attr) {
593                 
594                 $user = $this->get_subs_user($apidata);
595                 
596                 # XXX: id
597                 # XXX: lite
598                 
599                 $page = $this->trimmed('page');
600                 
601                 if (!$page || !is_numeric($page)) {
602                         $page = 1;
603                 }
604                 
605                 $profile = $user->getProfile();
606                 
607                 if (!$profile) {
608                         common_server_error(_('User has no profile.'));
609                         return;
610                 }
611                                 
612                 $sub = new Subscription();
613                 $sub->$user_attr = $profile->id;
614                 $sub->orderBy('created DESC');
615                 $sub->limit(($page-1)*100, 100);
616                 
617                 $others = array();
618
619                 if ($sub->find()) {
620                         while ($sub->fetch()) {
621                                 $others[] = Profile::staticGet($sub->$other_attr);
622                         }
623                 } else {
624                         // user has no followers
625                 }
626                 
627                 $type = $apidata['content-type'];
628                 
629                 $this->init_document($type);
630                 $this->show_profiles($others, $type);
631                 $this->end_document($type);
632                 exit();
633         }
634
635         function get_subs_user($apidata) {
636                 
637                 // function was called with an argument /statuses/user_timeline/api_arg.format
638                 if (isset($apidata['api_arg'])) {
639                 
640                         if (is_numeric($apidata['api_arg'])) {
641                                 $user = User::staticGet($apidata['api_arg']);
642                         } else {
643                                 $nickname = common_canonical_nickname($apidata['api_arg']);
644                                 $user = User::staticGet('nickname', $nickname);
645                         } 
646                 } else {
647                         
648                         // if no user was specified, then we'll use the authenticated user
649                         $user = $apidata['user'];
650                 }
651
652                 if (!$user) {
653                         // Set the user to be the auth user if asked-for can't be found
654                         // honestly! This is what Twitter does, I swear --Zach
655                         $user = $apidata['user'];
656                 }
657                 
658                 return $user;
659         }
660         
661         function show_profiles($profiles, $type) {
662                 switch ($type) {
663                  case 'xml':
664                         common_element_start('users', array('type' => 'array'));
665                         foreach ($profiles as $profile) {
666                                 $this->show_profile($profile);
667                         }
668                         common_element_end('users');
669                         break;
670                  case 'json':
671                         $arrays = array();
672                         foreach ($profiles as $profile) {
673                                 $arrays[] = $this->twitter_user_array($profile, true);
674                         }
675                         print json_encode($arrays);
676                         break;
677                  default:
678                         $this->client_error(_('unsupported file type'));
679                         exit();
680                 }
681         }
682         
683         /*
684         Returns a list of the users currently featured on the site with their current statuses inline. 
685         URL: http://server/api/statuses/featured.format 
686
687         Formats: xml, json
688         */
689         function featured($args, $apidata) {
690                 parent::handle($args);
691                 common_server_error("API method under construction.", $code=501);
692         }
693
694         function get_user($id, $apidata) {
695                 if (!$id) {
696                         return $apidata['user'];
697                 } else if (is_numeric($id)) {
698                         return User::staticGet($id);
699                 } else {
700                         return User::staticGet('nickname', $id);
701                 }
702         }
703 }
704
705