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