diff options
author | Brion Vibber <brion@pobox.com> | 2010-02-12 11:18:35 -0800 |
---|---|---|
committer | Brion Vibber <brion@pobox.com> | 2010-02-12 11:18:35 -0800 |
commit | 122c8677b7004bae4cfe7e2bd49fc1bc3187c72c (patch) | |
tree | 552bf77b587fbb674329a95745067f358c0a25d3 /plugins/OStatus/lib | |
parent | 3c79448cd817d01b4421262fefc29eb558cede20 (diff) | |
parent | fd3c9334bfcfe627446feb86ac3054b24ed05449 (diff) |
Merge branch 'testing' of gitorious.org:statusnet/mainline into 0.9.x
Diffstat (limited to 'plugins/OStatus/lib')
-rw-r--r-- | plugins/OStatus/lib/activity.php | 85 | ||||
-rw-r--r-- | plugins/OStatus/lib/feedmunger.php | 51 | ||||
-rw-r--r-- | plugins/OStatus/lib/hubdistribqueuehandler.php | 116 | ||||
-rw-r--r-- | plugins/OStatus/lib/huboutqueuehandler.php | 2 | ||||
-rw-r--r-- | plugins/OStatus/lib/salmon.php | 64 | ||||
-rw-r--r-- | plugins/OStatus/lib/webfinger.php | 143 | ||||
-rw-r--r-- | plugins/OStatus/lib/xrd.php | 183 |
7 files changed, 625 insertions, 19 deletions
diff --git a/plugins/OStatus/lib/activity.php b/plugins/OStatus/lib/activity.php new file mode 100644 index 000000000..36e227913 --- /dev/null +++ b/plugins/OStatus/lib/activity.php @@ -0,0 +1,85 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * An activity + * + * 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 OStatus + * @package StatusNet + * @author Evan Prodromou <evan@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +class ActivityNoun +{ + const ARTICLE = 'http://activitystrea.ms/schema/1.0/article'; + const BLOGENTRY = 'http://activitystrea.ms/schema/1.0/blog-entry'; + const NOTE = 'http://activitystrea.ms/schema/1.0/note'; + const STATUS = 'http://activitystrea.ms/schema/1.0/status'; + const FILE = 'http://activitystrea.ms/schema/1.0/file'; + const PHOTO = 'http://activitystrea.ms/schema/1.0/photo'; + const ALBUM = 'http://activitystrea.ms/schema/1.0/photo-album'; + const PLAYLIST = 'http://activitystrea.ms/schema/1.0/playlist'; + const VIDEO = 'http://activitystrea.ms/schema/1.0/video'; + const AUDIO = 'http://activitystrea.ms/schema/1.0/audio'; + const BOOKMARK = 'http://activitystrea.ms/schema/1.0/bookmark'; + const PERSON = 'http://activitystrea.ms/schema/1.0/person'; + const GROUP = 'http://activitystrea.ms/schema/1.0/group'; + const PLACE = 'http://activitystrea.ms/schema/1.0/place'; + const COMMENT = 'http://activitystrea.ms/schema/1.0/comment'; // tea + + public $type; + public $id; + public $title; + public $summary; + public $content; +} + +class Activity +{ + const NAMESPACE = 'http://activitystrea.ms/schema/1.0/'; + + const POST = 'http://activitystrea.ms/schema/1.0/post'; + const SHARE = 'http://activitystrea.ms/schema/1.0/share'; + const SAVE = 'http://activitystrea.ms/schema/1.0/save'; + const FAVORITE = 'http://activitystrea.ms/schema/1.0/favorite'; + const PLAY = 'http://activitystrea.ms/schema/1.0/play'; + const FOLLOW = 'http://activitystrea.ms/schema/1.0/follow'; + const FRIEND = 'http://activitystrea.ms/schema/1.0/make-friend'; + const JOIN = 'http://activitystrea.ms/schema/1.0/join'; + const TAG = 'http://activitystrea.ms/schema/1.0/tag'; + + public $actor; // an ActivityNoun + public $verb; // a string (the URL) + public $object; // an ActivityNoun + public $target; // an ActivityNoun + + static function fromAtomEntry($domEntry) + { + } + + function toAtomEntry() + { + } +} diff --git a/plugins/OStatus/lib/feedmunger.php b/plugins/OStatus/lib/feedmunger.php index cbaec6775..c895b6ce2 100644 --- a/plugins/OStatus/lib/feedmunger.php +++ b/plugins/OStatus/lib/feedmunger.php @@ -83,13 +83,17 @@ class FeedMunger $this->url = $url; } - function feedinfo() + function ostatusProfile() { - $feedinfo = new Feedinfo(); - $feedinfo->feeduri = $this->url; - $feedinfo->homeuri = $this->feed->link; - $feedinfo->huburi = $this->getHubLink(); - return $feedinfo; + $profile = new Ostatus_profile(); + $profile->feeduri = $this->url; + $profile->homeuri = $this->feed->link; + $profile->huburi = $this->getHubLink(); + $salmon = $this->getSalmonLink(); + if ($salmon) { + $profile->salmonuri = $salmon; + } + return $profile; } function getAtomLink($item, $attribs=array()) @@ -155,6 +159,16 @@ class FeedMunger return $this->getAtomLink($this->feed, array('rel' => 'hub')); } + function getSalmonLink() + { + return $this->getAtomLink($this->feed, array('rel' => 'salmon')); + } + + function getSelfLink() + { + return $this->getAtomLink($this->feed, array('rel' => 'self')); + } + /** * Get an appropriate avatar image source URL, if available. * @return mixed string or false @@ -203,12 +217,13 @@ class FeedMunger if (!$entry) { return null; } - + if ($preview) { $notice = new FeedSubPreviewNotice($this->profile(true)); $notice->id = -1; } else { $notice = new Notice(); + $notice->profile_id = $this->profileIdForEntry($index); } $link = $this->getAltLink($entry); @@ -221,7 +236,7 @@ class FeedMunger $notice->uri = $link; $notice->url = $link; $notice->content = $this->noticeFromEntry($entry); - $notice->rendered = common_render_content($notice->content, $notice); + $notice->rendered = common_render_content($notice->content, $notice); // @fixme this is failing on group posts $notice->created = common_sql_date($entry->updated); // @fixme $notice->is_local = Notice::GATEWAY; $notice->source = 'feed'; @@ -239,7 +254,22 @@ class FeedMunger return $notice; } + function profileIdForEntry($index=1) + { + // hack hack hack + // should get profile for this entry's author... + $remote = Ostatus_profile::staticGet('feeduri', $this->getSelfLink()); + if ($feed) { + return $feed->profile_id; + } else { + throw new Exception("Can't find feed profile"); + } + } + /** + * Parse location given as a GeoRSS-simple point, if provided. + * http://www.georss.org/simple + * * @param feed item $entry * @return mixed Location or false */ @@ -249,7 +279,10 @@ class FeedMunger $points = $dom->getElementsByTagNameNS('http://www.georss.org/georss', 'point'); for ($i = 0; $i < $points->length; $i++) { - $point = trim($points->item(0)->textContent); + $point = $points->item(0)->textContent; + $point = str_replace(',', ' ', $point); // per spec "treat commas as whitespace" + $point = preg_replace('/\s+/', ' ', $point); + $point = trim($point); $coords = explode(' ', $point); if (count($coords) == 2) { list($lat, $lon) = $coords; diff --git a/plugins/OStatus/lib/hubdistribqueuehandler.php b/plugins/OStatus/lib/hubdistribqueuehandler.php index 126f1355f..245a57f72 100644 --- a/plugins/OStatus/lib/hubdistribqueuehandler.php +++ b/plugins/OStatus/lib/hubdistribqueuehandler.php @@ -34,27 +34,101 @@ class HubDistribQueueHandler extends QueueHandler { assert($notice instanceof Notice); + $this->pushUser($notice); + foreach ($notice->getGroups() as $group) { + $this->pushGroup($notice, $group->group_id); + } + return true; + } + + function pushUser($notice) + { // See if there's any PuSH subscriptions, including OStatus clients. // @fixme handle group subscriptions as well // http://identi.ca/api/statuses/user_timeline/1.atom $feed = common_local_url('ApiTimelineUser', array('id' => $notice->profile_id, 'format' => 'atom')); + $this->pushFeed($feed, array($this, 'userFeedForNotice'), $notice); + } + + function pushGroup($notice, $group_id) + { + $feed = common_local_url('ApiTimelineGroup', + array('id' => $group_id, + 'format' => 'atom')); + $this->pushFeed($feed, array($this, 'groupFeedForNotice'), $group_id, $notice); + } + + /** + * @param string $feed URI to the feed + * @param callable $callback function to generate Atom feed update if needed + * any additional params are passed to the callback. + */ + function pushFeed($feed, $callback) + { + $hub = common_config('ostatus', 'hub'); + if ($hub) { + $this->pushFeedExternal($feed, $hub); + } + $sub = new HubSub(); $sub->topic = $feed; if ($sub->find()) { - common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $feed"); - $qm = QueueManager::get(); - $atom = $this->userFeedForNotice($notice); - while ($sub->fetch()) { - common_log(LOG_INFO, "Prepping PuSH distribution to $sub->callback for $feed"); - $data = array('sub' => clone($sub), - 'atom' => $atom); - $qm->enqueue($data, 'hubout'); - } + $args = array_slice(func_get_args(), 2); + $atom = call_user_func_array($callback, $args); + $this->pushFeedInternal($atom, $sub); } else { common_log(LOG_INFO, "No PuSH subscribers for $feed"); } + return true; + } + + /** + * Ping external hub about this update. + * The hub will pull the feed and check for new items later. + * Not guaranteed safe in an environment with database replication. + * + * @param string $feed feed topic URI + * @param string $hub PuSH hub URI + * @fixme can consolidate pings for user & group posts + */ + function pushFeedExternal($feed, $hub) + { + $client = new HTTPClient(); + try { + $data = array('hub.mode' => 'publish', + 'hub.url' => $feed); + $response = $client->post($hub, array(), $data); + if ($response->getStatus() == 204) { + common_log(LOG_INFO, "PuSH ping to hub $hub for $feed ok"); + return true; + } else { + common_log(LOG_ERR, "PuSH ping to hub $hub for $feed failed with HTTP " . + $response->getStatus() . ': ' . + $response->getBody()); + } + } catch (Exception $e) { + common_log(LOG_ERR, "PuSH ping to hub $hub for $feed failed: " . $e->getMessage()); + return false; + } + } + + /** + * Queue up direct feed update pushes to subscribers on our internal hub. + * @param string $atom update feed, containing only new/changed items + * @param HubSub $sub open query of subscribers + */ + function pushFeedInternal($atom, $sub) + { + common_log(LOG_INFO, "Preparing $sub->N PuSH distribution(s) for $sub->topic"); + $qm = QueueManager::get(); + while ($sub->fetch()) { + common_log(LOG_INFO, "Prepping PuSH distribution to $sub->callback for $sub->topic"); + $data = array('sub' => clone($sub), + 'atom' => $atom); + $qm->enqueue($data, 'hubout'); + } } /** @@ -83,5 +157,29 @@ class HubDistribQueueHandler extends QueueHandler common_log(LOG_DEBUG, $feed); return $feed; } + + function groupFeedForNotice($group_id, $notice) + { + // @fixme this feels VERY hacky... + // should probably be a cleaner way to do it + + ob_start(); + $api = new ApiTimelineGroupAction(); + $args = array('id' => $group_id, + 'format' => 'atom', + 'max_id' => $notice->id, + 'since_id' => $notice->id - 1); + $api->prepare($args); + $api->handle($args); + $feed = ob_get_clean(); + + // ...and override the content-type back to something normal... eww! + // hope there's no other headers that got set while we weren't looking. + header('Content-Type: text/html; charset=utf-8'); + + common_log(LOG_DEBUG, $feed); + return $feed; + } + } diff --git a/plugins/OStatus/lib/huboutqueuehandler.php b/plugins/OStatus/lib/huboutqueuehandler.php index cb44ad2c4..0791c7e5d 100644 --- a/plugins/OStatus/lib/huboutqueuehandler.php +++ b/plugins/OStatus/lib/huboutqueuehandler.php @@ -43,7 +43,7 @@ class HubOutQueueHandler extends QueueHandler common_log(LOG_ERR, "Failed PuSH to $sub->callback for $sub->topic: " . $e->getMessage()); // @fixme Reschedule a later delivery? - // Currently we have no way to do this other than 'send NOW' + return true; } return true; diff --git a/plugins/OStatus/lib/salmon.php b/plugins/OStatus/lib/salmon.php new file mode 100644 index 000000000..8c77222a6 --- /dev/null +++ b/plugins/OStatus/lib/salmon.php @@ -0,0 +1,64 @@ +<?php +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * A sample module to show best practices for StatusNet plugins + * + * PHP version 5 + * + * 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/>. + * + * @package StatusNet + * @author James Walker <james@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class Salmon +{ + public function post($endpoint_uri, $xml) + { + if (empty($endpoint_uri)) { + return FALSE; + } + + $headers = array('Content-type: application/atom+xml'); + + try { + $client = new HTTPClient(); + $client->setBody($xml); + $response = $client->post($endpoint_uri, $headers); + } catch (HTTP_Request2_Exception $e) { + return false; + } + if ($response->getStatus() != 200) { + return false; + } + + } + + public function createMagicEnv($text, $userid) + { + + + } + + + public function verifyMagicEnv($env) + { + + + } +} diff --git a/plugins/OStatus/lib/webfinger.php b/plugins/OStatus/lib/webfinger.php new file mode 100644 index 000000000..417d54904 --- /dev/null +++ b/plugins/OStatus/lib/webfinger.php @@ -0,0 +1,143 @@ +<?php +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * A sample module to show best practices for StatusNet plugins + * + * PHP version 5 + * + * 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/>. + * + * @package StatusNet + * @author James Walker <james@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +define('WEBFINGER_SERVICE_REL_VALUE', 'lrdd'); + +/** + * Implement the webfinger protocol. + */ +class Webfinger +{ + /** + * Perform a webfinger lookup given an account. + */ + public function lookup($id) + { + $id = $this->normalize($id); + list($name, $domain) = explode('@', $id); + + $links = $this->getServiceLinks($domain); + if (!$links) { + return false; + } + + $services = array(); + foreach ($links as $link) { + if ($link['template']) { + return $this->getServiceDescription($link['template'], $id); + } + if ($link['href']) { + return $this->getServiceDescription($link['href'], $id); + } + } + } + + /** + * Normalize an account ID + */ + function normalize($id) + { + if (substr($id, 0, 7) == 'acct://') { + return substr($id, 7); + } else if (substr($id, 0, 5) == 'acct:') { + return substr($id, 5); + } + + return $id; + } + + function getServiceLinks($domain) + { + $url = 'http://'. $domain .'/.well-known/host-meta'; + $content = $this->fetchURL($url); + if (empty($content)) { + common_log(LOG_DEBUG, 'Error fetching host-meta'); + return false; + } + $result = XRD::parse($content); + + // Ensure that the host == domain (spec may include signing later) + if ($result->host != $domain) { + return false; + } + + $links = array(); + foreach ($result->links as $link) { + if ($link['rel'] == WEBFINGER_SERVICE_REL_VALUE) { + $links[] = $link; + } + + } + return $links; + } + + function getServiceDescription($template, $id) + { + $url = $this->applyTemplate($template, 'acct:' . $id); + + $content = $this->fetchURL($url); + + return XRD::parse($content); + } + + function fetchURL($url) + { + try { + $client = new HTTPClient(); + $response = $client->get($url); + } catch (HTTP_Request2_Exception $e) { + return false; + } + + if ($response->getStatus() != 200) { + return false; + } + + return $response->getBody(); + } + + function applyTemplate($template, $id) + { + $template = str_replace('{uri}', urlencode($id), $template); + + return $template; + } + + function getHostMeta($domain, $template) { + $xrd = new XRD(); + $xrd->host = $domain; + $xrd->links[] = array('rel' => 'lrdd', + 'template' => $template, + 'title' => array('Resource Descriptor')); + + return $xrd->toXML(); + } +} + + diff --git a/plugins/OStatus/lib/xrd.php b/plugins/OStatus/lib/xrd.php new file mode 100644 index 000000000..16d27f8eb --- /dev/null +++ b/plugins/OStatus/lib/xrd.php @@ -0,0 +1,183 @@ +<?php +/** + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2010, StatusNet, Inc. + * + * A sample module to show best practices for StatusNet plugins + * + * PHP version 5 + * + * 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/>. + * + * @package StatusNet + * @author James Walker <james@status.net> + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + + +class XRD +{ + const XML_NS = 'http://www.w3.org/2000/xmlns/'; + + const XRD_NS = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'; + + const HOST_META_NS = 'http://host-meta.net/xrd/1.0'; + + public $expires; + + public $subject; + + public $host; + + public $alias = array(); + + public $types = array(); + + public $links = array(); + + public static function parse($xml) + { + $xrd = new XRD(); + + $dom = new DOMDocument(); + $dom->loadXML($xml); + $xrd_element = $dom->getElementsByTagName('XRD')->item(0); + + // Check for host-meta host + $host = $xrd_element->getElementsByTagName('Host')->item(0)->nodeValue; + if ($host) { + $xrd->host = $host; + } + + // Loop through other elements + foreach ($xrd_element->childNodes as $node) { + switch ($node->tagName) { + case 'Expires': + $xrd->expires = $node->nodeValue; + break; + case 'Subject': + $xrd->subject = $node->nodeValue; + break; + + case 'Alias': + $xrd->alias[] = $node->nodeValue; + break; + + case 'Link': + $xrd->links[] = $xrd->parseLink($node); + break; + + case 'Type': + $xrd->types[] = $xrd->parseType($node); + break; + + } + } + return $xrd; + } + + public function toXML() + { + $dom = new DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + + $xrd_dom = $dom->createElementNS(XRD::XRD_NS, 'XRD'); + $dom->appendChild($xrd_dom); + + if ($this->host) { + $host_dom = $dom->createElement('hm:Host', $this->host); + $xrd_dom->setAttributeNS(XRD::XML_NS, 'xmlns:hm', XRD::HOST_META_NS); + $xrd_dom->appendChild($host_dom); + } + + if ($this->expires) { + $expires_dom = $dom->createElement('Expires', $this->expires); + $xrd_dom->appendChild($expires_dom); + } + + if ($this->subject) { + $subject_dom = $dom->createElement('Subject', $this->subject); + $xrd_dom->appendChild($subject_dom); + } + + foreach ($this->alias as $alias) { + $alias_dom = $dom->createElement('Alias', $alias); + $xrd_dom->appendChild($alias_dom); + } + + foreach ($this->types as $type) { + $type_dom = $dom->createElement('Type', $type); + $xrd_dom->appendChild($type_dom); + } + + foreach ($this->links as $link) { + $link_dom = $this->saveLink($dom, $link); + $xrd_dom->appendChild($link_dom); + } + + return $dom->saveXML(); + } + + function parseType($element) + { + return array(); + } + + function parseLink($element) + { + $link = array(); + $link['rel'] = $element->getAttribute('rel'); + $link['type'] = $element->getAttribute('type'); + $link['href'] = $element->getAttribute('href'); + $link['template'] = $element->getAttribute('template'); + foreach ($element->childNodes as $node) { + switch($node->tagName) { + case 'Title': + $link['title'][] = $node->nodeValue; + } + } + + return $link; + } + + function saveLink($doc, $link) + { + $link_element = $doc->createElement('Link'); + if ($link['rel']) { + $link_element->setAttribute('rel', $link['rel']); + } + if ($link['type']) { + $link_element->setAttribute('type', $link['type']); + } + if ($link['href']) { + $link_element->setAttribute('href', $link['href']); + } + if ($link['template']) { + $link_element->setAttribute('template', $link['template']); + } + + if (is_array($link['title'])) { + foreach($link['title'] as $title) { + $title = $doc->createElement('Title', $title); + $link_element->appendChild($title); + } + } + + + return $link_element; + } +} + |