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