summaryrefslogtreecommitdiff
path: root/protocols/Twitter/src/twitter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Twitter/src/twitter.cpp')
-rw-r--r--protocols/Twitter/src/twitter.cpp477
1 files changed, 477 insertions, 0 deletions
diff --git a/protocols/Twitter/src/twitter.cpp b/protocols/Twitter/src/twitter.cpp
new file mode 100644
index 0000000000..b9a6c7242b
--- /dev/null
+++ b/protocols/Twitter/src/twitter.cpp
@@ -0,0 +1,477 @@
+/*
+Copyright © 2009 Jim Porter
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "twitter.h"
+//#include "tc2.h"
+
+#include <windows.h>
+#include <cstring>
+#include <sstream>
+#include <ctime>
+
+#include "tinyjson.hpp"
+#include <boost/lexical_cast.hpp>
+
+//#include "boost/spirit/include/classic_core.hpp"
+//#include "boost/spirit/include/classic_loops.hpp"
+
+typedef json::grammar<char> js;
+
+// utility functions
+
+template <typename T>
+static T cast_and_decode(boost::any &a,bool allow_null)
+{
+ if(allow_null && a.type() == typeid(void))
+ return T();
+ return boost::any_cast<T>(a);
+}
+
+template <>
+static std::string cast_and_decode<std::string>(boost::any &a,bool allow_null)
+{
+ if(allow_null && a.type() == typeid(void))
+ return std::string();
+ std::string s = boost::any_cast<std::string>(a);
+
+ // Twitter *only* encodes < and >, so decode them
+ size_t off;
+ while( (off = s.find("&lt;")) != std::string::npos)
+ s.replace(off,4,"<");
+ while( (off = s.find("&gt;")) != std::string::npos)
+ s.replace(off,4,">");
+
+ return s;
+}
+
+template <typename T>
+static T retrieve(const js::object &o,const std::string &key,bool allow_null = false)
+{
+ using boost::any_cast;
+
+ js::object::const_iterator i = o.find(key);
+ if(i == o.end())
+ throw std::exception( ("unable to retrieve key '"+key+"'").c_str());
+ try
+ {
+ return cast_and_decode<T>(*i->second,allow_null);
+ }
+ catch(const boost::bad_any_cast &)
+ {
+ throw std::exception( ("unable to cast key '"+key+"' to target type").c_str());
+ }
+}
+
+
+
+twitter::twitter() : base_url_("https://twitter.com/")
+{}
+
+bool twitter::set_credentials(const std::string &username, const std::wstring &consumerKey, const std::wstring &consumerSecret,
+ const std::wstring &oauthAccessToken, const std::wstring &oauthAccessTokenSecret, const std::wstring &pin, bool test)
+{
+ username_ = username;
+ consumerKey_ = consumerKey;
+ consumerSecret_ = consumerSecret;
+ oauthAccessToken_ = oauthAccessToken;
+ oauthAccessTokenSecret_ = oauthAccessTokenSecret;
+ pin_ = pin;
+
+ if(test)
+ return slurp(base_url_+"account/verify_credentials.json",http::get).code == 200;
+ else
+ return true;
+}
+
+http::response twitter::request_token() {
+ return slurp(base_url_+"oauth/request_token",http::get);
+}
+
+http::response twitter::request_access_tokens() {
+ return slurp(base_url_+"oauth/access_token", http::get);
+}
+
+void twitter::set_base_url(const std::string &base_url)
+{
+ base_url_ = base_url;
+}
+
+const std::string & twitter::get_username() const
+{
+ return username_;
+}
+
+const std::string & twitter::get_base_url() const
+{
+ return base_url_;
+}
+
+std::vector<twitter_user> twitter::get_friends()
+{
+ std::vector<twitter_user> friends;
+ http::response resp = slurp(base_url_+"statuses/friends.json",http::get);
+
+ if(resp.code != 200)
+ throw bad_response();
+
+ const js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() != typeid(js::array))
+ throw std::exception("unable to parse response");
+
+ const js::array &list = boost::any_cast<js::array>(*var);
+ for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
+ {
+ if((*i)->type() == typeid(js::object))
+ {
+ const js::object &one = boost::any_cast<js::object>(**i);
+
+ twitter_user user;
+ user.username = retrieve<std::string>(one,"screen_name");
+ user.real_name = retrieve<std::string>(one,"name",true);
+ user.profile_image_url = retrieve<std::string>(one,"profile_image_url",true);
+
+ if(one.find("status") != one.end())
+ {
+ js::object &status = retrieve<js::object>(one,"status");
+ user.status.text = retrieve<std::string>(status,"text");
+
+ user.status.id = retrieve<long long>(status,"id");
+
+ std::string timestr = retrieve<std::string>(status,"created_at");
+ user.status.time = parse_time(timestr);
+ }
+
+ friends.push_back(user);
+ }
+ }
+
+ return friends;
+}
+
+bool twitter::get_info(const std::string &name,twitter_user *info)
+{
+ if(!info)
+ return false;
+
+ std::string url = base_url_+"users/show/"+http::url_encode(name)+".json";
+
+ http::response resp = slurp(url,http::get);
+ if(resp.code != 200)
+ throw bad_response();
+
+ const js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() == typeid(js::object))
+ {
+ const js::object &user_info = boost::any_cast<js::object>(*var);
+ if(user_info.find("error") != user_info.end())
+ return false;
+
+ info->username = retrieve<std::string>(user_info,"screen_name");
+ info->real_name = retrieve<std::string>(user_info,"name",true);
+ info->profile_image_url = retrieve<std::string>(user_info,"profile_image_url",true);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+bool twitter::get_info_by_email(const std::string &email,twitter_user *info)
+{
+ if(!info)
+ return false;
+
+ std::string url = base_url_+"users/show.json?email="+http::url_encode(email);
+
+ http::response resp = slurp(url,http::get);
+ if(resp.code != 200)
+ throw bad_response();
+
+ js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() == typeid(js::object))
+ {
+ const js::object &user_info = boost::any_cast<js::object>(*var);
+ if(user_info.find("error") != user_info.end())
+ return false;
+
+ info->username = retrieve<std::string>(user_info,"screen_name");
+ info->real_name = retrieve<std::string>(user_info,"name",true);
+ info->profile_image_url = retrieve<std::string>(user_info,"profile_image_url",true);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+twitter_user twitter::add_friend(const std::string &name)
+{
+ std::string url = base_url_+"friendships/create/"+http::url_encode(name)+".json";
+
+ twitter_user ret;
+ http::response resp = slurp(url,http::post);
+ if(resp.code != 200)
+ throw bad_response();
+
+ js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() != typeid(js::object))
+ throw std::exception("unable to parse response");
+
+ const js::object &user_info = boost::any_cast<js::object>(*var);
+ ret.username = retrieve<std::string>(user_info,"screen_name");
+ ret.real_name = retrieve<std::string>(user_info,"name",true);
+ ret.profile_image_url = retrieve<std::string>(user_info,"profile_image_url",true);
+
+ if(user_info.find("status") != user_info.end())
+ {
+ // TODO: fill in more fields
+ const js::object &status = retrieve<js::object>(user_info,"status");
+ ret.status.text = retrieve<std::string>(status,"text");
+ }
+
+ return ret;
+}
+
+void twitter::remove_friend(const std::string &name)
+{
+ std::string url = base_url_+"friendships/destroy/"+http::url_encode(name)+".json";
+
+ slurp(url,http::post);
+}
+
+void twitter::set_status(const std::string &text)
+{
+ if(text.size())
+ {
+/* slurp(base_url_+"statuses/update.json",http::post,
+ "status="+http::url_encode(text)+
+ "&source=mirandaim");*/
+
+ //MessageBox(NULL, UTF8ToWide(text).c_str(), NULL, MB_OK);
+ std::wstring wTweet = UTF8ToWide(text);
+ OAuthParameters postParams;
+ postParams[L"status"] = UrlEncode(wTweet);
+
+ slurp(base_url_+"statuses/update.json",http::post, postParams);
+ }
+}
+
+void twitter::send_direct(const std::string &name,const std::string &text)
+{
+ std::wstring temp = UTF8ToWide(text);
+ OAuthParameters postParams;
+ postParams[L"text"] = UrlEncode(temp);
+ postParams[L"screen_name"] = UTF8ToWide(name);
+ slurp(base_url_+"direct_messages/new.json", http::post, postParams);
+}
+
+std::vector<twitter_user> twitter::get_statuses(int count,twitter_id id)
+{
+ using boost::lexical_cast;
+ std::vector<twitter_user> statuses;
+
+ std::string url = base_url_+"statuses/home_timeline.json?count="+
+ lexical_cast<std::string>(count);
+ if(id != 0)
+ url += "&since_id="+boost::lexical_cast<std::string>(id);
+
+ http::response resp = slurp(url,http::get);
+ if(resp.code != 200)
+ throw bad_response();
+
+ js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() != typeid(js::array))
+ throw std::exception("unable to parse response");
+
+ const js::array &list = boost::any_cast<js::array>(*var);
+ for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
+ {
+ if((*i)->type() == typeid(js::object))
+ {
+ const js::object &one = boost::any_cast<js::object>(**i);
+ const js::object &user = retrieve<js::object>(one,"user");
+
+ twitter_user u;
+ u.username = retrieve<std::string>(user,"screen_name");
+ bool isTruncated = retrieve<bool>(one,"truncated");
+
+ if (isTruncated) { // the tweet will be truncated unless we take action. i hate you twitter API
+
+ // here we grab the "retweeted_status" um.. section? it's in here that all the info we need is
+ const js::object &Retweet = retrieve<js::object>(one,"retweeted_status");
+ const js::object &RTUser = retrieve<js::object>(Retweet,"user");
+
+ std::string retweeteesName = retrieve<std::string>(RTUser,"screen_name"); // the user that is being retweeted
+ std::string retweetText = retrieve<std::string>(Retweet,"text"); // their tweet in all it's untruncated glory
+ u.status.text = "RT @" + retweeteesName + " " + retweetText; // mash it together in some format people will understand
+ }
+ else { // if it's not truncated, then the twitter API returns the native RT correctly anyway,
+ u.status.text = retrieve<std::string>(one,"text"); // so we can just pretend it doesn't happen
+ }
+
+ u.status.id = retrieve<long long>(one,"id");
+ std::string timestr = retrieve<std::string>(one,"created_at");
+ u.status.time = parse_time(timestr);
+
+ statuses.push_back(u);
+ }
+ }
+
+
+ return statuses;
+}
+
+std::vector<twitter_user> twitter::get_direct(twitter_id id)
+{
+ std::vector<twitter_user> messages;
+
+ std::string url = base_url_+"direct_messages.json";
+ if(id != 0)
+ url += "?since_id="+boost::lexical_cast<std::string>(id);
+
+ http::response resp = slurp(url,http::get);
+ if(resp.code != 200)
+ throw bad_response();
+
+ js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() != typeid(js::array))
+ throw std::exception("unable to parse response");
+
+ const js::array &list = boost::any_cast<js::array>(*var);
+ for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
+ {
+ if((*i)->type() == typeid(js::object))
+ {
+ const js::object &one = boost::any_cast<js::object>(**i);
+
+ twitter_user u;
+ u.username = retrieve<std::string>(one,"sender_screen_name");
+
+ u.status.text = retrieve<std::string>(one,"text");
+ u.status.id = retrieve<long long>(one,"id");
+ std::string timestr = retrieve<std::string>(one,"created_at");
+ u.status.time = parse_time(timestr);
+
+ messages.push_back(u);
+ }
+ }
+
+
+ return messages;
+}
+
+string twitter::urlencode(const string &c)
+{
+
+ string escaped;
+ int max = c.length();
+ for(int i=0; i<max; i++)
+ {
+ if ( (48 <= c[i] && c[i] <= 57) ||//0-9
+ (65 <= c[i] && c[i] <= 90) ||//ABC...XYZ
+ (97 <= c[i] && c[i] <= 122) || //abc...xyz
+ (c[i]=='~' || c[i]=='-' || c[i]=='_' || c[i]=='.')
+ )
+ {
+ escaped.append( &c[i], 1);
+ }
+ else
+ {
+ escaped.append("%");
+ escaped.append( char2hex(c[i]));//converts char 255 to string "FF"
+ }
+ }
+ return escaped;
+}
+
+
+wstring twitter::UrlEncode( const wstring& url )
+{
+ // multiple encodings r sux
+ return UTF8ToWide(urlencode(WideToUTF8(url)));
+}
+
+// char2hex and urlencode from http://www.zedwood.com/article/111/cpp-urlencode-function
+// modified according to http://oauth.net/core/1.0a/#encoding_parameters
+//
+//5.1. Parameter Encoding
+//
+//All parameter names and values are escaped using the [RFC3986]
+//percent-encoding (%xx) mechanism. Characters not in the unreserved character set
+//MUST be encoded. Characters in the unreserved character set MUST NOT be encoded.
+//Hexadecimal characters in encodings MUST be upper case.
+//Text names and values MUST be encoded as UTF-8
+// octets before percent-encoding them per [RFC3629].
+//
+// unreserved = ALPHA, DIGIT, '-', '.', '_', '~'
+
+string twitter::char2hex( char dec )
+{
+ char dig1 = (dec&0xF0)>>4;
+ char dig2 = (dec&0x0F);
+ if ( 0<= dig1 && dig1<= 9) dig1+=48; //0,48 in ascii
+ if (10<= dig1 && dig1<=15) dig1+=65-10; //A,65 in ascii
+ if ( 0<= dig2 && dig2<= 9) dig2+=48;
+ if (10<= dig2 && dig2<=15) dig2+=65-10;
+
+ string r;
+ r.append( &dig1, 1);
+ r.append( &dig2, 1);
+ return r;
+}
+
+// Some Unices get this, now we do too!
+time_t timegm(struct tm *t)
+{
+ _tzset();
+ t->tm_sec -= _timezone;
+ t->tm_isdst = 0;
+
+ return mktime(t);
+}
+
+static char *month_names[] = { "Jan","Feb","Mar","Apr","May","Jun",
+ "Jul","Aug","Sep","Oct","Nov","Dec" };
+
+int parse_month(const char *m)
+{
+ for(size_t i=0; i<12; i++)
+ {
+ if(strcmp(month_names[i],m) == 0)
+ return i;
+ }
+ return -1;
+}
+
+time_t parse_time(const std::string &s)
+{
+ struct tm t;
+ char day[4],month[4];
+ char plus;
+ int zone;
+ if(sscanf(s.c_str(),"%3s %3s %d %d:%d:%d %c%d %d",
+ day,month,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec,
+ &plus,&zone,&t.tm_year) == 9)
+ {
+ t.tm_year -= 1900;
+ t.tm_mon = parse_month(month);
+ if(t.tm_mon == -1)
+ return 0;
+ return timegm(&t);
+ }
+ return 0;
+} \ No newline at end of file