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