/* Copyright © 2012-15 Miranda NG team 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 . */ #include "stdafx.h" #include "twitter.h" #include "utility.h" // utility functions twitter::twitter() : base_url_("https://api.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_ + "1.1/account/verify_credentials.json", http::get).code == 200; else return true; } http::response twitter::request_token() { return slurp("https://api.twitter.com/oauth/request_token", http::get); } http::response twitter::request_access_tokens() { return slurp("https://api.twitter.com/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::get_friends() { std::vector friends; http::response resp = slurp(base_url_ + "1.1/statuses/friends.json", http::get); if (resp.code != 200) throw bad_response(); JSONNODE *root = json_parse(resp.data.c_str()); if (root == NULL) throw std::exception("unable to parse response"); JSONNODE *nodes = json_as_array(root); for (int i = 0;; i++) { JSONNODE *pNode = json_at(nodes, i); if (pNode == NULL) break; twitter_user user; user.username = json_as_pstring(json_get(pNode, "screen_name")); user.real_name = json_as_pstring(json_get(pNode, "name")); user.profile_image_url = json_as_pstring(json_get(pNode, "profile_image_url")); JSONNODE *pStatus = json_get(pNode, "status"); if (pStatus != NULL) { user.status.text = json_as_pstring(json_get(pStatus, "text")); user.status.id = str2int(json_as_pstring(json_get(pStatus, "id"))); std::string timestr = json_as_pstring(json_get(pStatus, "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_ + "1.1/users/show/" + http::url_encode(name) + ".json"; http::response resp = slurp(url, http::get); if (resp.code != 200) throw bad_response(); JSONNODE *root = json_parse(resp.data.c_str()); if (root == NULL) return false; if (json_get(root, "error") != NULL) return false; info->username = json_as_pstring(json_get(root, "screen_name")); info->real_name = json_as_pstring(json_get(root, "name")); info->profile_image_url = json_as_pstring(json_get(root, "profile_image_url")); return true; } bool twitter::get_info_by_email(const std::string &email, twitter_user *info) { if (!info) return false; std::string url = base_url_ + "1.1/users/show.json?email=" + http::url_encode(email); http::response resp = slurp(url, http::get); if (resp.code != 200) throw bad_response(); JSONNODE *root = json_parse(resp.data.c_str()); if (root == NULL) return false; if (json_get(root, "error") != NULL) return false; info->username = json_as_pstring(json_get(root, "screen_name")); info->real_name = json_as_pstring(json_get(root, "name")); info->profile_image_url = json_as_pstring(json_get(root, "profile_image_url")); return true; } twitter_user twitter::add_friend(const std::string &name) { std::string url = base_url_ + "1.1/friendships/create/" + http::url_encode(name) + ".json"; http::response resp = slurp(url, http::post); if (resp.code != 200) throw bad_response(); JSONNODE *root = json_parse(resp.data.c_str()); if (root == NULL) throw std::exception("unable to parse response"); twitter_user ret; ret.username = json_as_pstring(json_get(root, "screen_name")); ret.real_name = json_as_pstring(json_get(root, "name")); ret.profile_image_url = json_as_pstring(json_get(root, "profile_image_url")); JSONNODE *pStatus = json_get(root, "status"); if (pStatus != NULL) { ret.status.text = json_as_pstring(json_get(pStatus, "text")); ret.status.id = str2int(json_as_pstring(json_get(pStatus, "id"))); } return ret; } void twitter::remove_friend(const std::string &name) { std::string url = base_url_ + "1.1/friendships/destroy/" + http::url_encode(name) + ".json"; slurp(url, http::post); } void twitter::set_status(const std::string &text) { if (text.size()) { std::wstring wTweet = UTF8ToWide(text); OAuthParameters postParams; postParams[L"status"] = UrlEncode(wTweet); slurp(base_url_ + "1.1/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_ + "1.1/direct_messages/new.json", http::post, postParams); } std::vector twitter::get_statuses(int count, twitter_id id) { std::vector statuses; std::string url = base_url_ + "1.1/statuses/home_timeline.json?count=" + int2str(count); if (id != 0) url += "&since_id=" + int2str(id); http::response resp = slurp(url, http::get); if (resp.code != 200) throw bad_response(); JSONNODE *root = json_parse(resp.data.c_str()); if (root == NULL) throw std::exception("unable to parse response"); JSONNODE *pNodes = json_as_array(root); for (int i = 0;; i++) { JSONNODE *pNode = json_at(pNodes, i); if (pNode == NULL) break; JSONNODE *pUser = json_get(pNode, "user"); twitter_user u; u.username = json_as_pstring(json_get(pUser, "screen_name")); u.profile_image_url = json_as_pstring(json_get(pUser, "profile_image_url")); // the tweet will be truncated unless we take action. i hate you twitter API JSONNODE *pStatus = json_get(pNode, "retweeted_status"); if (pStatus != NULL) { // here we grab the "retweeted_status" um.. section? it's in here that all the info we need is // at this point the user will get no tweets and an error popup if the tweet happens to be exactly 140 chars, start with // "RT @", end in " ...", and notactually be a real retweet. it's possible but unlikely, wish i knew how to get // the retweet_count variable to work :( JSONNODE *pUser2 = json_get(pStatus, "user"); std::string retweeteesName = json_as_pstring(json_get(pUser2, "screen_name")); // the user that is being retweeted std::string retweetText = json_as_pstring(json_get(pUser2, "text")); // their tweet in all it's untruncated glory // fix "&" in the tweets :( for (size_t pos = 0; (pos = retweetText.find("&", pos)) != std::string::npos; pos++) retweetText.replace(pos, 5, "&"); 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, std::string rawText = json_as_pstring(json_get(pNode, "text")); // ok here i'm trying some way to fix all the "&" things that are showing up // i dunno why it's happening, so i'll just find and replace each occurance :/ for (size_t pos = 0; (pos = rawText.find("&", pos)) != std::string::npos; pos++) rawText.replace(pos, 5, "&"); u.status.text = rawText; } u.status.id = str2int(json_as_pstring(json_get(pNode, "id"))); std::string timestr = json_as_pstring(json_get(pNode, "created_at")); u.status.time = parse_time(timestr); statuses.push_back(u); } return statuses; } std::vector twitter::get_direct(twitter_id id) { std::vector messages; std::string url = base_url_ + "1.1/direct_messages.json"; if (id != 0) url += "?since_id=" + int2str(id); http::response resp = slurp(url, http::get); if (resp.code != 200) throw bad_response(); JSONNODE *root = json_parse(resp.data.c_str()); if (root == NULL) throw std::exception("unable to parse response"); JSONNODE *pNodes = json_as_array(root); for (int i = 0;; i++) { JSONNODE *pNode = json_at(pNodes, i); if (pNode == NULL) break; twitter_user u; u.username = json_as_pstring(json_get(pNode, "sender_screen_name")); u.status.text = json_as_pstring(json_get(pNode, "text")); u.status.id = str2int(json_as_pstring(json_get(pNode, "id"))); std::string timestr = json_as_pstring(json_get(pNode, "created_at")); u.status.time = parse_time(timestr); messages.push_back(u); } return messages; } string twitter::urlencode(const string &c) { string escaped; size_t 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 (int 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; }