diff --git a/actions/apifriendstimeline.php b/actions/apifriendstimeline.php new file mode 100644 index 0000000000..ea38ec022e --- /dev/null +++ b/actions/apifriendstimeline.php @@ -0,0 +1,161 @@ +. + * + * @category Personal + * @package StatusNet + * @author Zach Copley + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +require_once INSTALLDIR.'/lib/twitterapi.php'; + +class ApifriendstimelineAction extends TwitterapiAction +{ + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + return true; + + } + + function handle($args) { + + parent::handle($args); + common_debug(var_export($args, true)); + + if ($this->requiresAuth()) { + if ($this->showBasicAuthHeader()) { + $this->showTimeline(); + } + } else { + $this->showTimeline(); + } + } + + function showTimeline() + { + common_debug('Auth user = ' . var_export($this->auth_user, true)); + + $user = $this->getTargetUser($this->arg('id')); + + if (empty($user)) { + $this->clientError(_('No such user!'), 404, $this->arg('format')); + return; + } + + $profile = $user->getProfile(); + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s and friends"), $user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:FriendsTimeline:" . $user->id; + $link = common_local_url('all', + array('nickname' => $user->nickname)); + $subtitle = sprintf(_('Updates from %1$s and friends on %2$s!'), + $user->nickname, $sitename); + + $page = (int)$this->arg('page', 1); + $count = (int)$this->arg('count', 20); + $max_id = (int)$this->arg('max_id', 0); + $since_id = (int)$this->arg('since_id', 0); + $since = $this->arg('since'); + + if (!empty($this->auth_user) && $this->auth_user->id == $user->id) { + $notice = $user->noticeInbox(($page-1)*$count, + $count, $since_id, $max_id, $since); + } else { + $notice = $user->noticesWithFriends(($page-1)*$count, + $count, $since_id, $max_id, $since); + } + + switch($this->arg('format')) { + case 'xml': + $this->show_xml_timeline($notice); + break; + case 'rss': + $this->show_rss_timeline($notice, $title, $link, $subtitle); + break; + case 'atom': + + $target_id = $this->arg('id'); + + if (isset($target_id)) { + $selfuri = common_root_url() . + 'api/statuses/friends_timeline/' . + $target_id . '.atom'; + } else { + $selfuri = common_root_url() . + 'api/statuses/friends_timeline.atom'; + } + $this->show_atom_timeline($notice, $title, $id, $link, + $subtitle, null, $selfuri); + break; + case 'json': + $this->show_json_timeline($notice); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + } + + } + + function requiresAuth() + { + return true; + } + + /** + * Is this page read-only? + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this page last modified? + * + */ + + function lastModified() + { + + } + +} \ No newline at end of file diff --git a/lib/router.php b/lib/router.php index 2c4d63b0d7..0e5fe3a54f 100644 --- a/lib/router.php +++ b/lib/router.php @@ -262,15 +262,27 @@ class Router // statuses API + $m->connect('api/statuses/friends_timeline.:format', + array('action' => 'apifriendstimeline', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends_timeline/:id.:format', + array('action' => 'apifriendstimeline', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/home_timeline', + array('action' => 'apifriendstimeline')); + $m->connect('api/statuses/:method', array('action' => 'api', 'apiaction' => 'statuses'), - array('method' => '(public_timeline|home_timeline|friends_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?')); + array('method' => '(public_timeline|user_timeline|update|replies|mentions|show|friends|followers|featured)(\.(atom|rss|xml|json))?')); $m->connect('api/statuses/:method/:argument', array('action' => 'api', 'apiaction' => 'statuses'), - array('method' => '(user_timeline|home_timeline|friends_timeline|replies|mentions|show|destroy|friends|followers)')); + array('method' => '(user_timeline|replies|mentions|show|destroy|friends|followers)')); // users diff --git a/lib/twitterapi.php b/lib/twitterapi.php index d199e4dee2..ac5c5b4239 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -502,7 +502,7 @@ class TwitterapiAction extends Action $enclosure = $entry['enclosures'][0]; $this->element('enclosure', array('url'=>$enclosure['url'],'type'=>$enclosure['mimetype'],'length'=>$enclosure['size']), null); } - + if(array_key_exists('tags', $entry)){ foreach($entry['tags'] as $tag){ $this->element('category', null,$tag); @@ -934,7 +934,7 @@ class TwitterapiAction extends Action return; } - function clientError($msg, $code = 400, $content_type = 'json') + function clientError($msg, $code = 400, $format = 'xml') { static $status = array(400 => 'Bad Request', @@ -967,20 +967,23 @@ class TwitterapiAction extends Action $status_string = $status[$code]; header('HTTP/1.1 '.$code.' '.$status_string); - if ($content_type == 'xml') { + if ($format == 'xml') { $this->init_document('xml'); $this->elementStart('hash'); $this->element('error', null, $msg); $this->element('request', null, $_SERVER['REQUEST_URI']); $this->elementEnd('hash'); $this->end_document('xml'); - } else { + } elseif ($format == 'json'){ $this->init_document('json'); $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); print(json_encode($error_array)); $this->end_document('json'); - } + } else { + // If user didn't request a useful format, throw a regular client error + throw new ClientException($msg, $code); + } } function init_twitter_rss() @@ -1063,6 +1066,39 @@ class TwitterapiAction extends Action } } + function getTargetUser($id) + { + if (empty($id)) { + + // Twitter supports these other ways of passing the user ID + if (is_numeric($this->arg('id'))) { + return User::staticGet($this->arg('id')); + } else if ($this->arg('id')) { + $nickname = common_canonical_nickname($this->arg('id')); + return User::staticGet('nickname', $nickname); + } else if ($this->arg('user_id')) { + // This is to ensure that a non-numeric user_id still + // overrides screen_name even if it doesn't get used + if (is_numeric($this->arg('user_id'))) { + return User::staticGet('id', $this->arg('user_id')); + } + } else if ($this->arg('screen_name')) { + $nickname = common_canonical_nickname($this->arg('screen_name')); + return User::staticGet('nickname', $nickname); + } else { + // Fall back to trying the currently authenticated user + return $this->auth_user; + } + + } else if (is_numeric($id)) { + return User::staticGet($id); + } else { + $nickname = common_canonical_nickname($id); + return User::staticGet('nickname', $nickname); + } + } + + function get_group($id, $apidata=null) { if (empty($id)) { @@ -1170,4 +1206,85 @@ class TwitterapiAction extends Action } } + function showBasicAuthHeader() + { + $this->basicAuthProcessHeader(); + + if (!isset($this->auth_user)) { + header('WWW-Authenticate: Basic realm="StatusNet API"'); + + // show error if the user clicks 'cancel' + + $this->showBasicAuthError(); + return false; + + } else { + $nickname = $this->auth_user; + $password = $this->auth_pw; + $this->auth_user = common_check_user($nickname, $password); + + if (empty($this->auth_user)) { + + // basic authentication failed + + list($proxy, $ip) = common_client_ip(); + common_log(LOG_WARNING, + "Failed API auth attempt, nickname = $nickname, proxy = $proxy, ip = $ip."); + $this->showBasicAuthError(); + return false; + } + } + return true; + } + + function basicAuthProcessHeader() + { + if (isset($_SERVER['AUTHORIZATION']) || isset($_SERVER['HTTP_AUTHORIZATION'])) { + $authorization_header = isset($_SERVER['HTTP_AUTHORIZATION'])? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['AUTHORIZATION']; + } + + if (isset($_SERVER['PHP_AUTH_USER'])) { + $this->auth_user = $_SERVER['PHP_AUTH_USER']; + $this->auth_pw = $_SERVER['PHP_AUTH_PW']; + } elseif (isset($authorization_header) && strstr(substr($authorization_header, 0, 5), 'Basic')) { + // decode the HTTP_AUTHORIZATION header on php-cgi server self + // on fcgid server the header name is AUTHORIZATION + + $auth_hash = base64_decode(substr($authorization_header, 6)); + list($this->auth_user, $this->auth_pw) = explode(':', $auth_hash); + + // set all to null on a empty basic auth request + if ($this->auth_user == "") { + $this->auth_user = null; + $this->auth_pw = null; + } + } else { + $this->auth_user = null; + $this->auth_pw = null; + } + } + + function showBasicAuthError() + { + header('HTTP/1.1 401 Unauthorized'); + $msg = 'Could not authenticate you.'; + + if ($this->arg('format') == 'xml') { + header('Content-Type: application/xml; charset=utf-8'); + $this->startXML(); + $this->elementStart('hash'); + $this->element('error', null, $msg); + $this->element('request', null, $_SERVER['REQUEST_URI']); + $this->elementEnd('hash'); + $this->endXML(); + } elseif ($this->arg('format') == 'json') { + header('Content-Type: application/json; charset=utf-8'); + $error_array = array('error' => $msg, 'request' => $_SERVER['REQUEST_URI']); + print(json_encode($error_array)); + } else { + header('Content-type: text/plain'); + print "$msg\n"; + } + } + }