From 9b9d80cd97704426e54434d8777c5c15154014ad Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 25 Aug 2009 14:52:25 -0700 Subject: Pluginized Twitter settings stuff --- plugins/TwitterBridge/TwitterBridgePlugin.php | 101 ++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 plugins/TwitterBridge/TwitterBridgePlugin.php (limited to 'plugins/TwitterBridge/TwitterBridgePlugin.php') diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php new file mode 100644 index 000000000..f7daa9a22 --- /dev/null +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -0,0 +1,101 @@ +. + * + * @category Plugin + * @package Laconica + * @author Zach Copley + * @copyright 2009 Control Yourself, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +if (!defined('LACONICA')) { + exit(1); +} + +/** + * Plugin for sending and importing Twitter statuses + * + * This class allows users to link their Twitter accounts + * + * @category Plugin + * @package Laconica + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + * @link http://twitter.com/ + */ + +class TwitterBridgePlugin extends Plugin +{ + /** + * Initializer for the plugin. + */ + + function __construct() + { + parent::__construct(); + } + + /** + * Add Twitter-related paths to the router table + * + * Hook for RouterInitialized event. + * + * @return boolean hook return + */ + + function onRouterInitialized(&$m) + { + $m->connect('twitter/authorization', array('action' => 'twitterauthorization')); + $m->connect('settings/twitter', array('action' => 'twittersettings')); + + return true; + } + + function onEndConnectSettingsNav(&$action) + { + $action_name = $action->trimmed('action'); + + $action->menuItem(common_local_url('twittersettings'), + _('Twitter'), + _('Twitter integration options'), + $action_name === 'twittersettings'); + + return true; + } + + function onAutoload($cls) + { + switch ($cls) + { + case 'TwittersettingsAction': + case 'TwitterauthorizationAction': + require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'); + return false; + case 'TwitterOAuthClient': + require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroAuthclient.php'); + return false; + default: + return true; + } + } + + +} \ No newline at end of file -- cgit v1.2.3-54-g00ecf From 5efe588174c71979fc1970353c9a556ea441f138 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 26 Aug 2009 00:59:06 +0000 Subject: Moved the rest of the Twitter stuff into the TwitterBridge plugin --- lib/common.php | 1 - lib/twitter.php | 258 ---------- plugins/TwitterBridge/TwitterBridgePlugin.php | 2 +- .../TwitterBridge/daemons/synctwitterfriends.php | 280 +++++++++++ .../TwitterBridge/daemons/twitterqueuehandler.php | 73 +++ .../TwitterBridge/daemons/twitterstatusfetcher.php | 559 +++++++++++++++++++++ plugins/TwitterBridge/twitter.php | 258 ++++++++++ plugins/TwitterBridge/twitterauthorization.php | 2 + plugins/TwitterBridge/twittersettings.php | 4 +- scripts/synctwitterfriends.php | 279 ---------- scripts/twitterqueuehandler.php | 74 --- scripts/twitterstatusfetcher.php | 558 -------------------- 12 files changed, 1175 insertions(+), 1173 deletions(-) delete mode 100644 lib/twitter.php create mode 100755 plugins/TwitterBridge/daemons/synctwitterfriends.php create mode 100755 plugins/TwitterBridge/daemons/twitterqueuehandler.php create mode 100755 plugins/TwitterBridge/daemons/twitterstatusfetcher.php create mode 100644 plugins/TwitterBridge/twitter.php delete mode 100755 scripts/synctwitterfriends.php delete mode 100755 scripts/twitterqueuehandler.php delete mode 100755 scripts/twitterstatusfetcher.php (limited to 'plugins/TwitterBridge/TwitterBridgePlugin.php') diff --git a/lib/common.php b/lib/common.php index 62cf5640d..d23d9da7d 100644 --- a/lib/common.php +++ b/lib/common.php @@ -409,7 +409,6 @@ require_once INSTALLDIR.'/lib/theme.php'; require_once INSTALLDIR.'/lib/mail.php'; require_once INSTALLDIR.'/lib/subs.php'; require_once INSTALLDIR.'/lib/Shorturl_api.php'; -require_once INSTALLDIR.'/lib/twitter.php'; require_once INSTALLDIR.'/lib/clientexception.php'; require_once INSTALLDIR.'/lib/serverexception.php'; diff --git a/lib/twitter.php b/lib/twitter.php deleted file mode 100644 index 280cdb0a3..000000000 --- a/lib/twitter.php +++ /dev/null @@ -1,258 +0,0 @@ -. - */ - -if (!defined('LACONICA')) { - exit(1); -} - -define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1 - -function update_twitter_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 = update_twitter_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)) { - - $user = $flink->getUser(); - - // XXX: Hack to get around PHP cURL's use of @ being a a meta character - $statustxt = preg_replace('/^@/', ' @', $notice->content); - - $token = TwitterOAuthClient::unpackToken($flink->credentials); - - $client = new TwitterOAuthClient($token->key, $token->secret); - - $status = null; - - try { - $status = $client->statusesUpdate($statustxt); - } catch (OAuthClientCurlException $e) { - - if ($e->getMessage() == 'The requested URL returned error: 401') { - - $errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' . - 'Twitter OAuth access token.', - $user->nickname, $user->id); - common_log(LOG_WARNING, $errmsg); - - // Bad auth token! We need to delete the foreign_link - // to Twitter and inform the user. - - remove_twitter_link($flink); - return true; - - } else { - - // Some other error happened, so we should probably - // try to send again later. - - $errmsg = sprintf('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()); - common_log(LOG_WARNING, $errmsg); - - return false; - } - } - - if (empty($status)) { - - // This could represent a failure posting, - // or the Twitter API might just be behaving flakey. - - $errmsg = sprint('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.', - $notice->id); - common_log(LOG_INFO, $msg); - } - - return true; -} - -function remove_twitter_link($flink) -{ - $user = $flink->getUser(); - - common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' . - "user $user->nickname (user id: $user->id)."); - - $result = $flink->delete(); - - if (empty($result)) { - common_log(LOG_ERR, 'Could not remove Twitter bridge ' . - "Foreign_link for $user->nickname (user id: $user->id)!"); - common_log_db_error($flink, 'DELETE', __FILE__); - } - - // Notify the user that her Twitter bridge is down - - if (isset($user->email)) { - - $result = mail_twitter_bridge_removed($user); - - if (!$result) { - - $msg = 'Unable to send email to notify ' . - "$user->nickname (user id: $user->id) " . - 'that their Twitter bridge link was ' . - 'removed!'; - - common_log(LOG_WARNING, $msg); - } - } - -} - diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index f7daa9a22..a8de1c664 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -90,7 +90,7 @@ class TwitterBridgePlugin extends Plugin require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'); return false; case 'TwitterOAuthClient': - require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroAuthclient.php'); + require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php'); return false; default: return true; diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php new file mode 100755 index 000000000..b7be5d043 --- /dev/null +++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php @@ -0,0 +1,280 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$shortoptions = 'di::'; +$longoptions = array('id::', 'debug'); + +$helptext = << + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +$helptext = <<_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(); + + $token = TwitterOAuthClient::unpackToken($flink->credentials); + + $client = new TwitterOAuthClient($token->key, $token->secret); + + try { + $friends_ids = $client->friendsIds(); + } catch (OAuthCurlException $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 (OAuthCurlException $e) { + common_log(LOG_WARNING, $this->name() . + ' - cURL error getting Twitter statuses/friends ' . + "page $i - " . $e->getCode() . ' - ' . + $e->getMessage()); + } + + if (empty($more_friends)) { + common_log(LOG_WARNING, $this->name() . + " - Couldn't retrieve page $i " . + "of Twitter user $flink->foreign_id friends."); + continue; + } else { + $friends = array_merge($friends, $more_friends); + } + } + + return $friends; + } + + function subscribeTwitterFriends($flink) + { + $friends = $this->fetchTwitterFriends($flink); + + if (empty($friends)) { + common_debug($this->name() . + ' - Couldn\'t get friends from Twitter for ' . + "Twitter user $flink->foreign_id."); + return false; + } + + $user = $flink->getUser(); + + foreach ($friends as $friend) { + + $friend_name = $friend->screen_name; + $friend_id = (int) $friend->id; + + // Update or create the Foreign_user record for each + // Twitter friend + + if (!save_twitter_user($friend_id, $friend_name)) { + common_log(LOG_WARNING, $this-name() . + " - Couldn't save $screen_name's friend, $friend_name."); + continue; + } + + // Check to see if there's a related local user + + $friend_flink = Foreign_link::getByForeignID($friend_id, + TWITTER_SERVICE); + + if (!empty($friend_flink)) { + + // Get associated user and subscribe her + + $friend_user = User::staticGet('id', $friend_flink->user_id); + + if (!empty($friend_user)) { + $result = subs_subscribe_to($user, $friend_user); + + if ($result === true) { + common_log(LOG_INFO, + $this->name() . ' - Subscribed ' . + "$friend_user->nickname to $user->nickname."); + } else { + common_debug($this->name() . + ' - Tried subscribing ' . + "$friend_user->nickname to $user->nickname - " . + $result); + } + } + } + } + + return true; + } + +} + +$id = null; +$debug = null; + +if (have_option('i')) { + $id = get_option_value('i'); +} else if (have_option('--id')) { + $id = get_option_value('--id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; +} + +if (have_option('d') || have_option('debug')) { + $debug = true; +} + +$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug); +$syncer->runOnce(); + diff --git a/plugins/TwitterBridge/daemons/twitterqueuehandler.php b/plugins/TwitterBridge/daemons/twitterqueuehandler.php new file mode 100755 index 000000000..9aa9d1ed0 --- /dev/null +++ b/plugins/TwitterBridge/daemons/twitterqueuehandler.php @@ -0,0 +1,73 @@ +#!/usr/bin/env php +. + */ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$shortoptions = 'i::'; +$longoptions = array('id::'); + +$helptext = <<log(LOG_INFO, "INITIALIZE"); + return true; + } + + function handle_notice($notice) + { + return broadcast_twitter($notice); + } + + function finish() + { + } + +} + +if (have_option('i')) { + $id = get_option_value('i'); +} else if (have_option('--id')) { + $id = get_option_value('--id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; +} + +$handler = new TwitterQueueHandler($id); + +$handler->runOnce(); diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php new file mode 100755 index 000000000..3023bf423 --- /dev/null +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -0,0 +1,559 @@ +#!/usr/bin/env php +. + */ + +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 = << + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://laconi.ca/ + */ + +// NOTE: an Avatar path MUST be set in config.php for this +// script to work: e.g.: $config['avatar']['path'] = '/laconica/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. + + $token = TwitterOAuthClient::unpackToken($flink->credentials); + + $client = new TwitterOAuthClient($token->key, $token->secret); + + $timeline = null; + + try { + $timeline = $client->statusesFriendsTimeline(); + } catch (OAuthClientCurlException $e) { + common_log(LOG_WARNING, $this->name() . + ' - OAuth 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 Laconica + + $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 + // Laconica's (Laconica's = 96) + + $avatar->width = 73; + $avatar->height = 73; + } + + $avatar->original = 0; // we don't have the original + $avatar->mediatype = $mediatype; + $avatar->filename = $filename; + $avatar->url = Avatar::url($filename); + + common_debug($this->name() . " - New filename: $avatar->url"); + + $avatar->created = common_sql_now(); + + $id = $avatar->insert(); + + if (empty($id)) { + common_log_db_error($avatar, 'INSERT', __FILE__); + return null; + } + + common_debug($this->name() . + " - Saved new $size avatar for $profile_id."); + + return $id; + } + + function fetchAvatar($url, $filename) + { + $avatar_dir = INSTALLDIR . '/avatar/'; + + $avatarfile = $avatar_dir . $filename; + + $out = fopen($avatarfile, 'wb'); + if (!$out) { + common_log(LOG_WARNING, $this->name() . + " - Couldn't open file $filename"); + return false; + } + + common_debug($this->name() . " - Fetching Twitter avatar: $url"); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_FILE, $out); + curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); + $result = curl_exec($ch); + curl_close($ch); + + fclose($out); + + return $result; + } +} + +$id = null; +$debug = null; + +if (have_option('i')) { + $id = get_option_value('i'); +} else if (have_option('--id')) { + $id = get_option_value('--id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; +} + +if (have_option('d') || have_option('debug')) { + $debug = true; +} + +$fetcher = new TwitterStatusFetcher($id, 60, 2, $debug); +$fetcher->runOnce(); + diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php new file mode 100644 index 000000000..280cdb0a3 --- /dev/null +++ b/plugins/TwitterBridge/twitter.php @@ -0,0 +1,258 @@ +. + */ + +if (!defined('LACONICA')) { + exit(1); +} + +define('TWITTER_SERVICE', 1); // Twitter is foreign_service ID 1 + +function update_twitter_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 = update_twitter_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)) { + + $user = $flink->getUser(); + + // XXX: Hack to get around PHP cURL's use of @ being a a meta character + $statustxt = preg_replace('/^@/', ' @', $notice->content); + + $token = TwitterOAuthClient::unpackToken($flink->credentials); + + $client = new TwitterOAuthClient($token->key, $token->secret); + + $status = null; + + try { + $status = $client->statusesUpdate($statustxt); + } catch (OAuthClientCurlException $e) { + + if ($e->getMessage() == 'The requested URL returned error: 401') { + + $errmsg = sprintf('User %1$s (user id: %2$s) has an invalid ' . + 'Twitter OAuth access token.', + $user->nickname, $user->id); + common_log(LOG_WARNING, $errmsg); + + // Bad auth token! We need to delete the foreign_link + // to Twitter and inform the user. + + remove_twitter_link($flink); + return true; + + } else { + + // Some other error happened, so we should probably + // try to send again later. + + $errmsg = sprintf('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()); + common_log(LOG_WARNING, $errmsg); + + return false; + } + } + + if (empty($status)) { + + // This could represent a failure posting, + // or the Twitter API might just be behaving flakey. + + $errmsg = sprint('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.', + $notice->id); + common_log(LOG_INFO, $msg); + } + + return true; +} + +function remove_twitter_link($flink) +{ + $user = $flink->getUser(); + + common_log(LOG_INFO, 'Removing Twitter bridge Foreign link for ' . + "user $user->nickname (user id: $user->id)."); + + $result = $flink->delete(); + + if (empty($result)) { + common_log(LOG_ERR, 'Could not remove Twitter bridge ' . + "Foreign_link for $user->nickname (user id: $user->id)!"); + common_log_db_error($flink, 'DELETE', __FILE__); + } + + // Notify the user that her Twitter bridge is down + + if (isset($user->email)) { + + $result = mail_twitter_bridge_removed($user); + + if (!$result) { + + $msg = 'Unable to send email to notify ' . + "$user->nickname (user id: $user->id) " . + 'that their Twitter bridge link was ' . + 'removed!'; + + common_log(LOG_WARNING, $msg); + } + } + +} + diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index b04f35327..a54528434 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -31,6 +31,8 @@ if (!defined('LACONICA')) { exit(1); } +require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; + /** * Class for doing OAuth authentication against Twitter * diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php index a3e02e125..b3d4a971f 100644 --- a/plugins/TwitterBridge/twittersettings.php +++ b/plugins/TwitterBridge/twittersettings.php @@ -31,8 +31,8 @@ if (!defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/connectsettingsaction.php'; -require_once INSTALLDIR.'/lib/twitter.php'; +require_once INSTALLDIR . '/lib/connectsettingsaction.php'; +require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; /** * Settings for Twitter integration diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php deleted file mode 100755 index 2de464bcc..000000000 --- a/scripts/synctwitterfriends.php +++ /dev/null @@ -1,279 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'di::'; -$longoptions = array('id::', 'debug'); - -$helptext = << - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://laconi.ca/ - */ - -$helptext = <<_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(); - - $token = TwitterOAuthClient::unpackToken($flink->credentials); - - $client = new TwitterOAuthClient($token->key, $token->secret); - - try { - $friends_ids = $client->friendsIds(); - } catch (OAuthCurlException $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 (OAuthCurlException $e) { - common_log(LOG_WARNING, $this->name() . - ' - cURL error getting Twitter statuses/friends ' . - "page $i - " . $e->getCode() . ' - ' . - $e->getMessage()); - } - - if (empty($more_friends)) { - common_log(LOG_WARNING, $this->name() . - " - Couldn't retrieve page $i " . - "of Twitter user $flink->foreign_id friends."); - continue; - } else { - $friends = array_merge($friends, $more_friends); - } - } - - return $friends; - } - - function subscribeTwitterFriends($flink) - { - $friends = $this->fetchTwitterFriends($flink); - - if (empty($friends)) { - common_debug($this->name() . - ' - Couldn\'t get friends from Twitter for ' . - "Twitter user $flink->foreign_id."); - return false; - } - - $user = $flink->getUser(); - - foreach ($friends as $friend) { - - $friend_name = $friend->screen_name; - $friend_id = (int) $friend->id; - - // Update or create the Foreign_user record for each - // Twitter friend - - if (!save_twitter_user($friend_id, $friend_name)) { - common_log(LOG_WARNING, $this-name() . - " - Couldn't save $screen_name's friend, $friend_name."); - continue; - } - - // Check to see if there's a related local user - - $friend_flink = Foreign_link::getByForeignID($friend_id, - TWITTER_SERVICE); - - if (!empty($friend_flink)) { - - // Get associated user and subscribe her - - $friend_user = User::staticGet('id', $friend_flink->user_id); - - if (!empty($friend_user)) { - $result = subs_subscribe_to($user, $friend_user); - - if ($result === true) { - common_log(LOG_INFO, - $this->name() . ' - Subscribed ' . - "$friend_user->nickname to $user->nickname."); - } else { - common_debug($this->name() . - ' - Tried subscribing ' . - "$friend_user->nickname to $user->nickname - " . - $result); - } - } - } - } - - return true; - } - -} - -$id = null; -$debug = null; - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -if (have_option('d') || have_option('debug')) { - $debug = true; -} - -$syncer = new SyncTwitterFriendsDaemon($id, 60, 2, $debug); -$syncer->runOnce(); - diff --git a/scripts/twitterqueuehandler.php b/scripts/twitterqueuehandler.php deleted file mode 100755 index 00e735d98..000000000 --- a/scripts/twitterqueuehandler.php +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env php -. - */ - -define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - return true; - } - - function handle_notice($notice) - { - return broadcast_twitter($notice); - } - - function finish() - { - } - -} - -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new TwitterQueueHandler($id); - -$handler->runOnce(); diff --git a/scripts/twitterstatusfetcher.php b/scripts/twitterstatusfetcher.php deleted file mode 100755 index f5289c5f4..000000000 --- a/scripts/twitterstatusfetcher.php +++ /dev/null @@ -1,558 +0,0 @@ -#!/usr/bin/env php -. - */ - -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 = << - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://laconi.ca/ - */ - -// NOTE: an Avatar path MUST be set in config.php for this -// script to work: e.g.: $config['avatar']['path'] = '/laconica/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. - - $token = TwitterOAuthClient::unpackToken($flink->credentials); - - $client = new TwitterOAuthClient($token->key, $token->secret); - - $timeline = null; - - try { - $timeline = $client->statusesFriendsTimeline(); - } catch (OAuthClientCurlException $e) { - common_log(LOG_WARNING, $this->name() . - ' - OAuth 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 Laconica - - $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 - // Laconica's (Laconica'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(); - -- cgit v1.2.3-54-g00ecf From 0fd8e758ade32204b452cc9fd40e071f0ec8c0f6 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Wed, 14 Oct 2009 04:50:16 +0000 Subject: Make queuing and daemons work via events --- classes/Avatar.php | 2 +- plugins/TwitterBridge/TwitterBridgePlugin.php | 17 +++++++++++++++++ plugins/TwitterBridge/daemons/synctwitterfriends.php | 10 ++-------- plugins/TwitterBridge/daemons/twitterstatusfetcher.php | 9 ++++----- plugins/TwitterBridge/twitter.php | 3 +++ scripts/getvaliddaemons.php | 9 --------- 6 files changed, 27 insertions(+), 23 deletions(-) (limited to 'plugins/TwitterBridge/TwitterBridgePlugin.php') diff --git a/classes/Avatar.php b/classes/Avatar.php index 5e8b315fe..64f105179 100644 --- a/classes/Avatar.php +++ b/classes/Avatar.php @@ -81,7 +81,7 @@ class Avatar extends Memcached_DataObject if (empty($server)) { $server = common_config('site', 'server'); } - + common_debug('path = ' . $path); // XXX: protocol return 'http://'.$server.$path.$filename; diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index a8de1c664..69bec0651 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -97,5 +97,22 @@ class TwitterBridgePlugin extends Plugin } } + function onStartEnqueueNotice($notice, $transports) + { + array_push($transports, 'twitter'); + return true; + } + + function onGetValidDaemons($daemons) + { + array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/twitterqueuehandler.php'); + array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/synctwitterfriends.php'); + + if (common_config('twitterbridge', 'enabled')) { + array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'); + } + + return true; + } } \ No newline at end of file diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php index 0668c6222..ed2bf48a2 100755 --- a/plugins/TwitterBridge/daemons/synctwitterfriends.php +++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php @@ -33,6 +33,8 @@ 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 @@ -45,14 +47,6 @@ require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; * @link http://status.net/ */ -$helptext = <<filename = $filename; $avatar->url = Avatar::url($filename); - common_debug($this->name() . " - New filename: $avatar->url"); - $avatar->created = common_sql_now(); $id = $avatar->insert(); @@ -516,9 +517,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon function fetchAvatar($url, $filename) { - $avatar_dir = INSTALLDIR . '/avatar/'; - - $avatarfile = $avatar_dir . $filename; + $avatarfile = Avatar::path($filename); $out = fopen($avatarfile, 'wb'); if (!$out) { diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index afc3f55ba..ac1f49c36 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -23,6 +23,9 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { 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; diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index 6dd019712..7caea1bb7 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -49,15 +49,6 @@ if(common_config('xmpp','enabled')) { $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'; } -- cgit v1.2.3-54-g00ecf From 109a54c4f08177cce82ef0f4a4ec8cd41ed2ecec Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Sat, 17 Oct 2009 00:32:02 +0000 Subject: Changed config flag for importing friends' timeline and added some comments --- lib/default.php | 2 +- plugins/TwitterBridge/TwitterBridgePlugin.php | 79 ++++++++++++++++++++------- 2 files changed, 59 insertions(+), 22 deletions(-) (limited to 'plugins/TwitterBridge/TwitterBridgePlugin.php') diff --git a/lib/default.php b/lib/default.php index 9f3d4b1f9..68029c977 100644 --- a/lib/default.php +++ b/lib/default.php @@ -140,7 +140,7 @@ $default = array('enabled' => true), 'sms' => array('enabled' => true), - 'twitterbridge' => + 'twitterimport' => array('enabled' => false), 'integration' => array('source' => 'StatusNet', # source attribute for Twitter diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 69bec0651..1a27c30cd 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -1,6 +1,6 @@ . * * @category Plugin - * @package Laconica - * @author Zach Copley + * @package StatusNet + * @author Zach Copley * @copyright 2009 Control Yourself, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://laconi.ca/ */ -if (!defined('LACONICA')) { +if (!defined('STATUSNET')) { exit(1); } @@ -35,8 +35,8 @@ if (!defined('LACONICA')) { * This class allows users to link their Twitter accounts * * @category Plugin - * @package Laconica - * @author Zach Copley + * @package StatusNet + * @author Zach Copley * @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/ @@ -58,17 +58,27 @@ class TwitterBridgePlugin extends Plugin * * 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('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'); @@ -81,38 +91,65 @@ class TwitterBridgePlugin extends Plugin 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': - require_once(INSTALLDIR.'/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'); + switch ($cls) { + case 'TwittersettingsAction': + case 'TwitterauthorizationAction': + include_once INSTALLDIR.'/plugins/TwitterBridge/' . + strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; - case 'TwitterOAuthClient': - require_once(INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php'); + case 'TwitterOAuthClient': + include_once INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php'; return false; - default: + 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; } + /** + * 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('twitterbridge', 'enabled')) { - array_push($daemons, INSTALLDIR . '/plugins/TwitterBridge/daemons/twitterstatusfetcher.php'); + 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; } -} \ No newline at end of file +} -- cgit v1.2.3-54-g00ecf From 7539e26951ba9e1529d9a24a7a634861b7d080fe Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 20 Oct 2009 06:05:35 +0000 Subject: - Make Twitter bridge work with unqueuemanager - Add README --- lib/unqueuemanager.php | 5 -- plugins/TwitterBridge/README | 86 +++++++++++++++++++++++++++ plugins/TwitterBridge/TwitterBridgePlugin.php | 42 +++++++++++-- plugins/TwitterBridge/twittersettings.php | 2 +- 4 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 plugins/TwitterBridge/README (limited to 'plugins/TwitterBridge/TwitterBridgePlugin.php') diff --git a/lib/unqueuemanager.php b/lib/unqueuemanager.php index 6cfe5bcbd..51261bcd7 100644 --- a/lib/unqueuemanager.php +++ b/lib/unqueuemanager.php @@ -48,11 +48,6 @@ class UnQueueManager 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'; diff --git a/plugins/TwitterBridge/README b/plugins/TwitterBridge/README new file mode 100644 index 000000000..09352188e --- /dev/null +++ b/plugins/TwitterBridge/README @@ -0,0 +1,86 @@ +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. diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index 1a27c30cd..e69567fc7 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -29,6 +29,8 @@ if (!defined('STATUSNET')) { exit(1); } +require_once INSTALLDIR . '/plugins/TwitterBridge/twitter.php'; + /** * Plugin for sending and importing Twitter statuses * @@ -104,11 +106,11 @@ class TwitterBridgePlugin extends Plugin switch ($cls) { case 'TwittersettingsAction': case 'TwitterauthorizationAction': - include_once INSTALLDIR.'/plugins/TwitterBridge/' . + include_once INSTALLDIR . '/plugins/TwitterBridge/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'TwitterOAuthClient': - include_once INSTALLDIR.'/plugins/TwitterBridge/twitteroauthclient.php'; + include_once INSTALLDIR . '/plugins/TwitterBridge/twitteroauthclient.php'; return false; default: return true; @@ -118,17 +120,47 @@ class TwitterBridgePlugin extends Plugin /** * Add a Twitter queue item for each notice * - * @param Notice $notice the notice - * @param array $transports the list of transports (queues) + * @param Notice $notice the notice + * @param array &$transports the list of transports (queues) * * @return boolean hook return */ - function onStartEnqueueNotice($notice, $transports) + 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 * diff --git a/plugins/TwitterBridge/twittersettings.php b/plugins/TwitterBridge/twittersettings.php index 2afa85ba4..ca22c9553 100644 --- a/plugins/TwitterBridge/twittersettings.php +++ b/plugins/TwitterBridge/twittersettings.php @@ -152,7 +152,7 @@ class TwittersettingsAction extends ConnectSettingsAction false); $this->elementEnd('li'); - if (common_config('twitterbridge','enabled')) { + if (common_config('twitterimport','enabled')) { $this->elementStart('li'); $this->checkbox('noticerecv', _('Import my Friends Timeline.'), -- cgit v1.2.3-54-g00ecf From fd650715fa934df9b7e560881bfb622860588599 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 29 Oct 2009 13:55:37 -0400 Subject: Warning cleanup: drop reference on router parameter to RouterInitialized event handlers. We don't (and don't need to) pass a reference here, and the mix can trigger warnings. --- plugins/Facebook/FacebookPlugin.php | 2 +- plugins/OpenID/OpenIDPlugin.php | 2 +- plugins/TemplatePlugin.php | 2 +- plugins/TwitterBridge/TwitterBridgePlugin.php | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'plugins/TwitterBridge/TwitterBridgePlugin.php') diff --git a/plugins/Facebook/FacebookPlugin.php b/plugins/Facebook/FacebookPlugin.php index fc3adcfad..b68534b24 100644 --- a/plugins/Facebook/FacebookPlugin.php +++ b/plugins/Facebook/FacebookPlugin.php @@ -58,7 +58,7 @@ class FacebookPlugin extends Plugin * @return boolean hook return */ - function onRouterInitialized(&$m) + function onRouterInitialized($m) { // Facebook App stuff diff --git a/plugins/OpenID/OpenIDPlugin.php b/plugins/OpenID/OpenIDPlugin.php index 560da71e7..81e3ed9c4 100644 --- a/plugins/OpenID/OpenIDPlugin.php +++ b/plugins/OpenID/OpenIDPlugin.php @@ -62,7 +62,7 @@ class OpenIDPlugin extends Plugin * @return boolean hook return */ - function onRouterInitialized(&$m) + function onRouterInitialized($m) { $m->connect('main/openid', array('action' => 'openidlogin')); $m->connect('settings/openid', array('action' => 'openidsettings')); diff --git a/plugins/TemplatePlugin.php b/plugins/TemplatePlugin.php index cfa051162..5f3ad81f5 100644 --- a/plugins/TemplatePlugin.php +++ b/plugins/TemplatePlugin.php @@ -32,7 +32,7 @@ class TemplatePlugin extends Plugin { // capture the RouterInitialized event // and connect a new API method // for updating the template - function onRouterInitialized( &$m ) { + function onRouterInitialized( $m ) { $m->connect( 'template/update', array( 'action' => 'template', )); diff --git a/plugins/TwitterBridge/TwitterBridgePlugin.php b/plugins/TwitterBridge/TwitterBridgePlugin.php index e69567fc7..ad3c2e551 100644 --- a/plugins/TwitterBridge/TwitterBridgePlugin.php +++ b/plugins/TwitterBridge/TwitterBridgePlugin.php @@ -60,12 +60,12 @@ class TwitterBridgePlugin extends Plugin * * Hook for RouterInitialized event. * - * @param Net_URL_Mapper &$m path-to-action mapper + * @param Net_URL_Mapper $m path-to-action mapper * * @return boolean hook return */ - function onRouterInitialized(&$m) + function onRouterInitialized($m) { $m->connect('twitter/authorization', array('action' => 'twitterauthorization')); -- cgit v1.2.3-54-g00ecf