]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - actions/twitapistatuses.php
6cf1ffb48b534d531c97f27a4a90ae90a3ecb070
[quix0rs-gnu-social.git] / actions / twitapistatuses.php
1 <?php
2 /*
3  * Laconica - a distributed open-source microblogging tool
4  * Copyright (C) 2008, Controlez-Vous, Inc.
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 if (!defined('LACONICA')) { exit(1); }
21
22 require_once(INSTALLDIR.'/lib/twitterapi.php');
23
24 /* XXX: Please don't freak out about all the ugly comments in this file.
25  * They are mostly in here for reference while I work on the
26  * API. I'll fix things up later to make them look better later. -- Zach 
27  */
28 class TwitapistatusesAction extends TwitterapiAction {
29         
30         function public_timeline($args, $apidata) {
31                 parent::handle($args);
32
33                 $sitename = common_config('site', 'name');
34                 $siteserver = common_config('site', 'server'); 
35                 $title = sprintf(_("%s public timeline"), $sitename);
36                 $id = "tag:$siteserver:Statuses";
37                 $link = common_root_url();
38                 $subtitle = sprintf(_("%s updates from everyone!"), $sitemap);
39
40                 // Number of public statuses to return by default -- Twitter sends 20
41                 $MAX_PUBSTATUSES = 20;
42
43                 $notice = DB_DataObject::factory('notice');
44
45                 // FIXME: To really live up to the spec we need to build a list
46                 // of notices by users who have custom avatars, so fix this SQL -- Zach
47
48                 # FIXME: bad performance
49                 $notice->whereAdd('EXISTS (SELECT user.id from user where user.id = notice.profile_id)');
50                 $notice->orderBy('created DESC, notice.id DESC');
51                 $notice->limit($MAX_PUBSTATUSES);
52                 $cnt = $notice->find();
53                 
54                 if ($cnt > 0) {
55                         
56                         switch($apidata['content-type']) {
57                                 case 'xml': 
58                                         $this->show_xml_timeline($notice);
59                                         break;
60                                 case 'rss':
61                                         $this->show_rss_timeline($notice, $title, $id, $link, $subtitle);
62                                         break;
63                                 case 'atom': 
64                                         $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
65                                         break;
66                                 case 'json':
67                                         $this->show_json_timeline($notice);
68                                         break;
69                                 default:
70                                         common_user_error("API method not found!", $code = 404);
71                                         break;
72                         }
73                         
74                 } else {
75                         common_server_error('Couldn\'t find any statuses.', $code = 503);
76                 }
77  
78                 exit();
79         }       
80         
81         function show_xml_timeline($notice) {
82
83                 header('Content-Type: application/xml; charset=utf-8');         
84                 common_start_xml();
85                 common_element_start('statuses', array('type' => 'array'));
86
87                 if (is_array($notice)) {
88                         foreach ($notice as $n) {
89                                 $twitter_status = $this->twitter_status_array($n);                                              
90                                 $this->show_twitter_xml_status($twitter_status);        
91                         }
92                 } else {
93                         while ($notice->fetch()) {
94                                 $twitter_status = $this->twitter_status_array($notice);                                         
95                                 $this->show_twitter_xml_status($twitter_status);
96                         }
97                 }
98                 
99                 common_element_end('statuses');
100                 common_end_xml();
101         }       
102         
103         function show_rss_timeline($notice, $title, $id, $link, $subtitle) {
104                 
105                 header("Content-Type: application/rss+xml; charset=utf-8");
106                 
107                 $this->init_twitter_rss();
108                 
109                 common_element_start('channel');
110                 common_element('title', NULL, $title);
111                 common_element('link', NULL, $link);
112                 common_element('description', NULL, $subtitle);
113                 common_element('language', NULL, 'en-us');
114                 common_element('ttl', NULL, '40');
115         
116         
117                 if (is_array($notice)) {
118                         foreach ($notice as $n) {
119                                 $entry = $this->twitter_rss_entry_array($n);                                            
120                                 $this->show_twitter_rss_item($entry);
121                         } 
122                 } else {
123                         while ($notice->fetch()) {
124                                 $entry = $this->twitter_rss_entry_array($notice);                                               
125                                 $this->show_twitter_rss_item($entry);
126                         }
127                 }
128
129                 common_element_end('channel');                  
130                 $this->end_twitter_rss();
131         }
132
133         function show_atom_timeline($notice, $title, $id, $link, $subtitle=NULL) {
134                 
135                 header('Content-Type: application/atom+xml; charset=utf-8');
136
137                 $this->init_twitter_atom();
138
139                 common_element('title', NULL, $title);
140                 common_element('id', NULL, $id);
141                 common_element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), NULL);
142                 common_element('subtitle', NULL, $subtitle);
143
144                 if (is_array($notice)) {
145                         foreach ($notice as $n) {
146                                 $entry = $this->twitter_rss_entry_array($n);                                            
147                                 $this->show_twitter_atom_entry($entry);
148                         } 
149                 } else {
150                         while ($notice->fetch()) {
151                                 $entry = $this->twitter_rss_entry_array($notice);                                               
152                                 $this->show_twitter_atom_entry($entry);
153                         }
154                 }
155                 
156                 $this->end_twitter_atom();
157         }
158
159         function show_json_timeline($notice) {
160                 
161                 header('Content-Type: application/json; charset=utf-8');
162                 
163                 $statuses = array();
164                 
165                 if (is_array($notice)) {
166                         foreach ($notice as $n) {
167                                 $twitter_status = $this->twitter_status_array($n);
168                                 array_push($statuses, $twitter_status);
169                         } 
170                 } else {
171                         while ($notice->fetch()) {
172                                 $twitter_status = $this->twitter_status_array($notice);
173                                 array_push($statuses, $twitter_status);
174                         }
175                 }                       
176                 
177                 $this->show_twitter_json_statuses($statuses);                   
178         }
179                 
180         /*
181         Returns the 20 most recent statuses posted by the authenticating user and that user's friends. 
182         This is the equivalent of /home on the Web. 
183         
184         URL: http://server/api/statuses/friends_timeline.format
185         
186         Parameters:
187
188             * since.  Optional.  Narrows the returned results to just those statuses created after the specified 
189                         HTTP-formatted date.  The same behavior is available by setting an If-Modified-Since header in 
190                         your HTTP request.  
191                         Ex: http://server/api/statuses/friends_timeline.rss?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
192             * since_id.  Optional.  Returns only statuses with an ID greater than (that is, more recent than) 
193                         the specified ID.  Ex: http://server/api/statuses/friends_timeline.xml?since_id=12345
194             * count.  Optional.  Specifies the number of statuses to retrieve. May not be greater than 200.
195                         Ex: http://server/api/statuses/friends_timeline.xml?count=5 
196             * page. Optional. Ex: http://server/api/statuses/friends_timeline.rss?page=3
197         
198         Formats: xml, json, rss, atom
199         */
200         function friends_timeline($args, $apidata) {
201                 parent::handle($args);
202
203                 $since = $this->arg('since');
204                 $since_id = $this->arg('since_id');
205                 $count = $this->arg('count');
206                 $page = $this->arg('page');
207                 
208                 if (!$page) {
209                         $page = 1;
210                 }
211
212                 if (!$count) {
213                         $count = 20;
214                 }
215
216                 $user = $this->get_user($id, $apidata);
217                 $profile = $user->getProfile();
218                 
219                 $sitename = common_config('site', 'name');
220                 $siteserver = common_config('site', 'server'); 
221                 
222                 $title = sprintf(_("%s and friends"), $user->nickname);
223                 $id = "tag:$siteserver:friends:".$user->id;
224                 $link = common_local_url('all', array('nickname' => $user->nickname));
225                 $subtitle = sprintf(_("Updates from %s and friends on %s!"), $user->nickname, $sitename);
226
227                 $notice = new Notice();
228
229                 # XXX: chokety and bad
230
231                 $notice->whereAdd('EXISTS (SELECT subscribed from subscription where subscriber = '.$profile->id.' and subscribed = notice.profile_id)', 'OR');
232                 $notice->whereAdd('profile_id = ' . $profile->id, 'OR');
233
234                 # XXX: since
235                 # XXX: since_id
236                 
237                 $notice->orderBy('created DESC, notice.id DESC');
238
239                 $notice->limit((($page-1)*20), $count);
240
241                 $cnt = $notice->find();
242                 
243                 switch($apidata['content-type']) {
244                  case 'xml': 
245                         $this->show_xml_timeline($notice);
246                         break;
247                  case 'rss':
248                         $this->show_rss_timeline($notice, $title, $id, $link, $subtitle);
249                         break;
250                  case 'atom': 
251                         $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
252                         break;
253                  case 'json':
254                         $this->show_json_timeline($notice);
255                         break;
256                  default:
257                         common_user_error("API method not found!", $code = 404);
258                 }
259                 
260                 exit();
261         }
262
263         /*
264                 Returns the 20 most recent statuses posted from the authenticating user. It's also possible to
265         request another user's timeline via the id parameter below. This is the equivalent of the Web
266         /archive page for your own user, or the profile page for a third party.
267
268                 URL: http://server/api/statuses/user_timeline.format
269
270                 Formats: xml, json, rss, atom
271
272                 Parameters:
273
274                     * id. Optional. Specifies the ID or screen name of the user for whom to return the
275             friends_timeline. Ex: http://server/api/statuses/user_timeline/12345.xml or
276             http://server/api/statuses/user_timeline/bob.json. 
277                         * count. Optional. Specifies the number of
278             statuses to retrieve. May not be greater than 200. Ex:
279             http://server/api/statuses/user_timeline.xml?count=5 
280                         * since. Optional. Narrows the returned
281             results to just those statuses created after the specified HTTP-formatted date. The same
282             behavior is available by setting an If-Modified-Since header in your HTTP request. Ex:
283             http://server/api/statuses/user_timeline.rss?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT 
284                         * since_id. Optional. Returns only statuses with an ID greater than (that is, more recent than)
285             the specified ID. Ex: http://server/api/statuses/user_timeline.xml?since_id=12345 * page.
286             Optional. Ex: http://server/api/statuses/friends_timeline.rss?page=3
287         */
288         function user_timeline($args, $apidata) {
289                 parent::handle($args);
290                 
291                 $id = $this->arg('id');
292                 $count = $this->arg('count');
293                 $since = $this->arg('since');
294                 $since_id = $this->arg('since_id');
295                 
296                 if (!$page) {
297                         $page = 1;
298                 }
299
300                 if (!$count) {
301                         $count = 20;
302                 }
303
304                 $user = $this->get_user($id, $apidata['user']);
305                 
306                 if (!$user) {
307                         $this->client_error(_('No such user'), 404);
308                         return;
309                 }
310                 
311                 $profile = $user->getProfile();
312                 
313                 $sitename = common_config('site', 'name');
314                 $siteserver = common_config('site', 'server'); 
315                 
316                 $title = sprintf(_("%s timeline"), $user->nickname);
317                 $id = "tag:$siteserver:user:".$user->id;
318                 $link = common_local_url('showstream', array('nickname' => $user->nickname));
319                 $subtitle = sprintf(_("Updates from %s on %s!"), $user->nickname, $sitename);
320
321                 $notice = new Notice();
322
323                 $notice->profile_id = $user->id;
324                 
325                 # XXX: since
326                 # XXX: since_id
327                 
328                 $notice->orderBy('created DESC, notice.id DESC');
329
330                 $notice->limit((($page-1)*20), $count);
331
332                 $cnt = $notice->find();
333                 
334                 switch($apidata['content-type']) {
335                  case 'xml': 
336                         $this->show_xml_timeline($notice);
337                         break;
338                  case 'rss':
339                         $this->show_rss_timeline($notice, $title, $id, $link, $subtitle);
340                         break;
341                  case 'atom': 
342                         $this->show_atom_timeline($notice, $title, $id, $link, $subtitle);
343                         break;
344                  case 'json':
345                         $this->show_json_timeline($notice);
346                         break;
347                  default:
348                         common_user_error("API method not found!", $code = 404);
349                 }
350                 
351                 exit();
352         }
353
354         function show($args, $apidata) {
355                 parent::handle($args);
356                 
357                 $id = $apidata['api_arg'];              
358                 $notice = Notice::staticGet($id);
359
360                 if ($notice) {
361
362                         if ($apidata['content-type'] == 'xml') { 
363                                 $this->show_single_xml_status($notice);
364                         } elseif ($apidata['content-type'] == 'json') {
365                                 $this->show_single_json_status($notice);
366                         }
367                 } else {
368                         header('HTTP/1.1 404 Not Found');
369                 }
370                 
371                 exit();
372         }
373                 
374         function show_single_xml_status($notice) {
375                 header('Content-Type: application/xml; charset=utf-8');         
376                 common_start_xml();
377                 $twitter_status = $this->twitter_status_array($notice);                                         
378                 $this->show_twitter_xml_status($twitter_status);
379                 common_end_xml();
380                 exit();
381         }
382         
383         function show_single_json_status($notice) {
384                 header('Content-Type: application/json; charset=utf-8');
385                 $status = $this->twitter_status_array($notice);
386                 $this->show_twitter_json_statuses($status);
387                 exit();
388         }
389                 
390         function update($args, $apidata) {
391                 parent::handle($args);
392                 
393                 $user = $apidata['user'];
394                                 
395                 $notice = DB_DataObject::factory('notice');             
396                 
397                 $notice->profile_id = $user->id; # user id *is* profile id
398                 $notice->created = DB_DataObject_Cast::dateTime();      
399                 $notice->content = $this->trimmed('status');
400
401                 if (!$notice->content) {
402                         
403                         // XXX: Note: In this case, Twitter simply returns '200 OK'
404                         // No error is given, but the status is not posted to the 
405                         // user's timeline.  Seems bad.  Shouldn't we throw an 
406                         // errror? -- Zach
407                         exit();
408                         
409                 } else if (strlen($notice->content) > 140) {
410
411                         // XXX: Twitter truncates anything over 140, flags the status 
412                     // as "truncated."  Sending this error may screw up some clients
413                     // that assume Twitter will truncate for them.  Should we just
414                     // truncate too? -- Zach
415                         header('HTTP/1.1 406 Not Acceptable');                  
416                         print "That's too long. Max notice size is 140 chars.\n";
417                         exit();
418                 }
419
420                 $notice->rendered = common_render_content($notice->content, $notice);
421
422                 $id = $notice->insert();
423
424                 if (!$id) {
425                         common_server_error('Could not update status!', 500);
426                         exit();
427                 }
428
429                 $orig = clone($notice);
430                 $notice->uri = common_notice_uri($notice);
431
432                 if (!$notice->update($orig)) {
433                         common_server_error('Could not save status!', 500);
434                         exit();
435                 }
436
437         common_save_replies($notice);
438                 common_broadcast_notice($notice);
439
440                 // FIXME: Bad Hack 
441                 // I should be able to just sent this notice off for display,
442                 // but $notice->created does not contain a string at this
443                 // point and I don't know how to convert it to one here. So
444                 // I'm forced to have DBObject pull the notice back out of the
445                 // DB before printing. --Zach
446                 $apidata['api_arg'] = $id;
447                 $this->show($args, $apidata);
448
449                 exit();
450         }
451         
452         /*
453                 Returns the 20 most recent @replies (status updates prefixed with @username) for the authenticating user.
454                 URL: http://server/api/statuses/replies.format
455                 
456                 Formats: xml, json, rss, atom
457
458                 Parameters:
459
460                 * page. Optional. Retrieves the 20 next most recent replies. Ex: http://server/api/statuses/replies.xml?page=3 
461                 * since. Optional. Narrows the returned results to just those replies created after the specified HTTP-formatted date. The
462         same behavior is available by setting an If-Modified-Since header in your HTTP request. Ex:
463         http://server/api/statuses/replies.xml?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
464                 * since_id. Optional. Returns only statuses with an ID greater than (that is, more recent than) the specified
465                 ID. Ex: http://server/api/statuses/replies.xml?since_id=12345
466         */
467         function replies($args, $apidata) {
468                 parent::handle($args);
469
470                 $since = $this->arg('since');
471
472                 $count = $this->arg('count');
473                 $page = $this->arg('page');
474
475                 $user = $apidata['user'];
476                 $profile = $user->getProfile();
477
478                 $sitename = common_config('site', 'name');
479                 $siteserver = common_config('site', 'server'); 
480
481                 $title = sprintf(_("%s / Updates replying to %s"), $sitename, $user->nickname);
482                 $id = "tag:$siteserver:replies:".$user->id;
483                 $link = common_local_url('replies', array('nickname' => $user->nickname));
484                 $subtitle = "gar";
485                 $subtitle = sprintf(_("%s updates that reply to updates from %s / %."), $sitename, $user->nickname, $user->nickname);
486
487                 if (!$page) {
488                         $page = 1;
489                 }
490
491                 if (!$count) {
492                         $count = 20;
493                 }
494
495                 $reply = new Reply();
496
497                 $reply->profile_id = $user->id;
498
499                 $reply->orderBy('modified DESC');
500
501                 $page = ($this->arg('page')) ? ($this->arg('page')+0) : 1;
502
503                 $reply->limit((($page-1)*20), $count);
504
505                 $cnt = $reply->find();
506
507                 $notices = array();
508         
509                 if ($cnt) {
510                         while ($reply->fetch()) {
511                                 $notice = new Notice();
512                                 $notice->id = $reply->notice_id;
513                                 $result = $notice->find(true);
514                                 if (!$result) {
515                                         continue;
516                                 }
517                                 $notices[] = clone($notice);
518                         }
519                 }
520
521                 switch($apidata['content-type']) {
522                  case 'xml': 
523                         $this->show_xml_timeline($notices);
524                         break;
525                  case 'rss':
526                         $this->show_rss_timeline($notices, $title, $id, $link, $subtitle);
527                         break;
528                  case 'atom': 
529                         $this->show_atom_timeline($notices, $title, $id, $link, $subtitle);
530                         break;
531                  case 'json':
532                         $this->show_json_timeline($notices);
533                         break;
534                  default:
535                         common_user_error("API method not found!", $code = 404);
536                 }
537
538
539                 exit();
540
541
542         }
543
544         
545         
546         /*
547                 Destroys the status specified by the required ID parameter. The authenticating user must be
548         the author of the specified status.
549                 
550                  URL: http://server/api/statuses/destroy/id.format
551                 
552                  Formats: xml, json
553                 
554                  Parameters:
555                 
556                  * id. Required. The ID of the status to destroy. Ex:
557                 http://server/api/statuses/destroy/12345.json or
558                 http://server/api/statuses/destroy/23456.xml
559         
560         */
561         function destroy($args, $apidata) {
562                 parent::handle($args);
563                 common_server_error("API method under construction.", $code=501);
564         }
565         
566         # User Methods
567         
568         /*
569                 Returns up to 100 of the authenticating user's friends who have most recently updated, each with current status inline.
570         It's also possible to request another user's recent friends list via the id parameter below.
571                 
572                  URL: http://server/api/statuses/friends.format
573                 
574                  Formats: xml, json
575                 
576                  Parameters:
577                 
578                  * id. Optional. The ID or screen name of the user for whom to request a list of friends. Ex:
579                 http://server/api/statuses/friends/12345.json 
580                         or 
581                         http://server/api/statuses/friends/bob.xml
582                  * page. Optional. Retrieves the next 100 friends. Ex: http://server/api/statuses/friends.xml?page=2
583                  * lite. Optional. Prevents the inline inclusion of current status. Must be set to a value of true. Ex:
584                 http://server/api/statuses/friends.xml?lite=true
585                  * since. Optional. Narrows the returned results to just those friendships created after the specified
586                         HTTP-formatted date. The same behavior is available by setting an If-Modified-Since header in your HTTP
587                         request. Ex: http://server/api/statuses/friends.xml?since=Tue%2C+27+Mar+2007+22%3A55%3A48+GMT
588         */
589         function friends($args, $apidata) {
590                 parent::handle($args);
591                 common_server_error("API method under construction.", $code=501);
592         }
593         
594         /*
595                 Returns the authenticating user's followers, each with current status inline. They are ordered by the
596                 order in which they joined Twitter (this is going to be changed).
597                 
598                 URL: http://server/api/statuses/followers.format
599                 Formats: xml, json
600
601                 Parameters: 
602
603                     * id. Optional. The ID or screen name of the user for whom to request a list of followers. Ex:
604                 http://server/api/statuses/followers/12345.json 
605                                 or 
606                                 http://server/api/statuses/followers/bob.xml
607                     * page. Optional. Retrieves the next 100 followers. Ex: http://server/api/statuses/followers.xml?page=2   
608                     * lite. Optional. Prevents the inline inclusion of current status. Must be set to a value of true.
609                                 Ex: http://server/api/statuses/followers.xml?lite=true
610         */
611         function followers($args, $apidata) {
612                 parent::handle($args);
613                 common_server_error("API method under construction.", $code=501);
614         }
615         
616         /*
617         Returns a list of the users currently featured on the site with their current statuses inline. 
618         URL: http://server/api/statuses/featured.format 
619
620         Formats: xml, json
621         */
622         function featured($args, $apidata) {
623                 parent::handle($args);
624                 common_server_error("API method under construction.", $code=501);
625         }
626
627         function get_user($id, $apidata) {
628                 if (!$id) {
629                         return $apidata['user'];
630                 } else if (is_numeric($id)) {
631                         return User::staticGet($id);
632                 } else {
633                         return User::staticGet('nickname', $id);
634                 }
635         }
636 }
637
638