code:
<?php
/**
 * This test class pretends to be an RSS aggregator. It logs notifications
 * from the cloud.
 *
 * PHP version 5
 *
 * @category Plugin
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 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')) {
    exit(1);
}

/**
 * Dummy aggregator that acts as a proper notification handler. It
 * doesn't do anything but respond correctly when notified via
 * REST.  Mostly, this is just and action I used to develop the plugin
 * and easily test things end-to-end. I'm leaving it in here as it
 * may be useful for developing the plugin further.
 *
 * @category Plugin
 * @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 LoggingAggregatorAction extends Action
{
    var $challenge = null;
    var $url       = null;

    /**
     * Initialization.
     *
     * @param array $args Web and URL arguments
     *
     * @return boolean false if user doesn't exist
     */
    function prepare($args)
    {
        parent::prepare($args);

        $this->url       = $this->arg('url');
        $this->challenge = $this->arg('challenge');

        common_debug("args = " . var_export($this->args, true));
        common_debug('url = ' . $this->url . ' challenge = ' . $this->challenge);

        return true;
    }

    /**
     * Handle the request
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    function handle($args)
    {
        parent::handle($args);

        if (empty($this->url)) {
            // TRANS: Form validation error displayed when a URL parameter is missing.
            $this->showError(_m('A URL parameter is required.'));
            return;
        }

        if (!empty($this->challenge)) {
            // must be a GET
            if ($_SERVER['REQUEST_METHOD'] != 'GET') {
                // TRANS: Form validation error displayed when HTTP GET is not used.
                $this->showError(_m('This resource requires an HTTP GET.'));
                return;
            }

            header('Content-Type: text/xml');
            echo $this->challenge;
        } else {
            // must be a POST
            if ($_SERVER['REQUEST_METHOD'] != 'POST') {
                // TRANS: Form validation error displayed when HTTP POST is not used.
                $this->showError(_m('This resource requires an HTTP POST.'));
                return;
            }

            header('Content-Type: text/xml');
            Echo "<notifyResult success='true' msg='Thanks for the update.' />\n";
        }

        $this->ip = $_SERVER['REMOTE_ADDR'];

        common_log(LOG_INFO, 'RSSCloud Logging Aggregator - ' .
                   $this->ip . ' claims the feed at ' .
                   $this->url . ' has been updated.');
    }

    /**
     * Show an XML error when things go badly
     *
     * @param string $msg the error message
     *
     * @return void
     */
    function showError($msg)
    {
        header('HTTP/1.1 400 Bad Request');
        header('Content-Type: text/xml');
        echo "<?xml version='1.0'?>\n";
        echo "<notifyResult success='false' msg='$msg' />\n";
    }
}


code:
<?php
/**
 * Action to let RSSCloud aggregators request update notification when
 * user profile feeds change.
 *
 * PHP version 5
 *
 * @category Plugin
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 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')) {
    exit(1);
}

/**
 * Action class to handle RSSCloud notification (subscription) requests
 *
 * @category Plugin
 * @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 RSSCloudRequestNotifyAction extends Action
{
    /**
     * Initialization.
     *
     * @param array $args Web and URL arguments
     *
     * @return boolean false if user doesn't exist
     */
    function prepare($args)
    {
        parent::prepare($args);

        $this->ip   = $_SERVER['REMOTE_ADDR'];
        $this->port = $this->arg('port');
        $this->path = $this->arg('path');

        if ($this->path[0] != '/') {
            $this->path = '/' . $this->path;
        }

        $this->protocol  = $this->arg('protocol');
        $this->procedure = $this->arg('notifyProcedure');
        $this->domain    = $this->arg('domain');

        $this->feeds = $this->getFeeds();

        return true;
    }

    /**
     * Handle the request
     *
     * Checks for all the required parameters for a subscription,
     * validates that the feed being subscribed to is real, and then
     * saves the subsctiption.
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    function handle($args)
    {
        parent::handle($args);

        if ($_SERVER['REQUEST_METHOD'] != 'POST') {
            // TRANS: Form validation error displayed when POST is not used.
            $this->showResult(false, _m('Request must be POST.'));
            return;
        }

        $missing = array();

        if (empty($this->port)) {
            $missing[] = 'port';
        }

        if (empty($this->path)) {
            $missing[] = 'path';
        }

        if (empty($this->protocol)) {
            $missing[] = 'protocol';
        } else if (strtolower($this->protocol) != 'http-post') {
            // TRANS: Form validation error displayed when HTTP POST is not used.
            $msg = _m('Only HTTP POST notifications are supported at this time.');
            $this->showResult(false, $msg);
            return;
        }

        if (!isset($this->procedure)) {
            $missing[] = 'notifyProcedure';
        }

        if (!empty($missing)) {
            // TRANS: List separator.
            $separator = _m('SEPARATOR',', ');
            // TRANS: Form validation error displayed when a request body is missing expected parameters.
            // TRANS: %s is a list of parameters separated by a list separator (default: ", ").
            $msg = sprintf(_m('The following parameters were missing from the request body: %s.'),implode($separator, $missing));
            $this->showResult(false, $msg);
            return;
        }

        if (empty($this->feeds)) {
            // TRANS: Form validation error displayed when not providing any valid profile feed URLs.
            $msg = _m('You must provide at least one valid profile feed URL ' .
              '(url1, url2, url3 ... urlN).');
            $this->showResult(false, $msg);
            return;
        }

        // We have to validate everything before saving anything.
        // We only return one success or failure no matter how
        // many feeds the subscriber is trying to subscribe to
        foreach ($this->feeds as $feed) {
            if (!$this->validateFeed($feed)) {
                $nh = $this->getNotifyUrl();
                common_log(LOG_WARNING,
                           "RSSCloud plugin - $nh tried to subscribe to invalid feed: $feed");

                // TRANS: Form validation error displayed when not providing a valid feed URL.
                $msg = _m('Feed subscription failed: Not a valid feed.');
                $this->showResult(false, $msg);
                return;
            }

            if (!$this->testNotificationHandler($feed)) {
                // TRANS: Form validation error displayed when feed subscription failed.
                $msg = _m('Feed subscription failed: ' .
                'Notification handler does not respond correctly.');
                $this->showResult(false, $msg);
                return;
            }
        }

        foreach ($this->feeds as $feed) {
            $this->saveSubscription($feed);
        }

        // XXX: What to do about deleting stale subscriptions?
        // 25 hours seems harsh. WordPress doesn't ever remove
        // subscriptions.
        // TRANS: Success message after subscribing to one or more feeds.
        $msg = _m('Thanks for the subscription. ' .
          'When the feed(s) update(s), you will be notified.');

        $this->showResult(true, $msg);
    }

    /**
     * Validate that the requested feed is one we serve
     * up via RSSCloud.
     *
     * @param string $feed the feed in question
     *
     * @return void
     */
    function validateFeed($feed)
    {
        $user = $this->userFromFeed($feed);

        if (empty($user)) {
            return false;
        }

        return true;
    }

    /**
     * Pull all of the urls (url1, url2, url3...urlN) that
     * the subscriber wants to subscribe to.
     *
     * @return array $feeds the list of feeds
     */
    function getFeeds()
    {
        $feeds = array();

        while (list($key, $feed) = each($this->args)) {
            if (preg_match('/^url\d*$/', $key)) {
                $feeds[] = $feed;
            }
        }

        return $feeds;
    }

    /**
     * Test that a notification handler is there and is reponding
     * correctly.  This is called before adding a subscription.
     *
     * @param string $feed the feed to verify
     *
     * @return boolean success result
     */
    function testNotificationHandler($feed)
    {
        $notifyUrl = $this->getNotifyUrl();

        $notifier = new RSSCloudNotifier();

        if (isset($this->domain)) {
            // 'domain' param set, so we have to use GET and send a challenge
            common_log(LOG_INFO,
                       'RSSCloud plugin - Testing notification handler with challenge: ' .
                       $notifyUrl);
            return $notifier->challenge($notifyUrl, $feed);
        } else {
            common_log(LOG_INFO, 'RSSCloud plugin - Testing notification handler: ' .
                       $notifyUrl);

            return $notifier->postUpdate($notifyUrl, $feed);
        }
    }

    /**
     * Build the URL for the notification handler based on the
     * parameters passed in with the subscription request.
     *
     * @return string notification handler url
     */
    function getNotifyUrl()
    {
        if (isset($this->domain)) {
            return 'http://' . $this->domain . ':' . $this->port . $this->path;
        } else {
            return 'http://' . $this->ip . ':' . $this->port . $this->path;
        }
    }

    /**
     * Uses the nickname part of the subscribed feed URL to figure out
     * whethere there's really a user with such a feed.  Used to
     * validate feeds before adding a subscription.
     *
     * @param string $feed the feed in question
     *
     * @return boolean success
     */
    function userFromFeed($feed)
    {
        // We only do canonical RSS2 profile feeds (specified by ID), e.g.:
        // http://www.example.com/api/statuses/user_timeline/2.rss
        $path  = common_path('api/statuses/user_timeline/');
        $valid = '%^' . $path . '(?<id>.*)\.rss$%';

        if (preg_match($valid, $feed, $matches)) {
            $user = User::getKV('id', $matches['id']);
            if (!empty($user)) {
                return $user;
            }
        }

        return false;
    }

    /**
     * Save an RSSCloud subscription
     *
     * @param string $feed a valid profile feed
     *
     * @return boolean success result
     */
    function saveSubscription($feed)
    {
        $user = $this->userFromFeed($feed);

        $notifyUrl = $this->getNotifyUrl();

        $sub = RSSCloudSubscription::getSubscription($user->id, $notifyUrl);

        if ($sub) {
            common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl refreshed subscription" .
                         " to user $user->nickname (id: $user->id).");
        } else {
            $sub = new RSSCloudSubscription();

            $sub->subscribed = $user->id;
            $sub->url        = $notifyUrl;
            $sub->created    = common_sql_now();

            if (!$sub->insert()) {
                common_log_db_error($sub, 'INSERT', __FILE__);
                return false;
            }

            common_log(LOG_INFO, "RSSCloud plugin - $notifyUrl subscribed" .
                       " to user $user->nickname (id: $user->id)");
        }

        return true;
    }

    /**
     * Show an XML message indicating the subscription
     * was successful or failed.
     *
     * @param boolean $success whether it was good or bad
     * @param string  $msg     the message to output
     *
     * @return boolean success result
     */
    function showResult($success, $msg)
    {
        $this->startXML();
        $this->elementStart('notifyResult',
                            array('success' => ($success) ? 'true' : 'false',
                                  'msg'     => $msg));
        $this->endXML();
    }
}

