<?php

/**
 * A simple Twitter Client with OAuth support
 * Author: fbparis@gmail.com (http://fbparis.com/)
 */

require_once 'OAuth.php'// Get it here: http://oauth.googlecode.com/svn/code/php/OAuth.php

class TwitterClient {
    const 
accessTokenURL 'https://api.twitter.com/oauth/access_token';
    const 
authorizeURL 'https://twitter.com/oauth/authorize';
    const 
requestTokenURL 'https://api.twitter.com/oauth/request_token';
    
    public 
$host 'https://api.twitter.com/1/';
    public 
$safeMode true// a basic safe mode which can prevent your bot to explode twitter api rate limits
    
public $safeModeExceptions = array('statuses/update','statuses/destroy','account/rate_limit_status','account/end_session','statuses/retweet','friendships/create','friendships/destroy','direct_messages/new','direct_messages/destroy','favorites/create','favorites/destroy','blocks/create','blocks/destroy','report_spam','account/update_profile','account/update_profile_background_image','account/update_profile_image','account/update_profile_colors','account/update_delivery_device','users/profile_image','saved_searches/create','saved_searches/destroy','notifications/follow','notifications/leave');
    
    public 
$lastUrl ''// last accessed url
    
public $lastResponseHeaders = array(); // last HTTP response headers
    
public $lastResponseCode 0// last HTTP response code
    
public $lastResponse ''// last response, the raw version
    
public $rateLimit = array(); // twitter rate limits
    
