From 147dd16ab3f1d03a6bee8b3c8bf93170acab4ba9 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 18 Nov 2008 20:11:28 -0500 Subject: [PATCH] trac685 Twitter bridge - Shell script to sync all users' Twitter friends darcs-hash:20081119011128-7b5ce-74471277443b44d0075f66131028447cfda3b1e4.gz --- actions/twittersettings.php | 306 +++++++-------------------------- classes/Foreign_link.php | 9 +- lib/common.php | 1 + lib/twitter.php | 199 +++++++++++++++++++++ scripts/synctwitterfriends.php | 54 ++++++ 5 files changed, 315 insertions(+), 254 deletions(-) create mode 100644 lib/twitter.php create mode 100755 scripts/synctwitterfriends.php diff --git a/actions/twittersettings.php b/actions/twittersettings.php index bf8827958a..5237423616 100644 --- a/actions/twittersettings.php +++ b/actions/twittersettings.php @@ -25,14 +25,6 @@ define('SUBSCRIPTIONS', 80); class TwittersettingsAction extends SettingsAction { - var $twit_id; - var $twit_username; - var $twit_password; - var $friends_count = 0; - var $noticesync; - var $repliessync; - var $friendsync; - function get_instructions() { return _('Add your Twitter account to automatically send your notices to Twitter, ' . 'and subscribe to Twitter friends already here.'); @@ -68,11 +60,11 @@ class TwittersettingsAction extends SettingsAction { common_element_end('p'); common_submit('remove', _('Remove')); } else { - common_input('twitter_username', _('Twitter Username'), + common_input('twitter_username', _('Twitter user name'), ($this->arg('twitter_username')) ? $this->arg('twitter_username') : $profile->nickname, _('No spaces, please.')); // hey, it's what Twitter says - common_password('twitter_password', _('Twitter Password')); + common_password('twitter_password', _('Twitter password')); } common_element('h2', NULL, _('Preferences')); @@ -84,7 +76,7 @@ class TwittersettingsAction extends SettingsAction { ($flink) ? ($flink->noticesync & FOREIGN_NOTICE_SEND_REPLY) : true); common_checkbox('friendsync', _('Subscribe to my Twitter friends here.'), - ($flink) ? ($flink->friendsync & FOREIGN_FRIEND_RECV) : true); + ($flink) ? ($flink->friendsync & FOREIGN_FRIEND_RECV) : false); if ($flink) { common_submit('save', _('Save')); @@ -92,48 +84,46 @@ class TwittersettingsAction extends SettingsAction { common_submit('add', _('Add')); } - $this->show_twitter_subscriptions(); common_element_end('form'); - + common_show_footer(); } - + function subscribed_twitter_users() { $current_user = common_current_user(); - + $qry = 'SELECT user.* ' . - 'FROM subscription ' . + 'FROM subscription ' . 'JOIN user ON subscription.subscribed = user.id ' . - 'JOIN foreign_link ON foreign_link.user_id = user.id ' . - 'WHERE subscriber = %d ' . + 'JOIN foreign_link ON foreign_link.user_id = user.id ' . + 'WHERE subscriber = %d ' . 'ORDER BY user.nickname'; $user = new User(); - + $user->query(sprintf($qry, $current_user->id)); $users = array(); while ($user->fetch()) { - $users[] = clone($user); + + // Don't include the user's own self-subscription + if ($user->id != $current_user->id) { + $users[] = clone($user); + } } - + return $users; } - - + function show_twitter_subscriptions() { - - common_debug('show twitter subs'); + $friends = $this->subscribed_twitter_users(); - $friends_count = count($friends); - common_debug("friends count = $friends_count"); - if ($friends_count > 0) { common_element('h3', NULL, _('Twitter Friends')); @@ -148,7 +138,7 @@ class TwittersettingsAction extends SettingsAction { common_log_db_error($subs, 'SELECT', __FILE__); continue; } - + common_element_start('li'); common_element_start('a', array('title' => ($other->fullname) ? $other->fullname : @@ -166,7 +156,7 @@ class TwittersettingsAction extends SettingsAction { $other->nickname)); common_element_end('a'); common_element_end('li'); - + } common_element_end('ul'); @@ -174,8 +164,8 @@ class TwittersettingsAction extends SettingsAction { } - // XXX Figure out a way to show all Twitter friends... - + // XXX Figure out a way to show all Twitter friends... ? + /* if ($subs_count > SUBSCRIPTIONS) { common_element_start('p', array('id' => 'subscriptions_viewall')); @@ -189,8 +179,6 @@ class TwittersettingsAction extends SettingsAction { */ } - - function handle_post() { @@ -214,34 +202,35 @@ class TwittersettingsAction extends SettingsAction { function add_twitter_acct() { - $this->twit_username = $this->trimmed('twitter_username'); - $this->twit_password = $this->trimmed('twitter_password'); - $this->noticesync = $this->boolean('noticesync'); - $this->replysync = $this->boolean('replysync'); - $this->friendsync = $this->boolean('friendsync'); + $screen_name = $this->trimmed('twitter_username'); + $password = $this->trimmed('twitter_password'); + $noticesync = $this->boolean('noticesync'); + $replysync = $this->boolean('replysync'); + $friendsync = $this->boolean('friendsync'); - if (!Validate::string($this->twit_username, array('min_length' => 1, - 'max_length' => 15, - 'format' => VALIDATE_NUM . VALIDATE_ALPHA . '_'))) { - $this->show_form(_('Username must have only numbers, upper- and lowercase letters, and underscore (_). 15 chars max.')); + if (!Validate::string($screen_name, + array( 'min_length' => 1, + 'max_length' => 15, + 'format' => VALIDATE_NUM . VALIDATE_ALPHA . '_'))) { + $this->show_form( + _('Username must have only numbers, upper- and lowercase letters, and underscore (_). 15 chars max.')); return; } - // Verify this is a real Twitter user. - if (!$this->verify_credentials()) { + if (!$this->verify_credentials($screen_name, $password)) { $this->show_form(_('Could not verify your Twitter credentials!')); return; } - if (!$this->twitter_user_info()) { + $twit_user = twitter_user_info($screen_name, $password); + + if (!$twit_user) { $this->show_form(sprintf(_('Unable to retrieve account information for "%s" from Twitter.'), - $twitter_username)); + $screen_name)); return; } - $fuser_id = $this->update_twitter_user($this->twit_id, $this->twit_username); - - if (!$fuser_id) { + if (!save_twitter_user($twit_user->id, $screen_name)) { $this->show_form(_('Unable to save your Twitter settings!')); return; } @@ -250,12 +239,12 @@ class TwittersettingsAction extends SettingsAction { $flink = DB_DataObject::factory('foreign_link'); $flink->user_id = $user->id; - $flink->foreign_id = $fuser_id; + $flink->foreign_id = $twit_user->id; $flink->service = 1; // Twitter - $flink->credentials = $this->twit_password; + $flink->credentials = $password; $flink->created = common_sql_now(); - $this->set_flags($flink, $this->noticesync, $this->replysync, $this->friendsync); + $this->set_flags($flink, $noticesync, $replysync, $friendsync); $flink_id = $flink->insert(); @@ -265,27 +254,21 @@ class TwittersettingsAction extends SettingsAction { return; } - if ($this->friendsync) { - $this->save_friends(); + if ($friendsync) { + save_twitter_friends($user, $twit_user->id, $screen_name, $password); } $this->show_form(_('Twitter settings saved.'), true); } function remove_twitter_acct() { - $user = common_current_user(); - // For now we assume one Twitter acct per Laconica acct + $user = common_current_user(); $flink = Foreign_link::getByUserID($user->id, 1); $flink_foreign_id = $this->arg('flink_foreign_id'); - if (!$flink) { - common_debug("couldn't get flink"); - } - # Maybe an old tab open...? if ($flink->foreign_id != $flink_foreign_id) { - common_debug("flink user_id = " . $flink->user_id); $this->show_form(_('That is not your Twitter account.')); return; } @@ -302,11 +285,13 @@ class TwittersettingsAction extends SettingsAction { } function save_preferences() { - $this->noticesync = $this->boolean('noticesync'); - $this->friendsync = $this->boolean('friendsync'); - $this->replysync = $this->boolean('replysync'); + + $noticesync = $this->boolean('noticesync'); + $friendsync = $this->boolean('friendsync'); + $replysync = $this->boolean('replysync'); $user = common_current_user(); + $flink = Foreign_link::getByUserID($user->id, 1); if (!$flink) { @@ -315,8 +300,8 @@ class TwittersettingsAction extends SettingsAction { return; } - $this->twit_id = $flink->foreign_id; - $this->twit_password = $flink->credentials; + $twitter_id = $flink->foreign_id; + $password = $flink->credentials; $fuser = $flink->getForeignUser(); @@ -326,10 +311,10 @@ class TwittersettingsAction extends SettingsAction { return; } - $this->twit_username = $fuser->nickname; + $screen_name = $fuser->nickname; $original = clone($flink); - $this->set_flags($flink, $this->noticesync, $this->replysync, $this->friendsync); + $this->set_flags($flink, $noticesync, $replysync, $friendsync); $result = $flink->update($original); if ($result === FALSE) { @@ -338,39 +323,16 @@ class TwittersettingsAction extends SettingsAction { return; } - if ($this->friendsync) { - $this->save_friends(); + if ($friendsync) { + save_twitter_friends($user, $flink->foreign_id, $screen_name, $password); } $this->show_form(_('Twitter preferences saved.')); } - function twitter_user_info() { - $uri = "http://twitter.com/users/show/$this->twit_username.json"; - $data = $this->get_twitter_data($uri); - - if (!$data) { - return false; - } - - $twit_user = json_decode($data); - - if (!$twit_user) { - return false; - } - - $this->friends_count = $twit_user->friends_count; - $this->twit_id = $twit_user->id; - - common_debug("Twitter_id = $this->twit_id"); - common_debug("Friends_count = $this->friends_count"); - - return true; - } - - function verify_credentials() { + function verify_credentials($screen_name, $password) { $uri = 'http://twitter.com/account/verify_credentials.json'; - $data = $this->get_twitter_data($uri); + $data = get_twitter_data($uri, $screen_name, $password); if (!$data) { return false; @@ -389,35 +351,6 @@ class TwittersettingsAction extends SettingsAction { return false; } - function get_twitter_data($uri) { - - $options = array( - CURLOPT_USERPWD => sprintf("%s:%s", $this->twit_username, $this->twit_password), - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FAILONERROR => true, - CURLOPT_HEADER => false, - CURLOPT_FOLLOWLOCATION => true, - // CURLOPT_USERAGENT => "identi.ca", - CURLOPT_CONNECTTIMEOUT => 120, - CURLOPT_TIMEOUT => 120 - ); - - - $ch = curl_init($uri); - curl_setopt_array($ch, $options); - $data = curl_exec($ch); - $errmsg = curl_error($ch); - - if ($errmsg) { - common_debug("cURL error: $errmsg - trying to load: $uri with user $this->twit_user.", - __FILE__); - } - - curl_close($ch); - - return $data; - } - function set_flags(&$flink, $noticesync, $replysync, $friendsync) { if ($noticesync) { $flink->noticesync |= FOREIGN_NOTICE_SEND; @@ -437,128 +370,7 @@ class TwittersettingsAction extends SettingsAction { $flink->friendsync &= ~FOREIGN_FRIEND_RECV; } - $flink->profilesync = 0; // XXX: leave as default? - } - - function save_friends() { - - $uri = 'http://twitter.com/statuses/friends.json?page='; - - $this->twitter_user_info(); - - // Calculate how many pages to get... - $pages = ceil($this->friends_count / 100); - - common_debug("number of pages to get: $pages"); - - $friends = array(); - - for ($i = 1; $i <= $pages; $i++) { - - $data = $this->get_twitter_data($uri . $i); - - common_debug("fetching " . $uri . $i); - - if (!$data) { - return false; - } - - common_debug("got data"); - - $more_friends = json_decode($data); - - if (!$more_friends) { - return false; - } - - $friends = array_merge($friends, $more_friends); - - } - - common_debug("number of friends =" + count($friends)); - - $user = common_current_user(); - - foreach ($friends as $friend) { - - $friend_name = $friend->screen_name; - $friend_id = $friend->id; - - // Update or create the Foreign_user record - $this->update_twitter_user($friend_id, $friend_name); - - // Check to see if there's a related local user - $flink = Foreign_link::getByForeignID($friend_id, 1); - - if ($flink) { - - // Get associated user - $friend_user = User::staticGet('id', $flink->user_id); - subs_subscribe_to($user, $friend_user); - - } - } - - } - - // Creates or Updates a Twitter user - function update_twitter_user($twitter_id, $screen_name) { - - $fuser = null; - - $uri = "http://twitter.com/$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, 1); - - if ($fuser) { - - // Only update if Twitter screen name has changed - if ($fuser->nickname != $screen_name) { - - $original = clone($fuser); - $fuser->nickname = $screen_name; - $fuser->uri = $uri; - $result = $fuser->updateKeys($original); - - if (!$result) { - common_log_db_error($fuser, 'UPDATE', __FILE__); - return null; - } - - common_debug( - sprintf('Updated Twitter user %, screen name was: %, now: %s.', - $twitter_id, $original->nickname, $screen_name)); - } - - common_debug("No update for $screen_name needed."); - - } else { - - // Otherwise, create a new Twitter user - $fuser = DB_DataObject::factory('foreign_user'); - - $fuser->nickname = $screen_name; - $fuser->uri = $uri; - $fuser->id = $twitter_id; - $fuser->service = 1; // Twitter - $fuser->created = common_sql_now(); - $result = $fuser->insert(); - - if (!$result) { - common_debug("Failed to add new Twitter user: $twitter_id - $screen_name."); - common_log_db_error($fuser, 'INSERT', __FILE__); - return null; - } - - common_debug("Added new Twitter user: $twitter_id - $screen_name."); - - // common_debug(print_r($friend, true)); - } - - return $fuser->id; - + $flink->profilesync = 0; } } \ No newline at end of file diff --git a/classes/Foreign_link.php b/classes/Foreign_link.php index e3f7bfdd83..58c89b4e62 100644 --- a/classes/Foreign_link.php +++ b/classes/Foreign_link.php @@ -53,15 +53,10 @@ class Foreign_link extends Memcached_DataObject return NULL; } - - - // Convenience method - function getForeignUser() { + // Convenience method + function getForeignUser() { $fuser = new Foreign_user(); - - common_debug("service = " . $this->service); - common_debug("foreign_id = " . $this->foreign_id); $fuser->service = $this->service; $fuser->id = $this->foreign_id; diff --git a/lib/common.php b/lib/common.php index 2ab9c616c4..aac54b547c 100644 --- a/lib/common.php +++ b/lib/common.php @@ -146,6 +146,7 @@ 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'); function __autoload($class) { if ($class == 'OAuthRequest') { diff --git a/lib/twitter.php b/lib/twitter.php new file mode 100644 index 0000000000..6edbc3aa3e --- /dev/null +++ b/lib/twitter.php @@ -0,0 +1,199 @@ +. + */ + +if (!defined('LACONICA')) { exit(1); } + +function get_twitter_data($uri, $screen_name, $password) { + + $options = array( + CURLOPT_USERPWD => sprintf("%s:%s", $screen_name, $password), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FAILONERROR => true, + CURLOPT_HEADER => false, + CURLOPT_FOLLOWLOCATION => true, + // CURLOPT_USERAGENT => "identi.ca", + CURLOPT_CONNECTTIMEOUT => 120, + CURLOPT_TIMEOUT => 120 + ); + + + $ch = curl_init($uri); + curl_setopt_array($ch, $options); + $data = curl_exec($ch); + $errmsg = curl_error($ch); + + if ($errmsg) { + common_debug("Twitter bridge - cURL error: $errmsg - trying to load: $uri with user $twit_user.", + __FILE__); + } + + curl_close($ch); + + return $data; +} + +function twitter_user_info($screen_name, $password) { + + $uri = "http://twitter.com/users/show/$screen_name.json"; + $data = get_twitter_data($uri, $screen_name, $password); + + if (!$data) { + return false; + } + + $twit_user = json_decode($data); + + if (!$twit_user) { + return false; + } + + return $twit_user; +} + +function update_twitter_user($fuser, $twitter_id, $screen_name) { + + $original = clone($fuser); + $fuser->nickname = $screen_name; + $fuser->uri = 'http://twitter.com/' . $screen_name; + $result = $fuser->updateKeys($original); + + if (!$result) { + common_log_db_error($fuser, 'UPDATE', __FILE__); + return false; + } + + return true; +} + +function add_twitter_user($twitter_id, $screen_name) { + + // Otherwise, create a new Twitter user + $fuser = DB_DataObject::factory('foreign_user'); + + $fuser->nickname = $screen_name; + $fuser->uri = 'http://twitter.com/' . $screen_name; + $fuser->id = $twitter_id; + $fuser->service = 1; // Twitter + $fuser->created = common_sql_now(); + $result = $fuser->insert(); + + if (!$result) { + common_debug("Twitter bridge - failed to add new Twitter user: $twitter_id - $screen_name."); + common_log_db_error($fuser, 'INSERT', __FILE__); + return false; + } + + common_debug("Twitter bridge - Added new Twitter user: $screen_name ($twitter_id)."); + + return true; +} + +// 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, 1); + + if ($fuser) { + + // Only update if Twitter screen name has changed + if ($fuser->nickname != $screen_name) { + + common_debug('Twitter bridge - Updated nickname (and URI) for Twitter user ' . + "$fuser->id to $screen_name, was $fuser->nickname"); + + return update_twitter_user($fuser, $twitter_id, $screen_name); + } + + } else { + return add_twitter_user($twitter_id, $screen_name); + } + + return true; +} + +function retreive_twitter_friends($twitter_id, $screen_name, $password) { + + $uri = "http://twitter.com/statuses/friends/$twitter_id.json?page="; + $twitter_user = twitter_user_info($screen_name, $password); + + // Calculate how many pages to get... + $pages = ceil($twitter_user->friends_count / 100); + + if ($pages == 0) { + common_debug("Twitter bridge - Twitter user $screen_name has no friends! Lame."); + } + + $friends = array(); + + for ($i = 1; $i <= $pages; $i++) { + + $data = get_twitter_data($uri . $i, $screen_name, $password); + + if (!$data) { + return NULL; + } + + $more_friends = json_decode($data); + + if (!$more_friends) { + return NULL; + } + + $friends = array_merge($friends, $more_friends); + } + + return $friends; +} + +function save_twitter_friends($user, $twitter_id, $screen_name, $password) { + + $friends = retreive_twitter_friends($twitter_id, $screen_name, $password); + + if (is_null($friends)) { + common_debug("Twitter bridge - Couldn't get friends data from Twitter."); + return false; + } + + foreach ($friends as $friend) { + + $friend_name = $friend->screen_name; + $friend_id = $friend->id; + + // Update or create the Foreign_user record + if (!save_twitter_user($friend_id, $friend_name)) { + return false; + } + + // Check to see if there's a related local user + $flink = Foreign_link::getByForeignID($friend_id, 1); + + if ($flink) { + + // Get associated user and subscribe her + $friend_user = User::staticGet('id', $flink->user_id); + subs_subscribe_to($user, $friend_user); + common_debug("Twitter bridge - subscribed $friend_user->nickname to $user->nickname."); + } + } + + return true; +} + diff --git a/scripts/synctwitterfriends.php b/scripts/synctwitterfriends.php new file mode 100755 index 0000000000..bf1efebf86 --- /dev/null +++ b/scripts/synctwitterfriends.php @@ -0,0 +1,54 @@ +#!/usr/bin/env php +. + */ + +# Abort if called from a web server +if (isset($_SERVER) && array_key_exists('REQUEST_METHOD', $_SERVER)) { + print "This script must be run from the command line\n"; + exit(); +} + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); +define('LACONICA', true); + +require_once(INSTALLDIR . '/lib/common.php'); + +$flink = new Foreign_link(); +$flink->service = 1; // Twitter +$flink->find(); + +while ($flink->fetch()) { + + $user = User::staticGet($flink->user_id); + + print "Updating Twitter friends for user $user->nickname ($user->id)\n"; + + $fuser = $flink->getForeignUser(); + $result = save_twitter_friends($user, $fuser->id, $fuser->nickname, $flink->credentials); + + if ($result == false) { + print "Problems updating Twitter friends! Check the log.\n"; + exit(1); + } + +} + +exit(0); + +