license: mit


code:
<?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);
}

/**
 * Table Definition for rsscloud_subscription
 */

require_once INSTALLDIR . '/classes/Memcached_DataObject.php';

class RSSCloudSubscription extends Memcached_DataObject {

    var $__table='rsscloud_subscription'; // table name
    var $subscribed;                      // int    primary key user id
    var $url;                             // string primary key
    var $failures;                        // int
    var $created;                         // datestamp()
    var $modified;                        // timestamp()   not_null default_CURRENT_TIMESTAMP

    static function getKV($k,$v=NULL) { return DB_DataObject::staticGet('DataObjects_Grp',$k,$v); }

    function table()
    {

        $db = $this->getDatabaseConnection();
        $dbtype = $db->phptype;

        $cols = array('subscribed' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
                      'url'        => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
                      'failures'   => DB_DATAOBJECT_INT,
                      'created'    => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
                      'modified'  => ($dbtype == 'mysql' || $dbtype == 'mysqli') ?
                      DB_DATAOBJECT_MYSQLTIMESTAMP + DB_DATAOBJECT_NOTNULL :
                      DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME
                      );

        return $cols;
    }

    function keys()
    {
        return array('subscribed' => 'N', 'url' => 'N');
    }

    static function getSubscription($subscribed, $url)
    {
        $sub = new RSSCloudSubscription();
        $sub->whereAdd("subscribed = $subscribed");
        $sub->whereAdd("url = '$url'");
        $sub->limit(1);

        if ($sub->find()) {
            $sub->fetch();
            return $sub;
        }

        return false;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Class to ping an rssCloud endpoint when a feed has been updated
 *
 * 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  Plugin
 * @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);
}

