diff options
25 files changed, 3802 insertions, 1388 deletions
diff --git a/actions/apidirectmessage.php b/actions/apidirectmessage.php new file mode 100644 index 000000000..87ae44d7a --- /dev/null +++ b/actions/apidirectmessage.php @@ -0,0 +1,389 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a the direct messages from or to a user + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apiauth.php'; + +/** + * Show a list of direct messages from or to the authenticating user + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiDirectMessageAction extends ApiAuthAction +{ + var $format = null; + var $messages = null; + var $page = null; + var $count = null; + var $max_id = null; + var $since_id = null; + var $since = null; + var $title = null; + var $subtitle = null; + var $link = null; + var $selfuri_base = null; + var $id = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return; + } + } + + $this->user = $this->auth_user; + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $this->page = (int)$this->arg('page', 1); + $this->count = (int)$this->arg('count', 20); + $this->max_id = (int)$this->arg('max_id', 0); + $this->since_id = (int)$this->arg('since_id', 0); + $this->since = $this->arg('since'); + + $server = common_root_url(); + $taguribase = common_config('integration', 'taguri'); + + if ($this->arg('sent')) { + + // Action was called by /api/direct_messages/sent.format + + $this->title = sprintf( + _("Direct messages from %s"), + $this->user->nickname + ); + $this->subtitle = sprintf( + _("All the direct messages sent from %s"), + $this->user->nickname + ); + $this->link = $server . $this->user->nickname . '/outbox'; + $this->selfuri_base = common_root_url() . 'api/direct_messages/sent'; + $this->id = "tag:$taguribase:SentDirectMessages:" . $this->user->id; + } else { + $this->title = sprintf( + _("Direct messages to %s"), + $this->user->nickname + ); + $this->subtitle = sprintf( + _("All the direct messages sent to %s"), + $this->user->nickname + ); + $this->link = $server . $this->user->nickname . '/inbox'; + $this->selfuri_base = common_root_url() . 'api/direct_messages'; + $this->id = "tag:$taguribase:DirectMessages:" . $this->user->id; + } + + $this->format = $this->arg('format'); + + $this->messages = $this->getMessages(); + + return true; + } + + /** + * Handle the request + * + * Show the messages + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showMessages(); + } + + /** + * Show the messages + * + * @return void + */ + + function showMessages() + { + switch($this->format) { + case 'xml': + $this->showXmlDirectMessages(); + break; + case 'rss': + $this->showRssDirectMessages(); + break; + case 'atom': + $this->showAtomDirectMessages(); + break; + case 'json': + $this->showJsonDirectMessages(); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getMessages() + { + $message = new Message(); + + if ($this->arg('sent')) { + $message->from_profile = $this->user->id; + } else { + $message->to_profile = $this->user->id; + } + + if (!empty($this->max_id)) { + $message->whereAdd('id <= ' . $this->max_id); + } + + if (!empty($this->since_id)) { + $message->whereAdd('id > ' . $this->since_id); + } + + if (!empty($since)) { + $d = date('Y-m-d H:i:s', $this->since); + $message->whereAdd("created > '$d'"); + } + + $message->orderBy('created DESC, id DESC'); + $message->limit((($this->page - 1) * $this->count), $this->count); + $message->find(); + + $messages = array(); + + while ($message->fetch()) { + $messages[] = clone($message); + } + + return $messages; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this notice last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->messages)) { + return strtotime($this->messages[0]->created); + } + + return null; + } + + /** + * Shows a list of direct messages as Twitter-style XML array + * + * @return void + */ + + function showXmlDirectMessages() + { + $this->init_document('xml'); + $this->elementStart('direct-messages', array('type' => 'array')); + + foreach ($this->messages as $m) { + $dm_array = $this->directMessageArray($m); + $this->showXmlDirectMessage($dm_array); + } + + $this->elementEnd('direct-messages'); + $this->end_document('xml'); + } + + /** + * Shows a list of direct messages as a JSON encoded array + * + * @return void + */ + + function showJsonDirectMessages() + { + $this->init_document('json'); + + $dmsgs = array(); + + foreach ($this->messages as $m) { + $dm_array = $this->directMessageArray($m); + array_push($dmsgs, $dm_array); + } + + $this->show_json_objects($dmsgs); + $this->end_document('json'); + } + + /** + * Shows a list of direct messages as RSS items + * + * @return void + */ + + function showRssDirectMessages() + { + $this->init_document('rss'); + + $this->element('title', null, $this->title); + + $this->element('link', null, $this->link); + $this->element('description', null, $this->subtitle); + $this->element('language', null, 'en-us'); + + $this->element( + 'atom:link', + array( + 'type' => 'application/rss+xml', + 'href' => $this->selfuri_base . '.rss', + 'rel' => self + ), + null + ); + $this->element('ttl', null, '40'); + + foreach ($this->messages as $m) { + $entry = $this->rssDirectMessageArray($m); + $this->show_twitter_rss_item($entry); + } + + $this->end_twitter_rss(); + } + + /** + * Shows a list of direct messages as Atom entries + * + * @return void + */ + + function showAtomDirectMessages() + { + $this->init_document('atom'); + + $this->element('title', null, $this->title); + $this->element('id', null, $this->id); + + $selfuri = common_root_url() . 'api/direct_messages.atom'; + + $this->element( + 'link', array( + 'href' => $this->link, + 'rel' => 'alternate', + 'type' => 'text/html'), + null + ); + $this->element( + 'link', array( + 'href' => $this->selfuri_base . '.atom', 'rel' => 'self', + 'type' => 'application/atom+xml'), + null + ); + $this->element('updated', null, common_date_iso8601('now')); + $this->element('subtitle', null, $this->subtitle); + + foreach ($this->messages as $m) { + $entry = $this->rssDirectMessageArray($m); + $this->showTwitterAtomEntry($entry); + } + + $this->end_document('atom'); + } + + /** + * An entity tag for this notice + * + * Returns an Etag based on the action name, language, and + * timestamps of the notice + * + * @return string etag + */ + + function etag() + { + if (!empty($this->messages)) { + + $last = count($this->messages) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + strtotime($this->messages[0]->created), + strtotime($this->messages[$last]->created) + ) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apidirectmessagenew.php b/actions/apidirectmessagenew.php new file mode 100644 index 000000000..d836409ae --- /dev/null +++ b/actions/apidirectmessagenew.php @@ -0,0 +1,192 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Send a direct message via the API + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apiauth.php'; + +/** + * Creates a new direct message from the authenticating user to + * the user specified by id. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiDirectMessageNewAction extends ApiAuthAction +{ + var $format = null; + var $source = null; + var $user = null; + var $other = null; + var $content = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return; + } + } + + $this->user = $this->auth_user; + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $this->source = $this->trimmed('source'); // Not supported by Twitter. + + $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); + if (empty($thtis->source) || in_array($this->source, $reserved_sources)) { + $source = 'api'; + } + + $this->content = $this->trimmed('text'); + + $this->user = $this->auth_user; + + $user_param = $this->trimmed('user'); + $user_id = $this->arg('user_id'); + $screen_name = $this->trimmed('screen_name'); + + if (isset($user_param) || isset($user_id) || isset($screen_name)) { + $this->other = $this->getTargetUser($user_param); + } + + $this->format = $this->arg('format'); + + return true; + } + + /** + * Handle the request + * + * Save the new message + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->content)) { + $this->clientError( + _('No message text!'), + 406, + $this->format + ); + } else { + $content_shortened = common_shorten_links($this->content); + if (Message::contentTooLong($content_shortened)) { + $this->clientError( + sprintf( + _('That\'s too long. Max message size is %d chars.'), + Message::maxContent() + ), + 406, + $this->format + ); + return; + } + } + + if (empty($this->other)) { + $this->clientError(_('Recipient user not found.'), 403, $this->format); + return; + } else if (!$this->user->mutuallySubscribed($this->other)) { + $this->clientError( + _('Can\'t send direct messages to users who aren\'t your friend.'), + 403, + $this->format + ); + return; + } else if ($this->user->id == $this->other->id) { + + // Note: sending msgs to yourself is allowed by Twitter + + $errmsg = 'Don\'t send a message to yourself; ' . + 'just say it to yourself quietly instead.' + + $this->clientError(_($errmsg), 403, $this->format); + return; + } + + $message = Message::saveNew( + $this->user->id, + $this->other->id, + html_entity_decode($this->content, ENT_NOQUOTES, 'UTF-8'), + $this->source + ); + + if (is_string($message)) { + $this->serverError($message); + return; + } + + mail_notify_message($message, $this->user, $this->other); + + if ($this->format == 'xml') { + $this->showSingleXmlDirectMessage($message); + } elseif ($this->format == 'json') { + $this->showSingleJsondirectMessage($message); + } + } + +} + diff --git a/actions/apifriendshipscreate.php b/actions/apifriendshipscreate.php new file mode 100644 index 000000000..d691b5b4f --- /dev/null +++ b/actions/apifriendshipscreate.php @@ -0,0 +1,134 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Subscribe to a user via the API + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apiauth.php'; + +/** + * Allows the authenticating users to follow (subscribe) the user specified in + * the ID parameter. Returns the befriended user in the requested format when + * successful. Returns a string describing the failure condition when unsuccessful. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFriendshipsCreateAction extends ApiAuthAction +{ + + var $format = null; + var $user = null; + var $other = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return; + } + } + + $this->format = $this->arg('format'); + $this->user = $this->auth_user; + $this->other = $this->getTargetUser($id); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->other)) { + $this->clientError( + _('Could not follow user: User not found.'), + 403, + $this->format + ); + return; + } + + if ($this->user->isSubscribed($this->other)) { + $errmsg = sprintf( + _('Could not follow user: %s is already on your list.'), + $this->other->nickname + ); + $this->clientError($errmsg, 403, $this->format); + return; + } + + $result = subs_subscribe_to($this->user, $this->other); + + if (is_string($result)) { + $this->clientError($result, 403, $this->format); + return; + } + + $this->init_document($this->format); + $this->show_profile($this->other, $this->format); + $this->end_document($this->format); + } + +} diff --git a/actions/apifriendshipsdestroy.php b/actions/apifriendshipsdestroy.php new file mode 100644 index 000000000..d97c5aa6d --- /dev/null +++ b/actions/apifriendshipsdestroy.php @@ -0,0 +1,136 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Unsubscribe to a user via API + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apiauth.php'; + +/** + * Allows the authenticating users to unfollow (unsubscribe) the user specified in + * the ID parameter. Returns the unfollowed user in the requested format when + * successful. Returns a string describing the failure condition when unsuccessful. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFriendshipsDestroyAction extends ApiAuthAction +{ + + var $format = null; + var $user = null; + var $other = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return; + } + } + + $this->format = $this->arg('format'); + $this->user = $this->auth_user; + $this->other = $this->getTargetUser($id); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, + $this->format + ); + return; + } + + if (empty($this->other)) { + $this->clientError( + _('Could not unfollow user: User not found.'), + 403, + $this->format + ); + return; + } + + // Don't allow unsubscribing from yourself! + + if ($this->user->id == $this->other->id) { + $this->clientError( + _("You cannot unfollow yourself!"), + 403, + $this->format + ); + return; + } + + $result = subs_unsubscribe_user($this->user, $this->other->nickname); + + if (is_string($result)) { + $this->clientError($result, 403, $this->format); + return; + } + + $this->init_document($this->format); + $this->show_profile($this->other, $this->format); + $this->end_document($this->format); + } + +} diff --git a/actions/apifriendshipsexists.php b/actions/apifriendshipsexists.php new file mode 100644 index 000000000..3d6e7448d --- /dev/null +++ b/actions/apifriendshipsexists.php @@ -0,0 +1,128 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show whether there is a friendship between two users + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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'; + +/** + * Tests for the existence of friendship between two users. Will return true if + * user_a follows user_b, otherwise will return false. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFriendshipsExistsAction extends TwitterApiAction +{ + + var $format = null; + var $user_a = null; + var $user_b = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->format = $this->arg('format'); + + $user_a_id = $this->trimmed('user_a'); + $user_b_id = $this->trimmed('user_b'); + + common_debug("user_a = " . $user_a_id); + common_debug("user_b = " . $user_b_id); + + + $this->user_a = $this->getTargetUser($user_a_id); + + if (empty($this->user_a)) { + common_debug('gargargra'); + } + + $this->user_b = $this->getTargetUser($user_b_id); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (empty($this->user_a) || empty($this->user_b)) { + $this->clientError( + _('Two user ids or screen_names must be supplied.'), + 400, + $this->format + ); + return; + } + + $result = $this->user_a->isSubscribed($this->user_b); + + switch ($this->format) { + case 'xml': + $this->init_document('xml'); + $this->element('friends', null, $result); + $this->end_document('xml'); + break; + case 'json': + $this->init_document('json'); + print json_encode($result); + $this->end_document('json'); + break; + default: + break; + } + } + +} diff --git a/actions/apifriendshipsshow.php b/actions/apifriendshipsshow.php new file mode 100644 index 000000000..d35825a43 --- /dev/null +++ b/actions/apifriendshipsshow.php @@ -0,0 +1,175 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show information about the relationship between two users + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apibareauth.php'; + +/** + * Outputs detailed information about the relationship between two users + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiFriendshipsShowAction extends ApiBareAuthAction +{ + + var $format = null; + var $user = null; + var $source = null; + var $target = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return; + } + } + + $this->format = $this->arg('format'); + + $source_id = (int)$this->trimmed('source_id'); + $source_screen_name = $this->trimmed('source_screen_name'); + $target_id = (int)$this->trimmed('target_id'); + $target_screen_name = $this->trimmed('target_screen_name'); + + if (!empty($source_id)) { + $this->source = User::staticGet($source_id); + } elseif (!empty($source_screen_name)) { + $this->source = User::staticGet('nickname', $source_screen_name); + } else { + $this->source = $this->auth_user; + } + + if (!empty($target_id)) { + $this->target = User::staticGet($target_id); + } elseif (!empty($target_screen_name)) { + $this->target = User::staticGet('nickname', $target_screen_name); + } + + return true; + } + + + /** + * Determines whether this API resource requires auth. Overloaded to look + * return true in case source_id and source_screen_name are both empty + * + * @return boolean true or false + */ + + function requiresAuth() + { + if (common_config('site', 'private')) { + return true; + } + + $source_id = $this->trimmed('source_id'); + $source_screen_name = $this->trimmed('source_screen_name'); + + if (empty($source_id) && empty($source_screen_name)) { + return true; + } + + return false; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), 404); + return; + } + + if (empty($this->source)) { + $this->clientError( + _('Could not determine source user.'), + 404 + ); + return; + } + + if (empty($this->target)) { + $this->clientError( + _('Could not find target user.'), + 404 + ); + return; + } + + $result = $this->twitter_relationship_array($this->source, $this->target); + + switch ($this->format) { + case 'xml': + $this->init_document('xml'); + $this->show_twitter_xml_relationship($result[relationship]); + $this->end_document('xml'); + break; + case 'json': + $this->init_document('json'); + print json_encode($result); + $this->end_document('json'); + break; + default: + break; + } + + } + +} diff --git a/actions/apistatusesdestroy.php b/actions/apistatusesdestroy.php new file mode 100644 index 000000000..ae0f4c453 --- /dev/null +++ b/actions/apistatusesdestroy.php @@ -0,0 +1,152 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Destroy a notice through the API + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apiauth.php'; + +/** + * Deletes one of the authenticating user's statuses (notices). + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesDestroyAction extends ApiAuthAction +{ + + var $user = null; + var $status = null; + var $format = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return false; + } + } + + $this->user = $this->auth_user; + $this->notice_id = (int)$this->trimmed('id'); + + if (empty($notice_id)) { + $this->notice_id = (int)$this->arg('id'); + } + + $this->format = $this->arg('format'); + $this->notice = Notice::staticGet((int)$this->notice_id); + + return true; + } + + /** + * Handle the request + * + * Delete the notice and all related replies + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { + $this->clientError(_('This method requires a POST or DELETE.'), + 400, $this->format); + return; + } + + if (empty($this->notice)) { + $this->clientError(_('No status found with that ID.'), + 404, $this->format); + return; + } + + if ($this->user->id == $this->notice->profile_id) { + $replies = new Reply; + $replies->get('notice_id', $this->notice_id); + $replies->delete(); + $this->notice->delete(); + + if ($this->format == 'xml') { + $this->show_single_xml_status($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } else { + $this->clientError(_('You may not delete another user\'s status.'), + 403, $this->format); + } + + $this->showNotice(); + } + + /** + * Show the deleted notice + * + * @return void + */ + + function showNotice() + { + if (!empty($this->notice)) { + if ($this->format == 'xml') { + $this->show_single_xml_status($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } + } + +} diff --git a/actions/apistatusesshow.php b/actions/apistatusesshow.php new file mode 100644 index 000000000..55eea2356 --- /dev/null +++ b/actions/apistatusesshow.php @@ -0,0 +1,196 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a notice (as a Twitter-style status) + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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'; + +/** + * Returns the notice specified by id as a Twitter-style status and inline user + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesShowAction extends TwitterapiAction +{ + + var $notice_id = null; + var $notice = null; + var $format = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + // 'id' is an undocumented parameter in Twitter's API. Several + // clients make use of it, so we support it too. + + // show.json?id=12345 takes precedence over /show/12345.json + + $this->notice_id = (int)$this->trimmed('id'); + + if (empty($notice_id)) { + $this->notice_id = (int)$this->arg('id'); + } + + $this->format = $this->arg('format'); + $this->notice = Notice::staticGet((int)$this->notice_id); + + return true; + } + + /** + * Handle the request + * + * Check the format and show the notice + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + $this->showNotice(); + } + + /** + * Show the notice + * + * @return void + */ + + function showNotice() + { + if (!empty($this->notice)) { + if ($this->format == 'xml') { + $this->show_single_xml_status($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } else { + + // XXX: Twitter just sets a 404 header and doens't bother + // to return an err msg + + $deleted = Deleted_notice::staticGet($this->notice_id); + + if (!empty($deleted)) { + $this->clientError( + _('Status deleted.'), + 410, + $this->format + ); + } else { + $this->clientError( + _('No status with that ID found.'), + 404, + $this->format + ); + } + } + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this notice last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notice)) { + return strtotime($this->notice->created); + } + + return null; + } + + /** + * An entity tag for this notice + * + * Returns an Etag based on the action name, language, and + * timestamps of the notice + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notice)) { + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->notice->id, + strtotime($this->notice->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php new file mode 100644 index 000000000..fb1278559 --- /dev/null +++ b/actions/apistatusesupdate.php @@ -0,0 +1,240 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Post a notice (update your status) through the API + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apiauth.php'; + +/** + * Updates the authenticating user's status (posts a notice). + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiStatusesUpdateAction extends ApiAuthAction +{ + + var $user = null; + var $source = null; + var $status = null; + var $in_reply_to_status_id = null; + var $format = null; + + static $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return false; + } + } + + $this->user = $this->auth_user; + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return false; + } + + $this->status = $this->trimmed('status'); + + if (empty($this->status)) { + $this->clientError( + 'Client must provide a \'status\' parameter with a value.', + 400, + $this->format + ); + + return false; + } + + $this->source = $this->trimmed('source'); + + if (empty($this->source) || in_array($source, $this->reserved_sources)) { + $this->source = 'api'; + } + + $this->format = $this->arg('format'); + + $this->in_reply_to_status_id + = intval($this->trimmed('in_reply_to_status_id')); + + return true; + } + + /** + * Handle the request + * + * Make a new notice for the update, save it, and show it + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, $this->format + ); + return; + } + + $status_shortened = common_shorten_links($this->status); + + if (Notice::contentTooLong($status_shortened)) { + + // Note: Twitter truncates anything over 140, flags the status + // as "truncated." + + $this->clientError( + sprintf( + _('That\'s too long. Max notice size is %d chars.'), + Notice::maxContent() + ), + 406, + $this->format + ); + + return; + } + + // Check for commands + + $inter = new CommandInterpreter(); + $cmd = $inter->handle_command($this->user, $status_shortened); + + if ($cmd) { + + if ($this->supported($cmd)) { + $cmd->execute(new Channel()); + } + + // Cmd not supported? Twitter just returns your latest status. + // And, it returns your last status whether the cmd was successful + // or not! + + $this->notice = $this->user->getCurrentNotice(); + + } else { + + $reply_to = null; + + if (!empty($this->in_reply_to_status_id)) { + + // Check whether notice actually exists + + $reply = Notice::staticGet($this->in_reply_to_status_id); + + if ($reply) { + $reply_to = $this->in_reply_to_status_id; + } else { + $this->clientError( + _('Not found'), + $code = 404, + $this->format + ); + return; + } + } + + $this->notice = Notice::saveNew( + $this->user->id, + html_entity_decode($this->status, ENT_NOQUOTES, 'UTF-8'), + $this->source, + 1, + $reply_to + ); + + common_broadcast_notice($this->notice); + } + + $this->showNotice(); + } + + /** + * Show the resulting notice + * + * @return void + */ + + function showNotice() + { + if (!empty($this->notice)) { + if ($this->format == 'xml') { + $this->show_single_xml_status($this->notice); + } elseif ($this->format == 'json') { + $this->show_single_json_status($this->notice); + } + } + } + + /** + * Is this command supported when doing an update from the API? + * + * @param string $cmd the command to check for + * + * @return boolean true or false + */ + + function supported($cmd) + { + static $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand', + 'FavCommand', 'OnCommand', 'OffCommand'); + + if (in_array(get_class($cmd), $cmdlist)) { + return true; + } + + return false; + } + +} diff --git a/actions/apisubscriptions.php b/actions/apisubscriptions.php new file mode 100644 index 000000000..78dcd722d --- /dev/null +++ b/actions/apisubscriptions.php @@ -0,0 +1,275 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Base class for showing subscription information in the API + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apibareauth.php'; + +/** + * This class outputs a list of profiles as Twitter-style user and status objects. + * It is used by the API methods /api/statuses/(friends|followers). To support the + * social graph methods it also can output a simple list of IDs. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiSubscriptionsAction extends ApiBareAuthAction +{ + + var $page = null; + var $count = null; + var $user = null; + var $profiles = null; + var $format = null; + var $tag = null; + var $lite = null; + var $ids_only = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->page = (int)$this->arg('page', 1); + $this->tag = $this->arg('tag'); + $this->format = $this->arg('format'); + + // Note: Twitter no longer supports 'lite' + $this->lite = $this->arg('lite'); + + $this->ids_only = $this->arg('ids_only'); + + // If called as a social graph method, show 5000 per page, otherwise 100 + + $this->count = isset($this->ids_only) ? + 5000 : (int)$this->arg('count', 100); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return false; + } + } + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return false; + } + + $this->profiles = $this->getProfiles(); + + return true; + } + + /** + * Handle the request + * + * Show the profiles + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + $this->init_document($this->format); + + if (isset($this->ids_only)) { + $this->showIds(); + } else { + $this->showProfiles(isset($this->lite) ? false : true); + } + + $this->end_document($this->format); + } + + /** + * Get profiles - should get overrrided + * + * @return array Profiles + */ + + function getProfiles() + { + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest profile in the stream + */ + + function lastModified() + { + if (!empty($this->profiles) && (count($this->profiles) > 0)) { + return strtotime($this->profiles[0]->created); + } + + return null; + } + + /** + * An entity tag for this action + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last profiles in the subscriptions list + * There's also an indicator to show whether this action is being called + * as /api/statuses/(friends|followers) or /api/(friends|followers)/ids + * + * @return string etag + */ + + function etag() + { + if (!empty($this->profiles) && (count($this->profiles) > 0)) { + + $last = count($this->profiles) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + isset($this->ids_only) ? 'IDs' : 'Profiles', + strtotime($this->profiles[0]->created), + strtotime($this->profiles[$last]->created)) + ) + . '"'; + } + + return null; + } + + /** + * Show the profiles as Twitter-style useres and statuses + * + * @param boolean $include_statuses Whether to include the latest status + * with each user. Default true. + * + * @return void + */ + + function showProfiles($include_statuses = true) + { + switch ($this->format) { + case 'xml': + $this->elementStart('users', array('type' => 'array')); + foreach ($this->profiles as $profile) { + $this->show_profile( + $profile, + $this->format, + null, + $include_statuses + ); + } + $this->elementEnd('users'); + break; + case 'json': + $arrays = array(); + foreach ($this->profiles as $profile) { + $arrays[] = $this->twitter_user_array( + $profile, + $include_statuses + ); + } + print json_encode($arrays); + break; + default: + $this->clientError(_('Unsupported format.')); + break; + } + } + + /** + * Show the IDs of the profiles only. 5000 per page. To support + * the 'social graph' methods: /api/(friends|followers)/ids + * + * @return void + */ + + function showIds() + { + switch ($this->format) { + case 'xml': + $this->elementStart('ids'); + foreach ($this->profiles as $profile) { + $this->element('id', null, $profile->id); + } + $this->elementEnd('ids'); + break; + case 'json': + $ids = array(); + foreach ($this->profiles as $profile) { + $ids[] = (int)$profile->id; + } + print json_encode($ids); + break; + default: + $this->clientError(_('Unsupported format.')); + break; + } + } + +} diff --git a/actions/apitimelinefriends.php b/actions/apitimelinefriends.php new file mode 100644 index 000000000..65bbb5a74 --- /dev/null +++ b/actions/apitimelinefriends.php @@ -0,0 +1,255 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show the friends timeline + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apibareauth.php'; + +/** + * Returns the most recent notices (default 20) posted by the target user. + * This is the equivalent of 'You and friends' page accessed via Web. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineFriendsAction extends ApiBareAuthAction +{ + + var $user = null; + var $notices = null; + var $count = null; + var $max_id = null; + var $since_id = null; + var $since = null; + var $format = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->page = (int)$this->arg('page', 1); + $this->count = (int)$this->arg('count', 20); + $this->max_id = (int)$this->arg('max_id', 0); + $this->since_id = (int)$this->arg('since_id', 0); + $this->since = $this->arg('since'); + $this->format = $this->arg('format'); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return; + } + } + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->format); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $profile = $this->user->getProfile(); + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s and friends"), $this->user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:FriendsTimeline:" . $this->user->id; + $link = common_local_url( + 'all', array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('Updates from %1$s and friends on %2$s!'), + $this->user->nickname, $sitename + ); + + switch($this->format) { + case 'xml': + $this->show_xml_timeline($this->notices); + break; + case 'rss': + $this->show_rss_timeline($this->notices, $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( + $this->notices, $title, $id, $link, + $subtitle, null, $selfuri + ); + break; + case 'json': + $this->show_json_timeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + if (!empty($this->auth_user) && $this->auth_user->id == $this->user->id) { + $notice = $this->user->noticeInbox( + ($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since + ); + } else { + $notice = $this->user->noticesWithFriends( + ($this->page-1) * $this->count, + $this->count, $this->since_id, + $this->max_id, $this->since + ); + } + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelinementions.php b/actions/apitimelinementions.php new file mode 100644 index 000000000..93c6da307 --- /dev/null +++ b/actions/apitimelinementions.php @@ -0,0 +1,234 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show notices mentioning a user (@nickname) + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apibareauth.php'; + +/** + * Returns the most recent (default 20) mentions (status containing @nickname) + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineMentionsAction extends ApiBareAuthAction +{ + + var $user = null; + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->page = (int)$this->arg('page', 1); + $this->count = (int)$this->arg('count', 20); + $this->max_id = (int)$this->arg('max_id', 0); + $this->since_id = (int)$this->arg('since_id', 0); + $this->since = $this->arg('since'); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return; + } + } + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->arg('format')); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $profile = $this->user->getProfile(); + + $sitename = common_config('site', 'name'); + $title = sprintf( + _('%1$s / Updates mentioning %2$s'), + $sitename, $this->user->nickname + ); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:Mentions:" . $this->user->id; + $link = common_local_url( + 'replies', + array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('%1$s updates that reply to updates from %2$s / %3$s.'), + $sitename, $this->user->nickname, $profile->getBestName() + ); + + switch($this->arg('format')) { + case 'xml': + $this->show_xml_timeline($this->notices); + break; + case 'rss': + $this->show_rss_timeline($this->notices, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . + ltrim($_SERVER['QUERY_STRING'], 'p='); + $this->show_atom_timeline( + $this->notices, $title, $id, $link, $subtitle, + null, $selfuri + ); + break; + case 'json': + $this->show_json_timeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + $notice = $this->user->getReplies( + ($this->page - 1) * $this->count, $this->count, + $this->since_id, $this->max_id, $this->since + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelinepublic.php b/actions/apitimelinepublic.php new file mode 100644 index 000000000..10bde6f37 --- /dev/null +++ b/actions/apitimelinepublic.php @@ -0,0 +1,207 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show the public timeline + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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'; + +/** + * Returns the most recent notices (default 20) posted by everybody + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelinePublicAction extends TwitterapiAction +{ + + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->page = (int)$this->arg('page', 1); + $this->count = (int)$this->arg('count', 20); + $this->max_id = (int)$this->arg('max_id', 0); + $this->since_id = (int)$this->arg('since_id', 0); + $this->since = $this->arg('since'); + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s public timeline"), $sitename); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:PublicTimeline"; + $link = common_root_url(); + $subtitle = sprintf(_("%s updates from everyone!"), $sitename); + + switch($this->arg('format')) { + case 'xml': + $this->show_xml_timeline($this->notices); + break; + case 'rss': + $this->show_rss_timeline($this->notices, $title, $link, $subtitle); + break; + case 'atom': + $selfuri = common_root_url() . 'api/statuses/public_timeline.atom'; + $this->show_atom_timeline( + $this->notices, $title, $id, $link, + $subtitle, null, $selfuri + ); + break; + case 'json': + $this->show_json_timeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + $notice = Notice::publicStream( + ($this->page - 1) * $this->count, $this->count, $this->since_id, + $this->max_id, $this->since + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apitimelineuser.php b/actions/apitimelineuser.php new file mode 100644 index 000000000..c4d02bc62 --- /dev/null +++ b/actions/apitimelineuser.php @@ -0,0 +1,249 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's timeline + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apibareauth.php'; + +/** + * Returns the most recent notices (default 20) posted by the authenticating + * user. Another user's timeline can be requested via the id parameter. This + * is the API equivalent of the user profile web page. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiTimelineUserAction extends ApiBareAuthAction +{ + + var $user = null; + var $notices = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->page = (int)$this->arg('page', 1); + $this->count = (int)$this->arg('count', 20); + $this->max_id = (int)$this->arg('max_id', 0); + $this->since_id = (int)$this->arg('since_id', 0); + $this->since = $this->arg('since'); + + if ($this->requiresAuth()) { + if ($this->checkBasicAuthUser() == false) { + return; + } + } + + $this->user = $this->getTargetUser($this->arg('id')); + + if (empty($this->user)) { + $this->clientError(_('No such user!'), 404, $this->arg('format')); + return; + } + + $this->notices = $this->getNotices(); + + return true; + } + + /** + * Handle the request + * + * Just show the notices + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + $this->showTimeline(); + } + + /** + * Show the timeline of notices + * + * @return void + */ + + function showTimeline() + { + $profile = $this->user->getProfile(); + + $sitename = common_config('site', 'name'); + $title = sprintf(_("%s timeline"), $this->user->nickname); + $taguribase = common_config('integration', 'taguri'); + $id = "tag:$taguribase:UserTimeline:" . $this->user->id; + $link = common_local_url( + 'showstream', + array('nickname' => $this->user->nickname) + ); + $subtitle = sprintf( + _('Updates from %1$s on %2$s!'), + $this->user->nickname, $sitename + ); + + // FriendFeed's SUP protocol + // Also added RSS and Atom feeds + + $suplink = common_local_url('sup', null, null, $this->user->id); + header('X-SUP-ID: ' . $suplink); + + switch($this->arg('format')) { + case 'xml': + $this->show_xml_timeline($this->notices); + break; + case 'rss': + $this->show_rss_timeline( + $this->notices, $title, $link, + $subtitle, $suplink + ); + break; + case 'atom': + if (isset($apidata['api_arg'])) { + $selfuri = common_root_url() . + 'api/statuses/user_timeline/' . + $apidata['api_arg'] . '.atom'; + } else { + $selfuri = common_root_url() . + 'api/statuses/user_timeline.atom'; + } + $this->show_atom_timeline( + $this->notices, $title, $id, $link, + $subtitle, $suplink, $selfuri + ); + break; + case 'json': + $this->show_json_timeline($this->notices); + break; + default: + $this->clientError(_('API method not found!'), $code = 404); + break; + } + + } + + /** + * Get notices + * + * @return array notices + */ + + function getNotices() + { + $notices = array(); + + $notice = $this->user->getNotices( + ($this->page-1) * $this->count, $this->count, + $this->since_id, $this->max_id, $this->since + ); + + while ($notice->fetch()) { + $notices[] = clone($notice); + } + + return $notices; + } + + /** + * Is this action read only? + * + * @param array $args other arguments + * + * @return boolean true + */ + + function isReadOnly($args) + { + return true; + } + + /** + * When was this feed last modified? + * + * @return string datestamp of the latest notice in the stream + */ + + function lastModified() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + return strtotime($this->notices[0]->created); + } + + return null; + } + + /** + * An entity tag for this stream + * + * Returns an Etag based on the action name, language, user ID, and + * timestamps of the first and last notice in the timeline + * + * @return string etag + */ + + function etag() + { + if (!empty($this->notices) && (count($this->notices) > 0)) { + + $last = count($this->notices) - 1; + + return '"' . implode( + ':', + array($this->arg('action'), + common_language(), + $this->user->id, + strtotime($this->notices[0]->created), + strtotime($this->notices[$last]->created)) + ) + . '"'; + } + + return null; + } + +} diff --git a/actions/apiuserfollowers.php b/actions/apiuserfollowers.php new file mode 100644 index 000000000..5c0243449 --- /dev/null +++ b/actions/apiuserfollowers.php @@ -0,0 +1,85 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's followers (subscribers) + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apibareauth.php'; + +/** + * Ouputs the authenticating user's followers (subscribers), each with + * current Twitter-style status inline. They are ordered by the order + * in which they subscribed to the user, 100 at a time. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiUserFollowersAction extends ApiSubscriptionsAction +{ + /** + * Get the user's subscribers (followers) as an array of profiles + * + * @return array Profiles + */ + + function getProfiles() + { + $offset = ($this->page - 1) * $this->count; + $limit = $this->count + 1; + + $subs = null; + + if (isset($this->tag)) { + $subs = $this->user->getTaggedSubscribers( + $this->tag, $offset, $limit + ); + } else { + $subs = $this->user->getSubscribers( + $offset, + $limit + ); + } + + $profiles = array(); + + if (!empty($subs)) { + while ($subs->fetch()) { + $profiles[] = clone($subs); + } + } + + return $profiles; + } + +} diff --git a/actions/apiuserfriends.php b/actions/apiuserfriends.php new file mode 100644 index 000000000..8a42e36b9 --- /dev/null +++ b/actions/apiuserfriends.php @@ -0,0 +1,85 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's friends (subscriptions) + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apibareauth.php'; + +/** + * Ouputs the authenticating user's friends (subscriptions), each with + * current Twitter-style status inline. They are ordered by the date + * in which the user subscribed to them, 100 at a time. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiUserFriendsAction extends ApiSubscriptionsAction +{ + /** + * Get the user's subscriptions (friends) as an array of profiles + * + * @return array Profiles + */ + + function getProfiles() + { + $offset = ($this->page - 1) * $this->count; + $limit = $this->count + 1; + + $subs = null; + + if (isset($this->tag)) { + $subs = $this->user->getTaggedSubscriptions( + $this->tag, $offset, $limit + ); + } else { + $subs = $this->user->getSubscriptions( + $offset, + $limit + ); + } + + $profiles = array(); + + if (!empty($subs)) { + while ($subs->fetch()) { + $profiles[] = clone($subs); + } + } + + return $profiles; + } + +} diff --git a/actions/apiusershow.php b/actions/apiusershow.php new file mode 100644 index 000000000..2e2ceab41 --- /dev/null +++ b/actions/apiusershow.php @@ -0,0 +1,126 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Show a user's profile information + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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'; + +/** + * Ouputs information for a user, specified by ID or screen name. + * The user's most recent status will be returned inline. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiUserShowAction extends TwitterApiAction +{ + + var $format = null; + var $user = null; + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->format = $this->arg('format'); + + $email = $this->arg('email'); + + // XXX: email field deprecated in Twitter's API + + if (!empty($email)) { + $this->user = User::staticGet('email', $email); + } else { + $this->user = $this->getTargetUser($this->arg('id')); + } + + return true; + } + + /** + * Handle the request + * + * Check the format and show the user info + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if (empty($this->user)) { + $this->clientError(_('Not found.'), 404, $this->format); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError(_('API method not found!'), $code = 404); + return; + } + + $profile = $this->user->getProfile(); + + if (empty($profile)) { + $this->clientError(_('User has no profile.')); + return; + } + + $twitter_user = $this->twitter_user_array($this->user->getProfile(), true); + + if ($this->format == 'xml') { + $this->init_document('xml'); + $this->show_twitter_xml_user($twitter_user); + $this->end_document('xml'); + } elseif ($this->format == 'json') { + $this->init_document('json'); + $this->show_json_objects($twitter_user); + $this->end_document('json'); + } + + } + +} diff --git a/actions/twitapidirect_messages.php b/actions/twitapidirect_messages.php deleted file mode 100644 index 08b8f4e9c..000000000 --- a/actions/twitapidirect_messages.php +++ /dev/null @@ -1,305 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class Twitapidirect_messagesAction extends TwitterapiAction -{ - - function direct_messages($args, $apidata) - { - parent::handle($args); - return $this->show_messages($args, $apidata, 'received'); - } - - function sent($args, $apidata) - { - parent::handle($args); - return $this->show_messages($args, $apidata, 'sent'); - } - - function show_messages($args, $apidata, $type) - { - $user = $apidata['user']; // Always the auth user - - $message = new Message(); - $title = null; - $subtitle = null; - $link = null; - $server = common_root_url(); - - if ($type == 'received') { - $message->to_profile = $user->id; - $title = sprintf(_("Direct messages to %s"), $user->nickname); - $subtitle = sprintf(_("All the direct messages sent to %s"), - $user->nickname); - $link = $server . $user->nickname . '/inbox'; - } else { - $message->from_profile = $user->id; - $title = _('Direct Messages You\'ve Sent'); - $subtitle = sprintf(_("All the direct messages sent from %s"), - $user->nickname); - $link = $server . $user->nickname . '/outbox'; - } - - $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 ($max_id) { - $message->whereAdd("id <= $max_id"); - } - - if ($since_id) { - $message->whereAdd("id > $since_id"); - } - - if ($since) { - $d = date('Y-m-d H:i:s', $since); - $message->whereAdd("created > '$d'"); - } - - $message->orderBy('created DESC, id DESC'); - $message->limit((($page-1)*$count), $count); - $message->find(); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_dmsgs($message); - break; - case 'rss': - $this->show_rss_dmsgs($message, $title, $link, $subtitle); - break; - case 'atom': - $selfuri = common_root_url() . 'api/direct_messages'; - $selfuri .= ($type == 'received') ? '.atom' : '/sent.atom'; - $taguribase = common_config('integration', 'taguri'); - - if ($type == 'sent') { - $id = "tag:$taguribase:SentDirectMessages:" . $user->id; - } else { - $id = "tag:$taguribase:DirectMessages:" . $user->id; - } - - $this->show_atom_dmsgs($message, $title, $link, $subtitle, - $selfuri, $id); - break; - case 'json': - $this->show_json_dmsgs($message); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - - } - - // had to change this from "new" to "create" to avoid PHP reserved word - function create($args, $apidata) - { - parent::handle($args); - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError(_('This method requires a POST.'), - 400, $apidata['content-type']); - return; - } - - $user = $apidata['user']; - $source = $this->trimmed('source'); // Not supported by Twitter. - - $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); - if (empty($source) || in_array($source, $reserved_sources)) { - $source = 'api'; - } - - $content = $this->trimmed('text'); - - if (empty($content)) { - $this->clientError(_('No message text!'), - $code = 406, $apidata['content-type']); - } else { - $content_shortened = common_shorten_links($content); - if (Message::contentTooLong($content_shortened)) { - $this->clientError(sprintf(_('That\'s too long. Max message size is %d chars.'), - Message::maxContent()), - $code = 406, $apidata['content-type']); - return; - } - } - - $other = $this->get_user($this->trimmed('user')); - - if (empty($other)) { - $this->clientError(_('Recipient user not found.'), - $code = 403, $apidata['content-type']); - return; - } else if (!$user->mutuallySubscribed($other)) { - $this->clientError(_('Can\'t send direct messages to users who aren\'t your friend.'), - $code = 403, $apidata['content-type']); - return; - } else if ($user->id == $other->id) { - // Sending msgs to yourself is allowed by Twitter - $this->clientError(_('Don\'t send a message to yourself; just say it to yourself quietly instead.'), - $code = 403, $apidata['content-type']); - return; - } - - $message = Message::saveNew($user->id, $other->id, - html_entity_decode($content, ENT_NOQUOTES, 'UTF-8'), $source); - - if (is_string($message)) { - $this->serverError($message); - return; - } - - $this->notify($user, $other, $message); - - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_dmsg($message); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_dmsg($message); - } - - } - - function destroy($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - - function show_xml_dmsgs($message) - { - - $this->init_document('xml'); - $this->elementStart('direct-messages', array('type' => 'array')); - - if (is_array($message)) { - foreach ($message as $m) { - $twitter_dm = $this->twitter_dmsg_array($m); - $this->show_twitter_xml_dmsg($twitter_dm); - } - } else { - while ($message->fetch()) { - $twitter_dm = $this->twitter_dmsg_array($message); - $this->show_twitter_xml_dmsg($twitter_dm); - } - } - - $this->elementEnd('direct-messages'); - $this->end_document('xml'); - - } - - function show_json_dmsgs($message) - { - - $this->init_document('json'); - - $dmsgs = array(); - - if (is_array($message)) { - foreach ($message as $m) { - $twitter_dm = $this->twitter_dmsg_array($m); - array_push($dmsgs, $twitter_dm); - } - } else { - while ($message->fetch()) { - $twitter_dm = $this->twitter_dmsg_array($message); - array_push($dmsgs, $twitter_dm); - } - } - - $this->show_json_objects($dmsgs); - $this->end_document('json'); - - } - - function show_rss_dmsgs($message, $title, $link, $subtitle) - { - - $this->init_document('rss'); - - $this->elementStart('channel'); - $this->element('title', null, $title); - - $this->element('link', null, $link); - $this->element('description', null, $subtitle); - $this->element('language', null, 'en-us'); - $this->element('ttl', null, '40'); - - if (is_array($message)) { - foreach ($message as $m) { - $entry = $this->twitter_rss_dmsg_array($m); - $this->show_twitter_rss_item($entry); - } - } else { - while ($message->fetch()) { - $entry = $this->twitter_rss_dmsg_array($message); - $this->show_twitter_rss_item($entry); - } - } - - $this->elementEnd('channel'); - $this->end_twitter_rss(); - - } - - function show_atom_dmsgs($message, $title, $link, $subtitle, $selfuri, $id) - { - - $this->init_document('atom'); - - $this->element('title', null, $title); - $this->element('id', null, $id); - $this->element('link', array('href' => $link, 'rel' => 'alternate', 'type' => 'text/html'), null); - $this->element('link', array('href' => $selfuri, 'rel' => 'self', - 'type' => 'application/atom+xml'), null); - $this->element('updated', null, common_date_iso8601('now')); - $this->element('subtitle', null, $subtitle); - - if (is_array($message)) { - foreach ($message as $m) { - $entry = $this->twitter_rss_dmsg_array($m); - $this->show_twitter_atom_entry($entry); - } - } else { - while ($message->fetch()) { - $entry = $this->twitter_rss_dmsg_array($message); - $this->show_twitter_atom_entry($entry); - } - } - - $this->end_document('atom'); - } - - // swiped from MessageAction. Should it be place in util.php? - function notify($from, $to, $message) - { - mail_notify_message($message, $from, $to); - # XXX: Jabber, SMS notifications... probably queued - } - -} diff --git a/actions/twitapifriendships.php b/actions/twitapifriendships.php deleted file mode 100644 index eea8945c3..000000000 --- a/actions/twitapifriendships.php +++ /dev/null @@ -1,250 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapifriendshipsAction extends TwitterapiAction -{ - - function create($args, $apidata) - { - parent::handle($args); - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError(_('This method requires a POST.'), - 400, $apidata['content-type']); - return; - } - - $id = $apidata['api_arg']; - $other = $this->get_user($id); - - if (empty($other)) { - $this->clientError(_('Could not follow user: User not found.'), - 403, $apidata['content-type']); - return; - } - - $user = $apidata['user']; - - if ($user->isSubscribed($other)) { - $errmsg = sprintf(_('Could not follow user: %s is already on your list.'), - $other->nickname); - $this->clientError($errmsg, 403, $apidata['content-type']); - return; - } - - $sub = new Subscription(); - - $sub->query('BEGIN'); - - $sub->subscriber = $user->id; - $sub->subscribed = $other->id; - $sub->created = DB_DataObject_Cast::dateTime(); # current time - - $result = $sub->insert(); - - if (empty($result)) { - $errmsg = sprintf(_('Could not follow user: %s is already on your list.'), - $other->nickname); - $this->clientError($errmsg, 400, $apidata['content-type']); - return; - } - - $sub->query('COMMIT'); - - mail_subscribe_notify($other, $user); - - $type = $apidata['content-type']; - $this->init_document($type); - $this->show_profile($other, $type); - $this->end_document($type); - - } - - function destroy($args, $apidata) - { - parent::handle($args); - - if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { - $this->clientError(_('This method requires a POST or DELETE.'), - 400, $apidata['content-type']); - return; - } - - $id = $apidata['api_arg']; - - # We can't subscribe to a remote person, but we can unsub - - $other = $this->get_profile($id); - $user = $apidata['user']; // Alwyas the auth user - - if ($user->id == $other->id) { - $this->clientError(_("You cannot unfollow yourself!"), - 403, $apidata['content-type']); - return; - } - - $sub = new Subscription(); - $sub->subscriber = $user->id; - $sub->subscribed = $other->id; - - if ($sub->find(true)) { - $sub->query('BEGIN'); - $sub->delete(); - $sub->query('COMMIT'); - } else { - $this->clientError(_('You are not friends with the specified user.'), - 403, $apidata['content-type']); - return; - } - - $type = $apidata['content-type']; - $this->init_document($type); - $this->show_profile($other, $type); - $this->end_document($type); - - } - - function exists($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - $user_a_id = $this->trimmed('user_a'); - $user_b_id = $this->trimmed('user_b'); - - $user_a = $this->get_user($user_a_id); - $user_b = $this->get_user($user_b_id); - - if (empty($user_a) || empty($user_b)) { - $this->clientError(_('Two user ids or screen_names must be supplied.'), - 400, $apidata['content-type']); - return; - } - - $result = $user_a->isSubscribed($user_b); - - switch ($apidata['content-type']) { - case 'xml': - $this->init_document('xml'); - $this->element('friends', null, $result); - $this->end_document('xml'); - break; - case 'json': - $this->init_document('json'); - print json_encode($result); - $this->end_document('json'); - break; - default: - break; - } - - } - - function show($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - $source_id = (int)$this->trimmed('source_id'); - $source_screen_name = $this->trimmed('source_screen_name'); - - // If the source is not specified for an unauthenticated request, - // the method will return an HTTP 403. - - if (empty($source_id) && empty($source_screen_name)) { - if (empty($apidata['user'])) { - $this->clientError(_('Could not determine source user.'), - $code = 403); - return; - } - } - - $source = null; - - if (!empty($source_id)) { - $source = User::staticGet($source_id); - } elseif (!empty($source_screen_name)) { - $source = User::staticGet('nickname', $source_screen_name); - } else { - $source = $apidata['user']; - } - - // If a source or target is specified but does not exist, - // the method will return an HTTP 404. - - if (empty($source)) { - $this->clientError(_('Could not determine source user.'), - $code = 404); - return; - } - - $target_id = (int)$this->trimmed('target_id'); - $target_screen_name = $this->trimmed('target_screen_name'); - - $target = null; - - if (!empty($target_id)) { - $target = User::staticGet($target_id); - } elseif (!empty($target_screen_name)) { - $target = User::staticGet('nickname', $target_screen_name); - } else { - $this->clientError(_('Target user not specified.'), - $code = 403); - return; - } - - if (empty($target)) { - $this->clientError(_('Could not find target user.'), - $code = 404); - return; - } - - $result = $this->twitter_relationship_array($source, $target); - - switch ($apidata['content-type']) { - case 'xml': - $this->init_document('xml'); - $this->show_twitter_xml_relationship($result[relationship]); - $this->end_document('xml'); - break; - case 'json': - $this->init_document('json'); - print json_encode($result); - $this->end_document('json'); - break; - default: - break; - } - } - -} diff --git a/actions/twitapistatuses.php b/actions/twitapistatuses.php deleted file mode 100644 index 87043b182..000000000 --- a/actions/twitapistatuses.php +++ /dev/null @@ -1,606 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapistatusesAction extends TwitterapiAction -{ - - function public_timeline($args, $apidata) - { - // XXX: To really live up to the spec we need to build a list - // of notices by users who have custom avatars, so fix this SQL -- Zach - - parent::handle($args); - - $sitename = common_config('site', 'name'); - $title = sprintf(_("%s public timeline"), $sitename); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:PublicTimeline"; - $link = common_root_url(); - $subtitle = sprintf(_("%s updates from everyone!"), $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'); - - $notice = Notice::publicStream(($page-1)*$count, $count, $since_id, - $max_id, $since); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - $selfuri = common_root_url() . 'api/statuses/public_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); - break; - } - - } - - function friends_timeline($args, $apidata) - { - parent::handle($args); - - $this->auth_user = $apidata['user']; - $user = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($user)) { - $this->clientError(_('No such user!'), 404, - $apidata['content-type']); - 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($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - if (isset($apidata['api_arg'])) { - $selfuri = common_root_url() . - 'api/statuses/friends_timeline/' . - $apidata['api_arg'] . '.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 home_timeline($args, $apidata) - { - call_user_func(array($this, 'friends_timeline'), $args, $apidata); - } - - function user_timeline($args, $apidata) - { - parent::handle($args); - - $this->auth_user = $apidata['user']; - $user = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($user)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - $sitename = common_config('site', 'name'); - $title = sprintf(_("%s timeline"), $user->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:UserTimeline:".$user->id; - $link = common_local_url('showstream', - array('nickname' => $user->nickname)); - $subtitle = sprintf(_('Updates from %1$s on %2$s!'), - $user->nickname, $sitename); - - # FriendFeed's SUP protocol - # Also added RSS and Atom feeds - - $suplink = common_local_url('sup', null, null, $user->id); - header('X-SUP-ID: '.$suplink); - - $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'); - - $notice = $user->getNotices(($page-1)*$count, - $count, $since_id, $max_id, $since); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, - $subtitle, $suplink); - break; - case 'atom': - if (isset($apidata['api_arg'])) { - $selfuri = common_root_url() . - 'api/statuses/user_timeline/' . - $apidata['api_arg'] . '.atom'; - } else { - $selfuri = common_root_url() . - 'api/statuses/user_timeline.atom'; - } - $this->show_atom_timeline($notice, $title, $id, $link, - $subtitle, $suplink, $selfuri); - break; - case 'json': - $this->show_json_timeline($notice); - break; - default: - $this->clientError(_('API method not found!'), $code = 404); - } - - } - - function update($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - $this->clientError(_('This method requires a POST.'), - 400, $apidata['content-type']); - return; - } - - $user = $apidata['user']; // Always the auth user - - $status = $this->trimmed('status'); - $source = $this->trimmed('source'); - $in_reply_to_status_id = - intval($this->trimmed('in_reply_to_status_id')); - $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); - - if (empty($source) || in_array($source, $reserved_sources)) { - $source = 'api'; - } - - if (empty($status)) { - - // XXX: Note: In this case, Twitter simply returns '200 OK' - // No error is given, but the status is not posted to the - // user's timeline. Seems bad. Shouldn't we throw an - // errror? -- Zach - return; - - } else { - - $status_shortened = common_shorten_links($status); - - if (Notice::contentTooLong($status_shortened)) { - - // XXX: Twitter truncates anything over 140, flags the status - // as "truncated." Sending this error may screw up some clients - // that assume Twitter will truncate for them. Should we just - // truncate too? -- Zach - $this->clientError(sprintf(_('That\'s too long. Max notice size is %d chars.'), - Notice::maxContent()), - $code = 406, $apidata['content-type']); - return; - } - } - - // Check for commands - $inter = new CommandInterpreter(); - $cmd = $inter->handle_command($user, $status_shortened); - - if ($cmd) { - - if ($this->supported($cmd)) { - $cmd->execute(new Channel()); - } - - // cmd not supported? Twitter just returns your latest status. - // And, it returns your last status whether the cmd was successful - // or not! - $n = $user->getCurrentNotice(); - $apidata['api_arg'] = $n->id; - } else { - - $reply_to = null; - - if ($in_reply_to_status_id) { - - // check whether notice actually exists - $reply = Notice::staticGet($in_reply_to_status_id); - - if ($reply) { - $reply_to = $in_reply_to_status_id; - } else { - $this->clientError(_('Not found'), $code = 404, - $apidata['content-type']); - return; - } - } - - $notice = Notice::saveNew($user->id, - html_entity_decode($status, ENT_NOQUOTES, 'UTF-8'), - $source, 1, $reply_to); - - common_broadcast_notice($notice); - $apidata['api_arg'] = $notice->id; - } - - $this->show($args, $apidata); - } - - function mentions($args, $apidata) - { - parent::handle($args); - - $user = $this->get_user($apidata['api_arg'], $apidata); - $this->auth_user = $apidata['user']; - - if (empty($user)) { - $this->clientError(_('No such user!'), 404, - $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - $sitename = common_config('site', 'name'); - $title = sprintf(_('%1$s / Updates mentioning %2$s'), - $sitename, $user->nickname); - $taguribase = common_config('integration', 'taguri'); - $id = "tag:$taguribase:Mentions:".$user->id; - $link = common_local_url('replies', - array('nickname' => $user->nickname)); - $subtitle = sprintf(_('%1$s updates that reply to updates from %2$s / %3$s.'), - $sitename, $user->nickname, $profile->getBestName()); - - $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'); - - $notice = $user->getReplies(($page-1)*$count, - $count, $since_id, $max_id, $since); - - switch($apidata['content-type']) { - case 'xml': - $this->show_xml_timeline($notice); - break; - case 'rss': - $this->show_rss_timeline($notice, $title, $link, $subtitle); - break; - case 'atom': - $selfuri = common_root_url() . - ltrim($_SERVER['QUERY_STRING'], 'p='); - $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 replies($args, $apidata) - { - call_user_func(array($this, 'mentions'), $args, $apidata); - } - - function show($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - // 'id' is an undocumented parameter in Twitter's API. Several - // clients make use of it, so we support it too. - - // show.json?id=12345 takes precedence over /show/12345.json - - $this->auth_user = $apidata['user']; - $notice_id = $this->trimmed('id'); - - if (empty($notice_id)) { - $notice_id = $apidata['api_arg']; - } - - $notice = Notice::staticGet((int)$notice_id); - - if ($notice) { - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_status($notice); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_status($notice); - } - } else { - // XXX: Twitter just sets a 404 header and doens't bother - // to return an err msg - $deleted = Deleted_notice::staticGet($notice_id); - if (!empty($deleted)) { - $this->clientError(_('Status deleted.'), - 410, $apidata['content-type']); - } else { - $this->clientError(_('No status with that ID found.'), - 404, $apidata['content-type']); - } - } - } - - function destroy($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - // Check for RESTfulness - if (!in_array($_SERVER['REQUEST_METHOD'], array('POST', 'DELETE'))) { - // XXX: Twitter just prints the err msg, no XML / JSON. - $this->clientError(_('This method requires a POST or DELETE.'), - 400, $apidata['content-type']); - return; - } - - $user = $apidata['user']; // Always the auth user - $notice_id = $apidata['api_arg']; - $notice = Notice::staticGet($notice_id); - - if (empty($notice)) { - $this->clientError(_('No status found with that ID.'), - 404, $apidata['content-type']); - return; - } - - if ($user->id == $notice->profile_id) { - $replies = new Reply; - $replies->get('notice_id', $notice_id); - $replies->delete(); - $notice->delete(); - - if ($apidata['content-type'] == 'xml') { - $this->show_single_xml_status($notice); - } elseif ($apidata['content-type'] == 'json') { - $this->show_single_json_status($notice); - } - } else { - $this->clientError(_('You may not delete another user\'s status.'), - 403, $apidata['content-type']); - } - - } - - function friends($args, $apidata) - { - parent::handle($args); - $includeStatuses=! (boolean) $args['lite']; - return $this->subscriptions($apidata, 'subscribed', 'subscriber', false, $includeStatuses); - } - - function friendsIDs($args, $apidata) - { - parent::handle($args); - return $this->subscriptions($apidata, 'subscribed', 'subscriber', true); - } - - function followers($args, $apidata) - { - parent::handle($args); - $includeStatuses=! (boolean) $args['lite']; - return $this->subscriptions($apidata, 'subscriber', 'subscribed', false, $includeStatuses); - } - - function followersIDs($args, $apidata) - { - parent::handle($args); - return $this->subscriptions($apidata, 'subscriber', 'subscribed', true); - } - - function subscriptions($apidata, $other_attr, $user_attr, $onlyIDs=false, $includeStatuses=true) - { - $this->auth_user = $apidata['user']; - $user = $this->get_user($apidata['api_arg'], $apidata); - - if (empty($user)) { - $this->clientError('Not Found', 404, $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - $sub = new Subscription(); - $sub->$user_attr = $profile->id; - - $sub->orderBy('created DESC'); - - // Normally, page 100 friends at a time - - if (!$onlyIDs) { - $page = $this->arg('page', 1); - $count = $this->arg('count', 100); - $sub->limit(($page-1)*$count, $count); - } else { - - // If we're just looking at IDs, return - // ALL of them, unless the user specifies a page, - // in which case, return 500 per page. - - $page = $this->arg('page'); - if (!empty($page)) { - if ($page < 1) { - $page = 1; - } - $count = 500; - $sub->limit(($page-1)*$count, $count); - } - } - - $others = array(); - - if ($sub->find()) { - while ($sub->fetch()) { - $others[] = Profile::staticGet($sub->$other_attr); - } - } else { - // user has no followers - } - - $type = $apidata['content-type']; - - $this->init_document($type); - - if ($onlyIDs) { - $this->showIDs($others, $type); - } else { - $this->show_profiles($others, $type, $includeStatuses); - } - - $this->end_document($type); - } - - function show_profiles($profiles, $type, $includeStatuses) - { - switch ($type) { - case 'xml': - $this->elementStart('users', array('type' => 'array')); - foreach ($profiles as $profile) { - $this->show_profile($profile,$type,null,$includeStatuses); - } - $this->elementEnd('users'); - break; - case 'json': - $arrays = array(); - foreach ($profiles as $profile) { - $arrays[] = $this->twitter_user_array($profile, $includeStatuses); - } - print json_encode($arrays); - break; - default: - $this->clientError(_('unsupported file type')); - } - } - - function showIDs($profiles, $type) - { - switch ($type) { - case 'xml': - $this->elementStart('ids'); - foreach ($profiles as $profile) { - $this->element('id', null, $profile->id); - } - $this->elementEnd('ids'); - break; - case 'json': - $ids = array(); - foreach ($profiles as $profile) { - $ids[] = (int)$profile->id; - } - print json_encode($ids); - break; - default: - $this->clientError(_('unsupported file type')); - } - } - - function featured($args, $apidata) - { - parent::handle($args); - $this->serverError(_('API method under construction.'), $code=501); - } - - function supported($cmd) - { - $cmdlist = array('MessageCommand', 'SubCommand', 'UnsubCommand', - 'FavCommand', 'OnCommand', 'OffCommand'); - - if (in_array(get_class($cmd), $cmdlist)) { - return true; - } - - return false; - } - -} diff --git a/actions/twitapiusers.php b/actions/twitapiusers.php deleted file mode 100644 index 703fa6754..000000000 --- a/actions/twitapiusers.php +++ /dev/null @@ -1,80 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once(INSTALLDIR.'/lib/twitterapi.php'); - -class TwitapiusersAction extends TwitterapiAction -{ - - function show($args, $apidata) - { - parent::handle($args); - - if (!in_array($apidata['content-type'], array('xml', 'json'))) { - $this->clientError(_('API method not found!'), $code = 404); - return; - } - - $user = null; - $email = $this->arg('email'); - - // XXX: email field deprecated in Twitter's API - - if ($email) { - $user = User::staticGet('email', $email); - } else { - $user = $this->get_user($apidata['api_arg'], $apidata); - } - - if (empty($user)) { - $this->clientError(_('Not found.'), 404, $apidata['content-type']); - return; - } - - $profile = $user->getProfile(); - - if (!$profile) { - common_server_error(_('User has no profile.')); - return; - } - - $twitter_user = $this->twitter_user_array($user->getProfile(), true); - - if ($apidata['content-type'] == 'xml') { - $this->init_document('xml'); - $this->show_twitter_xml_user($twitter_user); - $this->end_document('xml'); - } elseif ($apidata['content-type'] == 'json') { - $this->init_document('json'); - $this->show_json_objects($twitter_user); - $this->end_document('json'); - } else { - - // This is in case 'show' was called via /account/verify_credentials - // without a format (xml or json). - header('Content-Type: text/html; charset=utf-8'); - print 'Authorized'; - } - - } -} diff --git a/lib/apiauth.php b/lib/apiauth.php new file mode 100644 index 000000000..f0b4b6bf7 --- /dev/null +++ b/lib/apiauth.php @@ -0,0 +1,174 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Base class for API actions that require authentication + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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'; + +/** + * Actions extending this class will require auth + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiAuthAction extends TwitterapiAction +{ + + var $auth_user = null; + + /** + * Does this API resource require authentication? + * + * @return boolean true + */ + + function requiresAuth() + { + return true; + } + + /** + * Check for a user specified via HTTP basic auth. If there isn't + * one, try to get one by outputting the basic auth header. + * + * @return boolean true or false + */ + + function checkBasicAuthUser() + { + $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; + } + + /** + * Read the HTTP headers and set the auth user. Decodes HTTP_AUTHORIZATION + * param to support basic auth when PHP is running in CGI mode. + * + * @return void + */ + + 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; + } + } + + /** + * Output an authentication error message. Use XML or JSON if one + * of those formats is specified, otherwise output plain text + * + * @return void + */ + + 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"; + } + } + +} diff --git a/lib/apibareauth.php b/lib/apibareauth.php new file mode 100644 index 000000000..a99d450ec --- /dev/null +++ b/lib/apibareauth.php @@ -0,0 +1,79 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Base class for API actions that require "bare auth". Bare auth means + * authentication is required only if the action is called without an argument + * or query param specifying user id. + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @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/apiauth.php'; + +/** + * Actions extending this class will require auth unless a target + * user ID has been specified + * + * @category API + * @package StatusNet + * @author Zach Copley <zach@status.net> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiBareAuthAction extends ApiAuthAction +{ + /** + * Does this API resource require authentication? + * + * @return boolean true or false + */ + + function requiresAuth() + { + // If the site is "private", all API methods except statusnet/config + // need authentication + + if (common_config('site', 'private')) { + return true; + } + + // check whether a user has been specified somehow + + $id = $this->arg('id'); + $user_id = $this->arg('user_id'); + $screen_name = $this->arg('screen_name'); + + if (empty($id) && empty($user_id) && empty($screen_name)) { + return true; + } + + return false; + } + +}
\ No newline at end of file diff --git a/lib/router.php b/lib/router.php index 91f886bce..5ee939657 100644 --- a/lib/router.php +++ b/lib/router.php @@ -270,22 +270,100 @@ class Router // statuses API - $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))?')); - - $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)')); + $m->connect('api/statuses/public_timeline.:format', + array('action' => 'ApiTimelinePublic', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends_timeline.:format', + array('action' => 'ApiTimelineFriends', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends_timeline/:id.:format', + array('action' => 'ApiTimelineFriends', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + $m->connect('api/statuses/home_timeline.:format', + array('action' => 'ApiTimelineFriends', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/home_timeline/:id.:format', + array('action' => 'ApiTimelineFriends', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/user_timeline.:format', + array('action' => 'ApiTimelineUser', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/user_timeline/:id.:format', + array('action' => 'ApiTimelineUser', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/mentions.:format', + array('action' => 'ApiTimelineMentions', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/mentions/:id.:format', + array('action' => 'ApiTimelineMentions', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/replies.:format', + array('action' => 'ApiTimelineMentions', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/replies/:id.:format', + array('action' => 'ApiTimelineMentions', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json|rss|atom)')); + + $m->connect('api/statuses/friends.:format', + array('action' => 'ApiUserFriends', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/friends/:id.:format', + array('action' => 'ApiUserFriends', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/followers.:format', + array('action' => 'ApiUserFollowers', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/followers/:id.:format', + array('action' => 'ApiUserFollowers', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/show.:format', + array('action' => 'ApiStatusesShow', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/show/:id.:format', + array('action' => 'ApiStatusesShow', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/update.:format', + array('action' => 'ApiStatusesUpdate', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/destroy.:format', + array('action' => 'ApiStatusesDestroy', + 'format' => '(xml|json)')); + + $m->connect('api/statuses/destroy/:id.:format', + array('action' => 'ApiStatusesDestroy', + 'id' => '[0-9]+', + 'format' => '(xml|json)')); // users - $m->connect('api/users/:method/:argument', - array('action' => 'api', - 'apiaction' => 'users'), - array('method' => 'show(\.(xml|json))?')); + $m->connect('api/users/show/:id.:format', + array('action' => 'ApiUserShow', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); $m->connect('api/users/:method', array('action' => 'api', @@ -294,68 +372,65 @@ class Router // direct messages - foreach (array('xml', 'json') as $e) { - $m->connect('api/direct_messages/new.'.$e, - array('action' => 'api', - 'apiaction' => 'direct_messages', - 'method' => 'create.'.$e)); - } - foreach (array('xml', 'json', 'rss', 'atom') as $e) { - $m->connect('api/direct_messages.'.$e, - array('action' => 'api', - 'apiaction' => 'direct_messages', - 'method' => 'direct_messages.'.$e)); - } + $m->connect('api/direct_messages.:format', + array('action' => 'ApiDirectMessage', + 'format' => '(xml|json|rss|atom)')); - foreach (array('xml', 'json', 'rss', 'atom') as $e) { - $m->connect('api/direct_messages/sent.'.$e, - array('action' => 'api', - 'apiaction' => 'direct_messages', - 'method' => 'sent.'.$e)); - } + $m->connect('api/direct_messages/sent.:format', + array('action' => 'ApiDirectMessage', + 'format' => '(xml|json|rss|atom)', + 'sent' => true)); - $m->connect('api/direct_messages/destroy/:argument', - array('action' => 'api', - 'apiaction' => 'direct_messages')); + $m->connect('api/direct_messages/new.:format', + array('action' => 'ApiDirectMessageNew', + 'format' => '(xml|json)')); // friendships - $m->connect('api/friendships/:method/:argument', - array('action' => 'api', - 'apiaction' => 'friendships'), - array('method' => '(create|destroy)')); + $m->connect('api/friendships/show.:format', + array('action' => 'ApiFriendshipsShow', + 'format' => '(xml|json)')); - $m->connect('api/friendships/:method', - array('action' => 'api', - 'apiaction' => 'friendships'), - array('method' => '(show|exists)(\.(xml|json))')); + $m->connect('api/friendships/exists.:format', + array('action' => 'ApiFriendshipsExists', + 'format' => '(xml|json)')); + + $m->connect('api/friendships/create.:format', + array('action' => 'ApiFriendshipsCreate', + 'format' => '(xml|json)')); + + $m->connect('api/friendships/destroy.:format', + array('action' => 'ApiFriendshipsDestroy', + 'format' => '(xml|json)')); + + $m->connect('api/friendships/create/:id.:format', + array('action' => 'ApiFriendshipsCreate', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + + $m->connect('api/friendships/destroy/:id.:format', + array('action' => 'ApiFriendshipsDestroy', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); // Social graph - $m->connect('api/friends/ids/:argument', - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'friendsIDs')); + $m->connect('api/friends/ids/:id.:format', + array('action' => 'apiFriends', + 'ids_only' => true)); - foreach (array('xml', 'json') as $e) { - $m->connect('api/friends/ids.'.$e, - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'friendsIDs.'.$e)); - } + $m->connect('api/followers/ids/:id.:format', + array('action' => 'apiFollowers', + 'ids_only' => true)); - $m->connect('api/followers/ids/:argument', - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'followersIDs')); + $m->connect('api/friends/ids.:format', + array('action' => 'apiFriends', + 'ids_only' => true)); - foreach (array('xml', 'json') as $e) { - $m->connect('api/followers/ids.'.$e, - array('action' => 'api', - 'apiaction' => 'statuses', - 'method' => 'followersIDs.'.$e)); - } + $m->connect('api/followers/ids.:format', + array('action' => 'apiFollowers', + 'ids_only' => true)); // account diff --git a/lib/twitterapi.php b/lib/twitterapi.php index 4a5de6ab3..b2104fddd 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -24,8 +24,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { class TwitterapiAction extends Action { - var $auth_user; - /** * Initialization. * @@ -324,51 +322,6 @@ class TwitterapiAction extends Action return $entry; } - function twitter_rss_dmsg_array($message) - { - - $entry = array(); - - $entry['title'] = sprintf('Message from %s to %s', - $message->getFrom()->nickname, $message->getTo()->nickname); - - $entry['content'] = common_xml_safe_str(trim($message->content)); - $entry['link'] = common_local_url('showmessage', array('message' => $message->id)); - $entry['published'] = common_date_iso8601($message->created); - - $taguribase = common_config('integration', 'taguri'); - - $entry['id'] = "tag:$taguribase,:$entry[link]"; - $entry['updated'] = $entry['published']; - $entry['author'] = $message->getFrom()->getBestName(); - - # RSS Item specific - $entry['description'] = $entry['content']; - $entry['pubDate'] = common_date_rfc2822($message->created); - $entry['guid'] = $entry['link']; - - return $entry; - } - - function twitter_dmsg_array($message) - { - $twitter_dm = array(); - - $from_profile = $message->getFrom(); - $to_profile = $message->getTo(); - - $twitter_dm['id'] = $message->id; - $twitter_dm['sender_id'] = $message->from_profile; - $twitter_dm['text'] = trim($message->content); - $twitter_dm['recipient_id'] = $message->to_profile; - $twitter_dm['created_at'] = $this->date_twitter($message->created); - $twitter_dm['sender_screen_name'] = $from_profile->nickname; - $twitter_dm['recipient_screen_name'] = $to_profile->nickname; - $twitter_dm['sender'] = $this->twitter_user_array($from_profile, false); - $twitter_dm['recipient'] = $this->twitter_user_array($to_profile, false); - - return $twitter_dm; - } function twitter_relationship_array($source, $target) { @@ -533,40 +486,6 @@ class TwitterapiAction extends Action $this->end_document('json'); } - function show_single_xml_dmsg($message) - { - $this->init_document('xml'); - $dmsg = $this->twitter_dmsg_array($message); - $this->show_twitter_xml_dmsg($dmsg); - $this->end_document('xml'); - } - - function show_single_json_dmsg($message) - { - $this->init_document('json'); - $dmsg = $this->twitter_dmsg_array($message); - $this->show_json_objects($dmsg); - $this->end_document('json'); - } - - function show_twitter_xml_dmsg($twitter_dm) - { - $this->elementStart('direct_message'); - foreach($twitter_dm as $element => $value) { - switch ($element) { - case 'sender': - case 'recipient': - $this->show_twitter_xml_user($value, $element); - break; - case 'text': - $this->element($element, null, common_xml_safe_str($value)); - break; - default: - $this->element($element, null, $value); - } - } - $this->elementEnd('direct_message'); - } function show_xml_timeline($notice) { @@ -687,6 +606,121 @@ class TwitterapiAction extends Action $this->end_twitter_rss(); } + + function showTwitterAtomEntry($entry) + { + $this->elementStart('entry'); + $this->element('title', null, $entry['title']); + $this->element('content', array('type' => 'html'), $entry['content']); + $this->element('id', null, $entry['id']); + $this->element('published', null, $entry['published']); + $this->element('updated', null, $entry['updated']); + $this->element('link', array('type' => 'text/html', + 'href' => $entry['link'], + 'rel' => 'alternate')); + $this->element('link', array('type' => $entry['avatar-type'], + 'href' => $entry['avatar'], + 'rel' => 'image')); + $this->elementStart('author'); + + $this->element('name', null, $entry['author-name']); + $this->element('uri', null, $entry['author-uri']); + + $this->elementEnd('author'); + $this->elementEnd('entry'); + } + + function showXmlDirectMessage($dm) + { + $this->elementStart('direct_message'); + foreach($dm as $element => $value) { + switch ($element) { + case 'sender': + case 'recipient': + $this->show_twitter_xml_user($value, $element); + break; + case 'text': + $this->element($element, null, common_xml_safe_str($value)); + break; + default: + $this->element($element, null, $value); + break; + } + } + $this->elementEnd('direct_message'); + } + + function directMessageArray($message) + { + $dmsg = array(); + + $from_profile = $message->getFrom(); + $to_profile = $message->getTo(); + + $dmsg['id'] = $message->id; + $dmsg['sender_id'] = $message->from_profile; + $dmsg['text'] = trim($message->content); + $dmsg['recipient_id'] = $message->to_profile; + $dmsg['created_at'] = $this->date_twitter($message->created); + $dmsg['sender_screen_name'] = $from_profile->nickname; + $dmsg['recipient_screen_name'] = $to_profile->nickname; + $dmsg['sender'] = $this->twitter_user_array($from_profile, false); + $dmsg['recipient'] = $this->twitter_user_array($to_profile, false); + + return $dmsg; + } + + function rssDirectMessageArray($message) + { + $entry = array(); + + $from = $message->getFrom(); + + $entry['title'] = sprintf('Message from %s to %s', + $from->nickname, $message->getTo()->nickname); + + $entry['content'] = common_xml_safe_str($message->rendered); + $entry['link'] = common_local_url('showmessage', array('message' => $message->id)); + $entry['published'] = common_date_iso8601($message->created); + + $taguribase = common_config('integration', 'taguri'); + + $entry['id'] = "tag:$taguribase:$entry[link]"; + $entry['updated'] = $entry['published']; + + $entry['author-name'] = $from->getBestName(); + $entry['author-uri'] = $from->homepage; + + $avatar = $from->getAvatar(AVATAR_STREAM_SIZE); + + $entry['avatar'] = (!empty($avatar)) ? $avatar->url : Avatar::defaultImage(AVATAR_STREAM_SIZE); + $entry['avatar-type'] = (!empty($avatar)) ? $avatar->mediatype : 'image/png'; + + // RSS item specific + + $entry['description'] = $entry['content']; + $entry['pubDate'] = common_date_rfc2822($message->created); + $entry['guid'] = $entry['link']; + + return $entry; + } + + function showSingleXmlDirectMessage($message) + { + $this->init_document('xml'); + $dmsg = $this->directMessageArray($message); + $this->showXmlDirectMessage($dmsg); + $this->end_document('xml'); + } + + function showSingleJsonDirectMessage($message) + { + $this->init_document('json'); + $dmsg = $this->directMessageArray($message); + $this->show_json_objects($dmsg); + $this->end_document('json'); + } + function show_atom_groups($group, $title, $id, $link, $subtitle=null, $selfuri=null) { @@ -934,7 +968,7 @@ class TwitterapiAction extends Action return; } - function clientError($msg, $code = 400, $content_type = 'json') + function clientError($msg, $code = 400, $format = 'xml') { $action = $this->trimmed('action'); @@ -948,20 +982,23 @@ class TwitterapiAction extends Action 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 serverError($msg, $code = 500, $content_type = 'json') @@ -1073,6 +1110,38 @@ 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)) { |