From 38203b745ae1903ba0804ed5532b78d255bea635 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sun, 5 Aug 2018 11:14:39 -0400 Subject: Added timeline polling Because the streaming API will sunset on August 16th, it is essential to implement timeline polling so that the library can still be used to access tweets. This commit breaks the current API even for clients still using the streaming API because the OAuth connection data was pulled from twitter::client into twitter::auth. Other than that, almost everything works the same, and if a client wants to update their libtwitter++ within the next week and a half, they are free to. --- src/client.cpp | 460 +++++++++------------------------------------------------ 1 file changed, 69 insertions(+), 391 deletions(-) (limited to 'src/client.cpp') diff --git a/src/client.cpp b/src/client.cpp index e16e30b..820db80 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -2,385 +2,65 @@ #include #include #include -#include -#include -#include #include #include - -// These are here for debugging curl stuff - -static -void dump(const char *text, - FILE *stream, unsigned char *ptr, size_t size) -{ - size_t i; - size_t c; - unsigned int width=80; - - fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n", - text, (long)size, (long)size); - - for(i=0; i= 0x20 && ptr[i+c] < 0x80) ? ptr[i+c] : '.'; - fputc(x, stream); - } - - fputc('\n', stream); /* newline */ - } -} - -static -int my_trace(CURL *handle, curl_infotype type, - char *data, size_t size, - void *userp) -{ - const char *text; - (void)handle; /* prevent compiler warning */ - - switch (type) { - case CURLINFO_TEXT: - fprintf(stderr, "== Info: %s", data); - default: /* in case a new one is introduced to shock us */ - return 0; - - case CURLINFO_HEADER_OUT: - text = "=> Send header"; - break; - case CURLINFO_DATA_OUT: - text = "=> Send data"; - break; - case CURLINFO_SSL_DATA_OUT: - text = "=> Send SSL data"; - break; - case CURLINFO_HEADER_IN: - text = "<= Recv header"; - break; - case CURLINFO_DATA_IN: - text = "<= Recv data"; - break; - case CURLINFO_SSL_DATA_IN: - text = "<= Recv SSL data"; - break; - } - - dump(text, stderr, (unsigned char *)data, size); - return 0; -} +#include "request.h" namespace twitter { - - class request - { - public: - - explicit request(std::string url) try - : _ios(_output), _conn(_ios) - { - _conn.add(url.c_str()); - } catch (const curl::curl_easy_exception& error) - { - error.print_traceback(); - - assert(false); - } - - std::string perform() - { - try - { - _conn.perform(); - } catch (const curl::curl_easy_exception& error) - { - std::throw_with_nested(connection_error()); - } - - int response_code = _conn.get_info().get(); - std::string result = _output.str(); - - if (response_code / 100 != 2) - { - nlohmann::json response_json; - - try - { - response_json = nlohmann::json::parse(result); - } catch (const std::invalid_argument& e) - { - std::throw_with_nested(invalid_response(result)); - } - - for (nlohmann::json& error : response_json["errors"]) - { - int error_code; - std::string error_message; - - try - { - error_code = error["code"].get(); - error_message = error["message"].get(); - } catch (const std::domain_error& e) - { - std::throw_with_nested(invalid_response(result)); - } - - switch (error_code) - { - case 32: - case 135: - case 215: - throw bad_auth(error_message); - - case 44: - throw invalid_media(error_message); - - case 64: - throw account_suspended(error_message); - - case 88: - throw rate_limit_exceeded(error_message); - - case 89: - throw bad_token(error_message); - - case 130: - throw server_overloaded(error_message); - - case 131: - throw server_error(error_message); - - case 185: - throw update_limit_exceeded(error_message); - - case 186: - throw bad_length(error_message); - - case 187: - throw duplicate_status(error_message); - - case 226: - throw suspected_spam(error_message); - - case 261: - throw write_restricted(error_message); - } - } - if (response_code == 429) - { - throw rate_limit_exceeded("HTTP 429 Too Many Requests"); - } else if (response_code == 500) - { - throw server_error("HTTP 500 Internal Server Error"); - } else if (response_code == 502) - { - throw server_unavailable("HTTP 502 Bad Gateway"); - } else if (response_code == 503) - { - throw server_overloaded("HTTP 503 Service Unavailable"); - } else if (response_code == 504) - { - throw server_timeout("HTTP 504 Gateway Timeout"); - } - - throw unknown_error(response_code, result); - } - - return result; - } - - private: - - std::ostringstream _output; - curl::curl_ios _ios; - - protected: - - curl::curl_easy _conn; - }; - - class get : public request - { - public: - - get(const OAuth::Client& oauth_client, std::string url) try - : request(url) - { - std::string oauth_header = oauth_client.getFormattedHttpHeader(OAuth::Http::Get, url, ""); - if (!oauth_header.empty()) - { - _headers.add(std::move(oauth_header)); - } - - _conn.add(_headers.get()); - } catch (const OAuth::ParseError& error) - { - std::cout << "Error generating OAuth header:" << std::endl; - std::cout << error.what() << std::endl; - std::cout << "This is likely due to a malformed URL." << std::endl; - - assert(false); - } catch (const curl::curl_easy_exception& error) - { - error.print_traceback(); - - assert(false); - } - - private: - - curl::curl_header _headers; - }; - - class post : public request - { - public: - - post(const OAuth::Client& oauth_client, std::string url, std::string datastr) try - : request(url) - { - std::string oauth_header = oauth_client.getFormattedHttpHeader(OAuth::Http::Post, url, datastr); - if (!oauth_header.empty()) - { - _headers.add(std::move(oauth_header)); - } - - _conn.add(_headers.get()); - _conn.add(datastr.c_str()); - } catch (const OAuth::ParseError& error) - { - std::cout << "Error generating OAuth header:" << std::endl; - std::cout << error.what() << std::endl; - std::cout << "This is likely due to a malformed URL." << std::endl; - - assert(false); - } catch (const curl::curl_easy_exception& error) - { - error.print_traceback(); - - assert(false); - } - - private: - - curl::curl_header _headers; - }; - - class multipost : public request - { - public: - - multipost(const OAuth::Client& oauth_client, std::string url, const curl_httppost* fields) try - : request(url) - { - std::string oauth_header = oauth_client.getFormattedHttpHeader(OAuth::Http::Post, url, ""); - if (!oauth_header.empty()) - { - _headers.add(std::move(oauth_header)); - } - - _conn.add(_headers.get()); - _conn.add(fields); - } catch (const OAuth::ParseError& error) - { - std::cout << "Error generating OAuth header:" << std::endl; - std::cout << error.what() << std::endl; - std::cout << "This is likely due to a malformed URL." << std::endl; - - assert(false); - } catch (const curl::curl_easy_exception& error) - { - error.print_traceback(); - - assert(false); - } - - private: - - curl::curl_header _headers; - }; - - client::client(const auth& _arg) - { - _oauth_consumer = - make_unique( - _arg.getConsumerKey(), - _arg.getConsumerSecret()); - - _oauth_token = - make_unique( - _arg.getAccessKey(), - _arg.getAccessSecret()); - - _oauth_client = - make_unique( - _oauth_consumer.get(), - _oauth_token.get()); - - _current_user = - make_unique( - get(*_oauth_client, + client::client( + const auth& _arg) : + auth_(_arg), + currentUser_( + get(auth_, "https://api.twitter.com/1.1/account/verify_credentials.json") - .perform()); + .perform()) + { } - - client::~client() = default; - + tweet client::updateStatus(std::string msg, std::list media_ids) const { std::stringstream datastrstream; datastrstream << "status=" << OAuth::PercentEncode(msg); - + if (!media_ids.empty()) { datastrstream << "&media_ids="; datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ","); } - + return tweet( - post(*_oauth_client, + post(auth_, "https://api.twitter.com/1.1/statuses/update.json", datastrstream.str()) .perform()); } - + tweet client::replyToTweet(std::string msg, tweet_id in_response_to, std::list media_ids) const { std::stringstream datastrstream; datastrstream << "status=" << OAuth::PercentEncode(msg); datastrstream << "&in_reply_to_status_id="; datastrstream << in_response_to; - + if (!media_ids.empty()) { datastrstream << "&media_ids="; datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ","); } - + return tweet( - post(*_oauth_client, + post(auth_, "https://api.twitter.com/1.1/statuses/update.json", datastrstream.str()) .perform()); } - + long client::uploadMedia(std::string media_type, const char* data, long data_length) const try { curl::curl_form form; std::string str_data_length = std::to_string(data_length); - + curl::curl_pair command_name(CURLFORM_COPYNAME, "command"); curl::curl_pair command_cont(CURLFORM_COPYCONTENTS, "INIT"); curl::curl_pair bytes_name(CURLFORM_COPYNAME, "total_bytes"); @@ -397,15 +77,15 @@ namespace twitter { curl::curl_pair category_cont(CURLFORM_COPYCONTENTS, "tweet_gif"); form.add(category_name, category_cont); } - + std::string init_response = - multipost(*_oauth_client, + multipost(auth_, "https://upload.twitter.com/1.1/media/upload.json", form.get()) .perform(); long media_id; - + try { nlohmann::json response_json = nlohmann::json::parse(init_response); @@ -429,29 +109,29 @@ namespace twitter { { assert(false); } - - multipost(*_oauth_client, "https://upload.twitter.com/1.1/media/upload.json", append_form_post).perform(); - + + multipost(auth_, "https://upload.twitter.com/1.1/media/upload.json", append_form_post).perform(); + curl_formfree(append_form_post); - + curl::curl_form finalize_form; std::string str_media_id = std::to_string(media_id); - + curl::curl_pair command3_name(CURLFORM_COPYNAME, "command"); curl::curl_pair command3_cont(CURLFORM_COPYCONTENTS, "FINALIZE"); curl::curl_pair media_id_name(CURLFORM_COPYNAME, "media_id"); curl::curl_pair media_id_cont(CURLFORM_COPYCONTENTS, str_media_id); finalize_form.add(command3_name, command3_cont); finalize_form.add(media_id_name, media_id_cont); - + std::string finalize_response = - multipost(*_oauth_client, + multipost(auth_, "https://upload.twitter.com/1.1/media/upload.json", finalize_form.get()) .perform(); nlohmann::json finalize_json; - + try { finalize_json = nlohmann::json::parse(finalize_response); @@ -459,26 +139,26 @@ namespace twitter { { std::throw_with_nested(invalid_response(finalize_response)); } - + if (finalize_json.find("processing_info") != finalize_json.end()) { std::stringstream datastr; datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id; - + for (;;) { - std::string status_response = get(*_oauth_client, datastr.str()).perform(); - + std::string status_response = get(auth_, datastr.str()).perform(); + try { nlohmann::json status_json = nlohmann::json::parse(status_response); std::string state = status_json["processing_info"]["state"].get(); - + if (state == "succeeded") { break; } - + int ttw = status_json["processing_info"]["check_after_secs"].get(); std::this_thread::sleep_for(std::chrono::seconds(ttw)); } catch (const std::invalid_argument& error) @@ -490,20 +170,18 @@ namespace twitter { } } } - + return media_id; } catch (const curl::curl_exception& error) { - error.print_traceback(); - - assert(false); + std::throw_with_nested(connection_error()); } - + std::set client::getFriends(user_id id) const { long long cursor = -1; std::set result; - + while (cursor != 0) { std::stringstream urlstream; @@ -511,14 +189,14 @@ namespace twitter { urlstream << id; urlstream << "&cursor="; urlstream << cursor; - + std::string url = urlstream.str(); - std::string response_data = get(*_oauth_client, url).perform(); - + std::string response_data = get(auth_, url).perform(); + try { nlohmann::json rjs = nlohmann::json::parse(response_data); - + cursor = rjs["next_cursor"].get(); result.insert(std::begin(rjs["ids"]), std::end(rjs["ids"])); } catch (const std::invalid_argument& error) @@ -529,25 +207,25 @@ namespace twitter { std::throw_with_nested(invalid_response(response_data)); } } - + return result; } - + std::set client::getFriends(const user& id) const { return getFriends(id.getID()); } - + std::set client::getFriends() const { return getFriends(getUser().getID()); } - + std::set client::getFollowers(user_id id) const { long long cursor = -1; std::set result; - + while (cursor != 0) { std::stringstream urlstream; @@ -555,14 +233,14 @@ namespace twitter { urlstream << id; urlstream << "&cursor="; urlstream << cursor; - + std::string url = urlstream.str(); - std::string response_data = get(*_oauth_client, url).perform(); - + std::string response_data = get(auth_, url).perform(); + try { nlohmann::json rjs = nlohmann::json::parse(response_data); - + cursor = rjs["next_cursor"].get(); result.insert(std::begin(rjs["ids"]), std::end(rjs["ids"])); } catch (const std::invalid_argument& error) @@ -573,43 +251,43 @@ namespace twitter { std::throw_with_nested(invalid_response(response_data)); } } - + return result; } - + std::set client::getFollowers(const user& id) const { return getFollowers(id.getID()); } - + std::set client::getFollowers() const { return getFollowers(getUser().getID()); } - + void client::follow(user_id toFollow) const { std::stringstream datastrstream; datastrstream << "follow=true&user_id="; datastrstream << toFollow; - - post(*_oauth_client, "https://api.twitter.com/1.1/friendships/create.json", datastrstream.str()).perform(); + + post(auth_, "https://api.twitter.com/1.1/friendships/create.json", datastrstream.str()).perform(); } - + void client::follow(const user& toFollow) const { return follow(toFollow.getID()); } - + void client::unfollow(user_id toUnfollow) const { std::stringstream datastrstream; datastrstream << "user_id="; datastrstream << toUnfollow; - - post(*_oauth_client, "https://api.twitter.com/1.1/friendships/destroy.json", datastrstream.str()).perform(); + + post(auth_, "https://api.twitter.com/1.1/friendships/destroy.json", datastrstream.str()).perform(); } - + void client::unfollow(const user& toUnfollow) const { return unfollow(toUnfollow.getID()); @@ -617,23 +295,23 @@ namespace twitter { const user& client::getUser() const { - return *_current_user; + return currentUser_; } - + const configuration& client::getConfiguration() const { if (!_configuration || (difftime(time(NULL), _last_configuration_update) > 60*60*24)) { _configuration = - make_unique( - get(*_oauth_client, + std::make_unique( + get(auth_, "https://api.twitter.com/1.1/help/configuration.json") .perform()); _last_configuration_update = time(NULL); } - + return *_configuration; } - + }; -- cgit 1.4.1