/**
 * Class for notifying cloud-enabled RSS aggregators that StatusNet
 * feeds have been updated.
 *
 * @category Plugin
 * @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 RSSCloudNotifier
{
    const MAX_FAILURES = 3;

    /**
     * Send an HTTP GET to the notification handler with a
     * challenge string to see if it repsonds correctly.
     *
     * @param string $endpoint URL of the notification handler
     * @param string $feed     the feed being subscribed to
     *
     * @return boolean success
     */
    function challenge($endpoint, $feed)
    {
        $code   = common_confirmation_code(128);
        $params = array('url' => $feed, 'challenge' => $code);
        $url    = $endpoint . '?' . http_build_query($params);

        try {
            $client   = new HTTPClient();
            $response = $client->get($url);
        } catch (HTTP_Request2_Exception $e) {
            common_log(LOG_INFO,
                       'RSSCloud plugin - failure testing notify handler ' .
                       $endpoint . ' - '  . $e->getMessage());
            return false;
        }

        // Check response is betweet 200 and 299 and body contains challenge data

        $status = $response->getStatus();
        $body   = $response->getBody();

        if ($status >= 200 && $status < 300) {
            // NOTE: the spec says that the body must contain the string
            // challenge.  It doesn't say that the body must contain the
            // challenge string ONLY, although that seems to be the way
            // the other implementors have interpreted it.

            if (strpos($body, $code) !== false) {
                common_log(LOG_INFO, 'RSSCloud plugin - ' .
                           "success testing notify handler:  $endpoint");
                return true;
            } else {
                common_log(LOG_INFO, 'RSSCloud plugin - ' .
                          'challenge/repsonse failed for notify handler ' .
                           $endpoint);
                common_debug('body = ' . var_export($body, true));
                return false;
            }
        } else {
            common_log(LOG_INFO, 'RSSCloud plugin - ' .
                       "failure testing notify handler:  $endpoint " .
                       ' - got HTTP ' . $status);
            common_debug('body = ' . var_export($body, true));
            return false;
        }
    }

    /**
     * HTTP POST a notification that a feed has been updated
     * ('ping the cloud').
     *
     * @param String $endpoint URL of the notification handler
     * @param String $feed     the feed being subscribed to
     *
     * @return boolean success
     */
    function postUpdate($endpoint, $feed)
    {
        $headers  = array();
        $postdata = array('url' => $feed);

        try {
            $client   = new HTTPClient();
            $response = $client->post($endpoint, $headers, $postdata);
        } catch (HTTP_Request2_Exception $e) {
            common_log(LOG_INFO, 'RSSCloud plugin - failure notifying ' .
                       $endpoint . ' that feed ' . $feed .
                       ' has changed: ' . $e->getMessage());
            return false;
        }

        $status = $response->getStatus();

        if ($status >= 200 && $status < 300) {
            common_log(LOG_INFO, 'RSSCloud plugin - success notifying ' .
                       $endpoint . ' that feed ' . $feed . ' has changed.');
            return true;
        } else {
            common_log(LOG_INFO, 'RSSCloud plugin - failure notifying ' .
                       $endpoint . ' that feed ' . $feed .
                       ' has changed: got HTTP ' . $status);
            return false;
        }
    }

    /**
     * Notify all subscribers to a profile feed that it has changed.
     *
     * @param Profile $profile the profile whose feed has been
     *        updated
     *
     * @return boolean success
     */
    function notify($profile)
    {
        $feed = common_path('api/statuses/user_timeline/') .
          $profile->id . '.rss';

        $cloudSub = new RSSCloudSubscription();

        $cloudSub->subscribed = $profile->id;

        if ($cloudSub->find()) {
            while ($cloudSub->fetch()) {
                $result = $this->postUpdate($cloudSub->url, $feed);
                if ($result == false) {
                    $this->handleFailure($cloudSub);
                }
            }
        }

        return true;
    }

    /**
     * Handle problems posting cloud notifications. Increment the failure
     * count, or delete the subscription if the maximum number of failures
     * is exceeded.
     *
     * XXX: Redo with proper DB_DataObject methods once I figure out what
     * what the problem is with pluginized DB_DataObjects. -Z
     *
     * @param RSSCloudSubscription $cloudSub the subscription in question
     *
     * @return boolean success
     */
    function handleFailure($cloudSub)
    {
        $failCnt = $cloudSub->failures + 1;

        if ($failCnt == self::MAX_FAILURES) {

            common_log(LOG_INFO,
                       'Deleting RSSCloud subcription ' .
                       '(max failure count reached), profile: ' .
                       $cloudSub->subscribed .
                       ' handler: ' .
                       $cloudSub->url);

            // XXX: WTF! ->delete() doesn't work. Clearly, there are some issues with
            // the DB_DataObject, or my understanding of it.  Have to drop into SQL.

            // $result = $cloudSub->delete();

            $qry = 'DELETE from rsscloud_subscription' .
              ' WHERE subscribed = ' . $cloudSub->subscribed .
              ' AND url = \'' . $cloudSub->url . '\'';

            $result = $cloudSub->query($qry);

            if (!$result) {
                common_log_db_error($cloudSub, 'DELETE', __FILE__);
                common_log(LOG_ERR, 'Could not delete RSSCloud subscription.');
            }
        } else {
            common_debug('Updating failure count on RSSCloud subscription. ' .
                         $failCnt);

            $failCnt = $cloudSub->failures + 1;

            // XXX: ->update() not working either, gar!

            $qry = 'UPDATE rsscloud_subscription' .
              ' SET failures = ' . $failCnt .
              ' WHERE subscribed = ' . $cloudSub->subscribed .
              ' AND url = \'' . $cloudSub->url . '\'';

            $result = $cloudSub->query($qry);

            if (!$result) {
                common_log_db_error($cloudsub, 'UPDATE', __FILE__);
                common_log(LOG_ERR,
                           'Could not update failure ' .
                           'count on RSSCloud subscription');
            }
        }
    }
}


code:
<?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); }

class RSSCloudQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'rsscloud';
    }

    function handle($notice)
    {
        try {
            $profile = $notice->getProfile();
        } catch (Exception $e) {
            common_log(LOG_ERR, "Dropping RSSCloud item for notice with bogus profile: " . $e->getMessage());
            return true;
        }
        $notifier = new RSSCloudNotifier();
        return $notifier->notify($profile);
    }
}


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
#: LoggingAggregator.php:86
msgid "A URL parameter is required."
msgstr ""

#. TRANS: Form validation error displayed when HTTP GET is not used.
#: LoggingAggregator.php:94
msgid "This resource requires an HTTP GET."
msgstr ""

#. TRANS: Form validation error displayed when HTTP POST is not used.
#: LoggingAggregator.php:104
msgid "This resource requires an HTTP POST."
msgstr ""

#. TRANS: Form validation error displayed when POST is not used.
#: RSSCloudRequestNotify.php:91
msgid "Request must be POST."
msgstr ""

#. TRANS: Form validation error displayed when HTTP POST is not used.
#: RSSCloudRequestNotify.php:109
msgid "Only HTTP POST notifications are supported at this time."
msgstr ""

#. TRANS: List separator.
#: RSSCloudRequestNotify.php:120
msgctxt "SEPARATOR"
msgid ", "
msgstr ""

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#: RSSCloudRequestNotify.php:123
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr ""

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
#: RSSCloudRequestNotify.php:130
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""

#. TRANS: Form validation error displayed when not providing a valid feed URL.
#: RSSCloudRequestNotify.php:146
msgid "Feed subscription failed: Not a valid feed."
msgstr ""

#. TRANS: Form validation error displayed when feed subscription failed.
#: RSSCloudRequestNotify.php:153
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""

#. TRANS: Success message after subscribing to one or more feeds.
#: RSSCloudRequestNotify.php:168
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""

#. TRANS: Plugin description.
#: RSSCloudPlugin.php:241
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""


code:
# Translation of StatusNet - RSSCloud to German (Deutsch)
# Exported from translatewiki.net
#
# Author: ChrisiPK
# Author: David Güdel
# Author: Fujnky
# Author: Giftpflanze
# Author: Umherirrender
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:52+0000\n"
"Language-Team: German <https://translatewiki.net/wiki/Portal:de>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: de\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Ein URL-Parameter ist erforderlich."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Diese Ressource erfordert eine HTTP-GET-Anfrage."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Diese Ressource erfordert eine HTTP-POST-Anfrage."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "Anfrage muss ein POST sein."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr "Nur HTTP-POST-Benachrichtigungen werden im Moment unterstützt."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr "Die folgenden Parameter fehlten aus den Anforderungshauptteil: %s."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"Du musst mindestens eine gültige Profil-Feed-URL angeben (url1, url2, url3 … "
"urlN)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr "Feed-Abonnement fehlgeschlagen: Ungültiger Feed."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"Feed-Abonnement fehlgeschlagen: Benachrichtigungs-Handler reagiert nicht "
"richtig."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Vielen Dank für das Abonnement. Du wirst benachrichtigt, wenn der Feed "
"aktualisiert wird."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"Das RSSClound-Plugin ermöglicht ihrere StatusNet-Instanz, in Echtzeit "
"Updates für Profil-RSS-Feeds mit Hilfe des RSSCloud-Protokolls zu veröffentlichen."


