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