code:
#!/usr/bin/env php
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2008-2010, 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/>.
 */

define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));

// Tune number of processes and how often to poll Twitter
// XXX: Should these things be in config.php?
define('MAXCHILDREN', 2);
define('POLL_INTERVAL', 70); // in seconds, Twitter API v1.1 says 15 calls every 15 mins

$shortoptions = 'di::';
$longoptions = array('id::', 'debug');

$helptext = <<<END_OF_TRIM_HELP
Batch script for retrieving Twitter messages from foreign service.

  -i --id              Identity (default 'generic')
  -d --debug           Debug (lots of log output)

END_OF_TRIM_HELP;

require_once INSTALLDIR . '/scripts/commandline.inc';
require_once INSTALLDIR . '/lib/common.php';
require_once INSTALLDIR . '/lib/daemon.php';
require_once dirname(__DIR__) . '/twitter.php';

/**
 * Fetch statuses from Twitter
 *
 * Fetches statuses from Twitter and inserts them as notices
 *
 * NOTE: an Avatar path MUST be set in config.php for this
 * script to work, e.g.:
 *     $config['avatar']['path'] = $config['site']['path'] . '/avatar/';
 *
 * @todo @fixme @gar Fix the above. For some reason $_path is always empty when
 * this script is run, so the default avatar path is always set wrong in
 * default.php. Therefore it must be set explicitly in config.php. --Z
 *
 * @category Twitter
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @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 TwitterStatusFetcher extends ParallelizingDaemon
{
    /**
     *  Constructor
     *
     * @param string  $id           the name/id of this daemon
     * @param int     $interval     sleep this long before doing everything again
     * @param int     $max_children maximum number of child processes at a time
     * @param boolean $debug        debug output flag
     *
     * @return void
     *
     **/
    function __construct($id = null, $interval = 60,
                         $max_children = 2, $debug = null)
    {
        parent::__construct($id, $interval, $max_children, $debug);
    }

    /**
     * Name of this daemon
     *
     * @return string Name of the daemon.
     */
    function name()
    {
        return ('twitterstatusfetcher.'.$this->_id);
    }

    /**
     * Find all the Twitter foreign links for users who have requested
     * importing of their friends' timelines
     *
     * @return array flinks an array of Foreign_link objects
     */
    function getObjects()
    {
        global $_DB_DATAOBJECT;
        $flink = new Foreign_link();
        $conn = &$flink->getDatabaseConnection();

        $flink->service = TWITTER_SERVICE;
        $flink->orderBy('last_noticesync');
        $flink->find();

        $flinks = array();

        while ($flink->fetch()) {

            if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
                FOREIGN_NOTICE_RECV) {
                $flinks[] = clone($flink);
                common_log(LOG_INFO, "sync: foreign id $flink->foreign_id");
            } else {
                common_log(LOG_INFO, "nothing to sync");
            }
        }

        $flink->free();
        unset($flink);

        $conn->disconnect();
        unset($_DB_DATAOBJECT['CONNECTIONS']);

        return $flinks;
    }

    // FIXME: make it so we can force a Foreign_link here without colliding with parent
    function childTask($flink) {
        // Each child ps needs its own DB connection

        // Note: DataObject::getDatabaseConnection() creates
        // a new connection if there isn't one already
        $conn = &$flink->getDatabaseConnection();

        $this->getTimeline($flink, 'home_timeline');
        $this->getTimeline($flink, 'mentions_timeline');

        $flink->last_friendsync = common_sql_now();
        $flink->update();

        $conn->disconnect();

        // XXX: Couldn't find a less brutal way to blow
        // away a cached connection
        global $_DB_DATAOBJECT;
        unset($_DB_DATAOBJECT['CONNECTIONS']);
    }

    function getTimeline(Foreign_link $flink, $timelineUri = 'home_timeline')
    {
        common_log(LOG_DEBUG, $this->name() . ' - Trying to get ' . $timelineUri .
                   ' timeline for Twitter user ' . $flink->foreign_id);

        $client = null;

        if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
            $token = TwitterOAuthClient::unpackToken($flink->credentials);
            $client = new TwitterOAuthClient($token->key, $token->secret);
            common_log(LOG_DEBUG, $this->name() . ' - Grabbing ' . $timelineUri . ' timeline with OAuth.');
        } else {
            common_log(LOG_ERR, "Skipping " . $timelineUri . " timeline for " .
                       $flink->foreign_id . " since not OAuth.");
        }

        $timeline = null;

        $lastId = Twitter_synch_status::getLastId($flink->foreign_id, $timelineUri);

        common_log(LOG_DEBUG, "Got lastId value '" . $lastId . "' for foreign id '" .
                     $flink->foreign_id . "' and timeline '" . $timelineUri. "'");

        try {
            $timeline = $client->statusesTimeline($lastId, $timelineUri);
        } catch (Exception $e) {
            common_log(LOG_ERR, $this->name() .
                       ' - Unable to get ' . $timelineUri . ' timeline for user ' . $flink->user_id .
                       ' - code: ' . $e->getCode() . 'msg: ' . $e->getMessage());
        }

        if (empty($timeline)) {
            common_log(LOG_DEBUG, $this->name() .  " - Empty '" . $timelineUri . "' timeline.");
            return;
        }

        common_log(LOG_INFO, $this->name() .
                   ' - Retrieved ' . sizeof($timeline) . ' statuses from ' . $timelineUri . ' timeline' .
                   ' - for user ' . $flink->user_id);

        if (!empty($timeline)) {
            $qm = QueueManager::get();

            // Reverse to preserve order
            foreach (array_reverse($timeline) as $status) {
                $data = array(
                    'status' => $status,
                    'for_user' => $flink->foreign_id,
                );
                $qm->enqueue($data, 'tweetin');
            }

            $lastId = twitter_id($timeline[0]);
            Twitter_synch_status::setLastId($flink->foreign_id, $timelineUri, $lastId);
            common_debug("Set lastId value '$lastId' for foreign id '{$flink->foreign_id}' and timeline '" .
                         $timelineUri . "'");
        }

        // Okay, record the time we synced with Twitter for posterity
        $flink->last_noticesync = common_sql_now();
        $flink->update();
    }
}

$id    = null;
$debug = null;

if (have_option('i')) {
    $id = get_option_value('i');
} else if (have_option('--id')) {
    $id = get_option_value('--id');
} else if (count($args) > 0) {
    $id = $args[0];
} else {
    $id = null;
}

if (have_option('d') || have_option('debug')) {
    $debug = true;
}

$fetcher = new TwitterStatusFetcher($id, POLL_INTERVAL, MAXCHILDREN, $debug);
$fetcher->runOnce();


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * 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    Brion Vibber <brion@status.net>
 * @copyright 2010 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/
 */

class OAuthData
{
    public $consumer_key, $consumer_secret, $token, $token_secret;
}

/**
 *
 */
abstract class JsonStreamReader
{
    const CRLF = "\r\n";

    public $id;
    protected $socket = null;
    protected $state = 'init'; // 'init', 'connecting', 'waiting', 'headers', 'active'

    public function __construct()
    {
        $this->id = get_class($this) . '.' . substr(md5(mt_rand()), 0, 8);
    }

    /**
     * Starts asynchronous connect operation...
     *
     * @fixme Can we do the open-socket fully async to? (need write select infrastructure)
     *
     * @param string $url
     */
    public function connect($url)
    {
        common_log(LOG_DEBUG, "$this->id opening connection to $url");

        $scheme = parse_url($url, PHP_URL_SCHEME);
        if ($scheme == 'http') {
            $rawScheme = 'tcp';
        } else if ($scheme == 'https') {
            $rawScheme = 'ssl';
        } else {
            // TRANS: Server exception thrown when an invalid URL scheme is detected.
            throw new ServerException(_m('Invalid URL scheme for HTTP stream reader.'));
        }

        $host = parse_url($url, PHP_URL_HOST);
        $port = parse_url($url, PHP_URL_PORT);
        if (!$port) {
            if ($scheme == 'https') {
                $port = 443;
            } else {
                $port = 80;
            }
        }

        $path = parse_url($url, PHP_URL_PATH);
        $query = parse_url($url, PHP_URL_QUERY);
        if ($query) {
            $path .= '?' . $query;
        }

        $errno = $errstr = null;
        $timeout = 5;
        //$flags = STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT;
        $flags = STREAM_CLIENT_CONNECT;
        // @fixme add SSL params
        $this->socket = stream_socket_client("$rawScheme://$host:$port", $errno, $errstr, $timeout, $flags);

        $this->send($this->httpOpen($host, $path));

        stream_set_blocking($this->socket, false);
        $this->state = 'waiting';
    }

    /**
     * Send some fun data off to the server.
     *
     * @param string $buffer
     */
    function send($buffer)
    {
        fwrite($this->socket, $buffer);
    }

    /**
     * Read next packet of data from the socket.
     *
     * @return string
     */
    function read()
    {
        $buffer = fread($this->socket, 65536);
        return $buffer;
    }

    /**
     * Build HTTP request headers.
     *
     * @param string $host
     * @param string $path
     * @return string
     */
    protected function httpOpen($host, $path)
    {
        $lines = array(
            "GET $path HTTP/1.1",
            "Host: $host",
            'User-Agent: ' . HTTPClient::userAgent() . ' (TwitterBridgePlugin)',
            "Connection: close",
            "",
            ""
        );
        return implode(self::CRLF, $lines);
    }

    /**
     * Close the current connection, if open.
     */
    public function close()
    {
        if ($this->isConnected()) {
            common_log(LOG_DEBUG, "$this->id closing connection.");
            fclose($this->socket);
            $this->socket = null;
        }
    }

    /**
     * Are we currently connected?
     *
     * @return boolean
     */
    public function isConnected()
    {
        return $this->socket !== null;
    }

    /**
     * Send any sockets we're listening on to the IO manager
     * to wait for input.
     *
     * @return array of resources
     */
    public function getSockets()
    {
        if ($this->isConnected()) {
            return array($this->socket);
        }
        return array();
    }