code:
# Translation of StatusNet - RSSCloud to Spanish (español)
# Exported from translatewiki.net
#
# Author: Armando-Martin
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Se requiere un parámetro de dirección URL."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Este recurso requiere una solicitud HTTP GET."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Este recurso necesita una solicitud HTTP POST."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "La solicitud debe ser POST."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr ""
"En este momento son compatibles solamente las notificaciones de HTTP POST."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr "Faltan los siguientes parámetros en el cuerpo de la solicitud:  %s ."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"Debe proporcionar al menos una dirección URL de canal web (feed), de perfil "
"válido, (url1, url2, url3... urlN)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr ""
"Error en la suscripción al canal web (feed): No es un canal web válido."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"Error en la suscripción al canal web (feed): el controlador de "
"notificaciones no responde correctamente."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Gracias por la suscripción. Cuando se actualice el canal web (feed), se le "
"notificará."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"El complemento (plugin) RSSCloud permite a su instancia StatusNet publicar "
"actualizaciones en tiempo real para canales RSS de perfil mediante el protocolo RSSCloud."


code:
# Translation of StatusNet - RSSCloud to Basque (euskara)
# Exported from translatewiki.net
#
# Author: Artsuaga
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "URL parametroa ezinbestekoa da."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Baliabide honek HTTP GET bat behar du."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Metodo honek POST bat behar du."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "Eskariak POST izan behar du."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr ""

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr ""

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr ""

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""


code:
# Translation of StatusNet - RSSCloud to French (français)
# Exported from translatewiki.net
#
# Author: Crochet.david
# Author: Gomoko
# Author: Od1n
# Author: Valeryan 24
# Author: Verdy p
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Language-Team: French <https://translatewiki.net/wiki/Portal:fr>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: fr\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Un paramètre d’URL est requis."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Cette ressource nécessite une requête HTTP GET."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Cette ressource nécessite une requête HTTP POST."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "La requête HTTP au nuage RSS doit être de type POST."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr "Seuls les notifications HTTP POST sont pris en charge en ce moment."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr ""
"Les paramètres suivants étaient absents du corps de la requête au nuage "
"RSS : %s."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"Vous devez fournir au moins une URL de flux de profil valide (url1, url2, "
"url3 ... urlN)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr "L’abonnement au flux a échoué : ce n’est pas un flux RSS valide."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"L’abonnement au flux RSS a échoué : le gestionnaire de notifications ne "
"répond pas correctement."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Merci pour l’abonnement. Vous serez informé lors des mises à jour de ce(s) "
"flux."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"L’extension RSSCloud permet à votre instance StatusNet de publier des mises "
"à jour en temps réel sur des flux RSS en utilisant le protocole RSSCloud."


code:
# Translation of StatusNet - RSSCloud to Galician (galego)
# Exported from translatewiki.net
#
# Author: Toliño
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Language-Team: Galician <https://translatewiki.net/wiki/Portal:gl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: gl\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Cómpre un parámetro de enderezo URL."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Este recurso necesita unha solicitude HTTP GET."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Este recurso necesita unha solicitude HTTP POST."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "A solicitude debe ser POST."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr "Nestes intres, unicamente as notificacións HTTP POST están soportadas."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr "Faltan os seguintes parámetros no corpo da solicitude: %s."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"Debe proporcionar, polo menos, un enderezo URL de fonte de novas de perfil "
"válido (url1, url2, url3, ..., urlN)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr ""
"Erro na subscrición á fonte de novas: Non é unha fonte de novas válida."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"Erro na subscrición á fonte de novas: O xestor de notificacións non responde "
"correctamente."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Grazas pola subcrición. Cando o(s) filtro(s) se actualice(n), recibirá unha "
"notificación."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"O complemento RSSCloud activa a súa instancia StatusNet para publicar "
"actualizacións en tempo real para as fontes de novas RSS dos perfís mediante "
"o protocolo RSSCloud."


code:
# Translation of StatusNet - RSSCloud to Interlingua (interlingua)
# Exported from translatewiki.net
#
# Author: McDutchie
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Language-Team: Interlingua <https://translatewiki.net/wiki/Portal:ia>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: ia\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Un parametro de URL es obligatori."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Iste ressource require un HTTP GET."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Iste ressource require un HTTP POST."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "Requesta debe esser POST."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr "Solmente le notificationes HTTP POST es presentemente supportate."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr "Le sequente parametros mancava del corpore del requesta: %s."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"Tu debe fornir al minus un URL de syndication de profilo valide (url1, url2, "
"url3 ... urlN)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr "Le subscription al syndication ha fallite: Non un syndication valide."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"Le subscription al syndication ha fallite: le gestor de notification non "
"responde correctemente."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Gratias pro le subscription. Quando le syndication(es) se actualisa, tu "
"recipera notification."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"Le plug-in RSSCloud permitte que tu installation de SatusNet publica "
"actualisationes in directo de syndicationes RSS de profilos usante le protocollo RSSCloud."


code:
# Translation of StatusNet - RSSCloud to Macedonian (македонски)
# Exported from translatewiki.net
#
# Author: Bjankuloski06
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Language-Team: Macedonian <https://translatewiki.net/wiki/Portal:mk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: mk\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=2; plural=(n == 1 || n%10 == 1) ? 0 : 1;\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Се бара параметар за URL."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Овој ресурс бара HTTP GET."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Овој ресурс бара HTTP POST."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "Барањето мора да биде POST."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr "Моментално се поддржани само известувања од типот HTTP POST."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr "Следниве параметри недостасуваа од содржината на барањето: %s."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"Мора да наведете барем едеа важечка URL-адреса за профилен канал (url1, "
"url2, url3 ... urlN)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr "Претплаќањето на каналот не успеа: Не е важечки канал."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"!Претплаќањето на каналот не успеа: ракувачот со известувања не одговара "
"правилно."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Ви благодариме шт осе претплативте. Ќе бидете известени за сите подновувања "
"на каналот/ите."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"Приклучокот RSSCloud овозможува Вашиот примерок на StatusNet да објавува "
"поднови во живо за профилни RSS-емитувања користејќи го протоколот RSSCloud."


code:
# Translation of StatusNet - RSSCloud to Norwegian (bokmål) (Norsk (bokmål))
# Expored from translatewiki.net
#
# Author: Nghtwlkr
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-12-16 15:08+0000\n"
"PO-Revision-Date: 2010-12-16 15:12:52+0000\n"
"Language-Team: Norwegian (bokmål) <http://translatewiki.net/wiki/Portal:no>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2010-11-30 20:43:54+0000\n"
"X-Generator: MediaWiki 1.18alpha (r78478); Translate extension (2010-09-17)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: no\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: RSSCloudRequestNotify.php:90
msgid "Request must be POST."
msgstr "Forespørsel må være POST."

#: RSSCloudRequestNotify.php:107
msgid "Only http-post notifications are supported at this time."
msgstr ""

#. TRANS: %s is a comma separated list of parameters.
#: RSSCloudRequestNotify.php:118
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr ""

#: RSSCloudRequestNotify.php:124
msgid ""
"You must provide at least one valid profile feed url (url1, url2, url3 ... "
"urlN)."
msgstr ""

#: RSSCloudRequestNotify.php:141
msgid "Feed subscription failed: Not a valid feed."
msgstr ""

#: RSSCloudRequestNotify.php:147
msgid ""
"Feed subscription failed - notification handler doesn't respond correctly."
msgstr ""

