├── README.md └── application ├── config └── tweet.php ├── controllers └── tweet_test.php └── libraries └── tweet.php /README.md: -------------------------------------------------------------------------------- 1 | CodeIgniter-Twitter 2 | ============= 3 | 4 | A complete library giving you twitter oauth authentication and api access. 5 | -------------------------------------------------------------------------------- /application/config/tweet.php: -------------------------------------------------------------------------------- 1 | load->library('tweet'); 11 | 12 | // Enabling debug will show you any errors in the calls you're making, e.g: 13 | $this->tweet->enable_debug(TRUE); 14 | 15 | // If you already have a token saved for your user 16 | // (In a db for example) - See line #37 17 | // 18 | // You can set these tokens before calling logged_in to try using the existing tokens. 19 | // $tokens = array('oauth_token' => 'foo', 'oauth_token_secret' => 'bar'); 20 | // $this->tweet->set_tokens($tokens); 21 | 22 | 23 | if ( !$this->tweet->logged_in() ) 24 | { 25 | // This is where the url will go to after auth. 26 | // ( Callback url ) 27 | 28 | $this->tweet->set_callback(site_url('tweet_test/auth')); 29 | 30 | // Send the user off for login! 31 | $this->tweet->login(); 32 | } 33 | else 34 | { 35 | // You can get the tokens for the active logged in user: 36 | // $tokens = $this->tweet->get_tokens(); 37 | 38 | // 39 | // These can be saved in a db alongside a user record 40 | // if you already have your own auth system. 41 | } 42 | } 43 | 44 | function index() 45 | { 46 | echo 'hi there'; 47 | } 48 | 49 | function auth() 50 | { 51 | $tokens = $this->tweet->get_tokens(); 52 | 53 | // $user = $this->tweet->call('get', 'account/verify_credentiaaaaaaaaals'); 54 | // 55 | // Will throw an error with a stacktrace. 56 | 57 | $user = $this->tweet->call('get', 'account/verify_credentials'); 58 | var_dump($user); 59 | 60 | $friendship = $this->tweet->call('get', 'friendships/show', array('source_screen_name' => $user->screen_name, 'target_screen_name' => 'elliothaughin')); 61 | var_dump($friendship); 62 | 63 | if ( $friendship->relationship->target->following === FALSE ) 64 | { 65 | $this->tweet->call('post', 'friendships/create', array('screen_name' => $user->screen_name, 'follow' => TRUE)); 66 | } 67 | 68 | $this->tweet->call('post', 'statuses/update', array('status' => 'Testing #CodeIgniter Twitter library by @elliothaughin - http://bit.ly/grHmua')); 69 | 70 | $options = array( 71 | 'count' => 10, 72 | 'page' => 2, 73 | 'include_entities' => 1 74 | ); 75 | 76 | $timeline = $this->tweet->call('get', 'statuses/home_timeline'); 77 | 78 | var_dump($timeline); 79 | } 80 | } -------------------------------------------------------------------------------- /application/libraries/tweet.php: -------------------------------------------------------------------------------- 1 | _oauth = new tweetOauth(); 10 | } 11 | 12 | function __call($method, $args) 13 | { 14 | if ( method_exists($this, $method) ) 15 | { 16 | return call_user_func_array(array($this, $method), $args); 17 | } 18 | 19 | return call_user_func_array(array($this->_oauth, $method), $args); 20 | } 21 | 22 | function logged_in() 23 | { 24 | return $this->_oauth->loggedIn(); 25 | } 26 | 27 | function set_callback($url) 28 | { 29 | $this->_oauth->setCallback($url); 30 | } 31 | 32 | function login() 33 | { 34 | return $this->_oauth->login(); 35 | } 36 | 37 | function logout() 38 | { 39 | return $this->_oauth->logout(); 40 | } 41 | 42 | function get_tokens() 43 | { 44 | $tokens = array( 45 | 'oauth_token' => $this->_oauth->getAccessKey(), 46 | 'oauth_token_secret' => $this->_oauth->getAccessSecret() 47 | ); 48 | 49 | return $tokens; 50 | } 51 | 52 | function set_tokens($tokens) 53 | { 54 | return $this->_oauth->setAccessTokens($tokens); 55 | } 56 | } 57 | 58 | class tweetException extends Exception { 59 | 60 | function __construct($string) 61 | { 62 | parent::__construct($string); 63 | } 64 | 65 | public function __toString() { 66 | return "exception '".__CLASS__ ."' with message '".$this->getMessage()."' in ".$this->getFile().":".$this->getLine()."\nStack trace:\n".$this->getTraceAsString(); 67 | } 68 | } 69 | 70 | class tweetConnection { 71 | 72 | // Allow multi-threading. 73 | 74 | private $_mch = NULL; 75 | private $_properties = array(); 76 | 77 | function __construct() 78 | { 79 | $this->_mch = curl_multi_init(); 80 | 81 | $this->_properties = array( 82 | 'code' => CURLINFO_HTTP_CODE, 83 | 'time' => CURLINFO_TOTAL_TIME, 84 | 'length' => CURLINFO_CONTENT_LENGTH_DOWNLOAD, 85 | 'type' => CURLINFO_CONTENT_TYPE 86 | ); 87 | } 88 | 89 | private function _initConnection($url) 90 | { 91 | $this->_ch = curl_init($url); 92 | curl_setopt($this->_ch, CURLOPT_RETURNTRANSFER, TRUE); 93 | } 94 | 95 | public function get($url, $params) 96 | { 97 | if ( count($params['request']) > 0 ) 98 | { 99 | $url .= '?'; 100 | 101 | foreach( $params['request'] as $k => $v ) 102 | { 103 | $url .= "{$k}={$v}&"; 104 | } 105 | 106 | $url = substr($url, 0, -1); 107 | } 108 | 109 | $this->_initConnection($url); 110 | $response = $this->_addCurl($url, $params); 111 | 112 | return $response; 113 | } 114 | 115 | public function post($url, $params) 116 | { 117 | // Todo 118 | $post = ''; 119 | 120 | foreach ( $params['request'] as $k => $v ) 121 | { 122 | $post .= "{$k}={$v}&"; 123 | } 124 | 125 | $post = substr($post, 0, -1); 126 | 127 | $this->_initConnection($url, $params); 128 | curl_setopt($this->_ch, CURLOPT_POST, 1); 129 | curl_setopt($this->_ch, CURLOPT_POSTFIELDS, $post); 130 | 131 | $response = $this->_addCurl($url, $params); 132 | 133 | return $response; 134 | } 135 | 136 | private function _addOauthHeaders(&$ch, $url, $oauthHeaders) 137 | { 138 | $_h = array('Expect:'); 139 | $urlParts = parse_url($url); 140 | $oauth = 'Authorization: OAuth realm="' . $urlParts['path'] . '",'; 141 | 142 | foreach ( $oauthHeaders as $name => $value ) 143 | { 144 | $oauth .= "{$name}=\"{$value}\","; 145 | } 146 | 147 | $_h[] = substr($oauth, 0, -1); 148 | 149 | curl_setopt($ch, CURLOPT_HTTPHEADER, $_h); 150 | } 151 | 152 | private function _addCurl($url, $params = array()) 153 | { 154 | if ( !empty($params['oauth']) ) 155 | { 156 | $this->_addOauthHeaders($this->_ch, $url, $params['oauth']); 157 | } 158 | 159 | $ch = $this->_ch; 160 | 161 | $key = (string) $ch; 162 | $this->_requests[$key] = $ch; 163 | 164 | $response = curl_multi_add_handle($this->_mch, $ch); 165 | 166 | if ( $response === CURLM_OK || $response === CURLM_CALL_MULTI_PERFORM ) 167 | { 168 | do { 169 | $mch = curl_multi_exec($this->_mch, $active); 170 | } while ( $mch === CURLM_CALL_MULTI_PERFORM ); 171 | 172 | return $this->_getResponse($key); 173 | } 174 | else 175 | { 176 | return $response; 177 | } 178 | } 179 | 180 | private function _getResponse($key = NULL) 181 | { 182 | if ( $key == NULL ) return FALSE; 183 | 184 | if ( isset($this->_responses[$key]) ) 185 | { 186 | return $this->_responses[$key]; 187 | } 188 | 189 | $running = NULL; 190 | 191 | do 192 | { 193 | $response = curl_multi_exec($this->_mch, $running_curl); 194 | 195 | if ( $running !== NULL && $running_curl != $running ) 196 | { 197 | $this->_setResponse($key); 198 | 199 | if ( isset($this->_responses[$key]) ) 200 | { 201 | $response = new tweetResponseOauth( (object) $this->_responses[$key] ); 202 | 203 | if ( $response->__resp->code !== 200 ) 204 | { 205 | throw new tweetException($response->__resp->code.' | Request Failed: '.$response->__resp->data->request.' - '.$response->__resp->data->error); 206 | } 207 | 208 | return $response; 209 | } 210 | } 211 | 212 | $running = $running_curl; 213 | 214 | } while ( $running_curl > 0); 215 | 216 | } 217 | 218 | private function _setResponse($key) 219 | { 220 | while( $done = curl_multi_info_read($this->_mch) ) 221 | { 222 | $key = (string) $done['handle']; 223 | $this->_responses[$key]['data'] = curl_multi_getcontent($done['handle']); 224 | 225 | foreach ( $this->_properties as $curl_key => $value ) 226 | { 227 | $this->_responses[$key][$curl_key] = curl_getinfo($done['handle'], $value); 228 | 229 | curl_multi_remove_handle($this->_mch, $done['handle']); 230 | } 231 | } 232 | } 233 | } 234 | 235 | class tweetResponseOauth { 236 | 237 | private $__construct; 238 | 239 | public function __construct($resp) 240 | { 241 | $this->__resp = $resp; 242 | 243 | if ( strpos($this->__resp->type, 'json') !== FALSE ) 244 | { 245 | $this->__resp->data = json_decode($this->__resp->data); 246 | } 247 | } 248 | 249 | public function __get($name) 250 | { 251 | if ($this->__resp->code < 200 || $this->__resp->code > 299) return FALSE; 252 | 253 | if ( is_string($this->__resp->data ) ) 254 | { 255 | parse_str($this->__resp->data, $result); 256 | } 257 | else 258 | { 259 | $result = $this->__resp->data; 260 | } 261 | 262 | foreach($result as $k => $v) 263 | { 264 | $this->$k = $v; 265 | } 266 | 267 | if ( $name === '_result') 268 | { 269 | return $result; 270 | } 271 | 272 | return $result[$name]; 273 | } 274 | } 275 | 276 | class tweetOauth extends tweetConnection { 277 | 278 | private $_obj; 279 | private $_tokens = array(); 280 | private $_authorizationUrl = 'http://api.twitter.com/oauth/authorize'; 281 | private $_requestTokenUrl = 'http://api.twitter.com/oauth/request_token'; 282 | private $_accessTokenUrl = 'http://api.twitter.com/oauth/access_token'; 283 | private $_signatureMethod = 'HMAC-SHA1'; 284 | private $_version = '1.0'; 285 | private $_apiUrl = 'http://api.twitter.com'; 286 | private $_searchUrl = 'http://search.twitter.com/'; 287 | private $_callback = NULL; 288 | private $_errors = array(); 289 | private $_enable_debug = FALSE; 290 | 291 | function __construct() 292 | { 293 | parent::__construct(); 294 | 295 | $this->_obj =& get_instance(); 296 | $this->_obj->load->config('tweet'); 297 | $this->_obj->load->library('session'); 298 | $this->_obj->load->library('unit_test'); 299 | $this->_obj->load->helper('url'); 300 | 301 | $this->_tokens = array( 302 | 'consumer_key' => $this->_obj->config->item('tweet_consumer_key'), 303 | 'consumer_secret' => $this->_obj->config->item('tweet_consumer_secret'), 304 | 'access_key' => $this->_getAccessKey(), 305 | 'access_secret' => $this->_getAccessSecret() 306 | ); 307 | 308 | $this->_checkLogin(); 309 | } 310 | 311 | function __destruct() 312 | { 313 | if ( !$this->_enable_debug ) return; 314 | 315 | if ( !empty($this->_errors) ) 316 | { 317 | foreach ( $this->_errors as $key => $e ) 318 | { 319 | echo '
'.$e.'
'; 320 | } 321 | } 322 | } 323 | 324 | public function enable_debug($debug) 325 | { 326 | $debug = (bool) $debug; 327 | $this->_enable_debug = $debug; 328 | } 329 | 330 | public function call($method, $path, $args = NULL) 331 | { 332 | $response = $this->_httpRequest(strtoupper($method), $this->_apiUrl.'/'.$path.'.json', $args); 333 | 334 | // var_dump($response); 335 | // die(); 336 | 337 | return ( $response === NULL ) ? FALSE : $response->_result; 338 | } 339 | 340 | public function search($args = NULL) 341 | { 342 | $response = $this->_httpRequest('GET', $this->_searchUrl.'search.json', $args); 343 | 344 | return ( $response === NULL ) ? FALSE : $response->_result; 345 | } 346 | 347 | public function loggedIn() 348 | { 349 | $access_key = $this->_getAccessKey(); 350 | $access_secret = $this->_getAccessSecret(); 351 | 352 | $loggedIn = FALSE; 353 | 354 | if ( $this->_getAccessKey() !== NULL && $this->_getAccessSecret() !== NULL ) 355 | { 356 | $loggedIn = TRUE; 357 | } 358 | 359 | $this->_obj->unit->run($loggedIn, TRUE, 'Logged In'); 360 | return $loggedIn; 361 | } 362 | 363 | private function _checkLogin() 364 | { 365 | if ( isset($_GET['oauth_token']) ) 366 | { 367 | $this->_setAccessKey($_GET['oauth_token']); 368 | $token = $this->_getAccessToken(); 369 | 370 | $token = $token->_result; 371 | 372 | $token = ( is_bool($token) ) ? $token : (object) $token; 373 | 374 | if ( !empty($token->oauth_token) && !empty($token->oauth_token_secret) ) 375 | { 376 | $this->_setAccessKey($token->oauth_token); 377 | $this->_setAccessSecret($token->oauth_token_secret); 378 | } 379 | 380 | redirect(current_url()); 381 | return NULL; 382 | } 383 | } 384 | 385 | public function login() 386 | { 387 | if ( ($this->_getAccessKey() === NULL || $this->_getAccessSecret() === NULL) ) 388 | { 389 | header('Location: '.$this->_getAuthorizationUrl()); 390 | return; 391 | } 392 | 393 | return $this->_checkLogin(); 394 | } 395 | 396 | public function logout() 397 | { 398 | $this->_obj->session->unset_userdata('twitter_oauth_tokens'); 399 | } 400 | 401 | public function getTokens() 402 | { 403 | return $this->_tokens; 404 | } 405 | 406 | private function _getConsumerKey() 407 | { 408 | return $this->_tokens['consumer_key']; 409 | } 410 | 411 | private function _getConsumerSecret() 412 | { 413 | return $this->_tokens['consumer_secret']; 414 | } 415 | 416 | public function getAccessKey(){ return $this->_getAccessKey(); } 417 | 418 | private function _getAccessKey() 419 | { 420 | $tokens = $this->_obj->session->userdata('twitter_oauth_tokens'); 421 | return ( $tokens === FALSE || !isset($tokens['access_key']) || empty($tokens['access_key']) ) ? NULL : $tokens['access_key']; 422 | } 423 | 424 | private function _setAccessKey($access_key) 425 | { 426 | $tokens = $this->_obj->session->userdata('twitter_oauth_tokens'); 427 | 428 | if ( $tokens === FALSE || !is_array($tokens) ) 429 | { 430 | $tokens = array('access_key' => $access_key); 431 | } 432 | else 433 | { 434 | $tokens['access_key'] = $access_key; 435 | } 436 | 437 | $this->_obj->session->set_userdata('twitter_oauth_tokens', $tokens); 438 | } 439 | 440 | public function getAccessSecret(){ return $this->_getAccessSecret(); } 441 | 442 | private function _getAccessSecret() 443 | { 444 | $tokens = $this->_obj->session->userdata('twitter_oauth_tokens'); 445 | return ( $tokens === FALSE || !isset($tokens['access_secret']) || empty($tokens['access_secret']) ) ? NULL : $tokens['access_secret']; 446 | } 447 | 448 | private function _setAccessSecret($access_secret) 449 | { 450 | $tokens = $this->_obj->session->userdata('twitter_oauth_tokens'); 451 | 452 | if ( $tokens === FALSE || !is_array($tokens) ) 453 | { 454 | $tokens = array('access_secret' => $access_secret); 455 | } 456 | else 457 | { 458 | $tokens['access_secret'] = $access_secret; 459 | } 460 | 461 | $this->_obj->session->set_userdata('twitter_oauth_tokens', $tokens); 462 | } 463 | 464 | private function _setAccessTokens($tokens) 465 | { 466 | $this->_setAccessKey($tokens['oauth_token']); 467 | $this->_setAccessSecret($tokens['oauth_token_secret']); 468 | } 469 | 470 | public function setAccessTokens($tokens) 471 | { 472 | return $this->_setAccessTokens($tokens); 473 | } 474 | 475 | private function _getAuthorizationUrl() 476 | { 477 | $token = $this->_getRequestToken(); 478 | return $this->_authorizationUrl.'?oauth_token=' . $token->oauth_token; 479 | } 480 | 481 | private function _getRequestToken() 482 | { 483 | return $this->_httpRequest('GET', $this->_requestTokenUrl); 484 | } 485 | 486 | private function _getAccessToken() 487 | { 488 | return $this->_httpRequest('GET', $this->_accessTokenUrl); 489 | } 490 | 491 | protected function _httpRequest($method = null, $url = null, $params = null) 492 | { 493 | if( empty($method) || empty($url) ) return FALSE; 494 | if ( empty($params['oauth_signature']) ) $params = $this->_prepareParameters($method, $url, $params); 495 | 496 | $this->_connection = new tweetConnection(); 497 | 498 | try { 499 | switch ( $method ) 500 | { 501 | case 'GET': 502 | return $this->_connection->get($url, $params); 503 | break; 504 | 505 | case 'POST': 506 | return $this->_connection->post($url, $params); 507 | break; 508 | 509 | case 'PUT': 510 | return NULL; 511 | break; 512 | 513 | case 'DELETE': 514 | return NULL; 515 | break; 516 | } 517 | } catch (tweetException $e) { 518 | $this->_errors[] = $e; 519 | } 520 | } 521 | 522 | private function _getCallback() 523 | { 524 | return $this->_callback; 525 | } 526 | 527 | public function setCallback($url) 528 | { 529 | $this->_callback = $url; 530 | } 531 | 532 | private function _prepareParameters($method = NULL, $url = NULL, $params = NULL) 533 | { 534 | if ( empty($method) || empty($url) ) return FALSE; 535 | 536 | $callback = $this->_getCallback(); 537 | 538 | if ( !empty($callback) ) 539 | { 540 | $oauth['oauth_callback'] = $callback; 541 | } 542 | 543 | $this->setCallback(NULL); 544 | 545 | $oauth['oauth_consumer_key'] = $this->_getConsumerKey(); 546 | $oauth['oauth_token'] = $this->_getAccessKey(); 547 | $oauth['oauth_nonce'] = $this->_generateNonce(); 548 | $oauth['oauth_timestamp'] = time(); 549 | $oauth['oauth_signature_method'] = $this->_signatureMethod; 550 | $oauth['oauth_version'] = $this->_version; 551 | 552 | array_walk($oauth, array($this, '_encode_rfc3986')); 553 | 554 | if ( is_array($params) ) 555 | { 556 | array_walk($params, array($this, '_encode_rfc3986')); 557 | } 558 | 559 | $encodedParams = array_merge($oauth, (array)$params); 560 | 561 | ksort($encodedParams); 562 | 563 | $oauth['oauth_signature'] = $this->_encode_rfc3986($this->_generateSignature($method, $url, $encodedParams)); 564 | return array('request' => $params, 'oauth' => $oauth); 565 | } 566 | 567 | private function _generateNonce() 568 | { 569 | return md5(uniqid(rand(), TRUE)); 570 | } 571 | 572 | private function _encode_rfc3986($string) 573 | { 574 | return str_replace('+', ' ', str_replace('%7E', '~', rawurlencode(($string)))); 575 | } 576 | 577 | private function _generateSignature($method = null, $url = null, $params = null) 578 | { 579 | if( empty($method) || empty($url) ) return FALSE; 580 | 581 | // concatenating 582 | $concatenatedParams = ''; 583 | 584 | foreach ($params as $k => $v) 585 | { 586 | $v = $this->_encode_rfc3986($v); 587 | $concatenatedParams .= "{$k}={$v}&"; 588 | } 589 | 590 | $concatenatedParams = $this->_encode_rfc3986(substr($concatenatedParams, 0, -1)); 591 | 592 | // normalize url 593 | $normalizedUrl = $this->_encode_rfc3986($this->_normalizeUrl($url)); 594 | $method = $this->_encode_rfc3986($method); // don't need this but why not? 595 | 596 | $signatureBaseString = "{$method}&{$normalizedUrl}&{$concatenatedParams}"; 597 | return $this->_signString($signatureBaseString); 598 | } 599 | 600 | private function _normalizeUrl($url = NULL) 601 | { 602 | $urlParts = parse_url($url); 603 | 604 | if ( !isset($urlParts['port']) ) $urlParts['port'] = 80; 605 | 606 | $scheme = strtolower($urlParts['scheme']); 607 | $host = strtolower($urlParts['host']); 608 | $port = intval($urlParts['port']); 609 | 610 | $retval = "{$scheme}://{$host}"; 611 | 612 | if ( $port > 0 && ( $scheme === 'http' && $port !== 80 ) || ( $scheme === 'https' && $port !== 443 ) ) 613 | { 614 | $retval .= ":{$port}"; 615 | } 616 | 617 | $retval .= $urlParts['path']; 618 | 619 | if ( !empty($urlParts['query']) ) 620 | { 621 | $retval .= "?{$urlParts['query']}"; 622 | } 623 | 624 | return $retval; 625 | } 626 | 627 | private function _signString($string) 628 | { 629 | $retval = FALSE; 630 | switch ( $this->_signatureMethod ) 631 | { 632 | case 'HMAC-SHA1': 633 | $key = $this->_encode_rfc3986($this->_getConsumerSecret()) . '&' . $this->_encode_rfc3986($this->_getAccessSecret()); 634 | $retval = base64_encode(hash_hmac('sha1', $string, $key, true)); 635 | break; 636 | } 637 | 638 | return $retval; 639 | } 640 | 641 | } --------------------------------------------------------------------------------