]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge branch '0.9.x' into pluginize-twitter-bridge
authorZach Copley <zach@status.net>
Tue, 13 Oct 2009 18:05:39 +0000 (11:05 -0700)
committerZach Copley <zach@status.net>
Tue, 13 Oct 2009 18:05:39 +0000 (11:05 -0700)
* 0.9.x:
  Include long-form attachment URL in notice if URL shortening is disabled.
  Updated default theme to use the CSS Sprites method for common icons
  Added a new plugin that requires a user to have a validated email address before being allowed to post notices
  Don't trigger E_NOTICE when looking for commands in the notice input

20 files changed:
actions/twitterauthorization.php [deleted file]
actions/twittersettings.php [deleted file]
lib/common.php
lib/connectsettingsaction.php
lib/router.php
lib/twitter.php [deleted file]
lib/twitterbasicauthclient.php [deleted file]
lib/twitteroauthclient.php [deleted file]
plugins/TwitterBridge/TwitterBridgePlugin.php [new file with mode: 0644]
plugins/TwitterBridge/daemons/synctwitterfriends.php [new file with mode: 0755]
plugins/TwitterBridge/daemons/twitterqueuehandler.php [new file with mode: 0755]
plugins/TwitterBridge/daemons/twitterstatusfetcher.php [new file with mode: 0755]
plugins/TwitterBridge/twitter.php [new file with mode: 0644]
plugins/TwitterBridge/twitterauthorization.php [new file with mode: 0644]
plugins/TwitterBridge/twitterbasicauthclient.php [new file with mode: 0644]
plugins/TwitterBridge/twitteroauthclient.php [new file with mode: 0644]
plugins/TwitterBridge/twittersettings.php [new file with mode: 0644]
scripts/synctwitterfriends.php [deleted file]
scripts/twitterqueuehandler.php [deleted file]
scripts/twitterstatusfetcher.php [deleted file]

diff --git a/actions/twitterauthorization.php b/actions/twitterauthorization.php
deleted file mode 100644 (file)
index 630ac42..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Class for doing OAuth authentication 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  TwitterauthorizationAction
- * @package   StatusNet
- * @author    Zach Copely <zach@status.net>
- * @copyright 2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-class TwitterauthorizationAction extends Action
-{
-
-    function prepare($args)
-    {
-        parent::prepare($args);
-
-        $this->oauth_token = $this->arg('oauth_token');
-
-        return true;
-    }
-
-    /**
-     * Handler method
-     *
-     * @param array $args is ignored since it's now passed in in prepare()
-     *
-     * @return nothing
-     */
-    function handle($args)
-    {
-        parent::handle($args);
-
-        if (!common_logged_in()) {
-            $this->clientError(_('Not logged in.'), 403);
-        }
-
-        $user  = common_current_user();
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
-        // If there's already a foreign link record, it means we already
-        // have an access token, and this is unecessary. So go back.
-
-        if (isset($flink)) {
-            common_redirect(common_local_url('twittersettings'));
-        }
-
-        // $this->oauth_token is only populated once Twitter authorizes our
-        // request token. If it's empty we're at the beginning of the auth
-        // process
-
-        if (empty($this->oauth_token)) {
-            $this->authorizeRequestToken();
-        } else {
-            $this->saveAccessToken();
-        }
-    }
-
-    /**
-     * Asks Twitter for a request token, and then redirects to Twitter
-     * to authorize it.
-     *
-     * @return nothing
-     */
-    function authorizeRequestToken()
-    {
-        try {
-
-            // Get a new request token and authorize it
-
-            $client  = new TwitterOAuthClient();
-            $req_tok =
-              $client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
-
-            // Sock the request token away in the session temporarily
-
-            $_SESSION['twitter_request_token']        = $req_tok->key;
-            $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
-
-            $auth_link = $client->getAuthorizeLink($req_tok);
-
-        } catch (TwitterOAuthClientException $e) {
-            $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
-                           $e->getCode(), $e->getMessage());
-            $this->serverError(_('Couldn\'t link your Twitter account.'));
-        }
-
-        common_redirect($auth_link);
-    }
-
-    /**
-     * Called when Twitter returns an authorized request token. Exchanges
-     * it for an access token and stores it.
-     *
-     * @return nothing
-     */
-    function saveAccessToken()
-    {
-
-        // Check to make sure Twitter returned the same request
-        // token we sent them
-
-        if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
-            $this->serverError(_('Couldn\'t link your Twitter account.'));
-        }
-
-        try {
-
-            $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
-                $_SESSION['twitter_request_token_secret']);
-
-            // Exchange the request token for an access token
-
-            $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
-
-            // Test the access token and get the user's Twitter info
-
-            $client       = new TwitterOAuthClient($atok->key, $atok->secret);
-            $twitter_user = $client->verifyCredentials();
-
-        } catch (OAuthClientException $e) {
-            $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
-                           $e->getCode(), $e->getMessage());
-            $this->serverError(_('Couldn\'t link your Twitter account.'));
-        }
-
-        // Save the access token and Twitter user info
-
-        $this->saveForeignLink($atok, $twitter_user);
-
-        // Clean up the the mess we made in the session
-
-        unset($_SESSION['twitter_request_token']);
-        unset($_SESSION['twitter_request_token_secret']);
-
-        common_redirect(common_local_url('twittersettings'));
-    }
-
-    /**
-     * Saves a Foreign_link between Twitter user and local user,
-     * which includes the access token and secret.
-     *
-     * @param OAuthToken $access_token the access token to save
-     * @param mixed      $twitter_user twitter API user object
-     *
-     * @return nothing
-     */
-    function saveForeignLink($access_token, $twitter_user)
-    {
-        $user = common_current_user();
-
-        $flink = new Foreign_link();
-
-        $flink->user_id     = $user->id;
-        $flink->foreign_id  = $twitter_user->id;
-        $flink->service     = TWITTER_SERVICE;
-
-        $creds = TwitterOAuthClient::packToken($access_token);
-
-        $flink->credentials = $creds;
-        $flink->created     = common_sql_now();
-
-        // Defaults: noticesync on, everything else off
-
-        $flink->set_flags(true, false, false, false);
-
-        $flink_id = $flink->insert();
-
-        if (empty($flink_id)) {
-            common_log_db_error($flink, 'INSERT', __FILE__);
-                $this->serverError(_('Couldn\'t link your Twitter account.'));
-        }
-
-        save_twitter_user($twitter_user->id, $twitter_user->screen_name);
-    }
-
-}
-
diff --git a/actions/twittersettings.php b/actions/twittersettings.php
deleted file mode 100644 (file)
index 8916994..0000000
+++ /dev/null
@@ -1,277 +0,0 @@
-<?php
-/**
- * StatusNet, the distributed open-source microblogging tool
- *
- * Settings for Twitter integration
- *
- * 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  Settings
- * @package   StatusNet
- * @author    Evan Prodromou <evan@status.net>
- * @copyright 2008-2009 StatusNet, Inc.
- * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link      http://status.net/
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/connectsettingsaction.php';
-require_once INSTALLDIR.'/lib/twitter.php';
-
-/**
- * Settings for Twitter integration
- *
- * @category Settings
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
- * @link     http://status.net/
- *
- * @see      SettingsAction
- */
-
-class TwittersettingsAction extends ConnectSettingsAction
-{
-    /**
-     * Title of the page
-     *
-     * @return string Title of the page
-     */
-
-    function title()
-    {
-        return _('Twitter settings');
-    }
-
-    /**
-     * Instructions for use
-     *
-     * @return instructions for use
-     */
-
-    function getInstructions()
-    {
-        return _('Connect your Twitter account to share your updates ' .
-                 'with your Twitter friends and vice-versa.');
-    }
-
-    /**
-     * Content area of the page
-     *
-     * Shows a form for associating a Twitter account with this
-     * StatusNet account. Also lets the user set preferences.
-     *
-     * @return void
-     */
-
-    function showContent()
-    {
-        if (!common_config('twitter', 'enabled')) {
-            $this->element('div', array('class' => 'error'),
-                           _('Twitter is not available.'));
-            return;
-        }
-
-        $user = common_current_user();
-
-        $profile = $user->getProfile();
-
-        $fuser = null;
-
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
-        if (!empty($flink)) {
-            $fuser = $flink->getForeignUser();
-        }
-
-        $this->elementStart('form', array('method' => 'post',
-                                          'id' => 'form_settings_twitter',
-                                          'class' => 'form_settings',
-                                          'action' =>
-                                          common_local_url('twittersettings')));
-
-        $this->hidden('token', common_session_token());
-
-        $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
-
-        if (empty($fuser)) {
-            $this->elementStart('ul', 'form_data');
-            $this->elementStart('li', array('id' => 'settings_twitter_login_button'));
-            $this->element('a', array('href' => common_local_url('twitterauthorization')),
-                           'Connect my Twitter account');
-            $this->elementEnd('li');
-            $this->elementEnd('ul');
-
-            $this->elementEnd('fieldset');
-        } else {
-            $this->element('legend', null, _('Twitter account'));
-            $this->elementStart('p', array('id' => 'form_confirmed'));
-            $this->element('a', array('href' => $fuser->uri), $fuser->nickname);
-            $this->elementEnd('p');
-            $this->element('p', 'form_note',
-                           _('Connected Twitter account'));
-
-            $this->submit('remove', _('Remove'));
-
-            $this->elementEnd('fieldset');
-
-            $this->elementStart('fieldset', array('id' => 'settings_twitter_preferences'));
-
-            $this->element('legend', null, _('Preferences'));
-            $this->elementStart('ul', 'form_data');
-            $this->elementStart('li');
-            $this->checkbox('noticesend',
-                            _('Automatically send my notices to Twitter.'),
-                            ($flink) ?
-                            ($flink->noticesync & FOREIGN_NOTICE_SEND) :
-                            true);
-            $this->elementEnd('li');
-            $this->elementStart('li');
-            $this->checkbox('replysync',
-                            _('Send local "@" replies to Twitter.'),
-                            ($flink) ?
-                            ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
-                            true);
-            $this->elementEnd('li');
-            $this->elementStart('li');
-            $this->checkbox('friendsync',
-                            _('Subscribe to my Twitter friends here.'),
-                            ($flink) ?
-                            ($flink->friendsync & FOREIGN_FRIEND_RECV) :
-                            false);
-            $this->elementEnd('li');
-
-            if (common_config('twitterbridge','enabled')) {
-                $this->elementStart('li');
-                $this->checkbox('noticerecv',
-                                _('Import my Friends Timeline.'),
-                                ($flink) ?
-                                ($flink->noticesync & FOREIGN_NOTICE_RECV) :
-                                false);
-                $this->elementEnd('li');
-            } else {
-                // preserve setting even if bidrection bridge toggled off
-
-                if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
-                    $this->hidden('noticerecv', true, 'noticerecv');
-                }
-            }
-
-            $this->elementEnd('ul');
-
-            if ($flink) {
-                $this->submit('save', _('Save'));
-            } else {
-                $this->submit('add', _('Add'));
-            }
-
-            $this->elementEnd('fieldset');
-        }
-
-        $this->elementEnd('form');
-    }
-
-    /**
-     * Handle posts to this form
-     *
-     * Based on the button that was pressed, muxes out to other functions
-     * to do the actual task requested.
-     *
-     * All sub-functions reload the form with a message -- success or failure.
-     *
-     * @return void
-     */
-
-    function handlePost()
-    {
-        // CSRF protection
-        $token = $this->trimmed('token');
-        if (!$token || $token != common_session_token()) {
-            $this->showForm(_('There was a problem with your session token. '.
-                              'Try again, please.'));
-            return;
-        }
-
-        if ($this->arg('save')) {
-            $this->savePreferences();
-        } else if ($this->arg('remove')) {
-            $this->removeTwitterAccount();
-        } else {
-            $this->showForm(_('Unexpected form submission.'));
-        }
-    }
-
-    /**
-     * Disassociate an existing Twitter account from this account
-     *
-     * @return void
-     */
-
-    function removeTwitterAccount()
-    {
-        $user = common_current_user();
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
-        $result = $flink->delete();
-
-        if (empty($result)) {
-            common_log_db_error($flink, 'DELETE', __FILE__);
-            $this->serverError(_('Couldn\'t remove Twitter user.'));
-            return;
-        }
-
-        $this->showForm(_('Twitter account removed.'), true);
-    }
-
-    /**
-     * Save user's Twitter-bridging preferences
-     *
-     * @return void
-     */
-
-    function savePreferences()
-    {
-        $noticesend = $this->boolean('noticesend');
-        $noticerecv = $this->boolean('noticerecv');
-        $friendsync = $this->boolean('friendsync');
-        $replysync  = $this->boolean('replysync');
-
-        $user = common_current_user();
-        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
-
-        if (empty($flink)) {
-            common_log_db_error($flink, 'SELECT', __FILE__);
-            $this->showForm(_('Couldn\'t save Twitter preferences.'));
-            return;
-        }
-
-        $original = clone($flink);
-        $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
-        $result = $flink->update($original);
-
-        if ($result === false) {
-            common_log_db_error($flink, 'UPDATE', __FILE__);
-            $this->showForm(_('Couldn\'t save Twitter preferences.'));
-            return;
-        }
-
-        $this->showForm(_('Twitter preferences saved.'), true);
-    }
-
-}
index ce33c871bfb15b83d1c0e4b207952c8a7b61cb54..e29456ed4e9d2345fbcb3ac76916d4fa0e28edbb 100644 (file)
@@ -223,7 +223,6 @@ require_once INSTALLDIR.'/lib/theme.php';
 require_once INSTALLDIR.'/lib/mail.php';
 require_once INSTALLDIR.'/lib/subs.php';
 require_once INSTALLDIR.'/lib/Shorturl_api.php';