    /**
     * Take a chunk of input over the horn and go go go! :D
     *
     * @param string $buffer
     */
    public function handleInput($socket)
    {
        if ($this->socket !== $socket) {
            // TRANS: Exception thrown when input from an inexpected socket is encountered.
            throw new Exception(_m('Got input from unexpected socket!'));
        }

        try {
            $buffer = $this->read();
            $lines = explode(self::CRLF, $buffer);
            foreach ($lines as $line) {
                $this->handleLine($line);
            }
        } catch (Exception $e) {
            common_log(LOG_ERR, "$this->id aborting connection due to error: " . $e->getMessage());
            fclose($this->socket);
            throw $e;
        }
    }

    protected function handleLine($line)
    {
        switch ($this->state)
        {
            case 'waiting':
                $this->handleLineWaiting($line);
                break;
            case 'headers':
                $this->handleLineHeaders($line);
                break;
            case 'active':
                $this->handleLineActive($line);
                break;
            default:
                // TRANS: Exception thrown when an invalid state is encountered in handleLine.
                // TRANS: %s is the invalid state.
                throw new Exception(sprintf(_m('Invalid state in handleLine: %s.'),$this->state));
        }
    }

    /**
     *
     * @param <type> $line
     */
    protected function handleLineWaiting($line)
    {
        $bits = explode(' ', $line, 3);
        if (count($bits) != 3) {
            // TRANS: Exception thrown when an invalid response line is encountered.
            // TRANS: %s is the invalid line.
            throw new Exception(sprintf(_m('Invalid HTTP response line: %s.'),$line));
        }

        list($http, $status, $text) = $bits;
        if (substr($http, 0, 5) != 'HTTP/') {
            // TRANS: Exception thrown when an invalid response line part is encountered.
            // TRANS: %1$s is the chunk, %2$s is the line.
            throw new Exception(sprintf(_m('Invalid HTTP response line chunk "%1$s": %2$s.'),$http, $line));
        }
        if ($status != '200') {
            // TRANS: Exception thrown when an invalid response code is encountered.
            // TRANS: %1$s is the response code, %2$s is the line.
            throw new Exception(sprintf(_m('Bad HTTP response code %1$s: %2$s.'),$status,$line));
        }
        common_log(LOG_DEBUG, "$this->id $line");
        $this->state = 'headers';
    }

    protected function handleLineHeaders($line)
    {
        if ($line == '') {
            $this->state = 'active';
            common_log(LOG_DEBUG, "$this->id connection is active!");
        } else {
            common_log(LOG_DEBUG, "$this->id read HTTP header: $line");
            $this->responseHeaders[] = $line;
        }
    }

    protected function handleLineActive($line)
    {
        if ($line == "") {
            // Server sends empty lines as keepalive.
            return;
        }
        $data = json_decode($line);
        if ($data) {
            $this->handleJson($data);
        } else {
            common_log(LOG_ERR, "$this->id received bogus JSON data: " . var_export($line, true));
        }
    }

    abstract protected function handleJson(stdClass $data);
}

license: mit


code:
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

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

require_once dirname(__DIR__) . '/twitter.php';

/**
 * Queue handler to deal with incoming Twitter status updates, as retrieved by
 * TwitterDaemon (twitterdaemon.php).
 *
 * The queue handler passes the status through TwitterImporter for import into the
 * local database (if necessary), then adds the imported notice to the local inbox
 * of the attached Twitter user.
 *
 * Warning: the way we do inbox distribution manually means that realtime, XMPP, etc
 * don't work on Twitter-borne messages. When TwitterImporter is changed to handle
 * that correctly, we'll only need to do this once...?
 */
class TweetInQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'tweetin';
    }

    function handle($data) : bool
    {
        // JSON object with Twitter data
        $status = $data['status'];

        // Twitter user ID this incoming data belongs to.
        $receiver = $data['for_user'];

        $importer = new TwitterImport();
        $notice = $importer->importStatus($status);
        if ($notice instanceof Notice) {
            try {
                $flink = Foreign_link::getByForeignID($receiver, TWITTER_SERVICE);
                common_log(LOG_DEBUG, "TweetInQueueHandler - Got flink so add notice ".
                           $notice->id." to attentions for user ".$flink->user_id);
                try {
                    Attention::saveNew($notice, $flink->getProfile());
                } catch (Exception $e) {
                    // Log the exception, but make sure we don't bail out, we
                    // still have a queue item to remove here-after.
                    common_log(LOG_ERR, "Failed adding notice {$notice->id} to attentions for user {$flink->user_id}: " .
                                        $e->getMessage());
                }
            } catch (NoResultException $e) {
               common_log(LOG_DEBUG, "TweetInQueueHandler - No flink found for foreign user ".$receiver);
            }
        }

        return true;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * 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>
 * @author    Julien C <chaumond@gmail.com>
 * @author    Brion Vibber <brion@status.net>
 * @copyright 2009-2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

require_once dirname(__DIR__) . '/twitter.php';

/**
 * Encapsulation of the Twitter status -> notice incoming bridge import.
 * Is used by both the polling twitterstatusfetcher.php daemon, and the
 * in-progress streaming import.
 *
 * @category Plugin
 * @package  StatusNet
 * @author   Zach Copley <zach@status.net>
 * @author   Julien C <chaumond@gmail.com>
 * @author   Brion Vibber <brion@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/
 * @link     http://twitter.com/
 */
class TwitterImport
{
    public $avatarsizename = 'reasonably_small'; // a Twitter size name for 128x128 px
    public $avatarsize = 128;   // they're square...

    public function importStatus($status)
    {
        // Hacktastic: filter out stuff coming from this StatusNet
        $source = mb_strtolower(common_config('integration', 'source'));

        if (preg_match("/$source/", mb_strtolower($status->source))) {
            common_debug(__METHOD__ . ' - Skipping import of status ' .
                         twitter_id($status) . " with source {$source}");
            return null;
        }

        // Don't save it if the user is protected
        // FIXME: save it but treat it as private
        if ($status->user->protected) {
            return null;
        }

        $notice = $this->saveStatus($status);

        return $notice;
    }

    function name()
    {
        return get_class($this);
    }

    function saveStatus($status)
    {
        $profile = $this->ensureProfile($status->user);

        if (empty($profile)) {
            common_log(LOG_ERR, __METHOD__ . ' - Problem saving notice. No associated Profile.');
            return null;
        }

        $statusId = twitter_id($status);
        $statusUri = $this->makeStatusURI($status->user->screen_name, $statusId);

        // check to see if we've already imported the status
        $n2s = Notice_to_status::getKV('status_id', $statusId);

        if (!empty($n2s)) {
            common_log(
                LOG_INFO,
                __METHOD__ . " - Ignoring duplicate import: {$statusId}"
            );
            return Notice::getKV('id', $n2s->notice_id);
        }

        $dupe = Notice::getKV('uri', $statusUri);
        if($dupe instanceof Notice) {
            // Add it to our record
            Notice_to_status::saveNew($dupe->id, $statusId);
            common_log(
                LOG_INFO,
                __METHOD__ . " - Ignoring duplicate import: {$statusId}"
            );
            return $dupe;
        }

        // If it's a retweet, save it as a repeat!
        if (!empty($status->retweeted_status)) {
            common_log(LOG_INFO, "Status {$statusId} is a retweet of " . twitter_id($status->retweeted_status) . ".");
            $original = $this->saveStatus($status->retweeted_status);
            if (empty($original)) {
                return null;
            } else {
                $author = $original->getProfile();
                // TRANS: Message used to repeat a notice. RT is the abbreviation of 'retweet'.
                // TRANS: %1$s is the repeated user's name, %2$s is the repeated notice.
                $content = sprintf(_m('RT @%1$s %2$s'),
                                   $author->nickname,
                                   $original->content);

                if (Notice::contentTooLong($content)) {
                    $contentlimit = Notice::maxContent();
                    $content = mb_substr($content, 0, $contentlimit - 4) . ' ...';
                }

                $repeat = Notice::saveNew($profile->id,
                                          $content,
                                          'twitter',
                                          array('repeat_of' => $original->id,
                                                'uri' => $statusUri,
                                                'is_local' => Notice::GATEWAY,
                                          		'object_type' => ActivityObject::NOTE,
                                          		'verb' => ActivityVerb::POST
                                          ));
                common_log(LOG_INFO, "Saved {$repeat->id} as a repeat of {$original->id}");
                Notice_to_status::saveNew($repeat->id, $statusId);
                return $repeat;
            }
        }

        $notice = new Notice();

        $notice->profile_id 	= $profile->id;
        $notice->uri        	= $statusUri;
        $notice->url        	= $statusUri;
        $notice->verb       	= ActivityVerb::POST;
        $notice->object_type 	= ActivityObject::NOTE;
        $notice->created    	= strftime(
            '%Y-%m-%d %H:%M:%S',
            strtotime($status->created_at)
        );

        $notice->source     	= 'twitter';

        $notice->reply_to   	= null;

        $replyTo = twitter_id($status, 'in_reply_to_status_id');
        if (!empty($replyTo)) {
            common_log(LOG_INFO, "Status {$statusId} is a reply to status {$replyTo}");
            $n2s = Notice_to_status::getKV('status_id', $replyTo);
            if (empty($n2s)) {
                common_log(LOG_INFO, "Couldn't find local notice for status {$replyTo}");
            } else {
                $reply = Notice::getKV('id', $n2s->notice_id);
                if (empty($reply)) {
                    common_log(LOG_INFO, "Couldn't find local notice for status {$replyTo}");
                } else {
                    common_log(LOG_INFO, "Found local notice {$reply->id} for status {$replyTo}");
                    $notice->reply_to     = $reply->id;
                    $notice->conversation = $reply->conversation;
                }
            }
        }

        $notice->is_local   = Notice::GATEWAY;

        $notice->content  = html_entity_decode($this->linkify($status, FALSE), ENT_QUOTES, 'UTF-8');
        $notice->rendered = $this->linkify($status, TRUE);

        if (Event::handle('StartNoticeSave', array(&$notice))) {

            if (empty($notice->conversation)) {
                $conv = Conversation::create();
                common_log(LOG_INFO, "No known conversation for status {$statusId} so a new one ({$conv->getID()}) was created.");
                $notice->conversation = $conv->getID();
            }

            $id = $notice->insert();

            if ($id === false) {
                common_log_db_error($notice, 'INSERT', __FILE__);
                common_log(LOG_ERR, __METHOD__ . ' - Problem saving notice.');
            }

            Event::handle('EndNoticeSave', array($notice));
        }

        Notice_to_status::saveNew($notice->id, $statusId);

        $this->saveStatusMentions($notice, $status);
        $this->saveStatusAttachments($notice, $status);

        $notice->blowOnInsert();

        return $notice;
    }

    /**
     * Make an URI for a status.
     *
     * @param object $status status object
     *
     * @return string URI
     */
    function makeStatusURI($username, $id)
    {
        return 'https://twitter.com/'
          . $username
          . '/status/'
          . $id;
    }

    /**
     * Look up a Profile by profileurl field.  Profile::getKV() was
     * not working consistently.
     *
     * @param string $nickname   local nickname of the Twitter user
     * @param string $profileurl the profile url
     *
     * @return mixed value the first Profile with that url, or null
     */
    protected function getProfileByUrl($nickname, $profileurl)
    {
        $profile = new Profile();
        $profile->nickname = $nickname;
        $profile->profileurl = $profileurl;
        $profile->limit(1);

        if (!$profile->find(true)) {
            $profile->profileurl = str_replace('https://', 'http://', $profileurl);
            if (!$profile->find(true)) {
                throw new NoResultException($profile);
            }
        }
        return $profile;
    }

    protected function ensureProfile($twuser)
    {
        // check to see if there's already a profile for this user
        $profileurl = 'https://twitter.com/' . $twuser->screen_name;
        try {
            $profile = $this->getProfileByUrl($twuser->screen_name, $profileurl);
            $this->updateAvatar($twuser, $profile);
            return $profile;
        } catch (NoResultException $e) {
            common_debug(__METHOD__ . ' - Adding profile and remote profile ' .
                         "for Twitter user: $profileurl.");
        }

        $profile = new Profile();
        $profile->query("BEGIN");
        $profile->nickname   = $twuser->screen_name;
        $profile->fullname   = $twuser->name;
        $profile->homepage   = $twuser->url;
        $profile->bio        = $twuser->description;
        $profile->location   = $twuser->location;
        $profile->profileurl = $profileurl;
        $profile->created    = common_sql_now();

        try {
            $id = $profile->insert();   // insert _should_ throw exception on failure
            if (empty($id)) {
                throw new Exception('Failed insert');
            }
        } catch(Exception $e) {
            common_log(LOG_WARNING, __METHOD__ . " Couldn't insert profile: " . $e->getMessage());
            common_log_db_error($profile, 'INSERT', __FILE__);
            $profile->query("ROLLBACK");
            return false;
        }

        $profile->query("COMMIT");
        $this->updateAvatar($twuser, $profile);
        return $profile;
    }

    /*
     * Checks whether we have to update the profile's avatar
     *
     * @return true when updated, false on failure, null when no action taken
     */
    protected function updateAvatar($twuser, Profile $profile)
    {
        $path_parts = pathinfo($twuser->profile_image_url);
        $ext        = isset($path_parts['extension'])
                        ? '.'.$path_parts['extension']
                        : '';	// some lack extension
        $img_root   = basename($path_parts['basename'], '_normal'.$ext);	// cut off extension
        $filename   = "Twitter_{$twuser->id}_{$img_root}_{$this->avatarsizename}{$ext}";

        try {
            $avatar = Avatar::getUploaded($profile);
            if ($avatar->filename === $filename) {
                return null;
            }
            common_debug(__METHOD__ . " - Updating profile avatar (profile_id={$profile->id}) " .
			"from {$avatar->filename} to {$filename}");
            // else we continue with creating a new avatar
        } catch (NoAvatarException $e) {
            // Avatar was not found. We can catch NoAvatarException or FileNotFoundException
            // but generally we just want to continue creating a new avatar.
            common_debug(__METHOD__ . " - No avatar found for (profile_id={$profile->id})");
        }

        $url        = "{$path_parts['dirname']}/{$img_root}_{$this->avatarsizename}{$ext}";
        $mediatype  = $this->getMediatype(mb_substr($ext, 1));

        try {
            $this->newAvatar($profile, $url, $filename, $mediatype);
        } catch (Exception $e) {
            if (file_exists(Avatar::path($filename))) {
                unlink(Avatar::path($filename));
            }
            return false;
        }

        return true;
    }

    protected function getMediatype($ext)
    {
        $mediatype = null;

        switch (strtolower($ext)) {
        case 'jpeg':
        case 'jpg':
            $mediatype = 'image/jpeg';
            break;
        case 'gif':
            $mediatype = 'image/gif';
            break;
        default:
            $mediatype = 'image/png';
        }

        return $mediatype;
    }

    protected function newAvatar(Profile $profile, $url, $filename, $mediatype)
    {
        // Clear out old avatars, won't do anything if there are none
        Avatar::deleteFromProfile($profile);

        // throws exception if unable to fetch
        $this->fetchRemoteUrl($url, Avatar::path($filename));

        $avatar = new Avatar();
        $avatar->profile_id = $profile->id;
        $avatar->original   = 1; // this is an original/"uploaded" avatar
        $avatar->mediatype  = $mediatype;
        $avatar->filename   = $filename;
        $avatar->width      = $this->avatarsize;
        $avatar->height     = $this->avatarsize;

        $avatar->created = common_sql_now();

        $id = $avatar->insert();

        if (empty($id)) {
            common_log(LOG_WARNING, __METHOD__ . " Couldn't insert avatar - " . $e->getMessage());
            common_log_db_error($avatar, 'INSERT', __FILE__);
            throw new ServerException('Could not insert avatar');
        }

        common_debug(__METHOD__ . " - Saved new avatar for {$profile->id}.");

        return $avatar;
    }

    /**
     * Fetch a remote avatar image and save to local storage.
     *
     * @param string $url avatar source URL
     * @param string $filename bare local filename for download
     * @return bool true on success, false on failure
     */
    protected function fetchRemoteUrl($url, $filename)
    {
        common_debug(__METHOD__ . " - Fetching Twitter avatar: {$url} to {$filename}");
        $request = HTTPClient::start();
        $request->setConfig('connect_timeout', 3);  // I had problems with throttling
        $request->setConfig('timeout', 6);          // and locking the process sucks.
        $response = $request->get($url);
        if ($response->isOk()) {
            if (!file_put_contents($filename, $response->getBody())) {
                throw new ServerException('Failed saving fetched file');
            }
        } else {
            throw new Exception('Unexpected HTTP status code');
        }
        return true;
    }

    const URL = 1;
    const HASHTAG = 2;
    const MENTION = 3;

    function linkify($status, $html = FALSE)
    {
        $text = $status->text;

        if (empty($status->entities)) {
            $statusId = twitter_id($status);
            common_log(LOG_WARNING, "No entities data for {$statusId}; trying to fake up links ourselves.");
            $text = common_replace_urls_callback($text, 'common_linkify');
            $text = preg_replace_callback('/(^|\&quot\;|\'|\(|\[|\{|\s+)#([\pL\pN_\-\.]{1,64})/',
                        function ($m) { return $m[1].'#'.TwitterStatusFetcher::tagLink($m[2]); }, $text);
            $text = preg_replace_callback('/(^|\s+)@([a-z0-9A-Z_]{1,64})/',
                        function ($m) { return $m[1].'@'.TwitterStatusFetcher::atLink($m[2]); }, $text);
            return $text;
        }

        // Move all the entities into order so we can
        // replace them and escape surrounding plaintext
        // in order

        $toReplace = array();

        if (!empty($status->entities->urls)) {
            foreach ($status->entities->urls as $url) {
                $toReplace[$url->indices[0]] = array(self::URL, $url);
            }
        }

        if (!empty($status->entities->hashtags)) {
            foreach ($status->entities->hashtags as $hashtag) {
                $toReplace[$hashtag->indices[0]] = array(self::HASHTAG, $hashtag);
            }
        }

        if (!empty($status->entities->user_mentions)) {
            foreach ($status->entities->user_mentions as $mention) {
                $toReplace[$mention->indices[0]] = array(self::MENTION, $mention);
            }
        }

        // sort in forward order by key

        ksort($toReplace);

        $result = '';
        $cursor = 0;

        foreach ($toReplace as $part) {
            list($type, $object) = $part;
            $start = $object->indices[0];
            $end = $object->indices[1];
            if ($cursor < $start) {
                // Copy in the preceding plaintext
                $result .= $this->twitEscape(mb_substr($text, $cursor, $start - $cursor));
                $cursor = $start;
            }
            $orig = $this->twitEscape(mb_substr($text, $start, $end - $start));
            switch($type) {
            case self::URL:
                $linkText = $this->makeUrlLink($object, $orig, $html);
                break;
            case self::HASHTAG:
                if ($html) {
                    $linkText = $this->makeHashtagLink($object, $orig);
                }else{
                    $linkText = $orig;
                }
                break;
            case self::MENTION:
                if ($html) {
                    $linkText = $this->makeMentionLink($object, $orig);
                }else{
                    $linkText = $orig;
                }
                break;
            default:
                $linkText = $orig;
                continue;
            }
            $result .= $linkText;
            $cursor = $end;
        }
        $last = $this->twitEscape(mb_substr($text, $cursor));
        $result .= $last;

        return $result;
    }

    function twitEscape($str)
    {
        // Twitter seems to preemptive turn < and > into < and >
        // but doesn't for &, so while you may have some magic protection
        // against XSS by not bothing to escape manually, you still get
        // invalid XHTML. Thanks!
        //
        // Looks like their web interface pretty much sends anything
        // through intact, so.... to do equivalent, decode all entities
        // and then re-encode the special ones.
        return htmlspecialchars(html_entity_decode($str, ENT_COMPAT, 'UTF-8'));
    }

    function makeUrlLink($object, $orig, $html)
    {
        if ($html) {
            return 'expanded_url).'" class="extlink">'.htmlspecialchars($object->display_url).'';
        }else{
            return htmlspecialchars($object->expanded_url);
        }
    }

    function makeHashtagLink($object, $orig)
    {
        return "#" . self::tagLink($object->text, substr($orig, 1));
    }

    function makeMentionLink($object, $orig)
    {
        return "@".self::atLink($object->screen_name, $object->name, substr($orig, 1));
    }

    static function tagLink($tag, $orig)
    {
        return "{$orig}";
    }

    static function atLink($screenName, $fullName, $orig)
    {
        if (!empty($fullName)) {
            return "{$orig}";
        } else {
            return "{$orig}";
        }
    }

    function saveStatusMentions($notice, $status)
    {
        $mentions = array();

        if (empty($status->entities) || empty($status->entities->user_mentions)) {
            return;
        }

        foreach ($status->entities->user_mentions as $mention) {
            try {
                $flink = Foreign_link::getByForeignID($mention->id, TWITTER_SERVICE);
                $user = $flink->getUser();
                $reply = new Reply();
                $reply->notice_id  = $notice->id;
                $reply->profile_id = $user->id;
                $reply->modified   = $notice->created;
                common_log(LOG_INFO, __METHOD__ . ": saving reply: notice {$notice->id} to profile {$user->id}");
                $id = $reply->insert();
            } catch (NoSuchUserException $e) {
                common_log(LOG_WARNING, 'No local user found for Foreign_link with id: '.$mention->id);
            } catch (NoResultException $e) {
                common_log(LOG_WARNING, 'No foreign link or profile found for Foreign_link with id: '.$mention->id);
            }
        }
    }

    /**
     * Record URL links from the notice. Needed to get thumbnail records
     * for referenced photo and video posts, etc.
     *
     * @param Notice $notice
     * @param object $status
     */
    function saveStatusAttachments(Notice $notice, $status)
    {
        if (common_config('attachments', 'process_links')) {
            if (!empty($status->entities) && !empty($status->entities->urls)) {
                foreach ($status->entities->urls as $url) {
                    try {
                        File::processNew($url->url, $notice);
                    } catch (ServerException $e) {
                        // Could not process attached URL
                    }
                }
            }
        }
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Class for doing OAuth calls against Twitter
 *
 * 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  Integration
 * @package   StatusNet
 * @author    Zach Copley <zach@status.net>
 * @copyright 2009-2010 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') && !defined('LACONICA')) {
    exit(1);
}

/**
 * Class for talking to the Twitter API with OAuth.
 *
 * @category Integration
 * @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 TwitterOAuthClient extends OAuthClient
{
    public static $requestTokenURL = 'https://api.twitter.com/oauth/request_token';
    public static $authorizeURL    = 'https://api.twitter.com/oauth/authorize';
    public static $signinUrl       = 'https://api.twitter.com/oauth/authenticate';
    public static $accessTokenURL  = 'https://api.twitter.com/oauth/access_token';

    /**
     * Constructor
     *
     * @param string $oauth_token        the user's token
     * @param string $oauth_token_secret the user's token secret
     *
     * @return nothing
     */
    function __construct($oauth_token = null, $oauth_token_secret = null)
    {
        $consumer_key    = common_config('twitter', 'consumer_key');
        $consumer_secret = common_config('twitter', 'consumer_secret');

        if (empty($consumer_key) && empty($consumer_secret)) {
            $consumer_key = common_config(
                'twitter',
                'global_consumer_key'
            );
            $consumer_secret = common_config(
                'twitter',
                'global_consumer_secret'
            );
        }

        parent::__construct(
            $consumer_key,
            $consumer_secret,
            $oauth_token,
            $oauth_token_secret
        );
    }

    // XXX: the following two functions are to support the horrible hack
    // of using the credentils field in Foreign_link to store both
    // the access token and token secret.  This hack should go away with
    // 0.9, in which we can make DB changes and add a new column for the
    // token itself.

    static function packToken($token)
    {
        return implode(chr(0), array($token->key, $token->secret));
    }

    static function unpackToken($str)
    {
        $vals = explode(chr(0), $str);
        return new OAuthToken($vals[0], $vals[1]);
    }

    static function isPackedToken($str)
    {
        if (strpos($str, chr(0)) === false) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Gets a request token from Twitter
     *
     * @return OAuthToken $token the request token
     */
    function getTwitterRequestToken()
    {
        return parent::getRequestToken(
            self::$requestTokenURL,
            common_local_url('twitterauthorization')
        );
    }

    /**
     * Builds a link to Twitter's endpoint for authorizing a request token
     *
     * @param OAuthToken $request_token token to authorize
     *
     * @return the link
     */
    function getTwitterAuthorizeLink($request_token, $signin = false)
    {
        $url = ($signin) ? self::$signinUrl : self::$authorizeURL;

        return parent::getAuthorizeLink($url,
                                        $request_token,
                                        common_local_url('twitterauthorization'));
    }

    /**
     * Fetches an access token from Twitter
     *
     * @param string $verifier 1.0a verifier
     *
     * @return OAuthToken $token the access token
     */
    function getTwitterAccessToken($verifier = null)
    {
        return parent::getAccessToken(
            self::$accessTokenURL,
            $verifier
        );
    }

    /**
     * Calls Twitter's /account/verify_credentials API method
     *
     * @return mixed the Twitter user
     */
    function verifyCredentials()
    {
        $url          = 'https://api.twitter.com/1.1/account/verify_credentials.json';
        $response     = $this->oAuthGet($url);
        $twitter_user = json_decode($response);
        return $twitter_user;
    }

    /**
     * Calls Twitter's /statuses/update API method
     *
     * @param string $status  text of the status
     * @param mixed  $params  optional other parameters to pass to Twitter,
     *                        as defined. For back-compatibility, if an int
     *                        is passed we'll consider it a reply-to ID.
     *
     * @return mixed the status
     */
    function statusesUpdate($status, $params=array())
    {
        $url      = 'https://api.twitter.com/1.1/statuses/update.json';
        if (is_numeric($params)) {
            $params = array('in_reply_to_status_id' => intval($params));
        }
        $params['status'] = $status;
        // We don't have to pass 'source' as the oauth key is tied to an app.

        $response = $this->oAuthPost($url, $params);
        $status   = json_decode($response);
        return $status;
    }

    /**
     * Calls Twitter's /statuses/home_timeline API method
     *
     * @param int    $since_id    show statuses after this id
     * @param string $timelineUri timeline to poll statuses from
     * @param int    $max_id      show statuses before this id
     * @param int    $cnt         number of statuses to show
     * @param int    $page        page number
     *
     * @return mixed an array of statuses
     */
    function statusesTimeline($since_id = null, $timelineUri = 'home_timeline',
                              $max_id = null, $cnt = 200, $page = null)
    {
        $url    = 'https://api.twitter.com/1.1/statuses/'.$timelineUri.'.json';

        $params = array('include_entities' => 'true',
                        'include_rts'      => 'true');

        if (!empty($since_id)) {
            $params['since_id'] = $since_id;
        }
        if (!empty($max_id)) {
            $params['max_id'] = $max_id;
        }
        if (!empty($cnt)) {
            $params['count'] = $cnt;
        }
        if (!empty($page)) {
            $params['page'] = $page;
        }

        $response = $this->oAuthGet($url, $params);
        $statuses = json_decode($response);
        return $statuses;
    }

    /**
     * Calls Twitter's /statuses/friends API method
     *
     * @param int $id          id of the user whom you wish to see friends of
     * @param int $user_id     numerical user id
     * @param int $screen_name screen name
     * @param int $page        page number
     *
     * @return mixed an array of twitter users and their latest status
     */
    function statusesFriends($id = null, $user_id = null, $screen_name = null,
                             $page = null)
    {
        $url = "https://api.twitter.com/1.1/friends/list.json";

        $params = array();

        if (!empty($id)) {
            $params['id'] = $id;
        }

        if (!empty($user_id)) {
            $params['user_id'] = $user_id;
        }

        if (!empty($screen_name)) {
            $params['screen_name'] = $screen_name;
        }

        if (!empty($page)) {
            $params['page'] = $page;
        }

        $response = $this->oAuthGet($url, $params);
        $friends  = json_decode($response);
        return $friends;
    }

    /**
     * Calls Twitter's /statuses/friends/ids API method
     *
     * @param int $id          id of the user whom you wish to see friends of
     * @param int $user_id     numerical user id
     * @param int $screen_name screen name
     * @param int $page        page number
     *
     * @return mixed a list of ids, 100 per page
     */
    function friendsIds($id = null, $user_id = null, $screen_name = null,
                         $page = null)
    {
        $url = "https://api.twitter.com/1.1/friends/ids.json";

        $params = array();

        if (!empty($id)) {
            $params['id'] = $id;
        }

        if (!empty($user_id)) {
            $params['user_id'] = $user_id;
        }

        if (!empty($screen_name)) {
            $params['screen_name'] = $screen_name;
        }

        if (!empty($page)) {
            $params['page'] = $page;
        }

        $response = $this->oAuthGet($url, $params);
        $ids      = json_decode($response);
        return $ids;
    }

    /**
     * Calls Twitter's /statuses/retweet/id.json API method
     *
     * @param int $id id of the notice to retweet
     *
     * @return retweeted status
     */

    function statusesRetweet($id)
    {
        $url = "https://api.twitter.com/1.1/statuses/retweet/$id.json";
        $response = $this->oAuthPost($url);
        $status = json_decode($response);
        return $status;
    }

    /**
     * Calls Twitter's /favorites/create API method
     *
     * @param int $id ID of the status to favorite
     *
     * @return object faved status
     */

    function favoritesCreate($id)
    {
        $url = "https://api.twitter.com/1.1/favorites/create.json";
        $params=array();
        $params['id'] = $id;
        $response = $this->oAuthPost($url, $params);
        $status = json_decode($response);
        return $status;
    }

    /**
     * Calls Twitter's /favorites/destroy API method
     *
     * @param int $id ID of the status to unfavorite
     *
     * @return object unfaved status
     */

    function favoritesDestroy($id)
    {
        $url = "https://api.twitter.com/1.1/favorites/destroy.json";
        $params=array();
        $params['id'] = $id;
        $response = $this->oAuthPost($url,$params);
        $status = json_decode($response);
        return $status;
    }

    /**
     * Calls Twitter's /statuses/destroy API method
     *
     * @param int $id ID of the status to destroy
     *
     * @return object destroyed
     */

    function statusesDestroy($id)
    {
        $url = "https://api.twitter.com/1.1/statuses/destroy/$id.json";
        $response = $this->oAuthPost($url);
        $status = json_decode($response);
        return $status;
    }
}


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/>.
 */

defined('GNUSOCIAL') || die();

require_once dirname(__DIR__) . '/twitter.php';

class TwitterQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'twitter';
    }

    function handle($notice) : bool
    {
        if (!($notice instanceof Notice)) {
            common_log(LOG_ERR, "Got a bogus notice, not broadcasting");
            return true;
        }

        $ok = broadcast_twitter($notice);
        return $ok || common_config('twitter', 'ignore_errors');
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * 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    Brion Vibber <brion@status.net>
 * @copyright 2010 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/
 */

/**
 * Multiuser stream listener for Twitter Site Streams API
 * http://dev.twitter.com/pages/site_streams
 *
 * The site streams API allows listening to updates for multiple users.
 * Pass in the user IDs to listen to in via followUser() -- note they
 * must each have a valid OAuth token for the application ID we're
 * connecting as.
 *
 * You'll need to be connecting with the auth keys for the user who
 * owns the application registration.
 *
 * The user each message is destined for will be passed to event handlers
 * in $context['for_user_id'].
 */
class TwitterSiteStream extends TwitterStreamReader
{
    protected $userIds;

    public function __construct(TwitterOAuthClient $auth, $baseUrl='https://sitestream.twitter.com')
    {
        parent::__construct($auth, $baseUrl);
    }

    public function connect($method='2b/site.json')
    {
        $params = array();
        if ($this->userIds) {
            $params['follow'] = implode(',', $this->userIds);
        }
        return parent::connect($method, $params);
    }

    /**
     * Set the users whose home streams should be pulled.
     * They all must have valid oauth tokens for this application.
     *
     * Must be called before connect().
     *
     * @param array $userIds
     */
    function followUsers($userIds)
    {
        $this->userIds = $userIds;
    }

    /**
     * Each message in the site stream tells us which user ID it should be
     * routed to; we'll need that to let the caller know what to do.
     *
     * @param array $data
     */
    function routeMessage(stdClass $data)
    {
        $context = array(
            'source' => 'sitestream',
            'for_user' => $data->for_user
        );
        parent::handleMessage($data->message, $context);
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * 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    Brion Vibber <brion@status.net>
 * @copyright 2010 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/
 */

/**
 * Base class for reading Twitter's User Streams and Site Streams
 * real-time streaming APIs.
 *
 * Caller can hook event callbacks for various types of messages;
 * the data from the stream and some context info will be passed
 * on to the callbacks.
 */
abstract class TwitterStreamReader extends JsonStreamReader
{
    protected $callbacks = array();

    function __construct(TwitterOAuthClient $auth, $baseUrl)
    {
        $this->baseUrl = $baseUrl;
        $this->oauth = $auth;
    }

    public function connect($method, $params=array())
    {
        $url = $this->oAuthUrl($this->baseUrl . '/' . $method, $params);
        return parent::connect($url);
    }

    /**
     * Sign our target URL with OAuth auth stuff.
     *
     * @param string $url
     * @param array $params
     * @return string
     */
    protected function oAuthUrl($url, $params=array())
    {
        // In an ideal world this would be better encapsulated. :)
        $request = OAuthRequest::from_consumer_and_token($this->oauth->consumer,
            $this->oauth->token, 'GET', $url, $params);
        $request->sign_request($this->oauth->sha1_method,
            $this->oauth->consumer, $this->oauth->token);

        return $request->to_url();
    }

    /**
     * Add an event callback to receive notifications when things come in
     * over the wire.
     *
     * Callbacks should be in the form: function(object $data, array $context)
     * where $context may list additional data on some streams, such as the
     * user to whom the message should be routed.
     *
     * Available events:
     *
     * Messaging:
     *
     * 'status': $data contains a status update in standard Twitter JSON format.
     *      $data->user: sending user in standard Twitter JSON format.
     *      $data->text... etc
     *
     * 'direct_message': $data contains a direct message in standard Twitter JSON format.
     *      $data->sender: sending user in standard Twitter JSON format.
     *      $data->recipient: receiving user in standard Twitter JSON format.
     *      $data->text... etc
     *
     *
     * Out of band events:
     *
     * 'follow': User has either started following someone, or is being followed.
     *      $data->source: following user in standard Twitter JSON format.
     *      $data->target: followed user in standard Twitter JSON format.
     *
     * 'favorite': Someone has favorited a status update.
     *      $data->source: user doing the favoriting, in standard Twitter JSON format.
     *      $data->target: user whose status was favorited, in standard Twitter JSON format.
     *      $data->target_object: the favorited status update in standard Twitter JSON format.
     *
     * 'unfavorite': Someone has unfavorited a status update.
     *      $data->source: user doing the unfavoriting, in standard Twitter JSON format.
     *      $data->target: user whose status was unfavorited, in standard Twitter JSON format.
     *      $data->target_object: the unfavorited status update in standard Twitter JSON format.
     *
     *
     * Meta information:
     *
     * 'friends':
     *      $data->friends: array of user IDs of the current user's friends.
     *
     * 'delete': Advisory that a Twitter status has been deleted; nice clients
     *           should follow suit.
     *      $data->id: ID of status being deleted
     *      $data->user_id: ID of its owning user
     *
     * 'scrub_geo': Advisory that a user is clearing geo data from their status
     *              stream; nice clients should follow suit.
     *      $data->user_id: ID of user
     *      $data->up_to_status_id: any notice older than this should be scrubbed.
     *
     * 'limit': Advisory that tracking has hit a resource limit.
     *      $data->track
     *
     * 'raw': receives the full JSON data for all message types.
     *
     * @param string $event
     * @param callable $callback
     */
    public function hookEvent($event, $callback)
    {
        $this->callbacks[$event][] = $callback;
    }

    /**
     * Call event handler callbacks for the given event.
     *
     * @param string $event
     * @param mixed $arg1 ... one or more params to pass on
     */
    protected function fireEvent($event, $arg1)
    {
        if (array_key_exists($event, $this->callbacks)) {
            $args = array_slice(func_get_args(), 1);
            foreach ($this->callbacks[$event] as $callback) {
                call_user_func_array($callback, $args);
            }
        }
    }

    protected function handleJson(stdClass $data)
    {
        $this->routeMessage($data);
    }

    abstract protected function routeMessage(stdClass $data);

    /**
     * Send the decoded JSON object out to any event listeners.
     *
     * @param array $data
     * @param array $context optional additional context data to pass on
     */
    protected function handleMessage(stdClass $data, array $context=array())
    {
        $this->fireEvent('raw', $data, $context);

        if (isset($data->text)) {
            $this->fireEvent('status', $data, $context);
            return;
        }
        if (isset($data->event)) {
            $this->fireEvent($data->event, $data, $context);
            return;
        }
        if (isset($data->friends)) {
            $this->fireEvent('friends', $data, $context);
        }

        $knownMeta = array('delete', 'scrub_geo', 'limit', 'direct_message');
        foreach ($knownMeta as $key) {
            if (isset($data->$key)) {
                $this->fireEvent($key, $data->$key, $context);
                return;
            }
        }
    }
}

/**
 * Multiuser stream listener for Twitter Site Streams API
 * http://dev.twitter.com/pages/site_streams
 *
 * The site streams API allows listening to updates for multiple users.
 * Pass in the user IDs to listen to in via followUser() -- note they
 * must each have a valid OAuth token for the application ID we're
 * connecting as.
 *
 * You'll need to be connecting with the auth keys for the user who
 * owns the application registration.
 *
 * The user each message is destined for will be passed to event handlers
 * in $context['for_user_id'].
 */
class TwitterSiteStream extends TwitterStreamReader
{
    protected $userIds;

    public function __construct(TwitterOAuthClient $auth, $baseUrl='https://sitestream.twitter.com')
    {
        parent::__construct($auth, $baseUrl);
    }

    public function connect($method='2b/site.json')
    {
        $params = array();
        if ($this->userIds) {
            $params['follow'] = implode(',', $this->userIds);
        }
        return parent::connect($method, $params);
    }

    /**
     * Set the users whose home streams should be pulled.
     * They all must have valid oauth tokens for this application.
     *
     * Must be called before connect().
     *
     * @param array $userIds
     */
    function followUsers($userIds)
    {
        $this->userIds = $userIds;
    }

    /**
     * Each message in the site stream tells us which user ID it should be
     * routed to; we'll need that to let the caller know what to do.
     *
     * @param array $data
     */
    function routeMessage(stdClass $data)
    {
        $context = array(
            'source' => 'sitestream',
            'for_user' => $data->for_user
        );
        parent::handleMessage($data->message, $context);
    }
}

/**
 * Stream listener for Twitter User Streams API
 * http://dev.twitter.com/pages/user_streams
 *
 * This will pull the home stream and additional events just for the user
 * we've authenticated as.
 */
class TwitterUserStream extends TwitterStreamReader
{
    public function __construct(TwitterOAuthClient $auth, $baseUrl='https://userstream.twitter.com')
    {
        parent::__construct($auth, $baseUrl);
    }

    public function connect($method='2/user.json')
    {
        return parent::connect($method);
    }

    /**
     * Each message in the user stream is just ready to go.
     *
     * @param array $data
     */
    function routeMessage(stdClass $data)
    {
        $context = array(
            'source' => 'userstream'
        );
        parent::handleMessage($data, $context);
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * 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    Brion Vibber <brion@status.net>
 * @copyright 2010 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/
 */

/**
 * Stream listener for Twitter User Streams API
 * http://dev.twitter.com/pages/user_streams
 *
 * This will pull the home stream and additional events just for the user
 * we've authenticated as.
 */
class TwitterUserStream extends TwitterStreamReader
{
    public function __construct(TwitterOAuthClient $auth, $baseUrl='https://userstream.twitter.com')
    {
        parent::__construct($auth, $baseUrl);
    }

    public function connect($method='2/user.json')
    {
        return parent::connect($method);
    }

    /**
     * Each message in the user stream is just ready to go.
     *
     * @param array $data
     */
    function routeMessage(stdClass $data)
    {
        $context = array(
            'source' => 'userstream'
        );
        parent::handleMessage($data, $context);
    }
}


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: 2019-06-08 18:20+0100\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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#. TRANS: Mail subject after forwarding notices to Twitter has stopped working.
#: twitter.php:462
msgid "Your Twitter bridge has been disabled"
msgstr ""

#. TRANS: Mail body after forwarding notices to Twitter has stopped working.
#. TRANS: %1$ is the name of the user the mail is sent to, %2$s is a URL to the
#. TRANS: Twitter settings, %3$s is the StatusNet sitename.
#: twitter.php:469
#, php-format
msgid ""
"Hi, %1$s. We're sorry to inform you that your link to Twitter has been "
"disabled. We no longer seem to have permission to update your Twitter "
"status. Did you maybe revoke %3$s's access?\n"
"\n"
"You can re-enable your Twitter bridge by visiting your Twitter settings "
"page:\n"
"\n"
"\t%2$s\n"
"\n"
"Regards,\n"
"%3$s"
msgstr ""

#. TRANS: Server exception thrown when an invalid URL scheme is detected.
#: lib/jsonstreamreader.php:67
msgid "Invalid URL scheme for HTTP stream reader."
msgstr ""

#. TRANS: Exception thrown when input from an inexpected socket is encountered.
#: lib/jsonstreamreader.php:185
msgid "Got input from unexpected socket!"
msgstr ""

#. TRANS: Exception thrown when an invalid state is encountered in handleLine.
#. TRANS: %s is the invalid state.
#: lib/jsonstreamreader.php:217
#, php-format
msgid "Invalid state in handleLine: %s."
msgstr ""

#. TRANS: Exception thrown when an invalid response line is encountered.
#. TRANS: %s is the invalid line.
#: lib/jsonstreamreader.php:231
#, php-format
msgid "Invalid HTTP response line: %s."
msgstr ""

#. TRANS: Exception thrown when an invalid response line part is encountered.
#. TRANS: %1$s is the chunk, %2$s is the line.
#: lib/jsonstreamreader.php:238
#, php-format
msgid "Invalid HTTP response line chunk \"%1$s\": %2$s."
msgstr ""

#. TRANS: Exception thrown when an invalid response code is encountered.
#. TRANS: %1$s is the response code, %2$s is the line.
#: lib/jsonstreamreader.php:243
#, php-format
msgid "Bad HTTP response code %1$s: %2$s."
msgstr ""

#. TRANS: Message used to repeat a notice. RT is the abbreviation of 'retweet'.
#. TRANS: %1$s is the repeated user's name, %2$s is the repeated notice.
#: lib/twitterimport.php:126
#, php-format
msgid "RT @%1$s %2$s"
msgstr ""

#. TRANS: Title for page with Twitter integration settings.
#: actions/twittersettings.php:68
msgid "Twitter settings"
msgstr ""

#. TRANS: Instructions for page with Twitter integration settings.
#: actions/twittersettings.php:80
msgid ""
"Connect your Twitter account to share your updates with your Twitter friends "
"and vice-versa."
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twittersettings.php:106
msgid "Twitter account"
msgstr ""

#. TRANS: Form note when a Twitter account has been connected.
#: actions/twittersettings.php:112
msgid "Connected Twitter account"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twittersettings.php:118
msgid "Disconnect my account from Twitter"
msgstr ""

#. TRANS: Form guide. %s is a URL to the password settings.
#. TRANS: This message contains a Markdown link in the form [description](link).
#: actions/twittersettings.php:124
#, php-format
msgid ""
"Disconnecting your Twitter account could make it impossible to log in! "
"Please [set a password](%s) first."
msgstr ""

#. TRANS: Form instructions. %1$s is the StatusNet sitename.
#: actions/twittersettings.php:133
#, php-format
msgid ""
"Keep your %1$s account but disconnect from Twitter. You can use your %1$s "
"password to log in."
msgstr ""

#. TRANS: Button text for disconnecting a Twitter account.
#: actions/twittersettings.php:141
msgctxt "BUTTON"
msgid "Disconnect"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twittersettings.php:149
msgid "Preferences"
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:154
msgid "Automatically send my notices to Twitter."
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:160
msgid "Send local \"@\" replies to Twitter."
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:166
msgid "Send local repeats to Twitter."
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:172
msgid "Subscribe to my Twitter friends here."
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:180
msgid "Import my friends timeline."
msgstr ""

#. TRANS: Button text for saving Twitter integration settings.
#. TRANS: Button text for saving the administrative Twitter bridge settings.
#: actions/twittersettings.php:195 actions/twitteradminpanel.php:311
msgctxt "BUTTON"
msgid "Save"
msgstr ""

#. TRANS: Button text for adding Twitter integration.
#: actions/twittersettings.php:198
msgctxt "BUTTON"
msgid "Add"
msgstr ""

#. TRANS: Client error displayed when the submitted form contains unexpected data.
#: actions/twittersettings.php:233
msgid "Unexpected form submission."
msgstr ""

#. TRANS: Error message possibly displayed when trying to remove a connected Twitter account when there isn't one connected.
#: actions/twittersettings.php:245
msgid "No Twitter connection to remove."
msgstr ""

#. TRANS: Server error displayed when trying to remove a connected Twitter account fails.
#: actions/twittersettings.php:253
msgid "Could not remove Twitter user."
msgstr ""

#. TRANS: Success message displayed after disconnecting a Twitter account.
#: actions/twittersettings.php:260
msgid "Twitter account disconnected."
msgstr ""

#. TRANS: Server error displayed when saving Twitter integration preferences fails.
#: actions/twittersettings.php:279
msgid "Your account is not linked to Twitter."
msgstr ""

#. TRANS: Server error displayed when saving Twitter integration preferences fails.
#: actions/twittersettings.php:290
msgid "Could not save Twitter preferences."
msgstr ""

#. TRANS: Success message after saving Twitter integration preferences.
#: actions/twittersettings.php:298
msgid "Twitter preferences saved."
msgstr ""

#. TRANS: Form validation error displayed when the checkbox to agree to the license has not been checked.
#: actions/twitterauthorization.php:98
msgid "You cannot register if you do not agree to the license."
msgstr ""

#. TRANS: Form validation error displayed when an unhandled error occurs.
#: actions/twitterauthorization.php:108
msgid "No known action for POST."
msgstr ""

#. TRANS: Server error displayed when linking to a Twitter account fails.
#: actions/twitterauthorization.php:135 actions/twitterauthorization.php:177
#: actions/twitterauthorization.php:248
msgid "Could not link your Twitter account."
msgstr ""

#. TRANS: Server error displayed when linking to a Twitter account fails because of an incorrect oauth_token.
#: actions/twitterauthorization.php:154
msgid "Could not link your Twitter account: oauth_token mismatch."
msgstr ""

#. TRANS: Page instruction. %s is the StatusNet sitename.
#: actions/twitterauthorization.php:257
#, php-format
msgid ""
"This is the first time you have logged into %s so we must connect your "
"Twitter account to a local account. You can either create a new account, or "
"connect with your existing account, if you have one."
msgstr ""

#. TRANS: Page title.
#: actions/twitterauthorization.php:263
msgid "Twitter Account Setup"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twitterauthorization.php:296
msgid "Connection options"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twitterauthorization.php:310
msgid "Create new account"
msgstr ""

#. TRANS: Sub form introduction text.
#: actions/twitterauthorization.php:313
msgid "Create a new user with this nickname."
msgstr ""

#. TRANS: Field label.
#: actions/twitterauthorization.php:321
msgid "New nickname"
msgstr ""

#. TRANS: Field title for nickname field.
#: actions/twitterauthorization.php:324
msgid "1-64 lowercase letters or numbers, no punctuation or spaces."
msgstr ""

#. TRANS: Field label.
#: actions/twitterauthorization.php:328
msgctxt "LABEL"
msgid "Email"
msgstr ""

#. TRANS: Field title for e-mail address field.
#: actions/twitterauthorization.php:330
msgid "Used only for updates, announcements, and password recovery"
msgstr ""

#. TRANS: Button text for creating a new StatusNet account in the Twitter connect page.
#: actions/twitterauthorization.php:339
msgctxt "BUTTON"
msgid "Create"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twitterauthorization.php:346
msgid "Connect existing account"
msgstr ""

#. TRANS: Sub form introduction text.
#: actions/twitterauthorization.php:349
msgid ""
"If you already have an account, login with your username and password to "
"connect it to your Twitter account."
msgstr ""

#. TRANS: Field label.
#: actions/twitterauthorization.php:353
msgid "Existing nickname"
msgstr ""

#. TRANS: Field label.
#: actions/twitterauthorization.php:357
msgid "Password"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twitterauthorization.php:365
msgid "License"
msgstr ""

#. TRANS: Text for license agreement checkbox.
#. TRANS: %s is the license as configured for the StatusNet site.
#: actions/twitterauthorization.php:376
#, php-format
msgid ""
"My text and files are available under %s except this private data: password, "
"email address, IM address, and phone number."
msgstr ""

#. TRANS: Button text for connecting an existing StatusNet account in the Twitter connect page..
#: actions/twitterauthorization.php:390
msgctxt "BUTTON"
msgid "Connect"
msgstr ""

#. TRANS: Client error displayed when trying to create a new user but a plugin aborted the process.
#: actions/twitterauthorization.php:427
msgid "Registration of new user was aborted, maybe you failed a captcha?"
msgstr ""

#. TRANS: Client error displayed when trying to create a new user while creating new users is not allowed.
#: actions/twitterauthorization.php:433 actions/twitterauthorization.php:443
msgid "Registration not allowed."
msgstr ""

#. TRANS: Client error displayed when trying to create a new user with an invalid invitation code.
#: actions/twitterauthorization.php:451
msgid "Not a valid invitation code."
msgstr ""

#. TRANS: Form validation error displayed when connecting an existing user to a Twitter user fails because
#. TRANS: the provided username and/or password are incorrect.
#: actions/twitterauthorization.php:503
msgid "Invalid username or password."
msgstr ""

#. TRANS: Server error displayed connecting a user to a Twitter user has failed.
#: actions/twitterauthorization.php:537
msgid "Error connecting user to Twitter."
msgstr ""

#: actions/twitterauthorization.php:570
msgid "No foreign link found for Twitter user"
msgstr ""

#. TRANS: Title for login using Twitter page.
#: actions/twitterlogin.php:52
msgctxt "TITLE"
msgid "Twitter Login"
msgstr ""

#. TRANS: Instructions for login using Twitter page.
#: actions/twitterlogin.php:58
msgid "Login with your Twitter account"
msgstr ""

#. TRANS: Alternative text for "sign in with Twitter" image.
#: actions/twitterlogin.php:68
msgid "Sign in with Twitter"
msgstr ""

#. TRANS: Page title for Twitter administration panel.
#: actions/twitteradminpanel.php:53
msgctxt "TITLE"
msgid "Twitter"
msgstr ""

#. TRANS: Instructions for Twitter bridge administration page.
#: actions/twitteradminpanel.php:64
msgid "Twitter bridge settings"
msgstr ""

#. TRANS: Client error displayed when a consumer key is invalid because it is too long.
#: actions/twitteradminpanel.php:153
msgid "Invalid consumer key. Maximum length is 255 characters."
msgstr ""

#. TRANS: Client error displayed when a consumer secret is invalid because it is too long.
#: actions/twitteradminpanel.php:160
msgid "Invalid consumer secret. Maximum length is 255 characters."
msgstr ""

#. TRANS: Fieldset legend for Twitter application settings.
#: actions/twitteradminpanel.php:217
msgid "Twitter application settings"
msgstr ""

#. TRANS: Field label for Twitter assigned consumer key.
#: actions/twitteradminpanel.php:224
msgid "Consumer key"
msgstr ""

#. TRANS: Field title for Twitter assigned consumer key.
#: actions/twitteradminpanel.php:226
msgid "The consumer key assigned by Twitter."
msgstr ""

#. TRANS: Field label for Twitter assigned consumer secret.
#: actions/twitteradminpanel.php:235
msgid "Consumer secret"
msgstr ""

#. TRANS: Field title for Twitter assigned consumer secret.
#: actions/twitteradminpanel.php:237
msgid "The consumer secret assigned by Twitter."
msgstr ""

#. TRANS: Form guide displayed when two required fields have already been provided.
#: actions/twitteradminpanel.php:248
msgid "Note: A global consumer key and secret are set."
msgstr ""

#. TRANS: Field label for Twitter application name.
#: actions/twitteradminpanel.php:256
msgid "Integration source"
msgstr ""

#. TRANS: Field title for Twitter application name.
#: actions/twitteradminpanel.php:258
msgid "The name of your Twitter application."
msgstr ""

#. TRANS: Fieldset legend for Twitter integration options.
#: actions/twitteradminpanel.php:271
msgid "Options"
msgstr ""

#. TRANS: Checkbox label for global setting.
#: actions/twitteradminpanel.php:279
msgid "Enable \"Sign-in with Twitter\""
msgstr ""

#. TRANS: Checkbox title.
#: actions/twitteradminpanel.php:282
msgid "This allow users to login with their Twitter credentials."
msgstr ""

#. TRANS: Checkbox label for global setting.
#: actions/twitteradminpanel.php:290
msgid "Enable Twitter import"
msgstr ""

#. TRANS: Checkbox title for global setting.
#: actions/twitteradminpanel.php:293
msgid ""
"Allow users to import their Twitter friends' timelines. Requires daemons to "
"be manually configured."
msgstr ""

#. TRANS: Button title for saving the administrative Twitter bridge settings.
#: actions/twitteradminpanel.php:313
msgid "Save the Twitter bridge settings."
msgstr ""

#. TRANS: Menu item in login navigation.
#. TRANS: Menu item in connection settings navigation.
#: TwitterBridgePlugin.php:152 TwitterBridgePlugin.php:177
msgctxt "MENU"
msgid "Twitter"
msgstr ""

#. TRANS: Title for menu item in login navigation.
#: TwitterBridgePlugin.php:154
msgid "Login or register using Twitter."
msgstr ""

#. TRANS: Title for menu item in connection settings navigation.
#: TwitterBridgePlugin.php:179
msgid "Twitter integration options"
msgstr ""

#. TRANS: Menu item in administrative panel that leads to the Twitter bridge configuration.
#: TwitterBridgePlugin.php:280
msgid "Twitter"
msgstr ""

#. TRANS: Menu item title in administrative panel that leads to the Twitter bridge configuration.
#: TwitterBridgePlugin.php:282
msgid "Twitter bridge configuration page."
msgstr ""

#. TRANS: Plugin description.
#: TwitterBridgePlugin.php:306
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with Twitter."
msgstr ""


code:
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 12:17+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Afrikaans (http://www.transifex.com/gnu-social/gnu-social/language/af/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: af\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Client error displayed when trying to log in using Twitter while
#. already logged in to StatusNet.
#: actions/twitterlogin.php:57
msgid "Already logged in."
msgstr "U is reeds aangeteken."

#. TRANS: Title for login using Twitter page.
#: actions/twitterlogin.php:66
msgctxt "TITLE"
msgid "Twitter Login"
msgstr ""

#. TRANS: Instructions for login using Twitter page.
#: actions/twitterlogin.php:72
msgid "Login with your Twitter account"
msgstr ""

#. TRANS: Alternative text for "sign in with Twitter" image.
#: actions/twitterlogin.php:91
msgid "Sign in with Twitter"
msgstr ""

#. TRANS: Page title for Twitter administration panel.
#: actions/twitteradminpanel.php:53
msgctxt "TITLE"
msgid "Twitter"
msgstr ""

#. TRANS: Instructions for Twitter bridge administration page.
#: actions/twitteradminpanel.php:64
msgid "Twitter bridge settings"
msgstr ""

#. TRANS: Client error displayed when a consumer key is invalid because it is
#. too long.
#: actions/twitteradminpanel.php:153
msgid "Invalid consumer key. Maximum length is 255 characters."
msgstr ""

#. TRANS: Client error displayed when a consumer secret is invalid because it
#. is too long.
#: actions/twitteradminpanel.php:160
msgid "Invalid consumer secret. Maximum length is 255 characters."
msgstr ""

#. TRANS: Fieldset legend for Twitter application settings.
#: actions/twitteradminpanel.php:217
msgid "Twitter application settings"
msgstr ""

#. TRANS: Field label for Twitter assigned consumer key.
#: actions/twitteradminpanel.php:224
msgid "Consumer key"
msgstr ""

#. TRANS: Field title for Twitter assigned consumer key.
#: actions/twitteradminpanel.php:226
msgid "The consumer key assigned by Twitter."
msgstr ""

#. TRANS: Field label for Twitter assigned consumer secret.
#: actions/twitteradminpanel.php:235
msgid "Consumer secret"
msgstr ""

#. TRANS: Field title for Twitter assigned consumer secret.
#: actions/twitteradminpanel.php:237
msgid "The consumer secret assigned by Twitter."
msgstr ""

#. TRANS: Form guide displayed when two required fields have already been
#. provided.
#: actions/twitteradminpanel.php:248
msgid "Note: A global consumer key and secret are set."
msgstr ""

#. TRANS: Field label for Twitter application name.
#: actions/twitteradminpanel.php:256
msgid "Integration source"
msgstr ""

#. TRANS: Field title for Twitter application name.
#: actions/twitteradminpanel.php:258
msgid "The name of your Twitter application."
msgstr ""

#. TRANS: Fieldset legend for Twitter integration options.
#: actions/twitteradminpanel.php:271
msgid "Options"
msgstr ""

#. TRANS: Checkbox label for global setting.
#: actions/twitteradminpanel.php:279
msgid "Enable \"Sign-in with Twitter\""
msgstr ""

#. TRANS: Checkbox title.
#: actions/twitteradminpanel.php:282
msgid "This allow users to login with their Twitter credentials."
msgstr ""

#. TRANS: Checkbox label for global setting.
#: actions/twitteradminpanel.php:290
msgid "Enable Twitter import"
msgstr ""

#. TRANS: Checkbox title for global setting.
#: actions/twitteradminpanel.php:293
msgid ""
"Allow users to import their Twitter friends' timelines. Requires daemons to "
"be manually configured."
msgstr ""

#. TRANS: Button text for saving the administrative Twitter bridge settings.
#. TRANS: Button text for saving Twitter integration settings.
#: actions/twitteradminpanel.php:311 actions/twittersettings.php:210
msgctxt "BUTTON"
msgid "Save"
msgstr "Stoor"

#. TRANS: Button title for saving the administrative Twitter bridge settings.
#: actions/twitteradminpanel.php:313
msgid "Save the Twitter bridge settings."
msgstr ""

#. TRANS: Client error displayed when the session token does not match or is
#. not given.
#: actions/twitterauthorization.php:121 actions/twittersettings.php:238
msgid "There was a problem with your session token. Try again, please."
msgstr ""

#. TRANS: Form validation error displayed when the checkbox to agree to the
#. license has not been checked.
#: actions/twitterauthorization.php:128
msgid "You cannot register if you do not agree to the license."
msgstr ""

#. TRANS: Form validation error displayed when an unhandled error occurs.
#: actions/twitterauthorization.php:138
msgid "Something weird happened."
msgstr ""

#. TRANS: Server error displayed when linking to a Twitter account fails.
#: actions/twitterauthorization.php:183 actions/twitterauthorization.php:233
#: actions/twitterauthorization.php:305
msgid "Could not link your Twitter account."
msgstr ""

#. TRANS: Server error displayed when linking to a Twitter account fails
#. because of an incorrect oauth_token.
#: actions/twitterauthorization.php:204
msgid "Could not link your Twitter account: oauth_token mismatch."
msgstr ""

#. TRANS: Page instruction. %s is the StatusNet sitename.
#: actions/twitterauthorization.php:318
#, php-format
msgid ""
"This is the first time you have logged into %s so we must connect your "
"Twitter account to a local account. You can either create a new account, or "
"connect with your existing account, if you have one."
msgstr ""

#. TRANS: Page title.
#: actions/twitterauthorization.php:325
msgid "Twitter Account Setup"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twitterauthorization.php:359
msgid "Connection options"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twitterauthorization.php:373
msgid "Create new account"
msgstr ""

#. TRANS: Sub form introduction text.
#: actions/twitterauthorization.php:376
msgid "Create a new user with this nickname."
msgstr ""

#. TRANS: Field label.
#: actions/twitterauthorization.php:384
msgid "New nickname"
msgstr ""

#. TRANS: Field title for nickname field.
#: actions/twitterauthorization.php:387
msgid "1-64 lowercase letters or numbers, no punctuation or spaces."
msgstr ""

#. TRANS: Field label.
#: actions/twitterauthorization.php:391
msgctxt "LABEL"
msgid "Email"
msgstr "E-pos"

#. TRANS: Field title for e-mail address field.
#: actions/twitterauthorization.php:393
msgid "Used only for updates, announcements, and password recovery"
msgstr ""

#. TRANS: Button text for creating a new StatusNet account in the Twitter
#. connect page.
#: actions/twitterauthorization.php:402
msgctxt "BUTTON"
msgid "Create"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twitterauthorization.php:409
msgid "Connect existing account"
msgstr ""

#. TRANS: Sub form introduction text.
#: actions/twitterauthorization.php:412
msgid ""
"If you already have an account, login with your username and password to "
"connect it to your Twitter account."
msgstr ""

#. TRANS: Field label.
#: actions/twitterauthorization.php:416
msgid "Existing nickname"
msgstr ""

#. TRANS: Field label.
#: actions/twitterauthorization.php:420
msgid "Password"
msgstr "Wagwoord"

#. TRANS: Fieldset legend.
#: actions/twitterauthorization.php:428
msgid "License"
msgstr "Lisensie"

#. TRANS: Text for license agreement checkbox.
#. TRANS: %s is the license as configured for the StatusNet site.
#: actions/twitterauthorization.php:439
#, php-format
msgid ""
"My text and files are available under %s except this private data: password,"
" email address, IM address, and phone number."
msgstr ""

#. TRANS: Button text for connecting an existing StatusNet account in the
#. Twitter connect page..
#: actions/twitterauthorization.php:453
msgctxt "BUTTON"
msgid "Connect"
msgstr ""

#. TRANS: Client error displayed when trying to create a new user while
#. creating new users is not allowed.
#: actions/twitterauthorization.php:498 actions/twitterauthorization.php:507
msgid "Registration not allowed."
msgstr "Registrasie nie toegelaat nie."

#. TRANS: Client error displayed when trying to create a new user with an
#. invalid invitation code.
#: actions/twitterauthorization.php:514
msgid "Not a valid invitation code."
msgstr ""

#. TRANS: Server error displayed when creating a new user has failed.
#: actions/twitterauthorization.php:542
msgid "Error registering user."
msgstr ""

#. TRANS: Server error displayed when connecting a user to a Twitter user has
#. failed.
#. TRANS: Server error displayed connecting a user to a Twitter user has
#. failed.
#: actions/twitterauthorization.php:553 actions/twitterauthorization.php:594
#: actions/twitterauthorization.php:614
msgid "Error connecting user to Twitter."
msgstr ""

#. TRANS: Form validation error displayed when connecting an existing user to
#. a Twitter user fails because
#. TRANS: the provided username and/or password are incorrect.
#: actions/twitterauthorization.php:575
msgid "Invalid username or password."
msgstr "Ongeldige gebruikersnaam of wagwoord."

#. TRANS: Title for page with Twitter integration settings.
#: actions/twittersettings.php:58
msgid "Twitter settings"
msgstr ""

#. TRANS: Instructions for page with Twitter integration settings.
#: actions/twittersettings.php:70
msgid ""
"Connect your Twitter account to share your updates with your Twitter friends"
" and vice-versa."
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twittersettings.php:119
msgid "Twitter account"
msgstr ""

#. TRANS: Form note when a Twitter account has been connected.
#: actions/twittersettings.php:125
msgid "Connected Twitter account"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twittersettings.php:131
msgid "Disconnect my account from Twitter"
msgstr ""

#. TRANS: Form guide. %s is a URL to the password settings.
#. TRANS: This message contains a Markdown link in the form
#. [description](link).
#: actions/twittersettings.php:137
#, php-format
msgid ""
"Disconnecting your Twitter account could make it impossible to log in! "
"Please [set a password](%s) first."
msgstr ""

#. TRANS: Form instructions. %1$s is the StatusNet sitename.
#: actions/twittersettings.php:146
#, php-format
msgid ""
"Keep your %1$s account but disconnect from Twitter. You can use your %1$s "
"password to log in."
msgstr ""

#. TRANS: Button text for disconnecting a Twitter account.
#: actions/twittersettings.php:154
msgctxt "BUTTON"
msgid "Disconnect"
msgstr ""

#. TRANS: Fieldset legend.
#: actions/twittersettings.php:162
msgid "Preferences"
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:167
msgid "Automatically send my notices to Twitter."
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:175
msgid "Send local \"@\" replies to Twitter."
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:183
msgid "Subscribe to my Twitter friends here."
msgstr ""

#. TRANS: Checkbox label.
#: actions/twittersettings.php:193
msgid "Import my friends timeline."
msgstr ""

#. TRANS: Button text for adding Twitter integration.
#: actions/twittersettings.php:213
msgctxt "BUTTON"
msgid "Add"
msgstr "Voeg by"

#. TRANS: Client error displayed when the submitted form contains unexpected
#. data.
#: actions/twittersettings.php:249
msgid "Unexpected form submission."
msgstr "Die vorm is onverwags ingestuur."

#. TRANS: Client error displayed when trying to remove a connected Twitter
#. account when there isn't one connected.
#: actions/twittersettings.php:265
msgid "No Twitter connection to remove."
msgstr ""

#. TRANS: Server error displayed when trying to remove a connected Twitter
#. account fails.
#: actions/twittersettings.php:273
msgid "Could not remove Twitter user."
msgstr ""

#. TRANS: Success message displayed after disconnecting a Twitter account.
#: actions/twittersettings.php:277
msgid "Twitter account disconnected."
msgstr ""

#. TRANS: Server error displayed when saving Twitter integration preferences
#. fails.
#: actions/twittersettings.php:299 actions/twittersettings.php:312
msgid "Could not save Twitter preferences."
msgstr ""

#. TRANS: Success message after saving Twitter integration preferences.
#: actions/twittersettings.php:321
msgid "Twitter preferences saved."
msgstr ""

#. TRANS: Menu item in login navigation.
#. TRANS: Menu item in connection settings navigation.
#: TwitterBridgePlugin.php:154 TwitterBridgePlugin.php:179
msgctxt "MENU"
msgid "Twitter"
msgstr ""

#. TRANS: Title for menu item in login navigation.
#: TwitterBridgePlugin.php:156
msgid "Login or register using Twitter."
msgstr ""

#. TRANS: Title for menu item in connection settings navigation.
#: TwitterBridgePlugin.php:181
msgid "Twitter integration options"
msgstr ""

#. TRANS: Menu item in administrative panel that leads to the Twitter bridge
#. configuration.
#: TwitterBridgePlugin.php:282
msgid "Twitter"
msgstr ""

#. TRANS: Menu item title in administrative panel that leads to the Twitter
#. bridge configuration.
#: TwitterBridgePlugin.php:284
msgid "Twitter bridge configuration page."
msgstr ""

#. TRANS: Plugin description.
#: TwitterBridgePlugin.php:308
msgid ""
"The Twitter \"bridge\" plugin allows integration of a StatusNet instance "
"with Twitter."
msgstr ""

#. TRANS: Message used to repeat a notice. RT is the abbreviation of
#. 'retweet'.
#. TRANS: %1$s is the repeated user's name, %2$s is the repeated notice.
#: lib/twitterimport.php:115
#, php-format
msgid "RT @%1$s %2$s"
msgstr "RT @%1$s %2$s"

#. TRANS: Server exception thrown when an invalid URL scheme is detected.
#: lib/jsonstreamreader.php:67
msgid "Invalid URL scheme for HTTP stream reader."
msgstr ""

#. TRANS: Exception thrown when input from an inexpected socket is
#. encountered.
#: lib/jsonstreamreader.php:185
msgid "Got input from unexpected socket!"
msgstr ""

#. TRANS: Exception thrown when an invalid state is encountered in handleLine.
#. TRANS: %s is the invalid state.
#: lib/jsonstreamreader.php:217
#, php-format
msgid "Invalid state in handleLine: %s."
msgstr ""

#. TRANS: Exception thrown when an invalid response line is encountered.
#. TRANS: %s is the invalid line.
#: lib/jsonstreamreader.php:231
#, php-format
msgid "Invalid HTTP response line: %s."
msgstr ""

#. TRANS: Exception thrown when an invalid response line part is encountered.
#. TRANS: %1$s is the chunk, %2$s is the line.
#: lib/jsonstreamreader.php:238
#, php-format
msgid "Invalid HTTP response line chunk \"%1$s\": %2$s."
msgstr ""

#. TRANS: Exception thrown when an invalid response code is encountered.
#. TRANS: %1$s is the response code, %2$s is the line.
#: lib/jsonstreamreader.php:243
#, php-format
msgid "Bad HTTP response code %1$s: %2$s."
msgstr ""

#. TRANS: Mail subject after forwarding notices to Twitter has stopped
#. working.
#: twitter.php:439
msgid "Your Twitter bridge has been disabled"
msgstr ""

#. TRANS: Mail body after forwarding notices to Twitter has stopped working.
#. TRANS: %1$ is the name of the user the mail is sent to, %2$s is a URL to
#. the
#. TRANS: Twitter settings, %3$s is the StatusNet sitename.
#: twitter.php:446
#, php-format
msgid ""
"Hi, %1$s. We're sorry to inform you that your link to Twitter has been disabled. We no longer seem to have permission to update your Twitter status. Did you maybe revoke %3$s's access?\n"
"\n"
"You can re-enable your Twitter bridge by visiting your Twitter settings page:\n"
"\n"
"\t%2$s\n"
"\n"
"Regards,\n"
"%3$s"
msgstr ""