#: RSSCloudRequestNotify.php:161
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""

#: LoggingAggregator.php:93
msgid "This resource requires an HTTP GET."
msgstr "Denne ressursen krever en HTTP GET."

#: LoggingAggregator.php:104
msgid "This resource requires an HTTP POST."
msgstr "Denne ressursen krever en HTTP POST."

#: RSSCloudPlugin.php:248
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""


code:
# Translation of StatusNet - RSSCloud to Dutch (Nederlands)
# Exported from translatewiki.net
#
# Author: Siebrand
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Language-Team: Dutch <https://translatewiki.net/wiki/Portal:nl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: nl\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Er is een URL-parameter vereist."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Deze bron heeft een HTTP GET nodig."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Deze bron heeft een HTTP POST nodig."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "Het verzoek moet POST zijn."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr "Op dit moment worden alleen mededelingen via HTTP POST ondersteund."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr "De volgende parameters missen in de verzoekinhoud: %s."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"U moet tenminste één geldige URL voor een profielfeed opgeven ( url1, url2, "
"url3, ..., urlN)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr "Abonneren op de feed is mislukt: geen geldige feed."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"Abonneren op de feed is mislukt: het verwerkingsproces heeft niet juist "
"geantwoord."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Dank u wel voor het abonneren. Als de feed of feeds bijgewerkt worden, wordt "
"u op de hoogte gesteld."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"De plug-in RSSCloud laat StatusNet real-time updates publiceren voor de RSS-"
"feeds van profielen met het protocol RSSCloud."


code:
# Translation of StatusNet - RSSCloud to Tagalog (Tagalog)
# Exported from translatewiki.net
#
# Author: AnakngAraw
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Language-Team: Tagalog <https://translatewiki.net/wiki/Portal:tl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: tl\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Kailangan ang isang parametro ng URL."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Ang pinagkunang ito ay nangangailangan ng HTTP GET."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Ang pinagkukunang ito ay nangangailangan ng HTTP POST."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "Ang hiling ay dapat na POST."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr ""
"Tanging mga pabatid na HTTP POST lamang ang tinatangkilik sa panahong ito."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr "Nawawala ang sumusunod na mga parametro mula sa katawan ng hiling: %s."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"Dapat kang magbigay ng kahit na isang katanggap-tanggap na URL ng pakain ng "
"balangkas (url1, url2, url3 ... urlN)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr "Nabigo ang pagtanggap ng pakain: Hindi isang tanggap na pakain."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"Nabigo ang pagpapasipi ng pakain: Hindi tama ang pagtugon ng tagapaghawak ng "
"pabatid."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Salamat sa pagtatanggap.  Kapag nagsapanahon ang (mga) pakain, pababatiran "
"ka."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"Ang pamasak ng RSSCloud ay nagpapahintulot sa iyong pagkakataon ng StatusNet "
"na maglathala ng mga pagsasapanahong pangtunay na oras para sa mga balangkas "
"ng mga pakain ng RSS na gumagamit ng protokolo ng RSSCloud."


code:
# Translation of StatusNet - RSSCloud to Ukrainian (українська)
# Exported from translatewiki.net
#
# Author: Boogie
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - RSSCloud\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:09:53+0000\n"
"Language-Team: Ukrainian <https://translatewiki.net/wiki/Portal:uk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-08-16 06:59:35+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: uk\n"
"X-Message-Group: #out-statusnet-plugin-rsscloud\n"
"Plural-Forms: nplurals=3; plural=(n%10 == 1 && n%100 != 11) ? 0 : ( (n%10 >= "
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"

#. TRANS: Form validation error displayed when a URL parameter is missing.
msgid "A URL parameter is required."
msgstr "Параметр URL-адреси є обов’язковим."

#. TRANS: Form validation error displayed when HTTP GET is not used.
msgid "This resource requires an HTTP GET."
msgstr "Цей ресурс вимагає форми HTTP GET."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "This resource requires an HTTP POST."
msgstr "Цей ресурс вимагає форми HTTP POST."

#. TRANS: Form validation error displayed when POST is not used.
msgid "Request must be POST."
msgstr "Запит вимагає форми POST."

#. TRANS: Form validation error displayed when HTTP POST is not used.
msgid "Only HTTP POST notifications are supported at this time."
msgstr "На даний момент підтримуються лише сповіщення форми http-post."

#. TRANS: List separator.
msgctxt "SEPARATOR"
msgid ", "
msgstr ", "

#. TRANS: Form validation error displayed when a request body is missing expected parameters.
#. TRANS: %s is a list of parameters separated by a list separator (default: ", ").
#, php-format
msgid "The following parameters were missing from the request body: %s."
msgstr "У формі запиту відсутні наступні параметри: %s."

#. TRANS: Form validation error displayed when not providing any valid profile feed URLs.
msgid ""
"You must provide at least one valid profile feed URL (url1, url2, url3 ... "
"urlN)."
msgstr ""
"Ви повинні зазначити щонайменше одну дійсну  URL-адресу веб-стрічки (URL-"
"адреси через кому та пробіл)."

#. TRANS: Form validation error displayed when not providing a valid feed URL.
msgid "Feed subscription failed: Not a valid feed."
msgstr "Підписатися до веб-стрічки не вдалося: ця веб-стрічка не є дійсною."

#. TRANS: Form validation error displayed when feed subscription failed.
msgid ""
"Feed subscription failed: Notification handler does not respond correctly."
msgstr ""
"Підписатися до веб-стрічки не вдалося: обробник сповіщень відреагував "
"неправильно."

#. TRANS: Success message after subscribing to one or more feeds.
msgid ""
"Thanks for the subscription. When the feed(s) update(s), you will be "
"notified."
msgstr ""
"Дякуємо за підписку. Коли веб-стрічки оновляться, вас буде поінформовано."

#. TRANS: Plugin description.
msgid ""
"The RSSCloud plugin enables your StatusNet instance to publish real-time "
"updates for profile RSS feeds using the RSSCloud protocol."
msgstr ""
"Додаток RSSCloud дозволяє вашому StatusNet-сумісному сайту публікувати "
"оновлення з веб-стрічок у реальному часі, використовуючи протокол RSSCloud."


code:
.fake: all clean

all: realtimeupdate.min.js

clean:
	rm -f js/realtimeupdate.min.js

realtimeupdate.min.js: js/realtimeupdate.js
	yui-compressor js/realtimeupdate.js > js/realtimeupdate.min.js

path: plugins/Realtime/Makefile


code:
As of StatusNet 1.0.x, actual formatting of the notices is done server-side,
loaded by AJAX after the real-time notification comes in. This has the drawback
that we may make extra HTTP requests and delay incoming notices a little, but
means that formatting and internationalization is consistent.

== TODO ==
* Update mark behaviour (on notice send)
* Pause, Send a notice ~ should not update counter
* Pause ~ retain up to 50-100 most recent notices
* Make it work for Conversation page (perhaps a little tricky)
* IE is updating the counter in document title all the time (Not sure if this
  is still an issue)
* Reconsider the timestamp approach

path: plugins/Realtime/README


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Superclass for plugins that do "real time" updates of timelines using Ajax
 *
 * 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  Plugin
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @author    Mikael Nordfeldth <mmn@hethane.se>
 * @copyright 2009 StatusNet, Inc.
 * @copyright 2014 Free Software Foundation, 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('GNUSOCIAL')) { exit(1); }