-require_once INSTALLDIR.'/lib/twitter.php';
 
 require_once INSTALLDIR.'/lib/clientexception.php';
 require_once INSTALLDIR.'/lib/serverexception.php';
index 2095a7cebfed12373e654d037221a1b4a4ef2e61..e5fb8727ba1dc4e89ead2563215cea53fc77319c 100644 (file)
@@ -98,34 +98,37 @@ class ConnectSettingsNav extends Widget
 
     function show()
     {
-        # action => array('prompt', 'title')
-        $menu = array();
-        if (common_config('xmpp', 'enabled')) {
-            $menu['imsettings'] =
-              array(_('IM'),
-                    _('Updates by instant messenger (IM)'));
-        }
-        if (common_config('sms', 'enabled')) {
-            $menu['smssettings'] =
-              array(_('SMS'),
-                    _('Updates by SMS'));
-        }
-        if (common_config('twitter', 'enabled')) {
-            $menu['twittersettings'] =
-              array(_('Twitter'),
-                    _('Twitter integration options'));
-        }
-
         $action_name = $this->action->trimmed('action');
         $this->action->elementStart('ul', array('class' => 'nav'));
 
-        foreach ($menu as $menuaction => $menudesc) {
-            $this->action->menuItem(common_local_url($menuaction),
-                                   $menudesc[0],
-                                   $menudesc[1],
-                                   $action_name === $menuaction);
+        if (Event::handle('StartConnectSettingsNav', array(&$this->action))) {
+
+            # action => array('prompt', 'title')
+            $menu = array();
+            if (common_config('xmpp', 'enabled')) {
+                $menu['imsettings'] =
+                  array(_('IM'),
+                        _('Updates by instant messenger (IM)'));
+            }
+            if (common_config('sms', 'enabled')) {
+                $menu['smssettings'] =
+                  array(_('SMS'),
+                        _('Updates by SMS'));
+            }
+
+            foreach ($menu as $menuaction => $menudesc) {
+                $this->action->menuItem(common_local_url($menuaction),
+                        $menudesc[0],
+                        $menudesc[1],
+                        $action_name === $menuaction);
+            }
+
+            Event::handle('EndConnectSettingsNav', array(&$this->action));
         }
 
         $this->action->elementEnd('ul');
     }
+
 }
+
+
index b9a45d867f0b3dba00819dc7d1ee2f6b0bb8c0ea..a5b6a9a3043f59eddd782cd780228abdb9dfa83a 100644 (file)
@@ -86,10 +86,6 @@ class Router
 
         $m->connect('doc/:title', array('action' => 'doc'));
 
-        // Twitter
-
-        $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
-
         // facebook
 
         $m->connect('facebook', array('action' => 'facebookhome'));
@@ -136,7 +132,7 @@ class Router
         // settings
 
         foreach (array('profile', 'avatar', 'password', 'im',
-                       'email', 'sms', 'twitter', 'userdesign', 'other') as $s) {
+                       'email', 'sms', 'userdesign', 'other') as $s) {
             $m->connect('settings/'.$s, array('action' => $s.'settings'));
         }
 
