From b1f337fe018dd3910c68589a98b0242470743a4c Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 6 Mar 2009 13:33:47 -0800 Subject: First crack at Twitter-like JSON search results for the API --- lib/jsonsearchresultslist.php | 259 ++++++++++++++++++++++++++++++++++++++++++ lib/router.php | 10 +- lib/twitterapi.php | 28 ++++- 3 files changed, 287 insertions(+), 10 deletions(-) create mode 100644 lib/jsonsearchresultslist.php (limited to 'lib') diff --git a/lib/jsonsearchresultslist.php b/lib/jsonsearchresultslist.php new file mode 100644 index 000000000..171e1db4d --- /dev/null +++ b/lib/jsonsearchresultslist.php @@ -0,0 +1,259 @@ +. + * + * @category Search + * @package Laconica + * @author Zach Copley + * @copyright 2008-2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +/** + * widget-like class for showing JSON search results + * + * @category Search + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * + */ + +class JSONSearchResultsList +{ + protected $notice; // protected attrs invisible to json_encode() + protected $rpp; + + // The below attributes are carefully named so the JSON output from + // this obj matches the output from search.twitter.com + + var $results; + var $since_id; + var $max_id; + var $refresh_url; + var $results_per_page; + var $completed_in; + var $page; + var $query; + + /** + * constructor + * + * @param Notice $notice stream of notices from DB_DataObject + */ + + function __construct($notice, $query, $rpp, $page, $since_id = 0) + { + $this->notice = $notice; + $this->query = urlencode($query); + $this->results_per_page = $this->rpp = $rpp; + $this->page = $page; + $this->since_id = $since_id; + $this->results = array(); + } + + /** + * show the list of search results + * + * @return int count of the search results listed. + */ + + function show() + { + $cnt = 0; + + $time_start = microtime(true); + + while ($this->notice->fetch() && $cnt <= $this->rpp) { + $cnt++; + + // XXX: Hmmm. this depends on desc sort order + if (!$this->max_id) { + $this->max_id = (int)$this->notice->id; + } + + if ($cnt > $this->rpp) { + break; + } + + $item = new ResultItem($this->notice); + array_push($this->results, $item); + } + + $time_end = microtime(true); + $this->completed_in = $time_end - $time_start; + + // Set other attrs + + $this->refresh_url = '?since_id=' . $this->max_id . + '&q=' . $this->query; + + // pagination stuff + + if ($cnt > $this->rpp) { + $this->next_page = '?page=' . ($this->page + 1) . + '&max_id=' . $this->max_id; + if ($this->rpp != 15) { + $this->next_page .= '&rpp=' . $this->rpp; + } + $this->next_page .= '&q=' . $this->query; + } + + if ($this->page > 1) { + $this->previous_page = '?page=' . ($this->page - 1) . + '&max_id=' . $this->max_id; + if ($this->rpp != 15) { + $this->previous_page .= '&rpp=' . $this->rpp; + } + $this->previous_page .= '&q=' . $this->query; + } + + print json_encode($this); + + return $cnt; + } +} + +/** + * widget for displaying a single JSON search result + * + * @category UI + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * @see JSONSearchResultsList + */ + +class ResultItem +{ + /** The notice this item is based on. */ + + protected $notice; // protected attrs invisible to json_encode() + + /** The profile associated with the notice. */ + + protected $profile; + + // The below attributes are carefully named so the JSON output from + // this obj matches the output from search.twitter.com + + var $text; + var $to_user_id; + var $to_user; + var $from_user; + var $id; + var $from_user_id; + var $iso_language_code; + var $source; + var $profile_image_url; + var $created_at; + + /** + * constructor + * + * Also initializes the profile attribute. + * + * @param Notice $notice The notice we'll display + */ + + function __construct($notice) + { + $this->notice = $notice; + $this->profile = $notice->getProfile(); + $this->buildResult(); + } + + /** + * Build a search result object + * + * This populates the the result in preparation for JSON encoding. + * + * @return void + */ + + function buildResult() + { + $this->text = $this->notice->content; + $replier_profile = null; + + if ($this->notice->reply_to) { + $reply = Notice::staticGet(intval($notice->reply_to)); + if ($reply) { + $replier_profile = $reply->getProfile(); + } + } + + $this->to_user_id = ($replier_profile) ? + intval($replier_profile->id) : null; + $this->to_user = ($replier_profile) ? + $replier_profile->nickname : null; + $this->from_user = $this->profile->nickname; + $this->id = $this->notice->id; + $this->from_user_id = $this->profile->id; + + $user = User::staticGet('id', $this->profile->id); + $this->iso_language_code = $this->user->language; + + $this->source = $this->getSourceLink($this->notice->source); + + $avatar = $this->profile->getAvatar(AVATAR_STREAM_SIZE); + $this->profile_image_url = ($avatar) ? + $avatar->displayUrl() : Avatar::defaultImage(AVATAR_STREAM_SIZE); + + $this->created_at = date('r', $this->notice->created); + } + + /** + * Show the source of the notice + * + * Either the name (and link) of the API client that posted the notice, + * or one of other other channels. + * + * @return string the source of the Notice + */ + + function getSourceLink($source) + { + $source_name = _($source); + switch ($source) { + case 'web': + case 'xmpp': + case 'mail': + case 'omb': + case 'api': + break; + default: + $ns = Notice_source::staticGet($source); + if ($ns) { + $source_name = '' . $ns->name . ''; + } + break; + } + return $source_name; + } + +} diff --git a/lib/router.php b/lib/router.php index 41c376a72..516b48122 100644 --- a/lib/router.php +++ b/lib/router.php @@ -354,17 +354,13 @@ class Router // search - - foreach (array('json', 'atom') as $e) { - $m->connect('api/search.'.$e, - array('action' => 'twitapisearch')); - } - + $m->connect('api/search.atom', array('action' => 'twitapisearchatom')); + $m->connect('api/search.json', array('action' => 'twitapisearchjson')); $m->connect('api/trends.json', array('action' => 'twitapitrends')); // user stuff - foreach (array('subscriptions', 'subscribers', + foreach (array('subscriptions', 'subscribers', 'nudge', 'xrds', 'all', 'foaf', 'replies', 'inbox', 'outbox', 'microsummary') as $a) { $m->connect(':nickname/'.$a, diff --git a/lib/twitterapi.php b/lib/twitterapi.php index 74f265cbb..1de169a0b 100644 --- a/lib/twitterapi.php +++ b/lib/twitterapi.php @@ -24,11 +24,33 @@ class TwitterapiAction extends Action var $auth_user; + /** + * Initialization. + * + * @param array $args Web and URL arguments + * + * @return boolean false if user doesn't exist + */ + + function prepare($args) + { + parent::prepare($args); + return true; + } + + /** + * Handle a request + * + * @param array $args Arguments from $_REQUEST + * + * @return void + */ + function handle($args) { parent::handle($args); } - + function twitter_user_array($profile, $get_notice=false) { @@ -86,7 +108,7 @@ class TwitterapiAction extends Action ($replier_profile) ? $replier_profile->nickname : null; if (isset($this->auth_user)) { - $twitter_status['favorited'] = + $twitter_status['favorited'] = ($this->auth_user->hasFave($notice)) ? 'true' : 'false'; } else { $twitter_status['favorited'] = 'false'; @@ -399,7 +421,7 @@ class TwitterapiAction extends Action $t = strtotime($dt); return date("D M d G:i:s O Y", $t); } - + // XXX: Candidate for a general utility method somewhere? function count_subscriptions($profile) { -- cgit v1.2.3-54-g00ecf