of registered users.
* xmppconfirmhandler.php - sends confirmation messages to registered
users.
-* twitterqueuehandler.php - sends queued notices to Twitter for user
- who have opted to set up Twitter bridging.
* facebookqueuehandler.php - sends queued notices to Facebook for users
of the built-in Facebook application.
config section below for how to configure to use STOMP. As of this
writing, the software has been tested with ActiveMQ (
-Twitter Bridge
---------------
-
-* OAuth
-
-As of 0.8.1, OAuth is used to to access protected resources on Twitter
-instead of HTTP Basic Auth. To use Twitter bridging you will need
-to register your instance of StatusNet as an application on Twitter
-(http://twitter.com/apps), and update the following variables in your
-config.php with the consumer key and secret Twitter generates for you:
-
- $config['twitter']['consumer_key'] = 'YOURKEY';
- $config['twitter']['consumer_secret'] = 'YOURSECRET';
-
-When registering your application with Twitter set the type to "Browser"
-and your Callback URL to:
-
- http://example.org/mublog/twitter/authorization
-
-The default access type should be, "Read & Write".
-
-* Importing statuses from Twitter
-
-To allow your users to import their friends' Twitter statuses, you will
-need to enable the bidirectional Twitter bridge in config.php:
-
- $config['twitterbridge']['enabled'] = true;
-
-and run the TwitterStatusFetcher daemon (scripts/twitterstatusfetcher.php).
-Additionally, you will want to set the integration source variable,
-which will keep notices posted to Twitter via StatusNet from looping
-back. The integration source should be set to the name of your
-application, exactly as you specified it on the settings page for your
-StatusNet application on Twitter, e.g.:
-
- $config['integration']['source'] = 'YourApp';
-
-* Twitter Friends Syncing
-
-Users may set a flag in their settings ("Subscribe to my Twitter friends
-here" under the Twitter tab) to have StatusNet attempt to locate and
-subscribe to "friends" (people they "follow") on Twitter who also have
-accounts on your StatusNet system, and who have previously set up a link
-for automatically posting notices to Twitter.
-
-As of 0.8.0, this is no longer accomplished via a cron job. Instead you
-must run the SyncTwitterFriends daemon (scripts/synctwitterfreinds.php).
-
Built-in Facebook Application
-----------------------------
enabled: Whether to enable SMS integration. Defaults to true. Queues
should also be enabled.
-twitter
--------
-
-For Twitter integration
-
-enabled: Whether to enable Twitter integration. Defaults to true.
- Queues should also be enabled.
-
integration
-----------
A catch-all for integration with other systems.
-source: The name to use for the source of posts to Twitter. Defaults
- to 'statusnet', but if you request your own source name from
- Twitter <http://twitter.com/help/request_source>, you can use
- that here instead. Status updates on Twitter will then have
- links to your site.
taguri: base for tag:// URIs. Defaults to site-server + ',2009'.
inboxes
path: path to backgrounds. Default is sub-path of install path; note
that you may need to change this if you change site-path too.
-twitterbridge
--------------
-
-A bi-direction bridge to Twitter (http://twitter.com/).
-
-enabled: default false. If true, will show user's Twitter friends'
- notices in their inbox and faves pages, only to the user. You
- must also run the twitterstatusfetcher.php script.
-
ping
----
+++ /dev/null
-<?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);
- }
-
-}
-
+++ /dev/null
-<?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);
- }
-
-}
if (empty($server)) {
$server = common_config('site', 'server');
}
-
+ common_debug('path = ' . $path);
// XXX: protocol
return 'http://'.$server.$path.$filename;
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';
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');
}
+
}
+
+
array('enabled' => true),
'sms' =>
array('enabled' => true),
- 'twitterbridge' =>
+ 'twitterimport' =>
array('enabled' => false),
'integration' =>
array('source' => 'StatusNet', # source attribute for Twitter
$m->connect('doc/:title', array('action' => 'doc'));
- // Twitter
-
- $m->connect('twitter/authorization', array('action' => 'twitterauthorization'));
-
// facebook
$m->connect('facebook', array('action' => 'facebookhome'));
// 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'));
}
+++ /dev/null
-<?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);
- }
- }
-
-}
+++ /dev/null
-<?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;
- }
-
-}
+++ /dev/null
-<?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;
- }
-
-}
jabber_public_notice($notice);
}
break;
- case 'twitter':
- if ($this->_isLocal($notice)) {
- broadcast_twitter($notice);
- }
- break;
case 'facebook':
if ($this->_isLocal($notice)) {
require_once INSTALLDIR . '/lib/facebookutil.php';
// XXX: Facebook says we don't need this FB_RequireFeatures(),
// but we actually do, for IE and Safari. Gar.
- $html = sprintf('<script type="text/javascript">
- $(document).ready(function () {
- FB_RequireFeatures(
- ["XFBML"],
- function() {
- FB.init("%s", "../xd_receiver.html");
- }
- ); });
-
- function goto_login() {
- window.location = "%s";
- }
-
- function goto_logout() {
- window.location = "%s";
- }
- </script>', $apikey,
- $login_url, $logout_url);
-
- $action->raw($html);
+ $js = '<script type="text/javascript">';
+ $js .= ' $(document).ready(function () {';
+ $js .= ' FB_RequireFeatures(';
+ $js .= ' ["XFBML"], function() {';
+ $js .= ' FB.init("%1$s", "../xd_receiver.html");';
+ $js .= ' }';
+ $js .= ' );';
+ $js .= ' });';
+
+ $js .= ' function goto_login() {';
+ $js .= ' window.location = "%2$s";';
+ $js .= ' }';
+
+ // The below function alters the logout link so that it logs the user out
+ // of Facebook Connect as well as the site. However, for some pages
+ // (FB Connect Settings) we need to output the FB Connect scripts (to
+ // show an existing FB connection even if the user isn't authenticated
+ // with Facebook connect) but NOT alter the logout link. And the only
+ // way to reliably do that is with the FB Connect .js libs. Crazy.
+
+ $js .= ' FB.ensureInit(function() {';
+ $js .= ' FB.Connect.ifUserConnected(';
+ $js .= ' function() { ';
+ $js .= ' $(\'#nav_logout a\').attr(\'href\', \'#\');';
+ $js .= ' $(\'#nav_logout a\').click(function() {';
+ $js .= ' FB.Connect.logoutAndRedirect(\'%3$s\');';
+ $js .= ' return false;';
+ $js .= ' })';
+ $js .= ' },';
+ $js .= ' function() {';
+ $js .= ' return false;';
+ $js .= ' }';
+ $js .= ' );';
+ $js .= ' });';
+ $js .= '</script>';
+
+ $js = sprintf($js, $apikey, $login_url, $logout_url);
+
+ // Compress the bugger down a bit
+ $js = str_replace(' ', '', $js);
+
+ $action->raw(" $js"); // leading two spaces to make it line up
}
}
function onEndShowStatusNetStyles($action)
{
-
if ($this->reqFbScripts($action)) {
$action->cssLink('plugins/FBConnect/FBConnectPlugin.css');
}
$action->elementEnd('li');
}
+ }
- $action->menuItem(common_local_url('all', array('nickname' => $user->nickname)),
- _('Home'), _('Personal profile and friends timeline'), false, 'nav_home');
- $action->menuItem(common_local_url('profilesettings'),
- _('Account'), _('Change your email, avatar, password, profile'), false, 'nav_account');
- $action->menuItem(common_local_url($connect),
- _('Connect'), _('Connect to services'), false, 'nav_connect');
- if (common_config('invite', 'enabled')) {
- $action->menuItem(common_local_url('invite'),
- _('Invite'),
- sprintf(_('Invite friends and colleagues to join you on %s'),
- common_config('site', 'name')),
- false, 'nav_invitecontact');
- }
-
- // Need to override the Logout link to make it do FB stuff
- if (!empty($fbuid)) {
-
- $logout_url = common_local_url('logout');
- $title = _('Logout from the site');
- $text = _('Logout');
-
- $html = sprintf('<li id="nav_logout"><a href="#" title="%s" ' .
- 'onclick="FB.Connect.logoutAndRedirect(\'%s\');">%s</a></li>',
- $title, $logout_url, $text);
-
- $action->raw($html);
-
- } else {
- $action->menuItem(common_local_url('logout'),
- _('Logout'), _('Logout from the site'), false, 'nav_logout');
- }
- }
- else {
- if (!common_config('site', 'openidonly')) {
- if (!common_config('site', 'closed')) {
- $action->menuItem(common_local_url('register'),
- _('Register'), _('Create an account'), false, 'nav_register');
- }
- $action->menuItem(common_local_url('login'),
- _('Login'), _('Login to the site'), false, 'nav_login');
- } else {
- $this->menuItem(common_local_url('openidlogin'),
- _('OpenID'), _('Login with OpenID'), false, 'nav_openid');
- }
- }
-
- $action->menuItem(common_local_url('doc', array('title' => 'help')),
- _('Help'), _('Help me!'), false, 'nav_help');
- if ($user || !common_config('site', 'private')) {
- $action->menuItem(common_local_url('peoplesearch'),
- _('Search'), _('Search for people or text'), false, 'nav_search');
- }
-
- // We are replacing the primary nav entirely; give other
- // plugins a chance to handle it here.
-
- Event::handle('EndPrimaryNav', array($action));
-
- return false;
+ return true;
}
function onStartShowLocalNavBlock($action)
}
function onStartLogout($action)
-{
+ {
$action->logout();
$fbuid = $this->loggedIn();
--- /dev/null
+This Twitter "bridge" plugin allows you to integrate your StatusNet
+instance with Twitter. Installing it will allow your users to:
+
+ - automatically post notices to thier Twitter accounts
+ - automatically subscribe to other Twitter users who are also using
+ your StatusNet install, if possible (requires running a daemon)
+ - import their Twitter friends' tweets (requires running a daemon)
+
+Installation
+------------
+
+To enable the plugin, add the following to your config.php:
+
+ require_once(INSTALLDIR . '/plugins/TwitterBridge/TwitterBridgePlugin.php');
+ $tb = new TwitterBridgePlugin();
+
+OAuth is used to to access protected resources on Twitter (as opposed to
+HTTP Basic Auth)*. To use Twitter bridging you will need to register
+your instance of StatusNet as an application on Twitter
+(http://twitter.com/apps), and update the following variables in your
+config.php with the consumer key and secret Twitter generates for you:
+
+ $config['twitter']['consumer_key'] = 'YOURKEY';
+ $config['twitter']['consumer_secret'] = 'YOURSECRET';
+
+When registering your application with Twitter set the type to "Browser"
+and your Callback URL to:
+
+ http://example.org/mublog/twitter/authorization
+
+The default access type should be, "Read & Write".
+
+* Note: The plugin will still push notices to Twitter for users who
+ have previously setup the Twitter bridge using their Twitter name and
+ password under an older versions of StatusNet, but all new Twitter
+ bridge connections will use OAuth.
+
+Deamons
+-------
+
+For friend syncing and importing notices running two additional daemon
+scripts is necessary (synctwitterfriends.php and
+twitterstatusfetcher.php).
+
+In the daemons subidrectory of the plugin are three scripts:
+
+* Twitter Friends Syncing (daemons/synctwitterfriends.php)
+
+Users may set a flag in their settings ("Subscribe to my Twitter friends
+here" under the Twitter tab) to have StatusNet attempt to locate and
+subscribe to "friends" (people they "follow") on Twitter who also have
+accounts on your StatusNet system, and who have previously set up a link
+for automatically posting notices to Twitter.
+
+The plugin will try to start this daemon when you run
+scripts/startdaemons.sh.
+
+* Importing statuses from Twitter (daemons/twitterstatusfetcher.php)
+
+To allow your users to import their friends' Twitter statuses, you will
+need to enable the bidirectional Twitter bridge in your config.php:
+
+ $config['twitterimport']['enabled'] = true;
+
+The plugin will then start the TwitterStatusFetcher daemon along with the
+other daemons when you run scripts/startdaemons.sh.
+
+Additionally, you will want to set the integration source variable,
+which will keep notices posted to Twitter via StatusNet from looping
+back. The integration source should be set to the name of your
+application, exactly as you specified it on the settings page for your
+StatusNet application on Twitter, e.g.:
+
+ $config['integration']['source'] = 'YourApp';
+
+* TwitterQueueHandler (daemons/twitterqueuehandler.php)
+
+This script sends queued notices to Twitter for user who have opted to
+set up Twitter bridging.
+
+It's not strictly necessary to run this queue handler, and sites that
+haven't enabled queuing are still able to push notices to Twitter, but
+for larger sites and sites that wish to improve performance, this
+script allows notices to be sent "offline" via a separate process.
+
+The plugin will start this script when you run scripts/startdaemons.sh.
--- /dev/null
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * PHP version 5
+ *
+ * LICENCE: This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @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('STATUSNET')) {
+ exit(1);
+}
+
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+
+/**
+ * Plugin for sending and importing Twitter statuses
+ *
+ * This class allows users to link their Twitter accounts
+ *
+ * @category Plugin
+ * @package StatusNet
+ * @author Zach Copley <zach@status.net>
+ * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ * @link http://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.
+ *
+ * @param Net_URL_Mapper &$m path-to-action mapper
+ *
+ * @return boolean hook return
+ */
+
+ function onRouterInitialized(&$m)
+ {
+ $m->connect('twitter/authorization',
+ array('action' => 'twitterauthorization'));
+ $m->connect('settings/twitter', array('action' => 'twittersettings'));
+
+ return true;
+ }
+
+ /**
+ * Add the Twitter Settings page to the Connect Settings menu
+ *
+ * @param Action &$action The calling page
+ *
+ * @return boolean hook return
+ */
+ function onEndConnectSettingsNav(&$action)
+ {
+ $action_name = $action->trimmed('action');
+
+ $action->menuItem(common_local_url('twittersettings'),
+ _('Twitter'),
+ _('Twitter integration options'),
+ $action_name === 'twittersettings');
+
+ return true;
+ }
+
+ /**
+ * Automatically load the actions and libraries used by the Twitter bridge
+ *
+ * @param Class $cls the class
+ *
+ * @return boolean hook return
+ *
+ */
+ function onAutoload($cls)
+ {
+ switch ($cls) {
+ case 'TwittersettingsAction':
+ case 'TwitterauthorizationAction':
+ include_once INSTALLDIR . '/plugins/TwitterBridge/' .
+ strtolower(mb_substr($cls, 0, -6)) . '.php';
+ return false;
+ case 'TwitterOAuthClient':
+ include_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php';
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Add a Twitter queue item for each notice
+ *
+ * @param Notice $notice the notice
+ * @param array &$transports the list of transports (queues)
+ *
+ * @return boolean hook return
+ */
+ function onStartEnqueueNotice($notice, &$transports)
+ {
+ array_push($transports, 'twitter');
+ return true;
+ }
+
+ /**
+ * broadcast the message when not using queuehandler
+ *
+ * @param Notice &$notice the notice
+ * @param array $queue destination queue
+ *
+ * @return boolean hook return
+ */
+ function onUnqueueHandleNotice(&$notice, $queue)
+ {
+ if (($queue == 'twitter') && ($this->_isLocal($notice))) {
+ broadcast_twitter($notice);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determine whether the notice was locally created
+ *
+ * @param Notice $notice
+ *
+ * @return boolean locality
+ */
+ function _isLocal($notice)
+ {
+ return ($notice->is_local == Notice::LOCAL_PUBLIC ||
+ $notice->is_local == Notice::LOCAL_NONPUBLIC);
+ }
+
+ /**
+ * Add Twitter bridge daemons to the list of daemons to start
+ *
+ * @param array $daemons the list fo daemons to run
+ *
+ * @return boolean hook return
+ *
+ */
+ function onGetValidDaemons($daemons)
+ {
+ array_push($daemons, INSTALLDIR .
+ '/plugins/TwitterBridge/daemons/twitterqueuehandler.php');
+ array_push($daemons, INSTALLDIR .
+ '/plugins/TwitterBridge/daemons/synctwitterfriends.php');
+
+ if (common_config('twitterimport', 'enabled')) {
+ array_push($daemons, INSTALLDIR
+ . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php');
+ }
+
+ return true;
+ }
+
+}
--- /dev/null
+#!/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';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitterbasicauthclient.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.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/
+ */
+
+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();
+
--- /dev/null
+#!/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();
--- /dev/null
+#!/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/common.php';
+require_once INSTALLDIR . '/lib/daemon.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitterbasicauthclient.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.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);
+
+ $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)
+ {
+ $avatarfile = Avatar::path($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();
+
--- /dev/null
+<?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
+
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitterbasicauthclient.php';
+require_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php';
+
+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);
+ }
+ }
+
+}
--- /dev/null
+<?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);
+ }
+
+}
+
--- /dev/null
+<?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;
+ }
+
+}
--- /dev/null
+<?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;
+ }
+
+}
--- /dev/null
+<?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('twitterimport','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);
+ }
+
+}
$daemons[] = INSTALLDIR.'/scripts/xmppconfirmhandler.php';
}
-if(common_config('twitterbridge','enabled')) {
- $daemons[] = INSTALLDIR.'/scripts/twitterstatusfetcher.php';
-}
-
-if (common_config('twitter', 'enabled')) {
- $daemons[] = INSTALLDIR.'/scripts/twitterqueuehandler.php';
- $daemons[] = INSTALLDIR.'/scripts/synctwitterfriends.php';
-}
-
if (common_config('sms', 'enabled')) {
$daemons[] = INSTALLDIR.'/scripts/smsqueuehandler.php';
}
+++ /dev/null
-#!/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();
-
+++ /dev/null
-#!/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();
+++ /dev/null
-#!/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();
-