    /**
     * Constructor
     * consumer_key and consumer_secret are required (you can register your app here: http://twitter.com/apps/new)
     * oauth_token and oauth_token_secret are optional and can be sent later via $object->set_token() or $object->get_oauth_token() methods
     */
    
function __construct($consumer_key,$consumer_secret,$oauth_token=null,$oauth_token_secret=null) {
        
$this->context stream_context_create(stream_context_get_options(stream_context_get_default()));
        
$this->http_option('max_redirects',1);
        
$this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
        
$this->consumer = new OAuthConsumer($consumer_key,$consumer_secret);
        if (!empty(
$oauth_token) && !empty($oauth_token_secret)) {
            
$this->token = new OAuthConsumer($oauth_token,$oauth_token_secret);
        } else {
            
$this->token null;
        }
    }
    
    
/**
     * You can use this method to get or set some http context options (see http://php.net/manual/en/context.http.php)
     * For example, to set the user agent to "Mozilla/5" call $object->http_option('user_agent','Mozilla/5')
     * If you must set non http options (ie: socket), you can use directly the stream_context_get_options() function with $object->context as context
     */
    
public function http_option($key,$value=null) {
        if (
$value === null) { // return the current value for this key
            
$options stream_context_get_options($this->context);
            return @
$options['http'][$key] ? $options['http'][$key] : '';
        } else { 
// set the option, return false if failed
            
return stream_context_set_option($this->context,'http',$key,$value);
        }
    }
    
    
/**
     * Something like getXAuthToken(), the dirty way :)
     * Your app's type must be "client" (desktop)
     * If so, with the username or email and the password, this method will authorize your app, retrieve the PIN code and get the token !
     * Return an associative array with keys "oauth_token", "oauth_token_secret", "screen_name", "user_id" on success or false on failure
     */
    
public function get_oauth_token($username_or_email,$password) {
        
$this->token null;
        
$this->rateLimit = array();
        
$request $this->oAuthRequest(self::requestTokenURL,'GET',array());
        
$token OAuthUtil::parse_parameters($request);
        
$this->token = new OAuthConsumer($token['oauth_token'],$token['oauth_token_secret']);
        
$page $this->http(self::authorizeURL,'POST',http_build_query(array(
            
'oauth_token'=>$token['oauth_token'],
            
'session[username_or_email]'=>$username_or_email,
            
'session[password]'=>$password
            
))
        ); 
        
$pin preg_match('#<div id="oauth_pin">.*?([0-9]+).*?</div>#s',$page,$m) ? $m[1] : '';
        if (!
$pin) {
            
$this->token null;
        } else {
            
$request $this->oAuthRequest(self::accessTokenURL,'GET',array('oauth_verifier' => $pin));
            
$token OAuthUtil::parse_parameters($request);
            
$this->token count($token) ? new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']) : null;
        }
        return 
$this->token === null false $token;
    }
    
    
/**
     * Manual get or set for the user token
     * Accepts several formats in input:
     *    - No argument, it will return the current token
     *  - An OAuthConsumer object
     *  - An associative array with keys "oauth_token" and "oauth_token_secret"
     *  - Two values: oauth_token and oauth_token_secret
     */
    
public function token() {
        switch (
func_num_args()) {
        case 
0:
            return 
$this->token;
        case 
1:
            
$token func_get_arg(0);
            if (
is_array($token) && array_key_exists('oauth_token',$token) && array_key_exists('oauth_token_secret',$token)) {
                
$this->rateLimit = array();
                
$this->token = new OAuthConsumer($token['oauth_token'],$token['oauth_token_secret']);
            } elseif (
is_object($token) && (get_class($token) == 'OAuthConsumer')) {
                
$this->rateLimit = array();
                
$this->token $token;
            } else {
                return 
false;
            }
            return 
true;
        case 
2:
            
$this->rateLimit = array();
            list(
$token,$token_secret) = func_get_args();
            
$this->token = new OAuthConsumer($token,$token_secret);
            return 
true;
        default:
            return 
false;
        }
    }
    
    
/**
     * Format and sign a GET OAuth API request (example: $object->get('account/verify_credentials'))
     */
    
public function get($url,$parameters=array()) {
        return 
$this->twitter_api_call($url,'GET',$parameters);
    }
    
    
/**
     * Format and sign a POST OAuth API request (example: $object->post('statuses/update',array('status'=>'test')))
     */
    
public function post($url,$parameters=array()) {
        return 
$this->twitter_api_call($url,'POST',$parameters);
    }

    
/**
     * Format and sign a DELETE OAuth API request (example: $object->delete('friendships/destroy',array('screen_name'=>'stagueve')))
     */
    
public function delete($url,$parameters=array()) {
        return 
$this->twitter_api_call($url,'DELETE',$parameters);
    }
    
    
/**
     * In safe mode, used to allow some non rate limited requests...
     */
    
protected function is_exception($url) {
        
$url strpos($url,$this->host) === substr($url,strlen($this->host)) : $url;
        foreach (
$this->safeModeExceptions as $e) if (strpos($url,$e) === 0) return true;
        return 
false;
    }
    
    
/**
     * In safe mode and if rate limited, for non allowed requests return false and set $object->lastResponseCode to 400
     * Else will return an array resulting of json_decode of Twitter's response (can be NULL on errors)
     */
    
protected function twitter_api_call($url,$method,$parameters) {
        if (
$this->safeMode && array_key_exists('Remaining',$this->rateLimit) && array_key_exists('Reset',$this->rateLimit) && !$this->is_exception($url)) {
            if ((
$this->rateLimit['Remaining'] == 0) && (time() < $this->rateLimit['Reset'])) {
                
$this->lastResponseCode 400;
                
$this->lastUrl '';
                
$this->lastResponseHeaders = array();
                return 
false;
            }
        }
        
$response $this->oAuthRequest($url,$method,$parameters);
        return @
json_decode(trim(preg_replace('#((id)|(cursor))":(\d+)#','\1":"\4"',$response)));    
    }
    
    
/**
     * Format and sign the request with OAuth and pass it to the http() method
     */
    
protected function oAuthRequest($url,$method,$parameters) {
        if (!
preg_match('#^https?://#si',$url)) {
            
$url "{$this->host}{$url}.json";
        }
        
$request OAuthRequest::from_consumer_and_token($this->consumer,$this->token,$method,$url,$parameters);
        
$request->sign_request($this->sha1_method,$this->consumer,$this->token);
        switch (
$method) {
            case 
'GET':
                return 
$this->http($request->to_url(),'GET');
            case 
'DELETE':
                return 
$this->http($request->to_url(),'DELETE');
            default:
                return 
$this->http($request->get_normalized_http_url(),$method,$request->to_postdata());
        }
    }
    
    
/**
     * Make an HTTP request
     * Infinite loops of redirections are not handled...
     * Last URL opened will be notified in $object->lastUrl
     * Last HTTP Response Code will be notified in $object->lastResponseCode (can be 0 if the request has failed)
     * Last HTTP Response Headers will be notified in $object->lastResponseHeaders (see http://php.net/manual/en/reserved.variables.httpresponseheader.php)
     * You can access the last raw response via $object->lastResponse
     * For Twitter's rate limited requests, the method will also update $object->rateLimit with the correct values
     */ 
    
protected function http($url,$method,$postfields='') {
        
$this->lastResponseCode 0;
        
$this->http_option('method',$method);
        
$this->http_option('content',$postfields);
        
$response = @file_get_contents($url,false,$this->context);
        
$this->lastUrl $url;
        
$this->lastResponseHeaders $http_response_header;
        
$this->lastResponse $response;
        if (
is_array($this->lastResponseHeaders)) {
            foreach (
$this->lastResponseHeaders as $i=>$header) {
                if (
$i == 0$this->lastResponseCode preg_match('#^HTTP/[0-9.]+ ([0-9]+)#s',$header,$m) ? intval($m[1]) : 0;
                elseif (
preg_match('#^Location: (.*)$#s',$header,$m)) return $this->http(trim($m[1]),'GET');
                else if ((
strpos($url,$this->host) === 0) && preg_match('#^X-RateLimit-(.*?): (.*)$#s',$header,$m)) $this->rateLimit[$m[1]] = trim($m[2]);
            }
        } else 
$this->lastResponseHeaders = array();
        return 
$response;
    }
}

?>