]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/twitapistatuses.php
Fixes #1277: Typo in variable name in actions/twitapidirect_messages.php.
[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 class TwitapistatusesAction extends TwitterapiAction
25 {
26
27     function public_timeline($args, $apidata)
28     {
29         parent::handle($args);
30
31         $sitename = common_config('site', 'name');
32         $siteserver = common_config('site', 'server');
33         $title = sprintf(_("%s public timeline"), $sitename);
34         $id = "tag:$siteserver:Statuses";
35         $link = common_root_url();
36         $subtitle = sprintf(_("%s updates from everyone!"), $sitename);
37
38         // Number of public statuses to return by default -- Twitter sends 20
39         $MAX_PUBSTATUSES = 20;
40
41         // FIXME: To really live up to the spec we need to build a list
42         // of notices by users who have custom avatars, so fix this SQL -- Zach
43
44         $page = $this->arg('page');
45         $since_id = $this->arg('since_id');
46         $before_id = $this->arg('before_id');
47
48         // NOTE: page, since_id, and before_id are extensions to Twitter API -- TB
49         if (!$page) {
50             $page = 1;
51         }
52         if (!$since_id) {
53             $since_id = 0;
54         }
55         if (!$before_id) {
56             $before_id = 0;
57         }
58
59         $since = strtotime($this->arg('since'));
60
61         $notice = Notice::publicStream((($page-1)*$MAX_PUBSTATUSES), $MAX_PUBSTATUSES, $since_id, $before_id, $since);
62
63         if ($notice) {
64
65             switch($apidata['content-type']) {
66                 case 'xml':
67                     $this->show_xml_timeline($notice);
68                     break;
69                 case 'rss':
70                     $this->show_rss_timeline($notice, $title, $link, $subtitle);
71                     break;
72                 case 'atom':
73                     $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
74                     break;
75                 case 'json':
76                     $this->show_json_timeline($notice);
77                     break;
78                 default:
79                     $this->clientError(_('API method not found!'), $code = 404);
80                     break;
81             }
82
83         } else {
84             $this->serverError(_('Couldn\'t find any statuses.'), $code = 503);
85         }
86
87     }
88
89     function friends_timeline($args, $apidata)
90     {
91         parent::handle($args);
92
93         $since = $this->arg('since');
94         $since_id = $this->arg('since_id');
95         $count = $this->arg('count');
96         $page = $this->arg('page');
97         $before_id = $this->arg('before_id');
98
99         if (!$page) {
100             $page = 1;
101         }
102
103         if (!$count) {
104             $count = 20;
105         }
106
107         if (!$since_id) {
108             $since_id = 0;
109         }
110
111         // NOTE: before_id is an extension to Twitter API -- TB
112         if (!$before_id) {
113             $before_id = 0;
114         }
115
116         $since = strtotime($this->arg('since'));
117
118         $user = $this->get_user($apidata['api_arg'], $apidata);
119         $this->auth_user = $user;
120
121         if (empty($user)) {
122              $this->clientError(_('No such user!'), 404, $apidata['content-type']);
123             return;
124         }
125
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, $since_id, $before_id, $since);
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, $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             $this->clientError(_('API method not found!'), $code = 404);
153         }
154
155     }
156
157     function user_timeline($args, $apidata)
158     {
159         parent::handle($args);
160
161         $this->auth_user = $apidata['user'];
162         $user = $this->get_user($apidata['api_arg'], $apidata);
163
164         if (!$user) {
165             $this->clientError('Not Found', 404, $apidata['content-type']);
166             return;
167         }
168
169         $profile = $user->getProfile();
170
171         if (!$profile) {
172             $this->serverError(_('User has no profile.'));
173             return;
174         }
175
176         $count = $this->arg('count');
177         $since = $this->arg('since');
178         $since_id = $this->arg('since_id');
179         $page = $this->arg('page');
180         $before_id = $this->arg('before_id');
181
182         if (!$page) {
183             $page = 1;
184         }
185
186         if (!$count) {
187             $count = 20;
188         }
189
190         if (!$since_id) {
191             $since_id = 0;
192         }
193
194         // NOTE: before_id is an extensions to Twitter API -- TB
195         if (!$before_id) {
196             $before_id = 0;
197         }
198
199         $since = strtotime($this->arg('since'));
200
201         $sitename = common_config('site', 'name');
202         $siteserver = common_config('site', 'server');
203
204         $title = sprintf(_("%s timeline"), $user->nickname);
205         $id = "tag:$siteserver:user:".$user->id;
206         $link = common_local_url('showstream', array('nickname' => $user->nickname));
207         $subtitle = sprintf(_('Updates from %1$s on %2$s!'), $user->nickname, $sitename);
208
209         # FriendFeed's SUP protocol
210         # Also added RSS and Atom feeds
211
212         $suplink = common_local_url('sup', null, null, $user->id);
213         header('X-SUP-ID: '.$suplink);
214
215         # XXX: since
216
217         $notice = $user->getNotices((($page-1)*20), $count, $since_id, $before_id, $since);
218
219         switch($apidata['content-type']) {
220          case 'xml':
221             $this->show_xml_timeline($notice);
222             break;
223          case 'rss':
224             $this->show_rss_timeline($notice, $title, $link, $subtitle, $suplink);
225             break;
226          case 'atom':
227             $this->show_atom_timeline($notice, $title, $id, $link, $subtitle, $suplink);
228             break;
229          case 'json':
230             $this->show_json_timeline($notice);
231             break;
232          default:
233             $this->clientError(_('API method not found!'), $code = 404);
234         }
235
236     }
237
238     function update($args, $apidata)
239     {
240
241         parent::handle($args);
242
243         if (!in_array($apidata['content-type'], array('xml', 'json'))) {
244             $this->clientError(_('API method not found!'), $code = 404);
245             return;
246         }
247
248         if ($_SERVER['REQUEST_METHOD'] != 'POST') {
249             $this->clientError(_('This method requires a POST.'), 400, $apidata['content-type']);
250             return;
251         }
252
253         $this->auth_user = $apidata['user'];
254         $user = $this->auth_user;
255         $status = $this->trimmed('status');
256         $source = $this->trimmed('source');
257         $in_reply_to_status_id = intval($this->trimmed('in_reply_to_status_id'));
258         $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api');
259         if (!$source || in_array($source, $reserved_sources)) {
260             $source = 'api';
261         }
262
263         if (!$status) {
264
265             // XXX: Note: In this case, Twitter simply returns '200 OK'
266             // No error is given, but the status is not posted to the
267             // user's timeline.     Seems bad.     Shouldn't we throw an
268             // errror? -- Zach
269             return;
270
271         } else {
272
273             $status_shortened = common_shorten_links($status);
274
275             if (mb_strlen($status_shortened) > 140) {
276
277                 // XXX: Twitter truncates anything over 140, flags the status
278                 // as "truncated." Sending this error may screw up some clients
279                 // that assume Twitter will truncate for them.    Should we just
280                 // truncate too? -- Zach
281                 $this->clientError(_('That\'s too long. Max notice size is 140 chars.'), $code = 406, $apidata['content-type']);
282                 return;
283
284             }
285         }
286
287         // Check for commands
288         $inter = new CommandInterpreter();
289         $cmd = $inter->handle_command($user, $status_shortened);
290
291         if ($cmd) {
292
293             if ($this->supported($cmd)) {
294                 $cmd->execute(new Channel());
295             }
296
297             // cmd not supported?  Twitter just returns your latest status.
298             // And, it returns your last status whether the cmd was successful
299             // or not!
300             $n = $user->getCurrentNotice();
301             $apidata['api_arg'] = $n->id;
302         } else {
303
304             $reply_to = null;
305
306             if ($in_reply_to_status_id) {
307
308                 // check whether notice actually exists
309                 $reply = Notice::staticGet($in_reply_to_status_id);
310
311                 if ($reply) {
312                     $reply_to = $in_reply_to_status_id;
313                 } else {
314                     $this->clientError(_('Not found'), $code = 404, $apidata['content-type']);
315                     return;
316                 }
317             }
318
319             $notice = Notice::saveNew($user->id, html_entity_decode($status, ENT_NOQUOTES, 'UTF-8'),
320                 $source, 1, $reply_to);
321
322             if (is_string($notice)) {
323                 $this->serverError($notice);
324                 return;
325             }
326
327             common_broadcast_notice($notice);
328             $apidata['api_arg'] = $notice->id;
329         }
330
331         $this->show($args, $apidata);
332     }
333
334     function replies($args, $apidata)
335     {
336
337         parent::handle($args);
338
339         $since = $this->arg('since');
340         $count = $this->arg('count');
341         $page = $this->arg('page');
342         $since_id = $this->arg('since_id');
343         $before_id = $this->arg('before_id');
344
345         $this->auth_user = $apidata['user'];
346         $user = $this->auth_user;
347         $profile = $user->getProfile();
348
349         $sitename = common_config('site', 'name');
350         $siteserver = common_config('site', 'server');
351
352         $title = sprintf(_('%1$s / Updates replying to %2$s'), $sitename, $user->nickname);
353         $id = "tag:$siteserver:replies:".$user->id;
354         $link = common_local_url('replies', array('nickname' => $user->nickname));
355         $subtitle = sprintf(_('%1$s updates that reply to updates from %2$s / %3$s.'), $sitename, $user->nickname, $profile->getBestName());
356
357         if (!$page) {
358             $page = 1;
359         }
360
361         if (!$count) {
362             $count = 20;
363         }
364
365         if (!$since_id) {
366             $since_id = 0;
367         }
368
369         // NOTE: before_id is an extension to Twitter API -- TB
370         if (!$before_id) {
371             $before_id = 0;
372         }
373
374         $since = strtotime($this->arg('since'));
375
376         $notice = $user->getReplies((($page-1)*20), $count, $since_id, $before_id, $since);
377         $notices = array();
378
379         while ($notice->fetch()) {
380             $notices[] = clone($notice);
381         }
382
383         switch($apidata['content-type']) {
384          case 'xml':
385             $this->show_xml_timeline($notices);
386             break;
387          case 'rss':
388             $this->show_rss_timeline($notices, $title, $link, $subtitle);
389             break;
390          case 'atom':
391             $this->show_atom_timeline($notices, $title, $id, $link, $subtitle);
392             break;
393          case 'json':
394             $this->show_json_timeline($notices);
395             break;
396          default:
397             $this->clientError(_('API method not found!'), $code = 404);
398         }
399
400     }
401
402     function show($args, $apidata)
403     {
404         parent::handle($args);
405
406         if (!in_array($apidata['content-type'], array('xml', 'json'))) {
407             $this->clientError(_('API method not found!'), $code = 404);
408             return;
409         }
410
411         $this->auth_user = $apidata['user'];
412         $notice_id = $apidata['api_arg'];
413         $notice = Notice::staticGet($notice_id);
414
415         if ($notice) {
416             if ($apidata['content-type'] == 'xml') {
417                 $this->show_single_xml_status($notice);
418             } elseif ($apidata['content-type'] == 'json') {
419                 $this->show_single_json_status($notice);
420             }
421         } else {
422             // XXX: Twitter just sets a 404 header and doens't bother to return an err msg
423             $this->clientError(_('No status with that ID found.'), 404, $apidata['content-type']);
424         }
425
426     }
427
428     function destroy($args, $apidata)
429     {
430
431         parent::handle($args);
432
433         if (!in_array($apidata['content-type'], array('xml', 'json'))) {
434             $this->clientError(_('API method not found!'), $code = 404);
435             return;
436         }
437
438         // Check for RESTfulness
439         if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) {
440             // XXX: Twitter just prints the err msg, no XML / JSON.
441             $this->clientError(_('This method requires a POST or DELETE.'), 400, $apidata['content-type']);
442             return;
443         }
444
445         $this->auth_user = $apidata['user'];
446         $user = $this->auth_user;
447         $notice_id = $apidata['api_arg'];
448         $notice = Notice::staticGet($notice_id);
449
450         if (!$notice) {
451             $this->clientError(_('No status found with that ID.'), 404, $apidata['content-type']);
452             return;
453         }
454
455         if ($user->id == $notice->profile_id) {
456             $replies = new Reply;
457             $replies->get('notice_id', $notice_id);
458             $replies->delete();
459             $notice->delete();
460
461             if ($apidata['content-type'] == 'xml') {
462                 $this->show_single_xml_status($notice);
463             } elseif ($apidata['content-type'] == 'json') {
464                 $this->show_single_json_status($notice);
465             }
466         } else {
467             $this->clientError(_('You may not delete another user\'s status.'), 403, $apidata['content-type']);
468         }
469
470     }
471
472     function friends($args, $apidata)
473     {
474         parent::handle($args);
475         return $this->subscriptions($apidata, 'subscribed', 'subscriber');
476     }
477
478     function friendsIDs($args, $apidata)
479     {
480         parent::handle($args);
481         return $this->subscriptions($apidata, 'subscribed', 'subscriber', true);
482     }
483
484     function followers($args, $apidata)
485     {
486         parent::handle($args);
487         return $this->subscriptions($apidata, 'subscriber', 'subscribed');
488     }
489
490     function followersIDs($args, $apidata)
491     {
492         parent::handle($args);
493         return $this->subscriptions($apidata, 'subscriber', 'subscribed', true);
494     }
495
496     function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false)
497     {
498
499         $this->auth_user = $apidata['user'];
500         $user = $this->get_user($apidata['api_arg'], $apidata);
501
502         if (!$user) {
503             $this->clientError('Not Found', 404, $apidata['content-type']);
504             return;
505         }
506
507         $page = $this->trimmed('page');
508
509         if (!$page || !is_numeric($page)) {
510             $page = 1;
511         }
512
513         $profile = $user->getProfile();
514
515         if (!$profile) {
516             $this->serverError(_('User has no profile.'));
517             return;
518         }
519
520         $sub = new Subscription();
521         $sub->$user_attr = $profile->id;
522
523         $since = strtotime($this->trimmed('since'));
524
525         if ($since) {
526             $d = date('Y-m-d H:i:s', $since);
527             $sub->whereAdd("created > '$d'");
528         }
529
530         $sub->orderBy('created DESC');
531
532         if (!$onlyIDs) {
533             $sub->limit(($page-1)*100, 100);
534         }
535
536         $others = array();
537
538         if ($sub->find()) {
539             while ($sub->fetch()) {
540                 $others[] = Profile::staticGet($sub->$other_attr);
541             }
542         } else {
543             // user has no followers
544         }
545
546         $type = $apidata['content-type'];
547
548         $this->init_document($type);
549
550         if ($onlyIDs) {
551             $this->showIDs($others, $type);
552         } else {
553             $this->show_profiles($others, $type);
554         }
555
556         $this->end_document($type);
557     }
558
559     function show_profiles($profiles, $type)
560     {
561         switch ($type) {
562          case 'xml':
563             $this->elementStart('users', array('type' => 'array'));
564             foreach ($profiles as $profile) {
565                 $this->show_profile($profile);
566             }
567             $this->elementEnd('users');
568             break;
569          case 'json':
570             $arrays = array();
571             foreach ($profiles as $profile) {
572                 $arrays[] = $this->twitter_user_array($profile, true);
573             }
574             print json_encode($arrays);
575             break;
576          default:
577             $this->clientError(_('unsupported file type'));
578         }
579     }
580
581     function showIDs($profiles, $type)
582     {
583         switch ($type) {
584          case 'xml':
585             $this->elementStart('ids');
586             foreach ($profiles as $profile) {
587                 $this->element('id', null, $profile->id);
588             }
589             $this->elementEnd('ids');
590             break;
591          case 'json':
592             $ids = array();
593             foreach ($profiles as $profile) {
594                 $ids[] = (int)$profile->id;
595             }
596             print json_encode($ids);
597             break;
598          default:
599             $this->clientError(_('unsupported file type'));
600         }
601     }
602
603     function featured($args, $apidata)
604     {
605         parent::handle($args);
606         $this->serverError(_('API method under construction.'), $code=501);
607     }
608
609     function supported($cmd)
610     {
611
612         $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand', 'FavCommand', 'OnCommand', 'OffCommand');
613
614         if (in_array(get_class($cmd), $cmdlist)) {
615             return true;
616         }
617
618         return false;
619     }
620
621 }