? .svn ? LICENSE.txt ? twitter.patch ? twitter_actions/.svn ? twitter_actions/twitter_actions.info ? twitter_actions/twitter_actions.module Index: twitter.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.inc,v retrieving revision 1.3.2.8 diff -u -p -r1.3.2.8 twitter.inc --- twitter.inc 8 Apr 2009 07:22:41 -0000 1.3.2.8 +++ twitter.inc 27 May 2009 22:48:38 -0000 @@ -60,6 +60,68 @@ function twitter_form($account = NULL) { */ /** + * OAuth request helper. All requests to twitter should go through this function + * @param $screen_name Screen name of user making the request + * @param $url we want to fetch + * @param $method method of request we are issuing GET or POST + * @param $data data to send with post request key value pairs + * @return result array on success FALSE on failure + */ +function twitter_oauth_request($screen_name, $url, $method = 'GET', $data = array()) { + static $users; + + // Use the libraries from OAuth integration + module_load_include('lib.php', 'oauth'); + + if (!isset($users[$screen_name])) { + $account = _twitter_get_tokens($screen_name); + if (!$account) { + return FALSE; + } + } + else { + $account = $users[$screen_name]; + } + + // Build the Consumer object + $consumer = new OAuthConsumer( + variable_get('oauth_twitter_consumer_key', ''), + variable_get('oauth_twitter_consumer_secret', '') + ); + + // Build the token object + $token = new OAuthToken( + $account['token_key'], + $account['token_secret'] + ); + + // Build the request data + $request = OAuthRequest::from_consumer_and_token( + $consumer, + $token, + $method, + $url, + $data + ); + $signature_method = new OAuthSignatureMethod_HMAC_SHA1(); + $request->sign_request($signature_method, $consumer, $token); + if ($method == 'GET') { + $request_result = drupal_http_request($request->to_url()); + } + else { + $request_result = drupal_http_request($request->get_normalized_http_url(), array(), 'POST', $request->to_postdata()); + } + if (_twitter_request_failure($request_result)) { + return FALSE; + } + if ($results = _twitter_convert_xml_to_array($request_result->data)) { + return $results; + } + + return FALSE; +} + +/** * Fetch the public timeline for a Twitter.com account. * * Note that this function only requires a screen name, and not a password. As @@ -115,8 +177,6 @@ function twitter_fetch_timeline($screen_ * * @param $screen_name * The screen name of a Twitter.com user. - * @param $password - * The password of a Twitter.com user. * @param $text * The text to post. Strings longer than 140 characters will be truncated by * Twitter. @@ -128,17 +188,19 @@ function twitter_fetch_timeline($screen_ * The full results of the Drupal HTTP request, including the HTTP response * code returned by Twitter.com. */ -function twitter_set_status($screen_name, $password, $text, $source = 'drupal') { +function twitter_set_status($screen_name, $text, $source = 'drupal') { $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/statuses/update.xml"; - $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password), - 'Content-type' => 'application/x-www-form-urlencoded'); - $data = 'status='. urlencode($text); + $data['status'] = $text; if (!empty($source)) { - $data .= "&source=". urlencode($source); + $data['source'] = $source; } + return twitter_oauth_request($screen_name, $url, 'POST', $data); +} - return drupal_http_request($url, $headers, 'POST', $data); +function twitter_update_friends($op, $screen_name, $friend_id) { + $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/friendships/". $op ."/". $friend_id .".xml"; + return twitter_oauth_request($screen_name, $url, 'POST'); } /** @@ -149,33 +211,30 @@ function twitter_set_status($screen_name * * @param $screen_name * The screen name of a Twitter.com user. - * @param $password - * The password of a Twitter.com user. * @param $cache * A boolean indicating whether the account info should be cached in the local * site's database after it's retrieved. + * @param $show_user + * Screen name of user we want to see datils for. If null screen name will be used. * @return * An single Twitter account. */ -function twitter_fetch_account_info($screen_name, $password, $cache = TRUE) { - $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/users/show/$screen_name.xml"; - $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password), - 'Content-type' => 'application/x-www-form-urlencoded'); - $results = drupal_http_request($url, $headers, 'GET'); +function twitter_fetch_account_info($screen_name, $cache = TRUE, $show_user = NULL) { + if (!$show_user) { + $show_user = $screen_name; + } + $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/users/show/$show_user.xml"; - if (_twitter_request_failure($results)) { + $results = twitter_oauth_request($screen_name, $url); + if (!$results) { return array(); } - - if ($results = _twitter_convert_xml_to_array($results->data)) { - if ($cache) { - foreach($results as $user) { - twitter_cache_account($user); - } + if ($cache) { + foreach($results as $user) { + twitter_cache_account($user); } - return $results[0]; } - return array(); + return $results[0]; } /** @@ -186,8 +245,6 @@ function twitter_fetch_account_info($scr * * @param $screen_name * The screen name of a Twitter.com user. - * @param $password - * The password of a Twitter.com user. * @param $cache * A boolean indicating whether the statuses should be cached in the local * site's database after they're retrieved. @@ -196,16 +253,13 @@ function twitter_fetch_account_info($scr * * @see twitter_fetch_timeline() */ -function twitter_fetch_statuses($screen_name, $password, $cache = TRUE) { - $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/statuses/$screen_name.xml"; - $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password), - 'Content-type' => 'application/x-www-form-urlencoded'); +function twitter_fetch_statuses($screen_name, $cache = TRUE) { + $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/statuses/user_timeline/$screen_name.xml"; - $results = drupal_http_request($url, $headers, 'GET'); - if (_twitter_request_failure($results)) { + $results = twitter_oauth_request($screen_name, $url); + if (!$results) { return array(); } - $results = _twitter_convert_xml_to_array($results->data); if ($cache && !empty($results)) { foreach($results as $status) { @@ -225,18 +279,22 @@ function twitter_fetch_statuses($screen_ * * @param $screen_name * The screen name of a Twitter.com user. + * @param $only_ids + * If only ids is set to true this function will return an array of ids user is friends with * @return * An array of Twitter accounts. * * @see twitter_fetch_followers() */ function twitter_fetch_friends($screen_name) { + $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/statuses/friends/$screen_name.xml"; - $results = drupal_http_request($url, array(), 'GET'); - if (_twitter_request_failure($results)) { + + $results = twitter_oauth_request($screen_name, $url); + if (!$results) { return array(); } - return _twitter_convert_xml_to_array($results->data); + return $results; } /** @@ -255,15 +313,14 @@ function twitter_fetch_friends($screen_n * * @see twitter_fetch_friends() */ -function twitter_fetch_followers($screen_name, $password) { +function twitter_fetch_followers($screen_name) { $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/statuses/followers/$screen_name.xml"; - $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password), - 'Content-type' => 'application/x-www-form-urlencoded'); - $results = drupal_http_request($url, $headers, 'GET'); - if (_twitter_request_failure($results)) { + + $results = twitter_oauth_request($screen_name, $url); + if (!$results) { return array(); } - return _twitter_convert_xml_to_array($results->data); + return $results; } /** @@ -276,57 +333,65 @@ function twitter_fetch_followers($screen * @return * A boolean indicating success or failure. */ -function twitter_authenticate($screen_name, $password) { +function twitter_authenticate($screen_name) { $url = "http://" . variable_get('twitter_api_url', 'twitter.com') . "/account/verify_credentials.xml"; - $headers = array('Authorization' => 'Basic '. base64_encode($screen_name .':'. $password), - 'Content-type' => 'application/x-www-form-urlencoded'); - $results = drupal_http_request($url, $headers, 'GET'); - drupal_http_request('http://' . variable_get('twitter_api_url', 'twitter.com') . '/account/end_session', $headers, 'GET'); - return ($results->code == '200'); + + $result = twitter_oauth_request($screen_name, $url); + twitter_oauth_request($screen_name, 'http://' . variable_get('twitter_api_url', 'twitter.com') . '/account/end_session'); + return ($result !== FALSE); } /** * Internal helper function to deal cleanly with various HTTP response codes. */ function _twitter_request_failure($results) { + $msg = $results->data; switch ($results->code) { case '304': // 304 Not Modified: there was no new data to return. return TRUE; case 400: // 400 Bad Request: your request is invalid, and we'll return an error message that tells you why. This is the status code returned if you've exceeded the rate limit - watchdog('twitter', '400 Bad Request.'); + watchdog('twitter', '400 Bad Request. '. $msg); return TRUE; case 401: // 401 Not Authorized: either you need to provide authentication credentials, or the credentials provided aren't valid. - watchdog('twitter', '401 Not Authorized.'); + watchdog('twitter', '401 Not Authorized. '. $msg); return TRUE; case 403: // 403 Forbidden: we understand your request, but are refusing to fulfill it. An accompanying error message should explain why. - watchdog('twitter', '403 Forbidden.'); + watchdog('twitter', '403 Forbidden. '. $msg); return TRUE; case 404: // 404 Not Found: either you're requesting an invalid URI or the resource in question doesn't exist (ex: no such user). - watchdog('twitter', '404 Not Found.'); + watchdog('twitter', '404 Not Found. '. $msg); return TRUE; case 500: // 500 Internal Server Error: we did something wrong. Please post to the group about it and the Twitter team will investigate. - watchdog('twitter', '500 Internal Server Error.'); + watchdog('twitter', '500 Internal Server Error. '. $msg); return TRUE; case 502: // 502 Bad Gateway: returned if Twitter is down or being upgraded. - watchdog('twitter', '502 Bad Gateway.'); + watchdog('twitter', '502 Bad Gateway. '. $msg); return TRUE; case 503: // 503 Service Unavailable: the Twitter servers are up, but are overloaded with requests. Try again later. - watchdog('twitter', '503 Service Unavailable.'); + watchdog('twitter', '503 Service Unavailable. '. $msg); return TRUE; } // 200 OK: everything went awesome. return FALSE; } - +/** + * Returns tokens stored for this screen name + * @param $screen_name Screen name we want tokens for + * @return tokens if they exist FALSE if not + */ +function _twitter_get_tokens($screen_name) { + $result = db_fetch_array(db_query("SELECT token_key, token_secret FROM {twitter_user} WHERE screen_name = '%s'", $screen_name)); + return $result; +} /** @@ -394,7 +459,6 @@ function twitter_cache_status($status = /** * User/account relationship code */ - function twitter_get_user_accounts($uid, $full_access = FALSE) { $drupal_user = user_load($uid); return module_invoke_all('twitter_accounts', $drupal_user, $full_access); @@ -402,23 +466,26 @@ function twitter_get_user_accounts($uid, function twitter_user_save($account = array(), $force_import = FALSE) { $account += array( - 'screen_name' => '', 'import' => 1, ); if (db_result(db_query("SELECT 1 FROM {twitter_user} WHERE uid = %d AND screen_name = '%s'", $account['uid'], $account['screen_name']))) { - drupal_write_record('twitter_user', $account, array('uid', 'screen_name')); } + $save = drupal_write_record('twitter_user', $account, array('uid', 'screen_name')); } else { - drupal_write_record('twitter_user', $account); + $save = drupal_write_record('twitter_user', $account); + } + if (!$save) { + drupal_set_message('Twitter user was not saved successfully.', 'error'); + return FALSE; } if ($force_import && $account['import']) { - if (empty($account['protected']) || empty($account['password'])) { + if (!empty($account['protected'])) { $statuses = twitter_fetch_timeline($account['screen_name']); } else { - twitter_fetch_account_info($account['screen_name'], $account['password']); - $statuses = twitter_fetch_statuses($account['screen_name'], $account['password']); + twitter_fetch_account_info($account['screen_name']); + $statuses = twitter_fetch_statuses($account['screen_name']); } if (!empty($statuses)) { @@ -426,6 +493,7 @@ function twitter_user_save($account = ar twitter_touch_account($account['screen_name']); } } + return TRUE; } function twitter_user_delete($uid, $screen_name = NULL) { @@ -446,6 +514,11 @@ function twitter_user_delete($uid, $scre function _twitter_convert_xml_to_array($data) { $results = array(); $xml = new SimpleXMLElement($data); + + if ($xml->getName() == 'status') { + $result = _twitter_convert_status($xml); + return $result; + } if (!empty($xml->name)) { // Top-level user information. $results[] = _twitter_convert_user($xml); @@ -461,6 +534,12 @@ function _twitter_convert_xml_to_array($ $results[] = _twitter_convert_status($status); } } + elseif (!empty($xml->id)) { + foreach ($xml->children() as $id) { + $id = (array)$id; + $results[] = $id[0]; + } + } return $results; } Index: twitter.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.install,v retrieving revision 1.2.2.5 diff -u -p -r1.2.2.5 twitter.install --- twitter.install 25 May 2009 19:55:30 -0000 1.2.2.5 +++ twitter.install 27 May 2009 22:48:38 -0000 @@ -128,10 +128,17 @@ function twitter_schema() { 'not null' => TRUE, 'default' => '', ), - 'password' => array( - 'description' => t("The password for the Twitter account."), - 'type' => 'varchar', - 'length' => 64 + 'token_key' => array( + 'description' => t('Tokens for request and services accesses.'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE + ), + 'token_secret' => array( + 'description' => t('Token "password".'), + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE ), 'import' => array( 'description' => t("Boolean flag indicating whether the {twitter_user}'s posts should be pulled in by the site."), Index: twitter.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.module,v retrieving revision 1.3.2.12 diff -u -p -r1.3.2.12 twitter.module --- twitter.module 9 Apr 2009 14:34:26 -0000 1.3.2.12 +++ twitter.module 27 May 2009 22:48:38 -0000 @@ -26,6 +26,14 @@ function twitter_menu() { 'file' => 'twitter.pages.inc', 'type' => MENU_LOCAL_TASK, ); + + $items['oauth_twitter/callback'] = array( + 'title' => 'OAuth Twitter callback', + 'access callback' => TRUE, + 'page callback' => 'oauth_twitter_callback', + 'file' => 'twitter.pages.inc', + 'type' => MENU_CALLBACK, + ); return $items; } @@ -113,7 +121,6 @@ function twitter_nodeapi(&$node, $op, $a case 'update': if (!empty($node->status) && !empty($node->twitter) && !empty($node->twitter['post'])) { $twitter_accounts = twitter_get_user_accounts($node->uid, TRUE); - $pass = $twitter_accounts[$node->twitter['account']]['password']; $replacements = array('!title' => $node->title, '!url' => url('node/'. $node->nid, array('absolute' => TRUE, 'alias' => TRUE)), @@ -126,7 +133,7 @@ function twitter_nodeapi(&$node, $op, $a } $status = strtr($node->twitter['status'], $replacements); - $result = twitter_set_status($node->twitter['account'], $pass, $status); + $result = twitter_set_status($node->twitter['account'], $status); if (_twitter_request_failure($result)) { drupal_set_message(t('An error occurred when posting to twitter: %code %error', array('%code' => $result->code, '%error' => $result->error)), 'warning'); @@ -283,19 +290,14 @@ function twitter_link_filter($text, $pre function twitter_twitter_accounts($drupal_user, $full_access = FALSE) { $accounts = array(); if (user_access('use global twitter account') && - ($name = variable_get('twitter_global_name', NULL)) && - ($pass = variable_get('twitter_global_password', NULL))) { + ($name = variable_get('twitter_global_name', NULL))) { $accounts[$name] = array( 'screen_name' => $name, - 'password' => $pass, ); } - $sql = "SELECT ta.*, tu.uid, tu.password, tu.import FROM {twitter_user} tu LEFT JOIN {twitter_account} ta ON (tu.screen_name = ta.screen_name) WHERE tu.uid = %d"; - if ($full_access) { - $sql .= " AND tu.password IS NOT NULL"; - } + $sql = "SELECT ta.*, tu.uid, tu.token_key, tu.token_secret, tu.import FROM {twitter_user} tu LEFT JOIN {twitter_account} ta ON (tu.screen_name = ta.screen_name) WHERE tu.uid = %d"; $args = array($drupal_user->uid); $results = db_query($sql, $args); Index: twitter.pages.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/twitter/twitter.pages.inc,v retrieving revision 1.2.2.7 diff -u -p -r1.2.2.7 twitter.pages.inc --- twitter.pages.inc 8 Apr 2009 22:13:35 -0000 1.2.2.7 +++ twitter.pages.inc 27 May 2009 22:48:38 -0000 @@ -76,6 +76,26 @@ function twitter_admin_form() { array('!identica' => l('Identi.ca', 'http://laconi.ca/trac/wiki/TwitterCompatibleAPI'))), ); + $form['consumer'] = array( + '#collapsible' => TRUE, + '#collapsed' => (variable_get('oauth_twitter_consumer_key', '') and variable_get('oauth_twitter_consumer_secret', '')), + '#description' => t('Consumer information is given from the server. Its different for each user. Consider creating a settings page on your module that records these values, so users might enter them.'), + '#title' => t('Consumer information'), + '#type' => 'fieldset', + ); + $form['consumer']['oauth_twitter_consumer_key'] = array( + '#description' => t('Token request consumer key. Consumer key that twitter assigned to your application.'), + '#default_value' => variable_get('oauth_twitter_consumer_key', ''), + '#title' => t('Consumer key'), + '#type' => 'textfield', + ); + $form['consumer']['oauth_twitter_consumer_secret'] = array( + '#description' => t('Token Resquest consumer secret. Consumer secret that twitter assigned to your application.'), + '#default_value' => variable_get('oauth_twitter_consumer_secret', ''), + '#title' => t('Consumer secret'), + '#type' => 'textfield', + ); + return system_settings_form($form); } @@ -195,62 +215,150 @@ function twitter_add_account($form_state global $user; $account = $user; } + + $form['help'] = array( + '#type' => 'item', + '#value' => t("By clicking on Authorize twitter button you'll be taken to Twitter web site where you will need to login and allow @site to communicate with Twitter on your behalf", array('@site' => variable_get('site_name',''))), + ); $form['uid'] = array( '#type' => 'value', '#value' => $account->uid, ); - $form['screen_name'] = array( - '#type' => 'textfield', - '#required' => TRUE, - '#title' => t('Twitter user name'), - ); - $form['password'] = array( - '#type' => 'password', - '#title' => t('Password'), - '#description' => t("If your Twitter account is protected, or you wish to post to Twitter from Drupal, you must enter the Twitter account's password.") - ); - - $form['import'] = array( - '#type' => 'checkbox', - '#title' => t('Import statuses from this account'), - '#default_value' => TRUE, - '#access' => FALSE, - ); - $form['submit'] = array( '#type' => 'submit', - '#value' => t('Add account'), + '#value' => t('Authorize twitter'), ); return $form; } -function twitter_add_account_validate($form, &$form_state) { +function twitter_add_account_submit($form, &$form_state) { module_load_include('inc', 'twitter'); + // Use the libraries from OAuth integration + module_load_include('lib.php', 'oauth'); - $verify = FALSE; - - $pass = $form_state['values']['password']; - $name = $form_state['values']['screen_name']; + $values = array(); - if (!empty($pass)) { - $verify = TRUE; - } + // Build the Consumer object + $consumer = new OAuthConsumer( + variable_get('oauth_twitter_consumer_key', ''), + variable_get('oauth_twitter_consumer_secret', '') + ); + + // Build the request data + $request = OAuthRequest::from_consumer_and_token( + $consumer, + NULL, + 'POST', + "https://" . variable_get('twitter_api_url', 'twitter.com') . '/oauth/request_token', + $values + ); + + $signature_method = new OAuthSignatureMethod_HMAC_SHA1(); + $request->sign_request($signature_method, $consumer, NULL); + + $token = drupal_http_request($request->get_normalized_http_url(), array(), 'POST', $request->to_postdata()); + // print_r($request->get_parameters()); + parse_str($token->data, $tok); + + // Build the token object + $token = new OAuthToken( + $tok['oauth_token'], + $tok['oauth_token_secret'] + ); + + // Save token and uid into session + $_SESSION['twitter_token'] = $tok; + $_SESSION['twitter_uid'] = $form_state['values']['uid']; + + // Build the request data + $request = OAuthRequest::from_consumer_and_token( + $consumer, + $token, + 'GET', + "https://" . variable_get('twitter_api_url', 'twitter.com') . '/oauth/authorize', + $values + ); + + $request->sign_request($signature_method, $consumer, NULL); + + drupal_goto($request->to_url()); +} - if ($verify) { - $valid = twitter_authenticate($name, $pass); - if (!$valid) { - form_set_error("password", t('Twitter authentication failed. Please check your account name and try again.')); - } +function oauth_twitter_callback() { + // Show a message in case there is no token in session + if (!$token = $_SESSION['twitter_token']) { + drupal_set_message(t('There is no Token Request saved'), 'error'); + return ''; } -} -function twitter_add_account_submit($form, &$form_state) { + // Use the libraries from OAuth integration + module_load_include('lib.php', 'oauth'); module_load_include('inc', 'twitter'); + + // Build the Consumer object + $consumer = new OAuthConsumer( + variable_get('oauth_twitter_consumer_key', ''), + variable_get('oauth_twitter_consumer_secret', '') + ); + + // Build the token object + $token = new OAuthToken( + $token['oauth_token'], + $token['oauth_token_secret'] + ); + + // Build the request data + $request = OAuthRequest::from_consumer_and_token( + $consumer, + $token, + 'GET', + "https://" . variable_get('twitter_api_url', 'twitter.com') . '/oauth/access_token' + ); + $signature_method = new OAuthSignatureMethod_HMAC_SHA1(); + $request->sign_request($signature_method, $consumer, $token); + $request_result = drupal_http_request($request->to_url()); + + $account = array(); + if ($request_result->code == 200) { + parse_str($request_result->data, $tok); + $account['uid'] = $_SESSION['twitter_uid']; + $account['token_key'] = $tok['oauth_token']; + $account['token_secret'] = $tok['oauth_token_secret']; + } + else { + watchdog('twitter', 'Twitter error:'. var_export($request_result, TRUE)); + drupal_set_message('There was an error authorizing your twitter account.', 'error'); + drupal_goto('user/'. $account['uid'] .'/edit'); + } - if (!empty($form_state['values']['screen_name'])) { - twitter_user_save($form_state['values'], TRUE); + // Lets verify credentioals to get screen name and check if all went well + // Build the token object + $token = new OAuthToken( + $account['token_key'], + $account['token_secret'] + ); + $request = OAuthRequest::from_consumer_and_token( + $consumer, + $token, + 'GET', + "https://" . variable_get('twitter_api_url', 'twitter.com') .'/account/verify_credentials.xml' + ); + $request->sign_request($signature_method, $consumer, $token); + $request_result = drupal_http_request($request->to_url()); + + if (_twitter_request_failure($request_result)) { + watchdog('twitter', 'Twitter error:'. var_export($request_result, TRUE)); + drupal_set_message('There was an error authorizing your twitter account.', 'error'); + } + + if ($results = _twitter_convert_xml_to_array($request_result->data)) { + $account['screen_name'] = $results[0]['screen_name']; + if (twitter_user_save($account, TRUE)) { + drupal_set_message(t('You successfully authorized @site to access your Twitter account.', array('@site' => variable_get('site_name', '')))); + } } + drupal_goto('user/'. $account['uid'] .'/edit'); }