diff --git a/lib/twitter.php b/lib/twitter.php
deleted file mode 100644 (file)
index afc3f55..0000000
+++ /dev/null
@@ -1,311 +0,0 @@
-<?php
-/*
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2008, 2009, StatusNet, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
-
-function updateTwitter_user($twitter_id, $screen_name)
-{
-    $uri = 'http://twitter.com/' . $screen_name;
-    $fuser = new Foreign_user();
-
-    $fuser->query('BEGIN');
-
-    // Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem
-    // to work so good with tables that have multiple column primary keys
-
-    // Any time we update the uri for a forein user we have to make sure there
-    // are no dupe entries first -- unique constraint on the uri column
-
-    $qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
-    $qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
-
-    $fuser->query($qry);
-
-    // Update the user
-
-    $qry = 'UPDATE foreign_user SET nickname = ';
-    $qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
-    $qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
-
-    $fuser->query('COMMIT');
-
-    $fuser->free();
-    unset($fuser);
-
-    return true;
-}
-
-function add_twitter_user($twitter_id, $screen_name)
-{
-
-    $new_uri = 'http://twitter.com/' . $screen_name;
-
-    // Clear out any bad old foreign_users with the new user's legit URL
-    // This can happen when users move around or fakester accounts get
-    // repoed, and things like that.
-
-    $luser = new Foreign_user();
-    $luser->uri = $new_uri;
-    $luser->service = TWITTER_SERVICE;
-    $result = $luser->delete();
-
-    if (empty($result)) {
-        common_log(LOG_WARNING,
-            "Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
-    }
-
-    $luser->free();
-    unset($luser);
-
-    // Otherwise, create a new Twitter user
-
-    $fuser = new Foreign_user();
-
-    $fuser->nickname = $screen_name;
-    $fuser->uri = 'http://twitter.com/' . $screen_name;
-    $fuser->id = $twitter_id;
-    $fuser->service = TWITTER_SERVICE;
-    $fuser->created = common_sql_now();
-    $result = $fuser->insert();
-
-    if (empty($result)) {
-        common_log(LOG_WARNING,
-            "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
-        common_log_db_error($fuser, 'INSERT', __FILE__);
-    } else {
-        common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
-    }
-
-    return $result;
-}
-
-// Creates or Updates a Twitter user
-function save_twitter_user($twitter_id, $screen_name)
-{
-
-    // Check to see whether the Twitter user is already in the system,
-    // and update its screen name and uri if so.
-
-    $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
-
-    if (!empty($fuser)) {
-
-        $result = true;
-
-        // Only update if Twitter screen name has changed
-
-        if ($fuser->nickname != $screen_name) {
-            $result = updateTwitter_user($twitter_id, $screen_name);
-
-            common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
-                "$fuser->id to $screen_name, was $fuser->nickname");
-        }
-
-        return $result;
-
-    } else {
-        return add_twitter_user($twitter_id, $screen_name);
-    }
-
-    $fuser->free();
-    unset($fuser);
-
-    return true;
-}
-
-function is_twitter_bound($notice, $flink) {
-
-    // Check to see if notice should go to Twitter
-    if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) {
-
-        // If it's not a Twitter-style reply, or if the user WANTS to send replies.
-        if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
-            ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
-            return true;
-        }
-    }
-
-    return false;
-}
-
-function broadcast_twitter($notice)
-{
-    $flink = Foreign_link::getByUserID($notice->profile_id,
-                                       TWITTER_SERVICE);
-
-    if (is_twitter_bound($notice, $flink)) {
-        if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
-            return broadcast_oauth($notice, $flink);
-        } else {
-            return broadcast_basicauth($notice, $flink);
-        }
-    }
-
-    return true;
-}
-
-function broadcast_oauth($notice, $flink) {
-    $user = $flink->getUser();
-    $statustxt = format_status($notice);
-    // Convert !groups to #hashes
-    $statustxt = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/', "\\1#\\2", $statustxt);
-    $token = TwitterOAuthClient::unpackToken($flink->credentials);
-    $client = new TwitterOAuthClient($token->key, $token->secret);
-    $status = null;
-
-    try {
-        $status = $client->statusesUpdate($statustxt);
-    } catch (OAuthClientCurlException $e) {
-        return process_error($e, $flink);
-    }
-
-    if (empty($status)) {
-
-        // This could represent a failure posting,
-        // or the Twitter API might just be behaving flakey.
-
-        $errmsg = sprintf('Twitter bridge - No data returned by Twitter API when ' .
-                          'trying to send update for %1$s (user id %2$s).',
-                          $user->nickname, $user->id);
-        common_log(LOG_WARNING, $errmsg);
-
-        return false;
-    }
-
-    // Notice crossed the great divide
-
-    $msg = sprintf('Twitter bridge - posted notice %s to Twitter using OAuth.',
-                   $notice->id);
-    common_log(LOG_INFO, $msg);
-
-    return true;
-}
-
-function broadcast_basicauth($notice, $flink)
-{
-    $user = $flink->getUser();
-
-    $statustxt = format_status($notice);
-
-    $client = new TwitterBasicAuthClient($flink);
-    $status = null;
-
-    try {
-        $status = $client->statusesUpdate($statustxt);
-    } catch (BasicAuthCurlException $e) {
-        return process_error($e, $flink);
-    }
-
-    if (empty($status)) {
-
-        $errmsg = sprintf('Twitter bridge - No data returned by Twitter API when ' .
-                          'trying to send update for %1$s (user id %2$s).',
-                          $user->nickname, $user->id);
-        common_log(LOG_WARNING, $errmsg);
-
-            $errmsg = sprintf('No data returned by Twitter API when ' .
-                             'trying to send update for %1$s (user id %2$s).',
-                             $user->nickname, $user->id);
-            common_log(LOG_WARNING, $errmsg);
-        return false;
-    }
-
-    $msg = sprintf('Twitter bridge - posted notice %s to Twitter using basic auth.',
-                   $notice->id);
-    common_log(LOG_INFO, $msg);
-
-    return true;
-}
-
-function process_error($e, $flink)
-{
-    $user        = $flink->getUser();
-    $errmsg      = $e->getMessage();
-    $delivered   = false;
-
-    switch($errmsg) {
-     case 'The requested URL returned error: 401':
-        $logmsg = sprintf('Twiter bridge - User %1$s (user id: %2$s) has an invalid ' .
-                          'Twitter screen_name/password combo or an invalid acesss token.',
-                          $user->nickname, $user->id);
-        $delivered = true;
-        remove_twitter_link($flink);
-        break;
-     case 'The requested URL returned error: 403':
-        $logmsg = sprintf('Twitter bridge - User %1$s (user id: %2$s) has exceeded ' .
-                          'his/her Twitter request limit.',
-                          $user->nickname, $user->id);
-        break;
-     default:
-        $logmsg = sprintf('Twitter bridge - cURL error trying to send notice to Twitter ' .
-                          'for user %1$s (user id: %2$s) - ' .
-                          'code: %3$s message: %4$s.',
-                          $user->nickname, $user->id,
-                          $e->getCode(), $e->getMessage());
-        break;
-    }
-
-    common_log(LOG_WARNING, $logmsg);
-
-    return $delivered;
-}
-
-function format_status($notice)
-{
-    // XXX: Hack to get around PHP cURL's use of @ being a a meta character
-    return preg_replace('/^@/', ' @', $notice->content);
-}
-
-function remove_twitter_link($flink)
-{
-    $user = $flink->getUser();
-
-    common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
-               "user $user->nickname (user id: $user->id).");
-
-    $result = $flink->delete();
-
-    if (empty($result)) {
-        common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
-                   "Foreign_link for $user->nickname (user id: $user->id)!");
-        common_log_db_error($flink, 'DELETE', __FILE__);
-    }
-
-    // Notify the user that her Twitter bridge is down
-
-    if (isset($user->email)) {
-
-        $result = mail_twitter_bridge_removed($user);
-
-        if (!$result) {
-
-            $msg = 'Unable to send email to notify ' .
-              "$user->nickname (user id: $user->id) " .
-              'that their Twitter bridge link was ' .
-              'removed!';
-
-            common_log(LOG_WARNING, $msg);
-        }
-    }
-
-}
diff --git a/lib/twitterbasicauthclient.php b/lib/twitterbasicauthclient.php
deleted file mode 100644 (file)
index 1040d72..0000000
+++ /dev/null
@@ -1,242 +0,0 @@
-<?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 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);
-}
-
-/**
- * Exception wrapper for cURL errors
- *
- * @category Integration
- * @package  StatusNet
- * @author Adrian Lang <mail@adrianlang.de>
- * @author Brenda Wallace <shiny@cpan.org>
- * @author Craig Andrews <candrews@integralblue.com>
- * @author Dan Moore <dan@moore.cx>
- * @author Evan Prodromou <evan@status.net>
- * @author mEDI <medi@milaro.net>
- * @author Sarven Capadisli <csarven@status.net>
- * @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 BasicAuthCurlException extends Exception
-{
-}
-
-/**
- * Class for talking to the Twitter API with HTTP Basic Auth.
- *
- * @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 TwitterBasicAuthClient
-{
-    var $screen_name = null;
-    var $password    = null;
-
-    /**
-     * constructor
-     *
-     * @param Foreign_link $flink a Foreign_link storing the
-     *                            Twitter user's password, etc.
-     */
-    function __construct($flink)
-    {
-        $fuser             = $flink->getForeignUser();
-        $this->screen_name = $fuser->nickname;
-        $this->password    = $flink->credentials;
-    }
-
-    /**
-     * Calls Twitter's /statuses/update API method
-     *
-     * @param string $status                text of the status
-     * @param int    $in_reply_to_status_id optional id of the status it's
-     *                                      a reply to
-     *
-     * @return mixed the status
-     */
-    function statusesUpdate($status, $in_reply_to_status_id = null)
-    {
-        $url      = 'https://twitter.com/statuses/update.json';
-        $params   = array('status' => $status,
-                          'source' => common_config('integration', 'source'),
-                          'in_reply_to_status_id' => $in_reply_to_status_id);
-        $response = $this->httpRequest($url, $params);
-        $status   = json_decode($response);
-        return $status;
-    }
-
-    /**
-     * Calls Twitter's /statuses/friends_timeline API method
-     *
-     * @param int $since_id show statuses after this id
-     * @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 statusesFriendsTimeline($since_id = null, $max_id = null,
-                                     $cnt = null, $page = null)
-    {
-        $url    = 'https://twitter.com/statuses/friends_timeline.json';
-        $params = array('since_id' => $since_id,
-                        'max_id' => $max_id,
-                        'count' => $cnt,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->httpRequest($url);
-        $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://twitter.com/statuses/friends.json";
-
-        $params = array('id' => $id,
-                        'user_id' => $user_id,
-                        'screen_name' => $screen_name,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->httpRequest($url);
-        $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://twitter.com/friends/ids.json";
-
-        $params = array('id' => $id,
-                        'user_id' => $user_id,
-                        'screen_name' => $screen_name,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->httpRequest($url);
-        $ids      = json_decode($response);
-        return $ids;
-    }
-
-    /**
-     * Make a HTTP request using cURL.
-     *
-     * @param string $url    Where to make the request
-     * @param array  $params post parameters
-     *
-     * @return mixed the request
-     */
-    function httpRequest($url, $params = null, $auth = true)
-    {
-        $options = array(
-                         CURLOPT_RETURNTRANSFER => true,
-                         CURLOPT_FAILONERROR    => true,
-                         CURLOPT_HEADER         => false,
-                         CURLOPT_FOLLOWLOCATION => true,
-                         CURLOPT_USERAGENT      => 'StatusNet',
-                         CURLOPT_CONNECTTIMEOUT => 120,
-                         CURLOPT_TIMEOUT        => 120,
-                         CURLOPT_HTTPAUTH       => CURLAUTH_ANY,
-                         CURLOPT_SSL_VERIFYPEER => false,
-
-                         // Twitter is strict about accepting invalid "Expect" headers
-
-                         CURLOPT_HTTPHEADER => array('Expect:')
-                         );
-
-        if (isset($params)) {
-            $options[CURLOPT_POST]       = true;
-            $options[CURLOPT_POSTFIELDS] = $params;
-        }
-
-        if ($auth) {
-            $options[CURLOPT_USERPWD] = $this->screen_name .
-              ':' . $this->password;
-        }
-
-        $ch = curl_init($url);
-        curl_setopt_array($ch, $options);
-        $response = curl_exec($ch);
-
-        if ($response === false) {
-            $msg  = curl_error($ch);
-            $code = curl_errno($ch);
-            throw new BasicAuthCurlException($msg, $code);
-        }
-
-        curl_close($ch);
-
-        return $response;
-    }
-
-}
diff --git a/lib/twitteroauthclient.php b/lib/twitteroauthclient.php
deleted file mode 100644 (file)
index bad2b74..0000000
+++ /dev/null
@@ -1,229 +0,0 @@
-<?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 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://twitter.com/oauth/request_token';
-    public static $authorizeURL    = 'https://twitter.com/oauth/authorize';
-    public static $accessTokenURL  = 'https://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');
-
-        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;
-        }
-    }
-
-    /**
-     * Builds a link to Twitter's endpoint for authorizing a request token
-     *
-     * @param OAuthToken $request_token token to authorize
-     *
-     * @return the link
-     */
-    function getAuthorizeLink($request_token)
-    {
-        return parent::getAuthorizeLink(self::$authorizeURL,
-                                        $request_token,
-                                        common_local_url('twitterauthorization'));
-    }
-
-    /**
-     * Calls Twitter's /account/verify_credentials API method
-     *
-     * @return mixed the Twitter user
-     */
-    function verifyCredentials()
-    {
-        $url          = 'https://twitter.com/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 int    $in_reply_to_status_id optional id of the status it's
-     *                                      a reply to
-     *
-     * @return mixed the status
-     */
-    function statusesUpdate($status, $in_reply_to_status_id = null)
-    {
-        $url      = 'https://twitter.com/statuses/update.json';
-        $params   = array('status' => $status,
-            'in_reply_to_status_id' => $in_reply_to_status_id);
-        $response = $this->oAuthPost($url, $params);
-        $status   = json_decode($response);
-        return $status;
-    }
-
-    /**
-     * Calls Twitter's /statuses/friends_timeline API method
-     *
-     * @param int $since_id show statuses after this id
-     * @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 statusesFriendsTimeline($since_id = null, $max_id = null,
-                                     $cnt = null, $page = null)
-    {
-
-        $url    = 'https://twitter.com/statuses/friends_timeline.json';
-        $params = array('since_id' => $since_id,
-                        'max_id' => $max_id,
-                        'count' => $cnt,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->oAuthGet($url);
-        $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://twitter.com/statuses/friends.json";
-
-        $params = array('id' => $id,
-                        'user_id' => $user_id,
-                        'screen_name' => $screen_name,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->oAuthGet($url);
-        $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://twitter.com/friends/ids.json";
-
-        $params = array('id' => $id,
-                        'user_id' => $user_id,
-                        'screen_name' => $screen_name,
-                        'page' => $page);
-        $qry    = http_build_query($params);
-
-        if (!empty($qry)) {
-            $url .= "?$qry";
-        }
-
-        $response = $this->oAuthGet($url);
-        $ids      = json_decode($response);
-        return $ids;
-    }
-
-}
diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php
new file mode 100644 (file)
index 0000000..a8de1c6
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Laconica, 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   Laconica
+ * @author    Zach Copley <zach@controlyourself.ca>
+ * @copyright 2009 Control Yourself, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://laconi.ca/
+ */
+
+if (!defined('LACONICA')) {
+    exit(1);
+}
+
+/**
+ * Plugin for sending and importing Twitter statuses
+ *
+ * This class allows users to link their Twitter accounts
+ *
+ * @category Plugin
+ * @package  Laconica
+ * @author   Zach Copley <zach@controlyourself.ca>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://laconi.ca/
+ * @link     http://twitter.com/
+ */
+
+class TwitterBridgePlugin extends Plugin
+{
+    /**
+     * Initializer for the plugin.
+     */
+
+    function __construct()
+    {
+        parent::__construct();
+    }
+
+    /**
+     * Add Twitter-related paths to the router table
+     *
+     * Hook for RouterInitialized event.
+     *
+     * @return boolean hook return
+     */
+
+    function onRouterInitialized(&$m)
+    {
+        $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
+        $m->connect('settings/twitter', array('action' => 'twittersettings'));
+
+        return true;
+    }
+
+    function onEndConnectSettingsNav(&$action)
+    {
+        $action_name = $action->trimmed('action');
+
+        $action->menuItem(common_local_url('twittersettings'),
+                          _('Twitter'),
+                          _('Twitter integration options'),
+                          $action_name === 'twittersettings');
+
+        return true;
+    }
+
+    function onAutoload($cls)
+    {
+        switch ($cls)
+        {
+         case 'TwittersettingsAction':
+         case 'TwitterauthorizationAction':
+            require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php');
+            return false;
+         case 'TwitterOAuthClient':
+            require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php');
+            return false;
+         default:
+            return true;
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php
new file mode 100755 (executable)
index 0000000..0668c62
--- /dev/null
@@ -0,0 +1,287 @@
+#!/usr/bin/env php
+<?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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'di::';
+$longoptions = array('id::', 'debug');
+
+$helptext = <<<END_OF_TRIM_HELP
+Batch script for synching local friends with Twitter friends.
+  -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/parallelizingdaemon.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Daemon to sync local friends with Twitter friends
+ *
+ * @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/
+ */
+
+$helptext = <<<END_OF_TWITTER_HELP
+Batch script for synching local friends with Twitter friends.
+
+END_OF_TWITTER_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/parallelizingdaemon.php';
+
+class SyncTwitterFriendsDaemon 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 ('synctwitterfriends.' . $this->_id);
+    }
+
+    /**
+     * Find all the Twitter foreign links for users who have requested
+     * automatically subscribing to their Twitter friends locally.
+     *
+     * @return array flinks an array of Foreign_link objects
+     */
+    function getObjects()
+    {
+        $flinks = array();
+        $flink = new Foreign_link();
+
+        $conn = &$flink->getDatabaseConnection();
+
+        $flink->service = TWITTER_SERVICE;
+        $flink->orderBy('last_friendsync');
+        $flink->limit(25);  // sync this many users during this run
+        $flink->find();
+
+        while ($flink->fetch()) {
+            if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) {
+                $flinks[] = clone($flink);
+            }
+        }
+
+        $conn->disconnect();
+
+        global $_DB_DATAOBJECT;
+        unset($_DB_DATAOBJECT['CONNECTIONS']);
+
+        return $flinks;
+    }
+
+    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->subscribeTwitterFriends($flink);
+
+        $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 fetchTwitterFriends($flink)
+    {
+        $friends = array();
+
+        $client = null;
+
+        if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
+            $token = TwitterOAuthClient::unpackToken($flink->credentials);
+            $client = new TwitterOAuthClient($token->key, $token->secret);
+            common_debug($this->name() . '- Grabbing friends IDs with OAuth.');
+        } else {
+            $client = new TwitterBasicAuthClient($flink);
+            common_debug($this->name() . '- Grabbing friends IDs with basic auth.');
+        }
+
+        try {
+            $friends_ids = $client->friendsIds();
+        } catch (Exception $e) {
+            common_log(LOG_WARNING, $this->name() .
+                       ' - cURL error getting friend ids ' .
+                       $e->getCode() . ' - ' . $e->getMessage());
+            return $friends;
+        }
+
+        if (empty($friends_ids)) {
+            common_debug($this->name() .
+                         " - Twitter user $flink->foreign_id " .
+                         'doesn\'t have any friends!');
+            return $friends;
+        }
+
+        common_debug($this->name() . ' - Twitter\'s API says Twitter user id ' .
+                     "$flink->foreign_id has " .
+                     count($friends_ids) . ' friends.');
+
+        // Calculate how many pages to get...
+        $pages = ceil(count($friends_ids) / 100);
+
+        if ($pages == 0) {
+            common_debug($this->name() . " - $user seems to have no friends.");
+        }
+
+        for ($i = 1; $i <= $pages; $i++) {
+
+        try {
+            $more_friends = $client->statusesFriends(null, null, null, $i);
+        } catch (Exception $e) {
+            common_log(LOG_WARNING, $this->name() .
+                       ' - cURL error getting Twitter statuses/friends ' .
+                       "page $i - " . $e->getCode() . ' - ' .
+                       $e->getMessage());
+        }
+
+            if (empty($more_friends)) {
+                common_log(LOG_WARNING, $this->name() .
+                           " - Couldn't retrieve page $i " .
+                           "of Twitter user $flink->foreign_id friends.");
+                continue;
+            } else {
+                $friends = array_merge($friends, $more_friends);
+            }
+        }
+
+        return $friends;
+    }
+
+    function subscribeTwitterFriends($flink)
+    {
+        $friends = $this->fetchTwitterFriends($flink);
+
+        if (empty($friends)) {
+            common_debug($this->name() .
+                         ' - Couldn\'t get friends from Twitter for ' .
+                         "Twitter user $flink->foreign_id.");
+            return false;
+        }
+
+        $user = $flink->getUser();
+
+        foreach ($friends as $friend) {
+
+            $friend_name = $friend->screen_name;
+            $friend_id = (int) $friend->id;
+
+            // Update or create the Foreign_user record for each
+            // Twitter friend
+
+            if (!save_twitter_user($friend_id, $friend_name)) {
+                common_log(LOG_WARNING, $this-name() .
+                           " - Couldn't save $screen_name's friend, $friend_name.");
+                continue;
+            }
+
+            // Check to see if there's a related local user
+
+            $friend_flink = Foreign_link::getByForeignID($friend_id,
+                                                         TWITTER_SERVICE);
+
+            if (!empty($friend_flink)) {
+
+                // Get associated user and subscribe her
+
+                $friend_user = User::staticGet('id', $friend_flink->user_id);
+
+                if (!empty($friend_user)) {
+                    $result = subs_subscribe_to($user, $friend_user);
+
+                    if ($result === true) {
+                        common_log(LOG_INFO,
+                                   $this->name() . ' - Subscribed ' .
+                                   "$friend_user->nickname to $user->nickname.");
+                    } else {
+                        common_debug($this->name() .
+                                     ' - Tried subscribing ' .
+                                     "$friend_user->nickname to $user->nickname - " .
+                                     $result);
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
+}
+
+$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;
+}
+
+$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug);
+$syncer->runOnce();
+
diff --git a/plugins/TwitterBridge/daemons/twitterqueuehandler.php b/plugins/TwitterBridge/daemons/twitterqueuehandler.php
new file mode 100755 (executable)
index 0000000..f0e76bb
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env php
+<?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/>.
+ */
+
+define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));
+
+$shortoptions = 'i::';
+$longoptions = array('id::');
+
+$helptext = <<<END_OF_ENJIT_HELP
+Daemon script for pushing new notices to Twitter.
+
+    -i --id           Identity (default none)
+
+END_OF_ENJIT_HELP;
+
+require_once INSTALLDIR . '/scripts/commandline.inc';
+require_once INSTALLDIR . '/lib/queuehandler.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+class TwitterQueueHandler extends QueueHandler
+{
+    function transport()
+    {
+        return 'twitter';
+    }
+
+    function start()
+    {
+        $this->log(LOG_INFO, "INITIALIZE");
+        return true;
+    }
+
+    function handle_notice($notice)
+    {
+        return broadcast_twitter($notice);
+    }
+
+    function finish()
+    {
+    }
+
+}
+
+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;
+}
+
+$handler = new TwitterQueueHandler($id);
+
+$handler->runOnce();
diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php
new file mode 100755 (executable)
index 0000000..4752ada
--- /dev/null
@@ -0,0 +1,566 @@
+#!/usr/bin/env php
+<?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/>.
+ */
+
+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', 60); // in seconds
+
+$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/daemon.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Fetcher for statuses from Twitter
+ *
+ * Fetches statuses from Twitter and inserts them as notices in local
+ * system.
+ *
+ * @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/
+ */
+
+// NOTE: an Avatar path MUST be set in config.php for this
+// script to work: e.g.: $config['avatar']['path'] = '/statusnet/avatar';
+
+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);
+            }
+        }
+
+        $flink->free();
+        unset($flink);
+
+        $conn->disconnect();
+        unset($_DB_DATAOBJECT['CONNECTIONS']);
+
+        return $flinks;
+    }
+
+    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);
+
+        $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($flink)
+    {
+        if (empty($flink)) {
+            common_log(LOG_WARNING, $this->name() .
+                       " - Can't retrieve Foreign_link for foreign ID $fid");
+            return;
+        }
+
+        common_debug($this->name() . ' - Trying to get timeline for Twitter user ' .
+                     $flink->foreign_id);
+
+        // XXX: Biggest remaining issue - How do we know at which status
+        // to start importing?  How many statuses?  Right now I'm going
+        // with the default last 20.
+
+        $client = null;
+
+        if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
+            $token = TwitterOAuthClient::unpackToken($flink->credentials);
+            $client = new TwitterOAuthClient($token->key, $token->secret);
+            common_debug($this->name() . ' - Grabbing friends timeline with OAuth.');
+        } else {
+            $client = new TwitterBasicAuthClient($flink);
+            common_debug($this->name() . ' - Grabbing friends timeline with basic auth.');
+        }
+
+        $timeline = null;
+
+        try {
+            $timeline = $client->statusesFriendsTimeline();
+        } catch (Exception $e) {
+            common_log(LOG_WARNING, $this->name() .
+                       ' - Twitter client unable to get friends timeline for user ' .
+                       $flink->user_id . ' - code: ' .
+                       $e->getCode() . 'msg: ' . $e->getMessage());
+        }
+
+        if (empty($timeline)) {
+            common_log(LOG_WARNING, $this->name() .  " - Empty timeline.");
+            return;
+        }
+
+        // Reverse to preserve order
+
+        foreach (array_reverse($timeline) as $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($this->name() . ' - Skipping import of status ' .
+                             $status->id . ' with source ' . $source);
+                continue;
+            }
+
+            $this->saveStatus($status, $flink);
+        }
+
+        // Okay, record the time we synced with Twitter for posterity
+
+        $flink->last_noticesync = common_sql_now();
+        $flink->update();
+    }
+
+    function saveStatus($status, $flink)
+    {
+        $id = $this->ensureProfile($status->user);
+
+        $profile = Profile::staticGet($id);
+
+        if (empty($profile)) {
+            common_log(LOG_ERR, $this->name() .
+                ' - Problem saving notice. No associated Profile.');
+            return null;
+        }
+
+        // XXX: change of screen name?
+
+        $uri = 'http://twitter.com/' . $status->user->screen_name .
+            '/status/' . $status->id;
+
+        $notice = Notice::staticGet('uri', $uri);
+
+        // check to see if we've already imported the status
+
+        if (empty($notice)) {
+
+            $notice = new Notice();
+
+            $notice->profile_id = $id;
+            $notice->uri        = $uri;
+            $notice->created    = strftime('%Y-%m-%d %H:%M:%S',
+                                           strtotime($status->created_at));
+            $notice->content    = common_shorten_links($status->text); // XXX
+            $notice->rendered   = common_render_content($notice->content, $notice);
+            $notice->source     = 'twitter';
+            $notice->reply_to   = null; // XXX: lookup reply
+            $notice->is_local   = Notice::GATEWAY;
+
+            if (Event::handle('StartNoticeSave', array(&$notice))) {
+                $id = $notice->insert();
+                Event::handle('EndNoticeSave', array($notice));
+            }
+        }
+
+        if (!Notice_inbox::pkeyGet(array('notice_id' => $notice->id,
+                                         'user_id' => $flink->user_id))) {
+            // Add to inbox
+            $inbox = new Notice_inbox();
+
+            $inbox->user_id   = $flink->user_id;
+            $inbox->notice_id = $notice->id;
+            $inbox->created   = $notice->created;
+            $inbox->source    = NOTICE_INBOX_SOURCE_GATEWAY; // From a private source
+
+            $inbox->insert();
+        }
+    }
+
+    function ensureProfile($user)
+    {
+        // check to see if there's already a profile for this user
+
+        $profileurl = 'http://twitter.com/' . $user->screen_name;
+        $profile = Profile::staticGet('profileurl', $profileurl);
+
+        if (!empty($profile)) {
+            common_debug($this->name() .
+                         " - Profile for $profile->nickname found.");
+
+            // Check to see if the user's Avatar has changed
+
+            $this->checkAvatar($user, $profile);
+            return $profile->id;
+
+        } else {
+            common_debug($this->name() . ' - Adding profile and remote profile ' .
+                         "for Twitter user: $profileurl.");
+
+            $profile = new Profile();
+            $profile->query("BEGIN");
+
+            $profile->nickname = $user->screen_name;
+            $profile->fullname = $user->name;
+            $profile->homepage = $user->url;
+            $profile->bio = $user->description;
+            $profile->location = $user->location;
+            $profile->profileurl = $profileurl;
+            $profile->created = common_sql_now();
+
+            $id = $profile->insert();
+
+            if (empty($id)) {
+                common_log_db_error($profile, 'INSERT', __FILE__);
+                $profile->query("ROLLBACK");
+                return false;
+            }
+
+            // check for remote profile
+
+            $remote_pro = Remote_profile::staticGet('uri', $profileurl);
+
+            if (empty($remote_pro)) {
+
+                $remote_pro = new Remote_profile();
+
+                $remote_pro->id = $id;
+                $remote_pro->uri = $profileurl;
+                $remote_pro->created = common_sql_now();
+
+                $rid = $remote_pro->insert();
+
+                if (empty($rid)) {
+                    common_log_db_error($profile, 'INSERT', __FILE__);
+                    $profile->query("ROLLBACK");
+                    return false;
+                }
+            }
+
+            $profile->query("COMMIT");
+
+            $this->saveAvatars($user, $id);
+
+            return $id;
+        }
+    }
+
+    function checkAvatar($twitter_user, $profile)
+    {
+        global $config;
+
+        $path_parts = pathinfo($twitter_user->profile_image_url);
+
+        $newname = 'Twitter_' . $twitter_user->id . '_' .
+            $path_parts['basename'];
+
+        $oldname = $profile->getAvatar(48)->filename;
+
+        if ($newname != $oldname) {
+            common_debug($this->name() . ' - Avatar for Twitter user ' .
+                         "$profile->nickname has changed.");
+            common_debug($this->name() . " - old: $oldname new: $newname");
+
+            $this->updateAvatars($twitter_user, $profile);
+        }
+
+        if ($this->missingAvatarFile($profile)) {
+            common_debug($this->name() . ' - Twitter user ' .
+                         $profile->nickname .
+                         ' is missing one or more local avatars.');
+            common_debug($this->name() ." - old: $oldname new: $newname");
+
+            $this->updateAvatars($twitter_user, $profile);
+        }
+
+    }
+
+    function updateAvatars($twitter_user, $profile) {
+
+        global $config;
+
+        $path_parts = pathinfo($twitter_user->profile_image_url);
+
+        $img_root = substr($path_parts['basename'], 0, -11);
+        $ext = $path_parts['extension'];
+        $mediatype = $this->getMediatype($ext);
+
+        foreach (array('mini', 'normal', 'bigger') as $size) {
+            $url = $path_parts['dirname'] . '/' .
+                $img_root . '_' . $size . ".$ext";
+            $filename = 'Twitter_' . $twitter_user->id . '_' .
+                $img_root . "_$size.$ext";
+
+            $this->updateAvatar($profile->id, $size, $mediatype, $filename);
+            $this->fetchAvatar($url, $filename);
+        }
+    }
+
+    function missingAvatarFile($profile) {
+
+        foreach (array(24, 48, 73) as $size) {
+
+            $filename = $profile->getAvatar($size)->filename;
+            $avatarpath = Avatar::path($filename);
+
+            if (file_exists($avatarpath) == FALSE) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    function getMediatype($ext)
+    {
+        $mediatype = null;
+
+        switch (strtolower($ext)) {
+        case 'jpg':
+            $mediatype = 'image/jpg';
+            break;
+        case 'gif':
+            $mediatype = 'image/gif';
+            break;
+        default:
+            $mediatype = 'image/png';
+        }
+
+        return $mediatype;
+    }
+
+    function saveAvatars($user, $id)
+    {
+        global $config;
+
+        $path_parts = pathinfo($user->profile_image_url);
+        $ext = $path_parts['extension'];
+        $end = strlen('_normal' . $ext);
+        $img_root = substr($path_parts['basename'], 0, -($end+1));
+        $mediatype = $this->getMediatype($ext);
+
+        foreach (array('mini', 'normal', 'bigger') as $size) {
+            $url = $path_parts['dirname'] . '/' .
+                $img_root . '_' . $size . ".$ext";
+            $filename = 'Twitter_' . $user->id . '_' .
+                $img_root . "_$size.$ext";
+
+            if ($this->fetchAvatar($url, $filename)) {
+                $this->newAvatar($id, $size, $mediatype, $filename);
+            } else {
+                common_log(LOG_WARNING, $this->id() .
+                           " - Problem fetching Avatar: $url");
+            }
+        }
+    }
+
+    function updateAvatar($profile_id, $size, $mediatype, $filename) {
+
+        common_debug($this->name() . " - Updating avatar: $size");
+
+        $profile = Profile::staticGet($profile_id);
+
+        if (empty($profile)) {
+            common_debug($this->name() . " - Couldn't get profile: $profile_id!");
+            return;
+        }
+
+        $sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
+        $avatar = $profile->getAvatar($sizes[$size]);
+
+        // Delete the avatar, if present
+
+        if ($avatar) {
+            $avatar->delete();
+        }
+
+        $this->newAvatar($profile->id, $size, $mediatype, $filename);
+    }
+
+    function newAvatar($profile_id, $size, $mediatype, $filename)
+    {
+        global $config;
+
+        $avatar = new Avatar();
+        $avatar->profile_id = $profile_id;
+
+        switch($size) {
+        case 'mini':
+            $avatar->width  = 24;
+            $avatar->height = 24;
+            break;
+        case 'normal':
+            $avatar->width  = 48;
+            $avatar->height = 48;
+            break;
+        default:
+
+            // Note: Twitter's big avatars are a different size than
+            // StatusNet's (StatusNet's = 96)
+
+            $avatar->width  = 73;
+            $avatar->height = 73;
+        }
+
+        $avatar->original = 0; // we don't have the original
+        $avatar->mediatype = $mediatype;
+        $avatar->filename = $filename;
+        $avatar->url = Avatar::url($filename);
+
+        common_debug($this->name() . " - New filename: $avatar->url");
+
+        $avatar->created = common_sql_now();
+
+        $id = $avatar->insert();
+
+        if (empty($id)) {
+            common_log_db_error($avatar, 'INSERT', __FILE__);
+            return null;
+        }
+
+        common_debug($this->name() .
+                     " - Saved new $size avatar for $profile_id.");
+
+        return $id;
+    }
+
+    function fetchAvatar($url, $filename)
+    {
+        $avatar_dir = INSTALLDIR . '/avatar/';
+
+        $avatarfile = $avatar_dir . $filename;
+
+        $out = fopen($avatarfile, 'wb');
+        if (!$out) {
+            common_log(LOG_WARNING, $this->name() .
+                       " - Couldn't open file $filename");
+            return false;
+        }
+
+        common_debug($this->name() . " - Fetching Twitter avatar: $url");
+
+        $ch = curl_init();
+        curl_setopt($ch, CURLOPT_URL, $url);
+        curl_setopt($ch, CURLOPT_FILE, $out);
+        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
+        $result = curl_exec($ch);
+        curl_close($ch);
+
+        fclose($out);
+
+        return $result;
+    }
+}
+
+$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, 60, 2, $debug);
+$fetcher->runOnce();
+
diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php
new file mode 100644 (file)
index 0000000..afc3f55
--- /dev/null
@@ -0,0 +1,311 @@
+<?php
+/*
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2008, 2009, StatusNet, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1
+
+function updateTwitter_user($twitter_id, $screen_name)
+{
+    $uri = 'http://twitter.com/' . $screen_name;
+    $fuser = new Foreign_user();
+
+    $fuser->query('BEGIN');
+
+    // Dropping down to SQL because regular DB_DataObject udpate stuff doesn't seem
+    // to work so good with tables that have multiple column primary keys
+
+    // Any time we update the uri for a forein user we have to make sure there
+    // are no dupe entries first -- unique constraint on the uri column
+
+    $qry = 'UPDATE foreign_user set uri = \'\' WHERE uri = ';
+    $qry .= '\'' . $uri . '\'' . ' AND service = ' . TWITTER_SERVICE;
+
+    $fuser->query($qry);
+
+    // Update the user
+
+    $qry = 'UPDATE foreign_user SET nickname = ';
+    $qry .= '\'' . $screen_name . '\'' . ', uri = \'' . $uri . '\' ';
+    $qry .= 'WHERE id = ' . $twitter_id . ' AND service = ' . TWITTER_SERVICE;
+
+    $fuser->query('COMMIT');
+
+    $fuser->free();
+    unset($fuser);
+
+    return true;
+}
+
+function add_twitter_user($twitter_id, $screen_name)
+{
+
+    $new_uri = 'http://twitter.com/' . $screen_name;
+
+    // Clear out any bad old foreign_users with the new user's legit URL
+    // This can happen when users move around or fakester accounts get
+    // repoed, and things like that.
+
+    $luser = new Foreign_user();
+    $luser->uri = $new_uri;
+    $luser->service = TWITTER_SERVICE;
+    $result = $luser->delete();
+
+    if (empty($result)) {
+        common_log(LOG_WARNING,
+            "Twitter bridge - removed invalid Twitter user squatting on uri: $new_uri");
+    }
+
+    $luser->free();
+    unset($luser);
+
+    // Otherwise, create a new Twitter user
+
+    $fuser = new Foreign_user();
+
+    $fuser->nickname = $screen_name;
+    $fuser->uri = 'http://twitter.com/' . $screen_name;
+    $fuser->id = $twitter_id;
+    $fuser->service = TWITTER_SERVICE;
+    $fuser->created = common_sql_now();
+    $result = $fuser->insert();
+
+    if (empty($result)) {
+        common_log(LOG_WARNING,
+            "Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name.");
+        common_log_db_error($fuser, 'INSERT', __FILE__);
+    } else {
+        common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id).");
+    }
+
+    return $result;
+}
+
+// Creates or Updates a Twitter user
+function save_twitter_user($twitter_id, $screen_name)
+{
+
+    // Check to see whether the Twitter user is already in the system,
+    // and update its screen name and uri if so.
+
+    $fuser = Foreign_user::getForeignUser($twitter_id, TWITTER_SERVICE);
+
+    if (!empty($fuser)) {
+
+        $result = true;
+
+        // Only update if Twitter screen name has changed
+
+        if ($fuser->nickname != $screen_name) {
+            $result = updateTwitter_user($twitter_id, $screen_name);
+
+            common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' .
+                "$fuser->id to $screen_name, was $fuser->nickname");
+        }
+
+        return $result;
+
+    } else {
+        return add_twitter_user($twitter_id, $screen_name);
+    }
+
+    $fuser->free();
+    unset($fuser);
+
+    return true;
+}
+
+function is_twitter_bound($notice, $flink) {
+
+    // Check to see if notice should go to Twitter
+    if (!empty($flink) && ($flink->noticesync & FOREIGN_NOTICE_SEND)) {
+
+        // If it's not a Twitter-style reply, or if the user WANTS to send replies.
+        if (!preg_match('/^@[a-zA-Z0-9_]{1,15}\b/u', $notice->content) ||
+            ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+function broadcast_twitter($notice)
+{
+    $flink = Foreign_link::getByUserID($notice->profile_id,
+                                       TWITTER_SERVICE);
+
+    if (is_twitter_bound($notice, $flink)) {
+        if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
+            return broadcast_oauth($notice, $flink);
+        } else {
+            return broadcast_basicauth($notice, $flink);
+        }
+    }
+
+    return true;
+}
+
+function broadcast_oauth($notice, $flink) {
+    $user = $flink->getUser();
+    $statustxt = format_status($notice);
+    // Convert !groups to #hashes
+    $statustxt = preg_replace('/(^|\s)!([A-Za-z0-9]{1,64})/', "\\1#\\2", $statustxt);
+    $token = TwitterOAuthClient::unpackToken($flink->credentials);
+    $client = new TwitterOAuthClient($token->key, $token->secret);
+    $status = null;
+
+    try {
+        $status = $client->statusesUpdate($statustxt);
+    } catch (OAuthClientCurlException $e) {
+        return process_error($e, $flink);
+    }
+
+    if (empty($status)) {
+
+        // This could represent a failure posting,
+        // or the Twitter API might just be behaving flakey.
+
+        $errmsg = sprintf('Twitter bridge - No data returned by Twitter API when ' .
+                          'trying to send update for %1$s (user id %2$s).',
+                          $user->nickname, $user->id);
+        common_log(LOG_WARNING, $errmsg);
+
+        return false;
+    }
+
+    // Notice crossed the great divide
+
+    $msg = sprintf('Twitter bridge - posted notice %s to Twitter using OAuth.',
+                   $notice->id);
+    common_log(LOG_INFO, $msg);
+
+    return true;
+}
+
+function broadcast_basicauth($notice, $flink)
+{
+    $user = $flink->getUser();
+
+    $statustxt = format_status($notice);
+
+    $client = new TwitterBasicAuthClient($flink);
+    $status = null;
+
+    try {
+        $status = $client->statusesUpdate($statustxt);
+    } catch (BasicAuthCurlException $e) {
+        return process_error($e, $flink);
+    }
+
+    if (empty($status)) {
+
+        $errmsg = sprintf('Twitter bridge - No data returned by Twitter API when ' .
+                          'trying to send update for %1$s (user id %2$s).',
+                          $user->nickname, $user->id);
+        common_log(LOG_WARNING, $errmsg);
+
+            $errmsg = sprintf('No data returned by Twitter API when ' .
+                             'trying to send update for %1$s (user id %2$s).',
+                             $user->nickname, $user->id);
+            common_log(LOG_WARNING, $errmsg);
+        return false;
+    }
+
+    $msg = sprintf('Twitter bridge - posted notice %s to Twitter using basic auth.',
+                   $notice->id);
+    common_log(LOG_INFO, $msg);
+
+    return true;
+}
+
+function process_error($e, $flink)
+{
+    $user        = $flink->getUser();
+    $errmsg      = $e->getMessage();
+    $delivered   = false;
+
+    switch($errmsg) {
+     case 'The requested URL returned error: 401':
+        $logmsg = sprintf('Twiter bridge - User %1$s (user id: %2$s) has an invalid ' .
+                          'Twitter screen_name/password combo or an invalid acesss token.',
+                          $user->nickname, $user->id);
+        $delivered = true;
+        remove_twitter_link($flink);
+        break;
+     case 'The requested URL returned error: 403':
+        $logmsg = sprintf('Twitter bridge - User %1$s (user id: %2$s) has exceeded ' .
+                          'his/her Twitter request limit.',
+                          $user->nickname, $user->id);
+        break;
+     default:
+        $logmsg = sprintf('Twitter bridge - cURL error trying to send notice to Twitter ' .
+                          'for user %1$s (user id: %2$s) - ' .
+                          'code: %3$s message: %4$s.',
+                          $user->nickname, $user->id,
+                          $e->getCode(), $e->getMessage());
+        break;
+    }
+
+    common_log(LOG_WARNING, $logmsg);
+
+    return $delivered;
+}
+
+function format_status($notice)
+{
+    // XXX: Hack to get around PHP cURL's use of @ being a a meta character
+    return preg_replace('/^@/', ' @', $notice->content);
+}
+
+function remove_twitter_link($flink)
+{
+    $user = $flink->getUser();
+
+    common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' .
+               "user $user->nickname (user id: $user->id).");
+
+    $result = $flink->delete();
+
+    if (empty($result)) {
+        common_log(LOG_ERR, 'Could not remove Twitter bridge ' .
+                   "Foreign_link for $user->nickname (user id: $user->id)!");
+        common_log_db_error($flink, 'DELETE', __FILE__);
+    }
+
+    // Notify the user that her Twitter bridge is down
+
+    if (isset($user->email)) {
+
+        $result = mail_twitter_bridge_removed($user);
+
+        if (!$result) {
+
+            $msg = 'Unable to send email to notify ' .
+              "$user->nickname (user id: $user->id) " .
+              'that their Twitter bridge link was ' .
+              'removed!';
+
+            common_log(LOG_WARNING, $msg);
+        }
+    }
+
+}
diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php
new file mode 100644 (file)
index 0000000..2a93ff1
--- /dev/null
@@ -0,0 +1,224 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for doing OAuth authentication 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  TwitterauthorizationAction
+ * @package   StatusNet
+ * @author    Zach Copely <zach@status.net>
+ * @copyright 2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Class for doing OAuth authentication against Twitter
+ *
+ * Peforms the OAuth "dance" between StatusNet and Twitter -- requests a token,
+ * authorizes it, and exchanges it for an access token.  It also creates a link
+ * (Foreign_link) between the StatusNet user and Twitter user and stores the
+ * access token and secret in the link.
+ *
+ * @category Twitter
+ * @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://laconi.ca/
+ *
+ */
+class TwitterauthorizationAction extends Action
+{
+    /**
+     * Initialize class members. Looks for 'oauth_token' parameter.
+     *
+     * @param array $args misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($args)
+    {
+        parent::prepare($args);
+
+        $this->oauth_token = $this->arg('oauth_token');
+
+        return true;
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $args is ignored since it's now passed in in prepare()
+     *
+     * @return nothing
+     */
+    function handle($args)
+    {
+        parent::handle($args);
+
+        if (!common_logged_in()) {
+            $this->clientError(_('Not logged in.'), 403);
+        }
+
+        $user  = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        // If there's already a foreign link record, it means we already
+        // have an access token, and this is unecessary. So go back.
+
+        if (isset($flink)) {
+            common_redirect(common_local_url('twittersettings'));
+        }
+
+        // $this->oauth_token is only populated once Twitter authorizes our
+        // request token. If it's empty we're at the beginning of the auth
+        // process
+
+        if (empty($this->oauth_token)) {
+            $this->authorizeRequestToken();
+        } else {
+            $this->saveAccessToken();
+        }
+    }
+
+    /**
+     * Asks Twitter for a request token, and then redirects to Twitter
+     * to authorize it.
+     *
+     * @return nothing
+     */
+    function authorizeRequestToken()
+    {
+        try {
+
+            // Get a new request token and authorize it
+
+            $client  = new TwitterOAuthClient();
+            $req_tok =
+              $client->getRequestToken(TwitterOAuthClient::$requestTokenURL);
+
+            // Sock the request token away in the session temporarily
+
+            $_SESSION['twitter_request_token']        = $req_tok->key;
+            $_SESSION['twitter_request_token_secret'] = $req_tok->secret;
+
+            $auth_link = $client->getAuthorizeLink($req_tok);
+
+        } catch (TwitterOAuthClientException $e) {
+            $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
+                           $e->getCode(), $e->getMessage());
+            $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        common_redirect($auth_link);
+    }
+
+    /**
+     * Called when Twitter returns an authorized request token. Exchanges
+     * it for an access token and stores it.
+     *
+     * @return nothing
+     */
+    function saveAccessToken()
+    {
+
+        // Check to make sure Twitter returned the same request
+        // token we sent them
+
+        if ($_SESSION['twitter_request_token'] != $this->oauth_token) {
+            $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        try {
+
+            $client = new TwitterOAuthClient($_SESSION['twitter_request_token'],
+                $_SESSION['twitter_request_token_secret']);
+
+            // Exchange the request token for an access token
+
+            $atok = $client->getAccessToken(TwitterOAuthClient::$accessTokenURL);
+
+            // Test the access token and get the user's Twitter info
+
+            $client       = new TwitterOAuthClient($atok->key, $atok->secret);
+            $twitter_user = $client->verifyCredentials();
+
+        } catch (OAuthClientException $e) {
+            $msg = sprintf('OAuth client cURL error - code: %1$s, msg: %2$s',
+                           $e->getCode(), $e->getMessage());
+            $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        // Save the access token and Twitter user info
+
+        $this->saveForeignLink($atok, $twitter_user);
+
+        // Clean up the the mess we made in the session
+
+        unset($_SESSION['twitter_request_token']);
+        unset($_SESSION['twitter_request_token_secret']);
+
+        common_redirect(common_local_url('twittersettings'));
+    }
+
+    /**
+     * Saves a Foreign_link between Twitter user and local user,
+     * which includes the access token and secret.
+     *
+     * @param OAuthToken $access_token the access token to save
+     * @param mixed      $twitter_user twitter API user object
+     *
+     * @return nothing
+     */
+    function saveForeignLink($access_token, $twitter_user)
+    {
+        $user = common_current_user();
+
+        $flink = new Foreign_link();
+
+        $flink->user_id     = $user->id;
+        $flink->foreign_id  = $twitter_user->id;
+        $flink->service     = TWITTER_SERVICE;
+
+        $creds = TwitterOAuthClient::packToken($access_token);
+
+        $flink->credentials = $creds;
+        $flink->created     = common_sql_now();
+
+        // Defaults: noticesync on, everything else off
+
+        $flink->set_flags(true, false, false, false);
+
+        $flink_id = $flink->insert();
+
+        if (empty($flink_id)) {
+            common_log_db_error($flink, 'INSERT', __FILE__);
+                $this->serverError(_('Couldn\'t link your Twitter account.'));
+        }
+
+        save_twitter_user($twitter_user->id, $twitter_user->screen_name);
+    }
+
+}
+
diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php
new file mode 100644 (file)
index 0000000..1040d72
--- /dev/null
@@ -0,0 +1,242 @@
+<?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 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);
+}
+
+/**
+ * Exception wrapper for cURL errors
+ *
+ * @category Integration
+ * @package  StatusNet
+ * @author Adrian Lang <mail@adrianlang.de>
+ * @author Brenda Wallace <shiny@cpan.org>
+ * @author Craig Andrews <candrews@integralblue.com>
+ * @author Dan Moore <dan@moore.cx>
+ * @author Evan Prodromou <evan@status.net>
+ * @author mEDI <medi@milaro.net>
+ * @author Sarven Capadisli <csarven@status.net>
+ * @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 BasicAuthCurlException extends Exception
+{
+}
+
+/**
+ * Class for talking to the Twitter API with HTTP Basic Auth.
+ *
+ * @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 TwitterBasicAuthClient
+{
+    var $screen_name = null;
+    var $password    = null;
+
+    /**
+     * constructor
+     *
+     * @param Foreign_link $flink a Foreign_link storing the
+     *                            Twitter user's password, etc.
+     */
+    function __construct($flink)
+    {
+        $fuser             = $flink->getForeignUser();
+        $this->screen_name = $fuser->nickname;
+        $this->password    = $flink->credentials;
+    }
+
+    /**
+     * Calls Twitter's /statuses/update API method
+     *
+     * @param string $status                text of the status
+     * @param int    $in_reply_to_status_id optional id of the status it's
+     *                                      a reply to
+     *
+     * @return mixed the status
+     */
+    function statusesUpdate($status, $in_reply_to_status_id = null)
+    {
+        $url      = 'https://twitter.com/statuses/update.json';
+        $params   = array('status' => $status,
+                          'source' => common_config('integration', 'source'),
+                          'in_reply_to_status_id' => $in_reply_to_status_id);
+        $response = $this->httpRequest($url, $params);
+        $status   = json_decode($response);
+        return $status;
+    }
+
+    /**
+     * Calls Twitter's /statuses/friends_timeline API method
+     *
+     * @param int $since_id show statuses after this id
+     * @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 statusesFriendsTimeline($since_id = null, $max_id = null,
+                                     $cnt = null, $page = null)
+    {
+        $url    = 'https://twitter.com/statuses/friends_timeline.json';
+        $params = array('since_id' => $since_id,
+                        'max_id' => $max_id,
+                        'count' => $cnt,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->httpRequest($url);
+        $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://twitter.com/statuses/friends.json";
+
+        $params = array('id' => $id,
+                        'user_id' => $user_id,
+                        'screen_name' => $screen_name,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->httpRequest($url);
+        $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://twitter.com/friends/ids.json";
+
+        $params = array('id' => $id,
+                        'user_id' => $user_id,
+                        'screen_name' => $screen_name,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->httpRequest($url);
+        $ids      = json_decode($response);
+        return $ids;
+    }
+
+    /**
+     * Make a HTTP request using cURL.
+     *
+     * @param string $url    Where to make the request
+     * @param array  $params post parameters
+     *
+     * @return mixed the request
+     */
+    function httpRequest($url, $params = null, $auth = true)
+    {
+        $options = array(
+                         CURLOPT_RETURNTRANSFER => true,
+                         CURLOPT_FAILONERROR    => true,
+                         CURLOPT_HEADER         => false,
+                         CURLOPT_FOLLOWLOCATION => true,
+                         CURLOPT_USERAGENT      => 'StatusNet',
+                         CURLOPT_CONNECTTIMEOUT => 120,
+                         CURLOPT_TIMEOUT        => 120,
+                         CURLOPT_HTTPAUTH       => CURLAUTH_ANY,
+                         CURLOPT_SSL_VERIFYPEER => false,
+
+                         // Twitter is strict about accepting invalid "Expect" headers
+
+                         CURLOPT_HTTPHEADER => array('Expect:')
+                         );
+
+        if (isset($params)) {
+            $options[CURLOPT_POST]       = true;
+            $options[CURLOPT_POSTFIELDS] = $params;
+        }
+
+        if ($auth) {
+            $options[CURLOPT_USERPWD] = $this->screen_name .
+              ':' . $this->password;
+        }
+
+        $ch = curl_init($url);
+        curl_setopt_array($ch, $options);
+        $response = curl_exec($ch);
+
+        if ($response === false) {
+            $msg  = curl_error($ch);
+            $code = curl_errno($ch);
+            throw new BasicAuthCurlException($msg, $code);
+        }
+
+        curl_close($ch);
+
+        return $response;
+    }
+
+}
diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php
new file mode 100644 (file)
index 0000000..bad2b74
--- /dev/null
@@ -0,0 +1,229 @@
+<?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 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://twitter.com/oauth/request_token';
+    public static $authorizeURL    = 'https://twitter.com/oauth/authorize';
+    public static $accessTokenURL  = 'https://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');
+
+        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;
+        }
+    }
+
+    /**
+     * Builds a link to Twitter's endpoint for authorizing a request token
+     *
+     * @param OAuthToken $request_token token to authorize
+     *
+     * @return the link
+     */
+    function getAuthorizeLink($request_token)
+    {
+        return parent::getAuthorizeLink(self::$authorizeURL,
+                                        $request_token,
+                                        common_local_url('twitterauthorization'));
+    }
+
+    /**
+     * Calls Twitter's /account/verify_credentials API method
+     *
+     * @return mixed the Twitter user
+     */
+    function verifyCredentials()
+    {
+        $url          = 'https://twitter.com/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 int    $in_reply_to_status_id optional id of the status it's
+     *                                      a reply to
+     *
+     * @return mixed the status
+     */
+    function statusesUpdate($status, $in_reply_to_status_id = null)
+    {
+        $url      = 'https://twitter.com/statuses/update.json';
+        $params   = array('status' => $status,
+            'in_reply_to_status_id' => $in_reply_to_status_id);
+        $response = $this->oAuthPost($url, $params);
+        $status   = json_decode($response);
+        return $status;
+    }
+
+    /**
+     * Calls Twitter's /statuses/friends_timeline API method
+     *
+     * @param int $since_id show statuses after this id
+     * @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 statusesFriendsTimeline($since_id = null, $max_id = null,
+                                     $cnt = null, $page = null)
+    {
+
+        $url    = 'https://twitter.com/statuses/friends_timeline.json';
+        $params = array('since_id' => $since_id,
+                        'max_id' => $max_id,
+                        'count' => $cnt,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->oAuthGet($url);
+        $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://twitter.com/statuses/friends.json";
+
+        $params = array('id' => $id,
+                        'user_id' => $user_id,
+                        'screen_name' => $screen_name,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->oAuthGet($url);
+        $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://twitter.com/friends/ids.json";
+
+        $params = array('id' => $id,
+                        'user_id' => $user_id,
+                        'screen_name' => $screen_name,
+                        'page' => $page);
+        $qry    = http_build_query($params);
+
+        if (!empty($qry)) {
+            $url .= "?$qry";
+        }
+
+        $response = $this->oAuthGet($url);
+        $ids      = json_decode($response);
+        return $ids;
+    }
+
+}
diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php
new file mode 100644 (file)
index 0000000..2afa85b
--- /dev/null
@@ -0,0 +1,272 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Settings for Twitter integration
+ *
+ * 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  Settings
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2008-2009 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET') && !defined('LACONICA')) {
+    exit(1);
+}
+
+require_once INSTALLDIR . '/lib/connectsettingsaction.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Settings for Twitter integration
+ *
+ * @category Settings
+ * @package  StatusNet
+ * @author   Evan Prodromou <evan@status.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link     http://status.net/
+ *
+ * @see      SettingsAction
+ */
+
+class TwittersettingsAction extends ConnectSettingsAction
+{
+    /**
+     * Title of the page
+     *
+     * @return string Title of the page
+     */
+
+    function title()
+    {
+        return _('Twitter settings');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return instructions for use
+     */
+
+    function getInstructions()
+    {
+        return _('Connect your Twitter account to share your updates ' .
+                 'with your Twitter friends and vice-versa.');
+    }
+
+    /**
+     * Content area of the page
+     *
+     * Shows a form for associating a Twitter account with this
+     * StatusNet account. Also lets the user set preferences.
+     *
+     * @return void
+     */
+
+    function showContent()
+    {
+
+        $user = common_current_user();
+
+        $profile = $user->getProfile();
+
+        $fuser = null;
+
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        if (!empty($flink)) {
+            $fuser = $flink->getForeignUser();
+        }
+
+        $this->elementStart('form', array('method' => 'post',
+                                          'id' => 'form_settings_twitter',
+                                          'class' => 'form_settings',
+                                          'action' =>
+                                          common_local_url('twittersettings')));
+
+        $this->hidden('token', common_session_token());
+
+        $this->elementStart('fieldset', array('id' => 'settings_twitter_account'));
+
+        if (empty($fuser)) {
+            $this->elementStart('ul', 'form_data');
+            $this->elementStart('li', array('id' => 'settings_twitter_login_button'));
+            $this->element('a', array('href' => common_local_url('twitterauthorization')),
+                           'Connect my Twitter account');
+            $this->elementEnd('li');
+            $this->elementEnd('ul');
+
+            $this->elementEnd('fieldset');
+        } else {
+            $this->element('legend', null, _('Twitter account'));
+            $this->elementStart('p', array('id' => 'form_confirmed'));
+            $this->element('a', array('href' => $fuser->uri), $fuser->nickname);
+            $this->elementEnd('p');
+            $this->element('p', 'form_note',
+                           _('Connected Twitter account'));
+
+            $this->submit('remove', _('Remove'));
+
+            $this->elementEnd('fieldset');
+
+            $this->elementStart('fieldset', array('id' => 'settings_twitter_preferences'));
+
+            $this->element('legend', null, _('Preferences'));
+            $this->elementStart('ul', 'form_data');
+            $this->elementStart('li');
+            $this->checkbox('noticesend',
+                            _('Automatically send my notices to Twitter.'),
+                            ($flink) ?
+                            ($flink->noticesync & FOREIGN_NOTICE_SEND) :
+                            true);
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->checkbox('replysync',
+                            _('Send local "@" replies to Twitter.'),
+                            ($flink) ?
+                            ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) :
+                            true);
+            $this->elementEnd('li');
+            $this->elementStart('li');
+            $this->checkbox('friendsync',
+                            _('Subscribe to my Twitter friends here.'),
+                            ($flink) ?
+                            ($flink->friendsync & FOREIGN_FRIEND_RECV) :
+                            false);
+            $this->elementEnd('li');
+
+            if (common_config('twitterbridge','enabled')) {
+                $this->elementStart('li');
+                $this->checkbox('noticerecv',
+                                _('Import my Friends Timeline.'),
+                                ($flink) ?
+                                ($flink->noticesync & FOREIGN_NOTICE_RECV) :
+                                false);
+                $this->elementEnd('li');
+            } else {
+                // preserve setting even if bidrection bridge toggled off
+
+                if ($flink && ($flink->noticesync & FOREIGN_NOTICE_RECV)) {
+                    $this->hidden('noticerecv', true, 'noticerecv');
+                }
+            }
+
+            $this->elementEnd('ul');
+
+            if ($flink) {
+                $this->submit('save', _('Save'));
+            } else {
+                $this->submit('add', _('Add'));
+            }
+
+            $this->elementEnd('fieldset');
+        }
+
+        $this->elementEnd('form');
+    }
+
+    /**
+     * Handle posts to this form
+     *
+     * Based on the button that was pressed, muxes out to other functions
+     * to do the actual task requested.
+     *
+     * All sub-functions reload the form with a message -- success or failure.
+     *
+     * @return void
+     */
+
+    function handlePost()
+    {
+        // CSRF protection
+        $token = $this->trimmed('token');
+        if (!$token || $token != common_session_token()) {
+            $this->showForm(_('There was a problem with your session token. '.
+                              'Try again, please.'));
+            return;
+        }
+
+        if ($this->arg('save')) {
+            $this->savePreferences();
+        } else if ($this->arg('remove')) {
+            $this->removeTwitterAccount();
+        } else {
+            $this->showForm(_('Unexpected form submission.'));
+        }
+    }
+
+    /**
+     * Disassociate an existing Twitter account from this account
+     *
+     * @return void
+     */
+
+    function removeTwitterAccount()
+    {
+        $user = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        $result = $flink->delete();
+
+        if (empty($result)) {
+            common_log_db_error($flink, 'DELETE', __FILE__);
+            $this->serverError(_('Couldn\'t remove Twitter user.'));
+            return;
+        }
+
+        $this->showForm(_('Twitter account removed.'), true);
+    }
+
+    /**
+     * Save user's Twitter-bridging preferences
+     *
+     * @return void
+     */
+
+    function savePreferences()
+    {
+        $noticesend = $this->boolean('noticesend');
+        $noticerecv = $this->boolean('noticerecv');
+        $friendsync = $this->boolean('friendsync');
+        $replysync  = $this->boolean('replysync');
+
+        $user = common_current_user();
+        $flink = Foreign_link::getByUserID($user->id, TWITTER_SERVICE);
+
+        if (empty($flink)) {
+            common_log_db_error($flink, 'SELECT', __FILE__);
+            $this->showForm(_('Couldn\'t save Twitter preferences.'));
+            return;
+        }
+
+        $original = clone($flink);
+        $flink->set_flags($noticesend, $noticerecv, $replysync, $friendsync);
+        $result = $flink->update($original);
+
+        if ($result === false) {
+            common_log_db_error($flink, 'UPDATE', __FILE__);
+            $this->showForm(_('Couldn\'t save Twitter preferences.'));
+            return;
+        }
+
+        $this->showForm(_('Twitter preferences saved.'), true);
+    }
+
+}
diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php
deleted file mode 100755 (executable)
index b30e700..0000000
+++ /dev/null
@@ -1,286 +0,0 @@
-#!/usr/bin/env php
-<?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/>.
- */
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-
-$shortoptions = 'di::';
-$longoptions = array('id::', 'debug');
-
-$helptext = <<<END_OF_TRIM_HELP
-Batch script for synching local friends with Twitter friends.
-  -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/parallelizingdaemon.php';
-
-/**
- * Daemon to sync local friends with Twitter friends
- *
- * @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/
- */
-
-$helptext = <<<END_OF_TWITTER_HELP
-Batch script for synching local friends with Twitter friends.
-
-END_OF_TWITTER_HELP;
-
-require_once INSTALLDIR . '/scripts/commandline.inc';
-require_once INSTALLDIR . '/lib/parallelizingdaemon.php';
-
-class SyncTwitterFriendsDaemon 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 ('synctwitterfriends.' . $this->_id);
-    }
-
-    /**
-     * Find all the Twitter foreign links for users who have requested
-     * automatically subscribing to their Twitter friends locally.
-     *
-     * @return array flinks an array of Foreign_link objects
-     */
-    function getObjects()
-    {
-        $flinks = array();
-        $flink = new Foreign_link();
-
-        $conn = &$flink->getDatabaseConnection();
-
-        $flink->service = TWITTER_SERVICE;
-        $flink->orderBy('last_friendsync');
-        $flink->limit(25);  // sync this many users during this run
-        $flink->find();
-
-        while ($flink->fetch()) {
-            if (($flink->friendsync & FOREIGN_FRIEND_RECV) == FOREIGN_FRIEND_RECV) {
-                $flinks[] = clone($flink);
-            }
-        }
-
-        $conn->disconnect();
-
-        global $_DB_DATAOBJECT;
-        unset($_DB_DATAOBJECT['CONNECTIONS']);
-
-        return $flinks;
-    }
-
-    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->subscribeTwitterFriends($flink);
-
-        $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 fetchTwitterFriends($flink)
-    {
-        $friends = array();
-
-        $client = null;
-
-        if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
-            $token = TwitterOAuthClient::unpackToken($flink->credentials);
-            $client = new TwitterOAuthClient($token->key, $token->secret);
-            common_debug($this->name() . '- Grabbing friends IDs with OAuth.');
-        } else {
-            $client = new TwitterBasicAuthClient($flink);
-            common_debug($this->name() . '- Grabbing friends IDs with basic auth.');
-        }
-
-        try {
-            $friends_ids = $client->friendsIds();
-        } catch (Exception $e) {
-            common_log(LOG_WARNING, $this->name() .
-                       ' - cURL error getting friend ids ' .
-                       $e->getCode() . ' - ' . $e->getMessage());
-            return $friends;
-        }
-
-        if (empty($friends_ids)) {
-            common_debug($this->name() .
-                         " - Twitter user $flink->foreign_id " .
-                         'doesn\'t have any friends!');
-            return $friends;
-        }
-
-        common_debug($this->name() . ' - Twitter\'s API says Twitter user id ' .
-                     "$flink->foreign_id has " .
-                     count($friends_ids) . ' friends.');
-
-        // Calculate how many pages to get...
-        $pages = ceil(count($friends_ids) / 100);
-
-        if ($pages == 0) {
-            common_debug($this->name() . " - $user seems to have no friends.");
-        }
-
-        for ($i = 1; $i <= $pages; $i++) {
-
-        try {
-            $more_friends = $client->statusesFriends(null, null, null, $i);
-        } catch (Exception $e) {
-            common_log(LOG_WARNING, $this->name() .
-                       ' - cURL error getting Twitter statuses/friends ' .
-                       "page $i - " . $e->getCode() . ' - ' .
-                       $e->getMessage());
-        }
-
-            if (empty($more_friends)) {
-                common_log(LOG_WARNING, $this->name() .
-                           " - Couldn't retrieve page $i " .
-                           "of Twitter user $flink->foreign_id friends.");
-                continue;
-            } else {
-                $friends = array_merge($friends, $more_friends);
-            }
-        }
-
-        return $friends;
-    }
-
-    function subscribeTwitterFriends($flink)
-    {
-        $friends = $this->fetchTwitterFriends($flink);
-
-        if (empty($friends)) {
-            common_debug($this->name() .
-                         ' - Couldn\'t get friends from Twitter for ' .
-                         "Twitter user $flink->foreign_id.");
-            return false;
-        }
-
-        $user = $flink->getUser();
-
-        foreach ($friends as $friend) {
-
-            $friend_name = $friend->screen_name;
-            $friend_id = (int) $friend->id;
-
-            // Update or create the Foreign_user record for each
-            // Twitter friend
-
-            if (!save_twitter_user($friend_id, $friend_name)) {
-                common_log(LOG_WARNING, $this-name() .
-                           " - Couldn't save $screen_name's friend, $friend_name.");
-                continue;
-            }
-
-            // Check to see if there's a related local user
-
-            $friend_flink = Foreign_link::getByForeignID($friend_id,
-                                                         TWITTER_SERVICE);
-
-            if (!empty($friend_flink)) {
-
-                // Get associated user and subscribe her
-
-                $friend_user = User::staticGet('id', $friend_flink->user_id);
-
-                if (!empty($friend_user)) {
-                    $result = subs_subscribe_to($user, $friend_user);
-
-                    if ($result === true) {
-                        common_log(LOG_INFO,
-                                   $this->name() . ' - Subscribed ' .
-                                   "$friend_user->nickname to $user->nickname.");
-                    } else {
-                        common_debug($this->name() .
-                                     ' - Tried subscribing ' .
-                                     "$friend_user->nickname to $user->nickname - " .
-                                     $result);
-                    }
-                }
-            }
-        }
-
-        return true;
-    }
-
-}
-
-$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;
-}
-
-$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug);
-$syncer->runOnce();
-
diff --git a/scripts/twitterqueuehandler.php b/scripts/twitterqueuehandler.php
deleted file mode 100755 (executable)
index ce4d824..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/env php
-<?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/>.
- */
-
-define('INSTALLDIR', realpath(dirname(__FILE__) . '/..'));
-
-$shortoptions = 'i::';
-$longoptions = array('id::');
-
-$helptext = <<<END_OF_ENJIT_HELP
-Daemon script for pushing new notices to Twitter.
-
-    -i --id           Identity (default none)
-
-END_OF_ENJIT_HELP;
-
-require_once INSTALLDIR.'/scripts/commandline.inc';
-
-require_once INSTALLDIR . '/lib/twitter.php';
-require_once INSTALLDIR . '/lib/queuehandler.php';
-
-class TwitterQueueHandler extends QueueHandler
-{
-    function transport()
-    {
-        return 'twitter';
-    }
-
-    function start()
-    {
-        $this->log(LOG_INFO, "INITIALIZE");
-        return true;
-    }
-
-    function handle_notice($notice)
-    {
-        return broadcast_twitter($notice);
-    }
-
-    function finish()
-    {
-    }
-
-}
-
-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;
-}
-
-$handler = new TwitterQueueHandler($id);
-
-$handler->runOnce();
diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php
deleted file mode 100755 (executable)
index 3cdf186..0000000
+++ /dev/null
@@ -1,565 +0,0 @@
-#!/usr/bin/env php
-<?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/>.
- */
-
-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', 60); // in seconds
-
-$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/daemon.php';
-
-/**
- * Fetcher for statuses from Twitter
- *
- * Fetches statuses from Twitter and inserts them as notices in local
- * system.
- *
- * @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/
- */
-
-// NOTE: an Avatar path MUST be set in config.php for this
-// script to work: e.g.: $config['avatar']['path'] = '/statusnet/avatar';
-
-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);
-            }
-        }
-
-        $flink->free();
-        unset($flink);
-
-        $conn->disconnect();
-        unset($_DB_DATAOBJECT['CONNECTIONS']);
-
-        return $flinks;
-    }
-
-    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);
-
-        $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($flink)
-    {
-        if (empty($flink)) {
-            common_log(LOG_WARNING, $this->name() .
-                       " - Can't retrieve Foreign_link for foreign ID $fid");
-            return;
-        }
-
-        common_debug($this->name() . ' - Trying to get timeline for Twitter user ' .
-                     $flink->foreign_id);
-
-        // XXX: Biggest remaining issue - How do we know at which status
-        // to start importing?  How many statuses?  Right now I'm going
-        // with the default last 20.
-
-        $client = null;
-
-        if (TwitterOAuthClient::isPackedToken($flink->credentials)) {
-            $token = TwitterOAuthClient::unpackToken($flink->credentials);
-            $client = new TwitterOAuthClient($token->key, $token->secret);
-            common_debug($this->name() . ' - Grabbing friends timeline with OAuth.');
-        } else {
-            $client = new TwitterBasicAuthClient($flink);
-            common_debug($this->name() . ' - Grabbing friends timeline with basic auth.');
-        }
-
-        $timeline = null;
-
-        try {
-            $timeline = $client->statusesFriendsTimeline();
-        } catch (Exception $e) {
-            common_log(LOG_WARNING, $this->name() .
-                       ' - Twitter client unable to get friends timeline for user ' .
-                       $flink->user_id . ' - code: ' .
-                       $e->getCode() . 'msg: ' . $e->getMessage());
-        }
-
-        if (empty($timeline)) {
-            common_log(LOG_WARNING, $this->name() .  " - Empty timeline.");
-            return;
-        }
-
-        // Reverse to preserve order
-
-        foreach (array_reverse($timeline) as $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($this->name() . ' - Skipping import of status ' .
-                             $status->id . ' with source ' . $source);
-                continue;
-            }
-
-            $this->saveStatus($status, $flink);
-        }
-
-        // Okay, record the time we synced with Twitter for posterity
-
-        $flink->last_noticesync = common_sql_now();
-        $flink->update();
-    }
-
-    function saveStatus($status, $flink)
-    {
-        $id = $this->ensureProfile($status->user);
-
-        $profile = Profile::staticGet($id);
-
-        if (empty($profile)) {
-            common_log(LOG_ERR, $this->name() .
-                ' - Problem saving notice. No associated Profile.');
-            return null;
-        }
-
-        // XXX: change of screen name?
-
-        $uri = 'http://twitter.com/' . $status->user->screen_name .
-            '/status/' . $status->id;
-
-        $notice = Notice::staticGet('uri', $uri);
-
-        // check to see if we've already imported the status
-
-        if (empty($notice)) {
-
-            $notice = new Notice();
-
-            $notice->profile_id = $id;
-            $notice->uri        = $uri;
-            $notice->created    = strftime('%Y-%m-%d %H:%M:%S',
-                                           strtotime($status->created_at));
-            $notice->content    = common_shorten_links($status->text); // XXX
-            $notice->rendered   = common_render_content($notice->content, $notice);
-            $notice->source     = 'twitter';
-            $notice->reply_to   = null; // XXX: lookup reply
-            $notice->is_local   = Notice::GATEWAY;
-
-            if (Event::handle('StartNoticeSave', array(&$notice))) {
-                $id = $notice->insert();
-                Event::handle('EndNoticeSave', array($notice));
-            }
-        }
-
-        if (!Notice_inbox::pkeyGet(array('notice_id' => $notice->id,
-                                         'user_id' => $flink->user_id))) {
-            // Add to inbox
-            $inbox = new Notice_inbox();
-
-            $inbox->user_id   = $flink->user_id;
-            $inbox->notice_id = $notice->id;
-            $inbox->created   = $notice->created;
-            $inbox->source    = NOTICE_INBOX_SOURCE_GATEWAY; // From a private source
-
-            $inbox->insert();
-        }
-    }
-
-    function ensureProfile($user)
-    {
-        // check to see if there's already a profile for this user
-
-        $profileurl = 'http://twitter.com/' . $user->screen_name;
-        $profile = Profile::staticGet('profileurl', $profileurl);
-
-        if (!empty($profile)) {
-            common_debug($this->name() .
-                         " - Profile for $profile->nickname found.");
-
-            // Check to see if the user's Avatar has changed
-
-            $this->checkAvatar($user, $profile);
-            return $profile->id;
-
-        } else {
-            common_debug($this->name() . ' - Adding profile and remote profile ' .
-                         "for Twitter user: $profileurl.");
-
-            $profile = new Profile();
-            $profile->query("BEGIN");
-
-            $profile->nickname = $user->screen_name;
-            $profile->fullname = $user->name;
-            $profile->homepage = $user->url;
-            $profile->bio = $user->description;
-            $profile->location = $user->location;
-            $profile->profileurl = $profileurl;
-            $profile->created = common_sql_now();
-
-            $id = $profile->insert();
-
-            if (empty($id)) {
-                common_log_db_error($profile, 'INSERT', __FILE__);
-                $profile->query("ROLLBACK");
-                return false;
-            }
-
-            // check for remote profile
-
-            $remote_pro = Remote_profile::staticGet('uri', $profileurl);
-
-            if (empty($remote_pro)) {
-
-                $remote_pro = new Remote_profile();
-
-                $remote_pro->id = $id;
-                $remote_pro->uri = $profileurl;
-                $remote_pro->created = common_sql_now();
-
-                $rid = $remote_pro->insert();
-
-                if (empty($rid)) {
-                    common_log_db_error($profile, 'INSERT', __FILE__);
-                    $profile->query("ROLLBACK");
-                    return false;
-                }
-            }
-
-            $profile->query("COMMIT");
-
-            $this->saveAvatars($user, $id);
-
-            return $id;
-        }
-    }
-
-    function checkAvatar($twitter_user, $profile)
-    {
-        global $config;
-
-        $path_parts = pathinfo($twitter_user->profile_image_url);
-
-        $newname = 'Twitter_' . $twitter_user->id . '_' .
-            $path_parts['basename'];
-
-        $oldname = $profile->getAvatar(48)->filename;
-
-        if ($newname != $oldname) {
-            common_debug($this->name() . ' - Avatar for Twitter user ' .
-                         "$profile->nickname has changed.");
-            common_debug($this->name() . " - old: $oldname new: $newname");
-
-            $this->updateAvatars($twitter_user, $profile);
-        }
-
-        if ($this->missingAvatarFile($profile)) {
-            common_debug($this->name() . ' - Twitter user ' .
-                         $profile->nickname .
-                         ' is missing one or more local avatars.');
-            common_debug($this->name() ." - old: $oldname new: $newname");
-
-            $this->updateAvatars($twitter_user, $profile);
-        }
-
-    }
-
-    function updateAvatars($twitter_user, $profile) {
-
-        global $config;
-
-        $path_parts = pathinfo($twitter_user->profile_image_url);
-
-        $img_root = substr($path_parts['basename'], 0, -11);
-        $ext = $path_parts['extension'];
-        $mediatype = $this->getMediatype($ext);
-
-        foreach (array('mini', 'normal', 'bigger') as $size) {
-            $url = $path_parts['dirname'] . '/' .
-                $img_root . '_' . $size . ".$ext";
-            $filename = 'Twitter_' . $twitter_user->id . '_' .
-                $img_root . "_$size.$ext";
-
-            $this->updateAvatar($profile->id, $size, $mediatype, $filename);
-            $this->fetchAvatar($url, $filename);
-        }
-    }
-
-    function missingAvatarFile($profile) {
-
-        foreach (array(24, 48, 73) as $size) {
-
-            $filename = $profile->getAvatar($size)->filename;
-            $avatarpath = Avatar::path($filename);
-
-            if (file_exists($avatarpath) == FALSE) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    function getMediatype($ext)
-    {
-        $mediatype = null;
-
-        switch (strtolower($ext)) {
-        case 'jpg':
-            $mediatype = 'image/jpg';
-            break;
-        case 'gif':
-            $mediatype = 'image/gif';
-            break;
-        default:
-            $mediatype = 'image/png';
-        }
-
-        return $mediatype;
-    }
-
-    function saveAvatars($user, $id)
-    {
-        global $config;
-
-        $path_parts = pathinfo($user->profile_image_url);
-        $ext = $path_parts['extension'];
-        $end = strlen('_normal' . $ext);
-        $img_root = substr($path_parts['basename'], 0, -($end+1));
-        $mediatype = $this->getMediatype($ext);
-
-        foreach (array('mini', 'normal', 'bigger') as $size) {
-            $url = $path_parts['dirname'] . '/' .
-                $img_root . '_' . $size . ".$ext";
-            $filename = 'Twitter_' . $user->id . '_' .
-                $img_root . "_$size.$ext";
-
-            if ($this->fetchAvatar($url, $filename)) {
-                $this->newAvatar($id, $size, $mediatype, $filename);
-            } else {
-                common_log(LOG_WARNING, $this->id() .
-                           " - Problem fetching Avatar: $url");
-            }
-        }
-    }
-
-    function updateAvatar($profile_id, $size, $mediatype, $filename) {
-
-        common_debug($this->name() . " - Updating avatar: $size");
-
-        $profile = Profile::staticGet($profile_id);
-
-        if (empty($profile)) {
-            common_debug($this->name() . " - Couldn't get profile: $profile_id!");
-            return;
-        }
-
-        $sizes = array('mini' => 24, 'normal' => 48, 'bigger' => 73);
-        $avatar = $profile->getAvatar($sizes[$size]);
-
-        // Delete the avatar, if present
-
-        if ($avatar) {
-            $avatar->delete();
-        }
-
-        $this->newAvatar($profile->id, $size, $mediatype, $filename);
-    }
-
-    function newAvatar($profile_id, $size, $mediatype, $filename)
-    {
-        global $config;
-
-        $avatar = new Avatar();
-        $avatar->profile_id = $profile_id;
-
-        switch($size) {
-        case 'mini':
-            $avatar->width  = 24;
-            $avatar->height = 24;
-            break;
-        case 'normal':
-            $avatar->width  = 48;
-            $avatar->height = 48;
-            break;
-        default:
-
-            // Note: Twitter's big avatars are a different size than
-            // StatusNet's (StatusNet's = 96)
-
-            $avatar->width  = 73;
-            $avatar->height = 73;
-        }
-
-        $avatar->original = 0; // we don't have the original
-        $avatar->mediatype = $mediatype;
-        $avatar->filename = $filename;
-        $avatar->url = Avatar::url($filename);
-
-        common_debug($this->name() . " - New filename: $avatar->url");
-
-        $avatar->created = common_sql_now();
-
-        $id = $avatar->insert();
-
-        if (empty($id)) {
-            common_log_db_error($avatar, 'INSERT', __FILE__);
-            return null;
-        }
-
-        common_debug($this->name() .
-                     " - Saved new $size avatar for $profile_id.");
-
-        return $id;
-    }
-
-    function fetchAvatar($url, $filename)
-    {
-        $avatar_dir = INSTALLDIR . '/avatar/';
-
-        $avatarfile = $avatar_dir . $filename;
-
-        $out = fopen($avatarfile, 'wb');
-        if (!$out) {
-            common_log(LOG_WARNING, $this->name() .
-                       " - Couldn't open file $filename");
-            return false;
-        }
-
-        common_debug($this->name() . " - Fetching Twitter avatar: $url");
-
-        $ch = curl_init();
-        curl_setopt($ch, CURLOPT_URL, $url);
-        curl_setopt($ch, CURLOPT_FILE, $out);
-        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
-        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
-        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
-        $result = curl_exec($ch);
-        curl_close($ch);
-
-        fclose($out);
-
-        return $result;
-    }
-}
-
-$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, 60, 2, $debug);
-$fetcher->runOnce();
-