/**
 * Superclass for plugin to do realtime updates
 *
 * Based on experience with the Comet and Meteor plugins,
 * this superclass extracts out some of the common functionality
 *
 * Currently depends on Favorite plugin.
 *
 * @category Plugin
 * @package  StatusNet
 * @author   Evan Prodromou <evan@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 RealtimePlugin extends Plugin
{
    protected $showurl = null;

    /**
     * When it's time to initialize the plugin, calculate and
     * pass the URLs we need.
     */
    function onInitializePlugin()
    {
        // FIXME: need to find a better way to pass this pattern in
        $this->showurl = common_local_url('shownotice',
                                            array('notice' => '0000000000'));
        return true;
    }

    function onCheckSchema()
    {
        $schema = Schema::get();
        $schema->ensureTable('realtime_channel', Realtime_channel::schemaDef());
        return true;
    }

    /**
     * Hook for RouterInitialized event.
     *
     * @param URLMapper $m path-to-action mapper
     * @return boolean hook return
     */
    public function onRouterInitialized(URLMapper $m)
    {
        $m->connect('main/channel/:channelkey/keepalive',
                    array('action' => 'keepalivechannel'),
                    array('channelkey' => '[a-z0-9]{32}'));
        $m->connect('main/channel/:channelkey/close',
                    array('action' => 'closechannel'),
                    array('channelkey' => '[a-z0-9]{32}'));
        return true;
    }

    function onEndShowScripts($action)
    {
        $channel = $this->_getChannel($action);

        if (empty($channel)) {
            return true;
        }

        $timeline = $this->_pathToChannel(array($channel->channel_key));

        // If there's not a timeline on this page,
        // just return true

        if (empty($timeline)) {
            return true;
        }

        $base = $action->selfUrl();
        if (mb_strstr($base, '?')) {
            $url = $base . '&realtime=1';
        } else {
            $url = $base . '?realtime=1';
        }

        $scripts = $this->_getScripts();

        foreach ($scripts as $script) {
            $action->script($script);
        }

        $user = common_current_user();

        if (!empty($user->id)) {
            $user_id = $user->id;
        } else {
            $user_id = 0;
        }

        if ($action->boolean('realtime')) {
            $realtimeUI = ' RealtimeUpdate.initPopupWindow();';
        }
        else {
            $pluginPath = common_path('plugins/Realtime/');
            $keepalive = common_local_url('keepalivechannel', array('channelkey' => $channel->channel_key));
            $close = common_local_url('closechannel', array('channelkey' => $channel->channel_key));
            $realtimeUI = ' RealtimeUpdate.initActions('.json_encode($url).', '.json_encode($timeline).', '.json_encode($pluginPath).', '.json_encode($keepalive).', '.json_encode($close).'); ';
        }

        $script = ' $(document).ready(function() { '.
          $realtimeUI.
            $this->_updateInitialize($timeline, $user_id).
          '}); ';
        $action->inlineScript($script);

        return true;
    }

    public function onEndShowStylesheets(Action $action)
    {
        $urlpath = self::staticPath(str_replace('Plugin','',__CLASS__),
                                    'css/realtimeupdate.css');
        $action->cssLink($urlpath, null, 'screen, projection, tv');
        return true;
    }

    public function onHandleQueuedNotice(Notice $notice)
    {
        $paths = array();

        // Add to the author's timeline

        try {
            $profile = $notice->getProfile();
        } catch (Exception $e) {
            $this->log(LOG_ERR, $e->getMessage());
            return true;
        }

        try {
            $user = $profile->getUser();
            $paths[] = array('showstream', $user->nickname, null);
        } catch (NoSuchUserException $e) {
            // We really should handle the remote profile views too
            $user = null;
        }

        // Add to the public timeline

        if ($notice->is_local == Notice::LOCAL_PUBLIC ||
            ($notice->is_local == Notice::REMOTE && !common_config('public', 'localonly'))) {
            $paths[] = array('public', null, null);
        }

        // Add to the tags timeline

        $tags = $this->getNoticeTags($notice);

        if (!empty($tags)) {
            foreach ($tags as $tag) {
                $paths[] = array('tag', $tag, null);
            }
        }

        // Add to inbox timelines
        // XXX: do a join

        $ni = $notice->whoGets();

        foreach (array_keys($ni) as $user_id) {
            $user = User::getKV('id', $user_id);
            $paths[] = array('all', $user->nickname, null);
        }

        // Add to the replies timeline

        $reply = new Reply();
        $reply->notice_id = $notice->id;

        if ($reply->find()) {
            while ($reply->fetch()) {
                $user = User::getKV('id', $reply->profile_id);
                if (!empty($user)) {
                    $paths[] = array('replies', $user->nickname, null);
                }
            }
        }

        // Add to the group timeline
        // XXX: join

        $gi = new Group_inbox();
        $gi->notice_id = $notice->id;

        if ($gi->find()) {
            while ($gi->fetch()) {
                $ug = User_group::getKV('id', $gi->group_id);
                $paths[] = array('showgroup', $ug->nickname, null);
            }
        }

        if (count($paths) > 0) {

            $json = $this->noticeAsJson($notice);

            $this->_connect();

            // XXX: We should probably fan-out here and do a
            // new queue item for each path

            foreach ($paths as $path) {

                list($action, $arg1, $arg2) = $path;

                $channels = Realtime_channel::getAllChannels($action, $arg1, $arg2);
                $this->log(LOG_INFO, sprintf(_("%d candidate channels for notice %d"),
                                             count($channels),
                                             $notice->id));

                foreach ($channels as $channel) {

                    // XXX: We should probably fan-out here and do a
                    // new queue item for each user/path combo

                    if (is_null($channel->user_id)) {
                        $profile = null;
                    } else {
                        $profile = Profile::getKV('id', $channel->user_id);
                    }
                    if ($notice->inScope($profile)) {
                        $this->log(LOG_INFO,
                                   sprintf(_("Delivering notice %d to channel (%s, %s, %s) for user '%s'"),
                                           $notice->id,
                                           $channel->action,
                                           $channel->arg1,
                                           $channel->arg2,
                                           ($profile) ? ($profile->nickname) : "<public>"));
                        $timeline = $this->_pathToChannel(array($channel->channel_key));
                        $this->_publish($timeline, $json);
                    }
                }
            }

            $this->_disconnect();
        }

        return true;
    }

    function onStartShowBody($action)
    {
        $realtime = $action->boolean('realtime');
        if (!$realtime) {
            return true;
        }

        $action->elementStart('body',
                              (common_current_user()) ? array('id' => $action->trimmed('action'),
                                                              'class' => 'user_in realtime-popup')
                              : array('id' => $action->trimmed('action'),
                                      'class'=> 'realtime-popup'));

        // XXX hack to deal with JS that tries to get the
        // root url from page output

        $action->elementStart('address');

        if (common_config('singleuser', 'enabled')) {
            $user = User::singleUser();
            $url = common_local_url('showstream', array('nickname' => $user->nickname));
        } else {
            $url = common_local_url('public');
        }

        $action->element('a', array('class' => 'url',
                                    'href' => $url),
                         '');

        $action->elementEnd('address');

        $action->showContentBlock();
        $action->showScripts();
        $action->elementEnd('body');
        return false; // No default processing
    }

    function noticeAsJson($notice)
    {
        // FIXME: this code should be abstracted to a neutral third
        // party, like Notice::asJson(). I'm not sure of the ethics
        // of refactoring from within a plugin, so I'm just abusing
        // the ApiAction method. Don't do this unless you're me!

        $act = new ApiAction('/dev/null');

        $arr = $act->twitterStatusArray($notice, true);
        $arr['url'] = $notice->getUrl(true);
        $arr['html'] = htmlspecialchars($notice->rendered);
        $arr['source'] = htmlspecialchars($arr['source']);
        $arr['conversation_url'] = $notice->getConversationUrl();

        $profile = $notice->getProfile();
        $arr['user']['profile_url'] = $profile->profileurl;

        // Add needed repeat data

        if (!empty($notice->repeat_of)) {
            $original = Notice::getKV('id', $notice->repeat_of);
            if ($original instanceof Notice) {
                $arr['retweeted_status']['url'] = $original->getUrl(true);
                $arr['retweeted_status']['html'] = htmlspecialchars($original->rendered);
                $arr['retweeted_status']['source'] = htmlspecialchars($original->source);
                $originalProfile = $original->getProfile();
                $arr['retweeted_status']['user']['profile_url'] = $originalProfile->profileurl;
                $arr['retweeted_status']['conversation_url'] = $original->getConversationUrl();
            }
            unset($original);
        }

        return $arr;
    }

    function getNoticeTags($notice)
    {
        $tags = null;

        $nt = new Notice_tag();
        $nt->notice_id = $notice->id;

        if ($nt->find()) {
            $tags = array();
            while ($nt->fetch()) {
                $tags[] = $nt->tag;
            }
        }

        $nt->free();
        $nt = null;

        return $tags;
    }

    function _getScripts()
    {
        $urlpath = self::staticPath(str_replace('Plugin','',__CLASS__),
                                    'js/realtimeupdate.js');
        return array($urlpath);
    }

    /**
     * Export any i18n messages that need to be loaded at runtime...
     *
     * @param Action $action
     * @param array $messages
     *
     * @return boolean hook return value
     */
    function onEndScriptMessages($action, &$messages)
    {
        // TRANS: Text label for realtime view "play" button, usually replaced by an icon.
        $messages['realtime_play'] = _m('BUTTON', 'Play');
        // TRANS: Tooltip for realtime view "play" button.
        $messages['realtime_play_tooltip'] = _m('TOOLTIP', 'Play');
        // TRANS: Text label for realtime view "pause" button
        $messages['realtime_pause'] = _m('BUTTON', 'Pause');
        // TRANS: Tooltip for realtime view "pause" button
        $messages['realtime_pause_tooltip'] = _m('TOOLTIP', 'Pause');
        // TRANS: Text label for realtime view "popup" button, usually replaced by an icon.
        $messages['realtime_popup'] = _m('BUTTON', 'Pop up');
        // TRANS: Tooltip for realtime view "popup" button.
        $messages['realtime_popup_tooltip'] = _m('TOOLTIP', 'Pop up in a window');

        return true;
    }

    function _updateInitialize($timeline, $user_id)
    {
        return "RealtimeUpdate.init($user_id, \"$this->showurl\"); ";
    }

    function _connect()
    {
    }

    function _publish($timeline, $json)
    {
    }

    function _disconnect()
    {
    }

    function _pathToChannel($path)
    {
        return '';
    }

    function _getTimeline($action)
    {
        $channel = $this->_getChannel($action);
        if (empty($channel)) {
            return null;
        }

        return $this->_pathToChannel(array($channel->channel_key));
    }

    function _getChannel($action)
    {
        $timeline = null;
        $arg1     = null;
        $arg2     = null;

        $action_name = $action->trimmed('action');

        // FIXME: lists
        // FIXME: search (!)
        // FIXME: profile + tag

        switch ($action_name) {
         case 'public':
            // no arguments
            break;
         case 'tag':
            $tag = $action->trimmed('tag');
            if (!empty($tag)) {
                $arg1 = $tag;
            } else {
                $this->log(LOG_NOTICE, "Unexpected 'tag' action without tag argument");
                return null;
            }
            break;
         case 'showstream':
         case 'all':
         case 'replies':
         case 'showgroup':
            $nickname = common_canonical_nickname($action->trimmed('nickname'));
            if (!empty($nickname)) {
                $arg1 = $nickname;
            } else {
                $this->log(LOG_NOTICE, "Unexpected $action_name action without nickname argument.");
                return null;
            }
            break;
         default:
            return null;
        }

        $user = common_current_user();

        $user_id = (!empty($user)) ? $user->id : null;

        $channel = Realtime_channel::getChannel($user_id,
                                                $action_name,
                                                $arg1,
                                                $arg2);

        return $channel;
    }

    function onStartReadWriteTables(&$alwaysRW, &$rwdb)
    {
        $alwaysRW[] = 'realtime_channel';
        return true;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * action to close a channel
 *
 * 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/>.
 *
 * @category  Realtime
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * Action to close a channel
 *
 * @category  Realtime
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class ClosechannelAction extends Action
{
    protected $channelKey = null;
    protected $channel    = null;

    /**
     * For initializing members of the class.
     *
     * @param array $argarray misc. arguments
     *
     * @return boolean true
     */
    function prepare($argarray)
    {
        parent::prepare($argarray);

        if (!$this->isPost()) {
            // TRANS: Client exception. Do not translate POST.
            throw new ClientException(_m('You have to POST it.'));
        }

        $this->channelKey = $this->trimmed('channelkey');

        if (empty($this->channelKey)) {
            // TRANS: Client exception thrown when the channel key argument is missing.
            throw new ClientException(_m('No channel key argument.'));
        }

        $this->channel = Realtime_channel::getKV('channel_key', $this->channelKey);

        if (empty($this->channel)) {
            // TRANS: Client exception thrown when referring to a non-existing channel.
            throw new ClientException(_m('No such channel.'));
        }

        return true;
    }

    /**
     * Handler method
     *
     * @param array $argarray is ignored since it's now passed in in prepare()
     *
     * @return void
     */
    function handle($argarray=null)
    {
        $this->channel->decrement();

        header('HTTP/1.1 204 No Content');

        return;
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        return false;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * action periodically pinged by a page to keep a channel alive
 *
 * 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/>.
 *
 * @category  Realtime
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    // This check helps protect against security problems;
    // your code file can't be executed directly from the web.
    exit(1);
}

/**
 * Action periodically pinged by a page to keep a channel alive
 *
 * @category  Realtime
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class KeepalivechannelAction extends Action
{
    protected $channelKey = null;
    protected $channel    = null;

    /**
     * For initializing members of the class.
     *
     * @param array $argarray misc. arguments
     *
     * @return boolean true
     */
    function prepare($argarray)
    {
        parent::prepare($argarray);

        if (!$this->isPost()) {
            // TRANS: Client exception. Do not translate POST.
            throw new ClientException(_m('You have to POST it.'));
        }

        $this->channelKey = $this->trimmed('channelkey');

        if (empty($this->channelKey)) {
            // TRANS: Client exception thrown when the channel key argument is missing.
            throw new ClientException(_m('No channel key argument.'));
        }

        $this->channel = Realtime_channel::getKV('channel_key', $this->channelKey);

        if (empty($this->channel)) {
            // TRANS: Client exception thrown when referring to a non-existing channel.
            throw new ClientException(_m('No such channel.'));
        }

        return true;
    }

    /**
     * Handler method
     *
     * @param array $argarray is ignored since it's now passed in in prepare()
     *
     * @return void
     */
    function handle($argarray=null)
    {
        $this->channel->touch();

        header('HTTP/1.1 204 No Content');

        return;
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        return false;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * A channel for real-time browser data
 *
 * 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/>.
 *
 * @category  Realtime
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

if (!defined('STATUSNET')) {
    exit(1);
}

/**
 * A channel for real-time browser data
 *
 * For each user currently browsing the site, we want to know which page they're on
 * so we can send real-time updates to their browser.
 *
 * @category Realtime
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * @see      DB_DataObject
 */
class Realtime_channel extends Managed_DataObject
{
    const TIMEOUT = 1800; // 30 minutes

    public $__table = 'realtime_channel'; // table name

    public $user_id;       // int -> user.id, can be null
    public $action;        // string
    public $arg1;          // argument
    public $arg2;          // argument, usually null
    public $channel_key;   // 128-bit shared secret key
    public $audience;      // listener count
    public $created;       // created date
    public $modified;      // modified date

    /**
     * The One True Thingy that must be defined and declared.
     */
    public static function schemaDef()
    {
        return array(
            'description' => 'A channel of realtime notice data',
            'fields' => array(
                'user_id' => array('type' => 'int',
                                   'not null' => false,
                                   'description' => 'user viewing page; can be null'),
                'action' => array('type' => 'varchar',
                                  'length' => 255,
                                  'not null' => true,
                                  'description' => 'page being viewed'),
                'arg1' => array('type' => 'varchar',
                                'length' => 255,
                                'not null' => false,
                                'description' => 'page argument, like username or tag'),
                'arg2' => array('type' => 'varchar',
                                'length' => 255,
                                'not null' => false,
                                'description' => 'second page argument, like tag for showstream'),
                'channel_key' => array('type' => 'varchar',
                               'length' => 32,
                               'not null' => true,
                               'description' => 'shared secret key for this channel'),
                'audience' => array('type' => 'integer',
                                    'not null' => true,
                                    'default' => 0,
                                    'description' => 'reference count'),
                'created' => array('type' => 'datetime',
                                   'not null' => true,
                                   'description' => 'date this record was created'),
                'modified' => array('type' => 'datetime',
                                    'not null' => true,
                                    'description' => 'date this record was modified'),
            ),
            'primary key' => array('channel_key'),
            'unique keys' => array('realtime_channel_user_page_idx' => array('user_id', 'action', 'arg1', 'arg2')),
            'foreign keys' => array(
                'realtime_channel_user_id_fkey' => array('user', array('user_id' => 'id')),
            ),
            'indexes' => array(
                'realtime_channel_modified_idx' => array('modified'),
                'realtime_channel_page_idx' => array('action', 'arg1', 'arg2')
            ),
        );
    }

    static function saveNew($user_id, $action, $arg1, $arg2)
    {
        $channel = new Realtime_channel();

        $channel->user_id = $user_id;
        $channel->action  = $action;
        $channel->arg1    = $arg1;
        $channel->arg2    = $arg2;
        $channel->audience  = 1;

        $channel->channel_key = common_random_hexstr(16); // 128-bit key, 32 hex chars

        $channel->created  = common_sql_now();
        $channel->modified = $channel->created;

        $channel->insert();

        return $channel;
    }

    static function getChannel($user_id, $action, $arg1, $arg2)
    {
        $channel = self::fetchChannel($user_id, $action, $arg1, $arg2);

        // Ignore (and delete!) old channels

        if (!empty($channel)) {
            $modTime = strtotime($channel->modified);
            if ((time() - $modTime) > self::TIMEOUT) {
                $channel->delete();
                $channel = null;
            }
        }

        if (empty($channel)) {
            $channel = self::saveNew($user_id, $action, $arg1, $arg2);
        }

        return $channel;
    }

    static function getAllChannels($action, $arg1, $arg2)
    {
        $channel = new Realtime_channel();

        $channel->action = $action;

        if (is_null($arg1)) {
            $channel->whereAdd('arg1 is null');
        } else {
            $channel->arg1 = $arg1;
        }

        if (is_null($arg2)) {
            $channel->whereAdd('arg2 is null');
        } else {
            $channel->arg2 = $arg2;
        }

        $channel->whereAdd('modified > "' . common_sql_date(time() - self::TIMEOUT) . '"');

        $channels = array();

        if ($channel->find()) {
            $channels = $channel->fetchAll();
        }

        return $channels;
    }

    static function fetchChannel($user_id, $action, $arg1, $arg2)
    {
        $channel = new Realtime_channel();

        if (is_null($user_id)) {
            $channel->whereAdd('user_id is null');
        } else {
            $channel->user_id = $user_id;
        }

        $channel->action = $action;

        if (is_null($arg1)) {
            $channel->whereAdd('arg1 is null');
        } else {
            $channel->arg1 = $arg1;
        }

        if (is_null($arg2)) {
            $channel->whereAdd('arg2 is null');
        } else {
            $channel->arg2 = $arg2;
        }

        if ($channel->find(true)) {
            $channel->increment();
            return $channel;
        } else {
            return null;
        }
    }

    function increment()
    {
        // XXX: race
        $orig = clone($this);
        $this->audience++;
        $this->modified = common_sql_now();
        $this->update($orig);
    }

    function touch()
    {
        // XXX: race
        $orig = clone($this);
        $this->modified = common_sql_now();
        $this->update($orig);
    }

    function decrement()
    {
        // XXX: race
        if ($this->audience == 1) {
            $this->delete();
        } else {
            $orig = clone($this);
            $this->audience--;
            $this->modified = common_sql_now();
            $this->update($orig);
        }
    }
}


code:
.realtime-popup address {
display:none;
}

.realtime-popup #content {
width:93.5%;
}

.realtime-popup #form_notice {
margin:18px 0 18px 1.795%;
width:93%;
max-width:451px;
}

.realtime-popup #form_notice label[for=notice_data-text],
.realtime-popup h1 {
display:none;
}

.realtime-popup #form_notice label.notice_data-attach,
.realtime-popup #form_notice input.notice_data-attach,
.realtime-popup #form_notice label.notice_data-geo {
top:0;
}

.realtime-popup #form_notice input.notice_data-attach {
left:auto;
right:0;
}

.realtime-popup .entity_profile {
width:70%;
}
.realtime-popup .entity_actions {
margin-left:1%;
}

#notices_primary {
position:relative;
}

#realtime_actions {
position: absolute;
top: -20px;
right: 0;
margin: 0 0 11px 0;
}

#realtime_actions li {
margin-left: 18px;
list-style-type: none;
float: left;
}

#realtime_actions button {
width: 16px;
height: 16px;
display: block;
border: none;
cursor: pointer;
text-indent: -9999px;
float: left;
}

#realtime_play {
margin-left: 4px;
}

#queued_counter {
float:left;
line-height:1.2;
}

#showstream #notices_primary {
margin-top: 18px;
}