First cut at some JSON Activity Streams output

This commit is contained in:
Zach Copley 2011-02-15 20:25:39 -08:00
parent ff502bb148
commit a1b436a8c6
6 changed files with 220 additions and 6 deletions

View File

@ -263,6 +263,12 @@ class ApiTimelineFriendsAction extends ApiBareAuthAction
case 'json': case 'json':
$this->showJsonTimeline($this->notices); $this->showJsonTimeline($this->notices);
break; break;
case 'as':
header('Content-Type: application/json; charset=utf-8');
$doc = new ActivityStreamJSONDocument($this->auth_user);
$doc->addItemsFromNotices($this->notices);
$this->raw($doc->asString());
break;
default: default:
// TRANS: Client error displayed when trying to handle an unknown API method. // TRANS: Client error displayed when trying to handle an unknown API method.
$this->clientError(_('API method not found.'), $code = 404); $this->clientError(_('API method not found.'), $code = 404);

View File

@ -1252,18 +1252,19 @@ class Notice extends Memcached_DataObject
function asActivity() function asActivity()
{ {
common_debug("a");
$act = self::cacheGet(Cache::codeKey('notice:as-activity:'.$this->id)); $act = self::cacheGet(Cache::codeKey('notice:as-activity:'.$this->id));
if (!empty($act)) { if (!empty($act)) {
return $act; return $act;
} }
$act = new Activity(); $act = new Activity();
if (Event::handle('StartNoticeAsActivity', array($this, &$act))) { if (Event::handle('StartNoticeAsActivity', array($this, &$act))) {
$profile = $this->getProfile(); $profile = $this->getProfile();
common_debug('b');
$act->actor = ActivityObject::fromProfile($profile); $act->actor = ActivityObject::fromProfile($profile);
$act->verb = ActivityVerb::POST; $act->verb = ActivityVerb::POST;
$act->objects[] = ActivityObject::fromNotice($this); $act->objects[] = ActivityObject::fromNotice($this);

View File

@ -337,6 +337,59 @@ class Activity
return null; return null;
} }
/**
* Returns an array based on this activity suitable
* for encoding as a JSON object
*
* @return array $activity
*/
function asArray()
{
$activity = array();
// actor
$activity['actor'] = $this->actor->asArray();
// body
$activity['body'] = $this->content;
// generator <--- might be useful; might be too much junk
// icon <-- should we use this?
// object
if ($this->verb == ActivityVerb::POST && count($this->objects) == 1) {
$activity['object'] = $this->objects[0]->asArray();
} else {
$activity['object'] = array();
foreach($this->objects as $object) {
$activity['object'][] = $object->asArray();
}
}
$activity['postedTime'] = self::iso8601Date($this->time); // Change to exactly be RFC3339?
// provider <--- again not sure we should use this
// target
if (!empty($this->target)) {
$activity['target'] = $this->target->asArray();
}
// title
$activity['title'] = $this->title;
// updatedTime <-- should we use? spec says activity MAY have this
// verb
$activity['verb'] = $this->verb;
// TODO: extensions (ActivityContext, OStatus stuff, etc.)
return $activity;
}
function asString($namespace=false, $author=true, $source=false) function asString($namespace=false, $author=true, $source=false)
{ {
$xs = new XMLStringer(true); $xs = new XMLStringer(true);

View File

@ -179,7 +179,7 @@ class ActivityObject
if (empty($this->type)) { if (empty($this->type)) {
$this->type = self::PERSON; // XXX: is this fair? $this->type = self::PERSON; // XXX: is this fair?
} }
// start with <atom:title> // start with <atom:title>
$title = ActivityUtils::childHtmlContent($element, self::TITLE); $title = ActivityUtils::childHtmlContent($element, self::TITLE);
@ -419,7 +419,7 @@ class ActivityObject
static function fromNotice(Notice $notice) static function fromNotice(Notice $notice)
{ {
$object = new ActivityObject(); $object = new ActivityObject();
if (Event::handle('StartActivityObjectFromNotice', array($notice, &$object))) { if (Event::handle('StartActivityObjectFromNotice', array($notice, &$object))) {
$object->type = ActivityObject::NOTE; $object->type = ActivityObject::NOTE;
@ -526,7 +526,7 @@ class ActivityObject
return $object; return $object;
} }
function outputTo($xo, $tag='activity:object') function outputTo($xo, $tag='activity:object')
{ {
if (!empty($tag)) { if (!empty($tag)) {
@ -633,4 +633,46 @@ class ActivityObject
return $xs->getString(); return $xs->getString();
} }
/*
* Returns an array based on this Activity Object suitable for
* encoding as JSON.
*
* @return array $object the activity object array
*/
function asArray()
{
$object = array();
// TODO: attachedObjects
// displayName
$object['displayName'] = $this->title;
// TODO: downstreamDuplicates
// TODO: embedCode
// id
$object['id'] = $this->id;
// TODO: image
// Need to make MediaLink serialization
// objectType
$object['type'] = $this->type;
// summary
$object['summary'] = $this->summary;
// TODO: upstreamDuplicates
// url (XXX: need to put the right thing here...)
$object['url'] = $this->id;
// TODO: extensions (OStatus stuff, etc.)
return $object;
}
} }

View File

@ -0,0 +1,112 @@
<?php
/**
* StatusNet, the distributed open-source microblogging tool
*
* Class for serializing Activity Streams in JSON
*
* 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 Feed
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @copyright 2011 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);
}
/**
* A class for generating JSON documents that represent an Activity Streams
*
* @category Feed
* @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 ActivityStreamJSONDocument
{
/* Top level array representing the document */
protected $doc = array();
/* The current authenticated user */
protected $cur = null;
/**
* Constructor
*
* @param User $cur the current authenticated user
*/
function __construct($cur = null)
{
$this->cur = $cur;
$this->doc['items'] = array();
}
/**
* Add more than one Item to the document
*
* @param mixed $notices an array of Notice objects or handle
*
*/
function addItemsFromNotices($notices)
{
common_debug("addItemsFromNotices");
if (is_array($notices)) {
foreach ($notices as $notice) {
$this->addItemFromNotice($notice);
}
} else {
while ($notices->fetch()) {
$this->addItemFromNotice($notices);
}
}
}
/**
* Add a single Notice to the document
*
* @param Notice $notice a Notice to add
*/
function addItemFromNotice($notice)
{
$cur = empty($this->cur) ? common_current_user() : $this->cur;
$act = $notice->asActivity();
$act->extra[] = $notice->noticeInfo($cur);
array_push($this->doc['items'], $act->asArray());
}
/*
* Return the entire document as a big string of JSON
*
* @return string encoded JSON output
*/
function asString()
{
return json_encode($this->doc);
}
}

View File

@ -411,7 +411,7 @@ class Router
$m->connect('api/statuses/friends_timeline.:format', $m->connect('api/statuses/friends_timeline.:format',
array('action' => 'ApiTimelineFriends', array('action' => 'ApiTimelineFriends',
'format' => '(xml|json|rss|atom)')); 'format' => '(xml|json|rss|atom|as)'));
$m->connect('api/statuses/friends_timeline/:id.:format', $m->connect('api/statuses/friends_timeline/:id.:format',
array('action' => 'ApiTimelineFriends', array('action' => 'ApiTimelineFriends',