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