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