/* Copyright (C) 2008 by John Hobbs john@velvetcache.org This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef _KT_DEBUG #include #endif #include #include "libtweet.h" namespace vc { /*! \class Tweet libTweet is a simple and straight forward C++ library to use the Twitter.com API. It is meant as a study project and is not really suitable for production use in it's current state. There are a lot of things I would like to do with this class to make it better. - Use an XML parser - Implement the rest of the API - Real error checking and exceptions If you have questions, comments or patches, contact me at john@velvetcache.org Thanks! */ Tweet::Tweet () { curl = NULL; setupCurl (); } Tweet::~Tweet () { if(curl) { logout(); curl_easy_cleanup(curl); } } /*! Validate your credentials with Twitter and "log in" to libTweet. \param _username The username to try. \param _password The password to try. \return True if the credentials are valid. */ TweetResponse Tweet::login (std::string _username, std::string _password) { #ifdef _KT_DEBUG std::cout << __PRETTY_FUNCTION__ << std::endl; #endif std::string _credentials (_username+":"+_password); curl_easy_setopt(curl, CURLOPT_POST, 0); curl_easy_setopt(curl, CURLOPT_USERPWD, _credentials.c_str()); TweetResponse retval (doCurl(TWITTER_API_VERIFY_CREDENTIALS)); if(!retval.error) { username = _username; password = _password; credentials = _credentials; } return retval; } /*! Log the user out of Twitter. \return True if user was logged out. False on error. */ TweetResponse Tweet::logout () { #ifdef _KT_DEBUG std::cout << __PRETTY_FUNCTION__ << std::endl; #endif // This method seems to stall cURL, so we will bypass it for now. //! \todo Figure out if this is acceptable behavior! // Zap the credentials! username = ""; password = ""; credentials = ""; // Brute force hack, completely destroy our curl and start over. curl_easy_cleanup(curl); curl = NULL; setupCurl(); // Just lie and say we did it for real... return TweetResponse (200,"","No Error"); } /*! Send a status update to Twitter. \param message The string message. Must be less than or equal to 140 characters. \return True on success, false on any failure. \sa login() */ TweetResponse Tweet::update (std::string message) { #ifdef _KT_DEBUG std::cout << __PRETTY_FUNCTION__ << std::endl; #endif if("" == username || "" == password) // Must be logged in! return TweetResponse (-1,"","No Credentials Provided"); if(message.size() > 140) return TweetResponse (-1,"","Tweet Is Too Big"); char * data = NULL; data = curl_easy_escape(curl, message.c_str(), strlen(message.c_str())); if(data == NULL) return TweetResponse (-1,"","CURL Failure"); message = ""; if("" != source) { message += "source="; message += source; message += "&"; } message += "status="; message += data; curl_free(data); curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, -1); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (void *) message.c_str()); curl_easy_setopt(curl, CURLOPT_USERPWD, credentials.c_str()); TweetResponse retval (doCurl(TWITTER_API_UPDATE)); // Just setting POSTFIELDS to NULL doesn't get us back to a 0 byte POST // we have to set the POSTFIELDSIZE to 0 as well. curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, 0); return retval; } std::list Tweet::getTimeline () { std::list retval; curl_easy_setopt(curl, CURLOPT_POST, 0); curl_easy_setopt(curl, CURLOPT_USERPWD, credentials.c_str()); TweetResponse ret (doCurl(TWITTER_API_FRIENDS_TIMELINE)); if(!ret.error) { XMLNode root = XMLNode::parseString (ret.responseString.c_str(),"statuses"); int n = root.nChildNode("status"); #ifdef _KT_DEBUG std::cout << "Found " << n << " status updates." << std::endl; for(int i = 0; i < n; ++i) { //! \todo WORK FROM HERE XMLNode node = root.getChildNode("status",i); retval.push_back(TwitterStatusUpdate(node)); std::cout << retval.back().toString() << std::endl; } #endif } return retval; } TweetResponse Tweet::doCurl (std::string url) { #ifdef _KT_DEBUG std::cout << __PRETTY_FUNCTION__ << std::endl; #endif curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); CURLcode cres = curl_easy_perform(curl); if(cres == CURLE_OK) { long responseCode(0); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode); std::string temp(curlBuffer); curlBuffer = ""; std::string errorString("No Error"); #ifdef _KT_DEBUG std::cout << "ResponseCode: " << responseCode << std::endl; std::cout << temp << std::endl; #endif switch (responseCode) { case 200: case 304: break; case 400: errorString = "Bad Request"; //! \todo Parse return for error string break; case 401: errorString = "Bad Credentials"; break; case 403: errorString = "Forbidden"; //! \todo Parse return for error string break; case 404: errorString = "Resource Not Found"; //! \todo Parse return for error string, could be missing user, etc. break; case 500: case 502: errorString = "Twitter Server Failure"; break; case 503: errorString = "Twitter Overloaded"; break; default: errorString = "Undefined Error"; break; } return TweetResponse (responseCode, temp, errorString); } else { std::string temp(curlBuffer); curlBuffer = ""; std::cout << errorBuffer << std::endl; std::string err ("cURL Error: "); err.append(errorBuffer); return TweetResponse (-1, temp, err); } } void Tweet::setupCurl() { #ifdef _KT_DEBUG std::cout << __PRETTY_FUNCTION__ << std::endl; #endif if(NULL != curl) return; curl = curl_easy_init(); if(curl) { curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &errorBuffer); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_HEADER, 0); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriter); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &curlBuffer); curl_easy_setopt(curl, CURLOPT_TIMEOUT, MAX_HTTP_TIMEOUT); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, MAX_TCP_TIMEOUT); curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); #ifdef _KT_SSL curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(curl, CURLOPT_CAINFO, RES_CA_CERT); #endif } } /*! Somewhat tangental function, turns a URL into a TinyUrl. \param urlString A url to modify. \return True if succesfull, the urlString passed will then be the new TinyUrl. On failure it returns false, and the urlString passed will not be changed. */ bool Tweet::tinyMyUrl (std::string & urlString) { #ifdef _KT_DEBUG std::cout << __PRETTY_FUNCTION__ << std::endl; #endif bool retval(false); std::string url (TINY_URL+urlString); CURL * tinyCurl = NULL; tinyCurl = curl_easy_init(); if(tinyCurl) { curl_easy_setopt(tinyCurl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(tinyCurl, CURLOPT_HEADER, 0); curl_easy_setopt(tinyCurl, CURLOPT_FOLLOWLOCATION, 0); curl_easy_setopt(tinyCurl, CURLOPT_WRITEFUNCTION, curlWriter); curl_easy_setopt(tinyCurl, CURLOPT_WRITEDATA, &curlBuffer); curl_easy_setopt(tinyCurl, CURLOPT_TIMEOUT, MAX_HTTP_TIMEOUT); curl_easy_setopt(tinyCurl, CURLOPT_CONNECTTIMEOUT, MAX_TCP_TIMEOUT); curl_easy_setopt(tinyCurl, CURLOPT_URL, url.c_str()); if(CURLE_OK == curl_easy_perform(tinyCurl)) { if("" != curlBuffer) { urlString = curlBuffer; curlBuffer = ""; retval = true; } } curl_easy_cleanup(tinyCurl); } return retval; } /*! Another tangental function to fetch an image onto disk with curl. Supposed to be used to get Twitter user images. \param url The URL of the image (or really any file) \param dest The disk location to save to. */ bool Tweet::getImage (std::string url, std::string dest) { //! \todo Implement return false; } /*! Sets the source parameter for this application. To send no source, i.e. unset source, just call this with no parameter. \param _source The new source field. */ void Tweet::setSource (std::string _source) { source = _source; } /*----------------------------------------------------------------------------*/ /*! \class TweetResponse Response class for a twitter action. */ TweetResponse::TweetResponse (int _httpCode, std::string _responseString, std::string _errorString) : error(200 != _httpCode), httpCode(_httpCode), responseString(_responseString), errorString(_errorString) {} #ifdef _KT_DEBUG /*! Turns a TweetResponse into a formatted string. Handy for debugging. */ std::string TweetResponse::toString () { std::ostringstream oss; oss << "(TweetResponse)\n" << "\tError : " << ((error) ? "Yes" : "No") << "\n" << "\tHTTP Code : " << httpCode << "\n" << "\tResponse String :\n" << responseString << "\n" << "\tError String : " << errorString; return oss.str(); } #endif /*----------------------------------------------------------------------------*/ /*! \class TwitterUser A twitter user. */ TwitterUser::TwitterUser (XMLNode & node) { load(node); } TwitterUser::TwitterUser () {} TwitterUser::TwitterUser (const TwitterUser & l) { id = l.id; name = l.name; screen_name = l.screen_name; location = l.location; description = l.description; profile_image_url = l.profile_image_url; url = l.url; _protected = l._protected; followers_count = l.followers_count; } void TwitterUser::load (XMLNode & node) { id = atoi(node.getChildNode("id").getText()); name = node.getChildNode("name").getText(); screen_name = node.getChildNode("screen_name").getText(); location = node.getChildNode("location").getText(); description = node.getChildNode("description").getText(); profile_image_url = node.getChildNode("profile_image_url").getText(); url = node.getChildNode("url").getText(); _protected = (std::string("true") == std::string(node.getChildNode("protected").getText())); followers_count = atoi(node.getChildNode("followers_count").getText()); } std::string TwitterUser::toString () { std::ostringstream oss; oss << "(TwitterUser)\n" << "\tid : " << id << "\n" << "\tname : " << name << "\n" << "\tscreen_name : " << screen_name << "\n" << "\tlocation : " << location << "\n" << "\tdescription :\n" << description << "\n" << "\tprofile_image_url : " << profile_image_url << "\n" << "\turl : " << url << "\n" << "\tprotected : " << ((_protected) ? "Yes" : "No") << "\n" << "\tfollowers_count : " << followers_count; return oss.str(); } /*----------------------------------------------------------------------------*/ /*! \class TwitterStatusUpdate A twitter status event. */ TwitterStatusUpdate::TwitterStatusUpdate (XMLNode & node) { created_at = node.getChildNode("created_at").getText(); id = atoi(node.getChildNode("id").getText()); text = node.getChildNode("text").getText(); source = node.getChildNode("source").getText(); truncated = (std::string("true") == std::string(node.getChildNode("truncated").getText())); in_reply_to_status_id = atoi(node.getChildNode("in_reply_to_status_id").getText()); in_reply_to_user_id = atoi(node.getChildNode("in_reply_to_user_id").getText()); favorited = (std::string("true") == std::string(node.getChildNode("favorited").getText())); XMLNode t = node.getChildNode("user"); user.load(t); } TwitterStatusUpdate::TwitterStatusUpdate (const TwitterStatusUpdate & l) { created_at = l.created_at; id = l.id; text = l.text; source = l.source; truncated = l.truncated; in_reply_to_status_id = l.in_reply_to_status_id; in_reply_to_user_id = l.in_reply_to_user_id; favorited = l.favorited; user = l.user; } std::string TwitterStatusUpdate::toString () { std::ostringstream oss; oss << "(TwitterStatusUpdate)\n" << "\tcreated_at : " << created_at << "\n" << "\tid : " << id << "\n" << "\ttext :\n" << text << "\n" << "\tsource : " << source << "\n" << "\ttruncated : " << ((truncated) ? "Yes" : "No") << "\n" << "\tin_reply_to_status_id : " << in_reply_to_status_id << "\n" << "\tin_reply_to_user_id : " << in_reply_to_user_id << "\n" << "\tfavorited : " << ((favorited) ? "Yes" : "No"); return oss.str(); } };