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. --- CMakeLists.txt | 16 +- src/auth.cpp | 45 ----- src/auth.h | 44 +++-- src/client.cpp | 460 ++++++++------------------------------------------- src/client.h | 66 ++++---- src/notification.cpp | 460 +++++++++++++++++++++++++-------------------------- src/notification.h | 58 +++---- src/request.cpp | 264 +++++++++++++++++++++++++++++ src/request.h | 73 ++++++++ src/stream.cpp | 98 +++++------ src/stream.h | 34 ++-- src/timeline.cpp | 82 +++++++++ src/timeline.h | 31 ++++ src/tweet.cpp | 8 +- src/twitter.h | 1 + src/util.h | 16 +- 16 files changed, 934 insertions(+), 822 deletions(-) delete mode 100644 src/auth.cpp create mode 100644 src/request.cpp create mode 100644 src/request.h create mode 100644 src/timeline.cpp create mode 100644 src/timeline.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c38613..08851cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,19 @@ include_directories(vendor/liboauthcpp/include) add_subdirectory(vendor/curlcpp) include_directories(${CURLCPP_SOURCE_DIR}/include) -add_library(twitter++ src/client.cpp src/stream.cpp src/auth.cpp src/tweet.cpp src/codes.cpp src/notification.cpp src/direct_message.cpp src/list.cpp src/user.cpp src/configuration.cpp) -set_property(TARGET twitter++ PROPERTY CXX_STANDARD 11) +add_library(twitter++ + src/client.cpp + src/request.cpp + src/timeline.cpp + src/stream.cpp + src/tweet.cpp + src/codes.cpp + src/notification.cpp + src/direct_message.cpp + src/list.cpp + src/user.cpp + src/configuration.cpp) + +set_property(TARGET twitter++ PROPERTY CXX_STANDARD 14) set_property(TARGET twitter++ PROPERTY CXX_STANDARD_REQUIRED ON) target_link_libraries(twitter++ oauthcpp curlcpp curl pthread) diff --git a/src/auth.cpp b/src/auth.cpp deleted file mode 100644 index f0f17e0..0000000 --- a/src/auth.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "auth.h" - -namespace twitter { - - void auth::setConsumerKey(std::string _arg) - { - _consumer_key = _arg; - } - - void auth::setConsumerSecret(std::string _arg) - { - _consumer_secret = _arg; - } - - void auth::setAccessKey(std::string _arg) - { - _access_key = _arg; - } - - void auth::setAccessSecret(std::string _arg) - { - _access_secret = _arg; - } - - std::string auth::getConsumerKey() const - { - return _consumer_key; - } - - std::string auth::getConsumerSecret() const - { - return _consumer_secret; - } - - std::string auth::getAccessKey() const - { - return _access_key; - } - - std::string auth::getAccessSecret() const - { - return _access_secret; - } - -}; diff --git a/src/auth.h b/src/auth.h index ef07e81..30c178c 100644 --- a/src/auth.h +++ b/src/auth.h @@ -2,28 +2,36 @@ #define AUTH_H_48EF85FD #include +#include "../vendor/liboauthcpp/include/liboauthcpp/liboauthcpp.h" namespace twitter { - + class auth { - public: - void setConsumerKey(std::string _arg); - void setConsumerSecret(std::string _arg); - void setAccessKey(std::string _arg); - void setAccessSecret(std::string _arg); - - std::string getConsumerKey() const; - std::string getConsumerSecret() const; - std::string getAccessKey() const; - std::string getAccessSecret() const; - - private: - std::string _consumer_key; - std::string _consumer_secret; - std::string _access_key; - std::string _access_secret; + public: + + auth( + std::string consumerKey, + std::string consumerSecret, + std::string accessKey, + std::string accessSecret) : + consumer_(std::move(consumerKey), std::move(consumerSecret)), + token_(std::move(accessKey), std::move(accessSecret)), + client_(&consumer_, &token_) + { + } + + const OAuth::Client& getClient() const + { + return client_; + } + + private: + + OAuth::Consumer consumer_; + OAuth::Token token_; + OAuth::Client client_; }; - + }; #endif /* end of include guard: AUTH_H_48EF85FD */ 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; } - + }; diff --git a/src/client.h b/src/client.h index 2230dbb..c0a63eb 100644 --- a/src/client.h +++ b/src/client.h @@ -10,58 +10,66 @@ #include "auth.h" #include "configuration.h" #include "util.h" - -namespace OAuth { - class Consumer; - class Token; - class Client; -}; +#include "timeline.h" namespace twitter { - + class client { public: - - client(const auth& _auth); - ~client(); - + + client(const auth& arg); + tweet updateStatus(std::string msg, std::list media_ids = {}) const; long uploadMedia(std::string media_type, const char* data, long data_length) const; - + tweet replyToTweet(std::string msg, tweet_id in_response_to, std::list media_ids = {}) const; - + std::set getFriends(user_id id) const; std::set getFriends(const user& u) const; std::set getFriends() const; - + std::set getFollowers(user_id id) const; std::set getFollowers(const user& u) const; std::set getFollowers() const; - + void follow(user_id toFollow) const; void follow(const user& toFollow) const; - + void unfollow(user_id toUnfollow) const; void unfollow(const user& toUnfollow) const; - + const user& getUser() const; - + const configuration& getConfiguration() const; - + + timeline& getHomeTimeline() + { + return homeTimeline_; + } + + timeline& getMentionsTimeline() + { + return mentionsTimeline_; + } + private: - - friend class stream; - - std::unique_ptr _oauth_consumer; - std::unique_ptr _oauth_token; - std::unique_ptr _oauth_client; - - std::unique_ptr _current_user; - + + const auth& auth_; + + user currentUser_; + mutable std::unique_ptr _configuration; mutable time_t _last_configuration_update; + + timeline homeTimeline_ { + auth_, + "https://api.twitter.com/1.1/statuses/home_timeline.json"}; + + timeline mentionsTimeline_ { + auth_, + "https://api.twitter.com/1.1/statuses/mentions_timeline.json"}; }; - + }; #endif /* end of include guard: TWITTER_H_ABFF6A12 */ diff --git a/src/notification.cpp b/src/notification.cpp index 0e46112..0d09fcf 100644 --- a/src/notification.cpp +++ b/src/notification.cpp @@ -6,18 +6,16 @@ #include "client.h" namespace twitter { - + notification::type notification::getType() const { return _type; } - - notification::notification(const client& tclient, std::string data) + + notification::notification(const user& current_user, std::string data) { - const user& current_user = tclient.getUser(); - nlohmann::json json; - + try { json = nlohmann::json::parse(data); @@ -25,7 +23,7 @@ namespace twitter { { std::throw_with_nested(invalid_response(data)); } - + try { if (!json["event"].is_null()) @@ -33,48 +31,48 @@ namespace twitter { std::string event = json["event"]; user source(json["source"].dump()); user target(json["target"].dump()); - + if (event == "user_update") { _type = type::update_user; - + new(&_user) user(source); } else if (event == "block") { _type = type::block; - + new(&_user) user(target); } else if (event == "unblock") { _type = type::unblock; - + new(&_user) user(target); } else if (event == "favorite") { new(&_user_and_tweet._tweet) tweet(json["target_object"].dump()); - + if (current_user == source) { _type = type::favorite; - + new(&_user_and_tweet._user) user(target); } else { _type = type::favorited; - + new(&_user_and_tweet._user) user(source); } } else if (event == "unfavorite") { new(&_user_and_tweet._tweet) tweet(json["target_object"].dump()); - + if (current_user == source) { _type = type::unfavorite; - + new(&_user_and_tweet._user) user(target); } else { _type = type::unfavorited; - + new(&_user_and_tweet._user) user(source); } } else if (event == "follow") @@ -82,93 +80,93 @@ namespace twitter { if (current_user == source) { _type = type::follow; - + new(&_user) user(target); } else { _type = type::followed; - + new(&_user) user(source); } } else if (event == "unfollow") { _type = type::unfollow; - + new(&_user) user(target); } else if (event == "list_created") { _type = type::list_created; - + new(&_list) list(json["target_object"].dump()); } else if (event == "list_destroyed") { _type = type::list_destroyed; - + new(&_list) list(json["target_object"].dump()); } else if (event == "list_updated") { _type = type::list_updated; - + new(&_list) list(json["target_object"].dump()); } else if (event == "list_member_added") { new(&_user_and_list._list) list(json["target_object"].dump()); - + if (current_user == source) { _type = type::list_add; - + new(&_user_and_list._user) user(target); } else { _type = type::list_added; - + new(&_user_and_list._user) user(source); } } else if (event == "list_member_removed") { new(&_user_and_list._list) list(json["target_object"].dump()); - + if (current_user == source) { _type = type::list_remove; - + new(&_user_and_list._user) user(target); } else { _type = type::list_removed; - + new(&_user_and_list._user) user(source); } } else if (event == "list_member_subscribe") { new(&_user_and_list._list) list(json["target_object"].dump()); - + if (current_user == source) { _type = type::list_subscribe; - + new(&_user_and_list._user) user(target); } else { _type = type::list_subscribed; - + new(&_user_and_list._user) user(source); } } else if (event == "list_member_unsubscribe") { new(&_user_and_list._list) list(json["target_object"].dump()); - + if (current_user == source) { _type = type::list_unsubscribe; - + new(&_user_and_list._user) user(target); } else { _type = type::list_unsubscribed; - + new(&_user_and_list._user) user(source); } } else if (event == "quoted_tweet") { _type = type::quoted; - + new(&_user_and_tweet._user) user(source); new(&_user_and_tweet._tweet) tweet(json["target_object"].dump()); } else { @@ -177,7 +175,7 @@ namespace twitter { } else if (!json["warning"].is_null()) { new(&_warning) std::string(json["warning"]["message"].get()); - + auto warning_code = json["warning"]["code"].get(); if (warning_code == "FALLING_BEHIND") { @@ -191,27 +189,27 @@ namespace twitter { } else if (!json["delete"].is_null()) { _type = type::deletion; - + _user_id_and_tweet_id._tweet_id = json["delete"]["status"]["id"].get(); _user_id_and_tweet_id._user_id = json["delete"]["status"]["user_id"].get(); } else if (!json["scrub_geo"].is_null()) { _type = type::scrub_location; - + _user_id_and_tweet_id._tweet_id = json["scrub_geo"]["up_to_status_id"].get(); _user_id_and_tweet_id._user_id = json["scrub_geo"]["user_id"].get(); } else if (!json["limit"].is_null()) { _type = type::limit; - + _limit = json["limit"]["track"].get(); } else if (!json["status_withheld"].is_null()) { _type = type::withhold_status; - + _withhold_status._user_id = json["status_withheld"]["user_id"].get(); _withhold_status._tweet_id = json["status_withheld"]["id"].get(); - + new(&_withhold_status._countries) std::vector(); for (auto s : json["status_withheld"]["withheld_in_countries"]) { @@ -220,9 +218,9 @@ namespace twitter { } else if (!json["user_withheld"].is_null()) { _type = type::withhold_user; - + _withhold_user._user_id = json["user_withheld"]["id"].get(); - + new(&_withhold_user._countries) std::vector(); for (auto s : json["user_withheld"]["withheld_in_countries"]) { @@ -231,7 +229,7 @@ namespace twitter { } else if (!json["disconnect"].is_null()) { _type = type::disconnect; - + switch (json["disconnect"]["code"].get()) { case 1: _disconnect = disconnect_code::shutdown; break; @@ -249,16 +247,16 @@ namespace twitter { } else if (!json["friends"].is_null()) { _type = type::friends; - + new(&_friends) std::set(std::begin(json["friends"]), std::end(json["friends"])); } else if (!json["direct_message"].is_null()) { _type = type::direct; - + new(&_direct_message) direct_message(json["direct_message"].dump()); } else { _type = type::tweet; - + new(&_tweet) tweet(data); } } catch (const std::domain_error& error) @@ -266,20 +264,20 @@ namespace twitter { std::throw_with_nested(invalid_response(data)); } } - + notification::notification(const notification& other) { _type = other._type; - + switch (_type) { case type::tweet: { new(&_tweet) tweet(other._tweet); - + break; } - + case type::update_user: case type::block: case type::unblock: @@ -288,10 +286,10 @@ namespace twitter { case type::unfollow: { new(&_user) user(other._user); - + break; } - + case type::favorite: case type::favorited: case type::unfavorite: @@ -300,19 +298,19 @@ namespace twitter { { new(&_user_and_tweet._user) user(other._user_and_tweet._user); new(&_user_and_tweet._tweet) tweet(other._user_and_tweet._tweet); - + break; } - + case type::list_created: case type::list_destroyed: case type::list_updated: { new(&_list) list(other._list); - + break; } - + case type::list_add: case type::list_added: case type::list_remove: @@ -324,73 +322,73 @@ namespace twitter { { new(&_user_and_list._user) user(other._user_and_list._user); new(&_user_and_list._list) list(other._user_and_list._list); - + break; } - + case type::stall: case type::follow_limit: case type::unknown_warning: { new(&_warning) std::string(other._warning); - + break; } - + case type::deletion: case type::scrub_location: { _user_id_and_tweet_id._user_id = other._user_id_and_tweet_id._user_id; _user_id_and_tweet_id._tweet_id = other._user_id_and_tweet_id._tweet_id; - + break; } - + case type::limit: { _limit = other._limit; - + break; } - + case type::withhold_status: { _withhold_status._user_id = other._withhold_status._user_id; _withhold_status._tweet_id = other._withhold_status._tweet_id; new(&_withhold_status._countries) std::vector(other._withhold_status._countries); - + break; } - + case type::withhold_user: { _withhold_user._user_id = other._withhold_user._user_id; new(&_withhold_user._countries) std::vector(other._withhold_user._countries); - + break; } - + case type::disconnect: { _disconnect = other._disconnect; - + break; } - + case type::friends: { new(&_friends) std::set(other._friends); - + break; } - + case type::direct: { new(&_direct_message) direct_message(other._direct_message); - + break; } - + case type::unknown: case type::invalid: { @@ -398,19 +396,19 @@ namespace twitter { } } } - + notification::notification(notification&& other) : notification() { swap(*this, other); } - + notification& notification::operator=(notification other) { swap(*this, other); - + return *this; } - + notification::~notification() { switch (_type) @@ -418,10 +416,10 @@ namespace twitter { case type::tweet: { _tweet.~tweet(); - + break; } - + case type::update_user: case type::block: case type::unblock: @@ -430,10 +428,10 @@ namespace twitter { case type::unfollow: { _user.~user(); - + break; } - + case type::favorite: case type::favorited: case type::unfavorite: @@ -442,19 +440,19 @@ namespace twitter { { _user_and_tweet._user.~user(); _user_and_tweet._tweet.~tweet(); - + break; } - + case type::list_created: case type::list_destroyed: case type::list_updated: { _list.~list(); - + break; } - + case type::list_add: case type::list_added: case type::list_remove: @@ -466,51 +464,51 @@ namespace twitter { { _user_and_list._user.~user(); _user_and_list._list.~list(); - + break; } - + case type::stall: case type::follow_limit: case type::unknown_warning: { using string_type = std::string; _warning.~string_type(); - + break; } - + case type::withhold_status: { using list_type = std::vector; _withhold_status._countries.~list_type(); - + break; } - + case type::withhold_user: { using list_type = std::vector; _withhold_user._countries.~list_type(); - + break; } - + case type::friends: { using list_type = std::set; _friends.~list_type(); - + break; } - + case type::direct: { _direct_message.~direct_message(); - + break; } - + case type::deletion: case type::scrub_location: case type::limit: @@ -522,11 +520,11 @@ namespace twitter { } } } - + void swap(notification& first, notification& second) { using type = notification::type; - + type tempType = first._type; tweet tempTweet; user tempUser; @@ -539,16 +537,16 @@ namespace twitter { disconnect_code tempDisconnectCode; std::set tempFriends; direct_message tempDirectMessage; - + switch (first._type) { case type::tweet: { tempTweet = std::move(first._tweet); - + break; } - + case type::update_user: case type::block: case type::unblock: @@ -557,10 +555,10 @@ namespace twitter { case type::unfollow: { tempUser = std::move(first._user); - + break; } - + case type::favorite: case type::favorited: case type::unfavorite: @@ -569,19 +567,19 @@ namespace twitter { { tempTweet = std::move(first._user_and_tweet._tweet); tempUser = std::move(first._user_and_tweet._user); - + break; } - + case type::list_created: case type::list_destroyed: case type::list_updated: { tempList = std::move(first._list); - + break; } - + case type::list_add: case type::list_added: case type::list_remove: @@ -593,97 +591,97 @@ namespace twitter { { tempList = std::move(first._user_and_list._list); tempUser = std::move(first._user_and_list._user); - + break; } - + case type::stall: case type::follow_limit: case type::unknown_warning: { tempWarning = std::move(first._warning); - + break; } - + case type::deletion: case type::scrub_location: { tempUserId = first._user_id_and_tweet_id._user_id; tempTweetId = first._user_id_and_tweet_id._tweet_id; - + break; } - + case type::limit: { tempLimit = first._limit; - + break; } - + case type::withhold_status: { tempUserId = first._withhold_status._user_id; tempTweetId = first._withhold_status._tweet_id; tempCountries = std::move(first._withhold_status._countries); - + break; } - + case type::withhold_user: { tempUserId = first._withhold_user._user_id; tempCountries = std::move(first._withhold_user._countries); - + break; } - + case type::disconnect: { tempDisconnectCode = first._disconnect; - + break; } - + case type::friends: { tempFriends = std::move(first._friends); - + break; } - + case type::direct: { tempDirectMessage = std::move(first._direct_message); - + break; } - + case type::invalid: case type::unknown: { break; } } - + first.~notification(); - + first._type = second._type; - + // Okay now you need to initialize the first with the data from the second // And then destruct the second and initialize it with the data stored in temp // This is hell - + switch (second._type) { case type::tweet: { new(&first._tweet) tweet(std::move(second._tweet)); - + break; } - + case type::update_user: case type::block: case type::unblock: @@ -692,10 +690,10 @@ namespace twitter { case type::unfollow: { new(&first._user) user(std::move(second._user)); - + break; } - + case type::favorite: case type::favorited: case type::unfavorite: @@ -704,19 +702,19 @@ namespace twitter { { new(&first._user_and_tweet._user) user(std::move(second._user_and_tweet._user)); new(&first._user_and_tweet._tweet) tweet(std::move(second._user_and_tweet._tweet)); - + break; } - + case type::list_created: case type::list_destroyed: case type::list_updated: { new(&first._list) list(std::move(second._list)); - + break; } - + case type::list_add: case type::list_added: case type::list_remove: @@ -728,94 +726,94 @@ namespace twitter { { new(&first._user_and_list._user) user(std::move(second._user_and_list._user)); new(&first._user_and_list._list) list(std::move(second._user_and_list._list)); - + break; } - + case type::stall: case type::follow_limit: case type::unknown_warning: { new(&first._warning) std::string(std::move(second._warning)); - + break; } - + case type::deletion: case type::scrub_location: { first._user_id_and_tweet_id._user_id = second._user_id_and_tweet_id._user_id; first._user_id_and_tweet_id._tweet_id = second._user_id_and_tweet_id._tweet_id; - + break; } - + case type::limit: { first._limit = second._limit; - + break; } - + case type::withhold_status: { first._withhold_status._user_id = second._withhold_status._user_id; first._withhold_status._tweet_id = second._withhold_status._tweet_id; new(&first._withhold_status._countries) std::vector(std::move(second._withhold_status._countries)); - + break; } - + case type::withhold_user: { first._withhold_user._user_id = second._withhold_user._user_id; new(&first._withhold_user._countries) std::vector(std::move(second._withhold_user._countries)); - + break; } - + case type::disconnect: { first._disconnect = second._disconnect; - + break; } - + case type::friends: { new(&first._friends) std::set(std::move(second._friends)); - + break; } - + case type::direct: { new(&first._direct_message) direct_message(std::move(second._direct_message)); - + break; } - + case type::invalid: case type::unknown: { break; } } - + // Now destruct the second and initialize it with data from the first second.~notification(); - + second._type = tempType; - + switch (tempType) { case type::tweet: { new(&second._tweet) tweet(std::move(tempTweet)); - + break; } - + case type::update_user: case type::block: case type::unblock: @@ -824,10 +822,10 @@ namespace twitter { case type::unfollow: { new(&second._user) user(std::move(tempUser)); - + break; } - + case type::favorite: case type::favorited: case type::unfavorite: @@ -836,19 +834,19 @@ namespace twitter { { new(&second._user_and_tweet._user) user(std::move(tempUser)); new(&second._user_and_tweet._tweet) tweet(std::move(tempTweet)); - + break; } - + case type::list_created: case type::list_destroyed: case type::list_updated: { new(&second._list) list(std::move(tempList)); - + break; } - + case type::list_add: case type::list_added: case type::list_remove: @@ -860,73 +858,73 @@ namespace twitter { { new(&second._user_and_list._user) user(std::move(tempUser)); new(&second._user_and_list._list) list(std::move(tempList)); - + break; } - + case type::stall: case type::follow_limit: case type::unknown_warning: { new(&second._warning) std::string(std::move(tempWarning)); - + break; } - + case type::deletion: case type::scrub_location: { second._user_id_and_tweet_id._user_id = tempUserId; second._user_id_and_tweet_id._tweet_id = tempTweetId; - + break; } - + case type::limit: { second._limit = tempLimit; - + break; } - + case type::withhold_status: { second._withhold_status._user_id = tempUserId; second._withhold_status._tweet_id = tempTweetId; new(&second._withhold_status._countries) std::vector(std::move(tempCountries)); - + break; } - + case type::withhold_user: { second._withhold_user._user_id = tempUserId; new(&second._withhold_user._countries) std::vector(std::move(tempCountries)); - + break; } - + case type::disconnect: { second._disconnect = tempDisconnectCode; - + break; } - + case type::friends: { new(&second._friends) std::set(std::move(tempFriends)); - + break; } - + case type::direct: { new(&second._direct_message) direct_message(std::move(tempDirectMessage)); - + break; } - + case type::invalid: case type::unknown: { @@ -934,7 +932,7 @@ namespace twitter { } } } - + const tweet& notification::getTweet() const { switch (_type) @@ -943,7 +941,7 @@ namespace twitter { { return _tweet; } - + case type::favorite: case type::favorited: case type::unfavorite: @@ -952,14 +950,14 @@ namespace twitter { { return _user_and_tweet._tweet; } - + default: { assert(false); } } } - + const user& notification::getUser() const { switch (_type) @@ -973,7 +971,7 @@ namespace twitter { { return _user; } - + case type::favorite: case type::favorited: case type::unfavorite: @@ -982,7 +980,7 @@ namespace twitter { { return _user_and_tweet._user; } - + case type::list_add: case type::list_added: case type::list_remove: @@ -994,25 +992,25 @@ namespace twitter { { return _user_and_list._user; } - + default: { assert(false); } } } - + const list& notification::getList() const { switch (_type) - { + { case type::list_created: case type::list_destroyed: case type::list_updated: { return _list; } - + case type::list_add: case type::list_added: case type::list_remove: @@ -1024,14 +1022,14 @@ namespace twitter { { return _user_and_list._list; } - + default: { assert(false); } } } - + tweet_id notification::getTweetID() const { switch (_type) @@ -1041,19 +1039,19 @@ namespace twitter { { return _user_id_and_tweet_id._tweet_id; } - + case type::withhold_status: { return _withhold_status._tweet_id; } - + default: { assert(false); } } } - + void notification::setTweetID(tweet_id _arg) { switch (_type) @@ -1063,34 +1061,34 @@ namespace twitter { { _user_id_and_tweet_id._tweet_id = _arg;; } - + case type::withhold_status: { _withhold_status._tweet_id = _arg; } - + default: { assert(false); } } } - + user_id notification::getUserID() const { switch (_type) - { + { case type::deletion: case type::scrub_location: { return _user_id_and_tweet_id._user_id; } - + case type::withhold_status: { return _withhold_status._user_id; } - + case type::withhold_user: { return _withhold_user._user_id; @@ -1102,22 +1100,22 @@ namespace twitter { } } } - + void notification::setUserID(user_id _arg) { switch (_type) - { + { case type::deletion: case type::scrub_location: { _user_id_and_tweet_id._user_id = _arg; } - + case type::withhold_status: { _withhold_status._user_id = _arg; } - + case type::withhold_user: { _withhold_user._user_id = _arg; @@ -1129,16 +1127,16 @@ namespace twitter { } } } - + const std::vector& notification::getCountries() const { switch (_type) - { + { case type::withhold_status: { return _withhold_status._countries; } - + case type::withhold_user: { return _withhold_user._countries; @@ -1150,49 +1148,49 @@ namespace twitter { } } } - + disconnect_code notification::getDisconnectCode() const { assert(_type == type::disconnect); - + return _disconnect; } - + void notification::setDisconnectCode(disconnect_code _arg) { assert(_type == type::disconnect); - + _disconnect = _arg; } - + const std::set& notification::getFriends() const { assert(_type == type::friends); - + return _friends; } - + const direct_message& notification::getDirectMessage() const { assert(_type == type::direct); - + return _direct_message; } - + int notification::getLimit() const { assert(_type == type::limit); - + return _limit; } - + void notification::setLimit(int _arg) { assert(_type == type::limit); - + _limit = _arg; } - + const std::string& notification::getWarning() const { switch (_type) @@ -1203,12 +1201,12 @@ namespace twitter { { return _warning; } - + default: { assert(false); } } } - + }; diff --git a/src/notification.h b/src/notification.h index a6dd6f4..ad649c3 100644 --- a/src/notification.h +++ b/src/notification.h @@ -10,9 +10,9 @@ #include "direct_message.h" namespace twitter { - + class client; - + enum class disconnect_code { shutdown, @@ -27,13 +27,13 @@ namespace twitter { load, unknown }; - + class notification { public: enum class type { // Tweet object tweet, - + // User object update_user, block, @@ -41,19 +41,19 @@ namespace twitter { follow, followed, unfollow, - + // User and tweet favorite, favorited, unfavorite, unfavorited, quoted, - + // List list_created, list_destroyed, list_updated, - + // User and list list_add, list_added, @@ -63,16 +63,16 @@ namespace twitter { list_subscribed, list_unsubscribe, list_unsubscribed, - + // Warning stall, follow_limit, unknown_warning, - + // User ID and tweet ID deletion, scrub_location, - + // Special limit, withhold_status, @@ -80,78 +80,78 @@ namespace twitter { disconnect, friends, direct, - + // Nothing unknown, invalid }; - + type getType() const; - + notification() {} - notification(const client& tclient, std::string data); - + notification(const user& currentUser, std::string data); + notification(const notification& other); notification(notification&& other); notification& operator=(notification other); ~notification(); - + friend void swap(notification& first, notification& second); - + const tweet& getTweet() const; tweet& getTweet() { return const_cast(static_cast(*this).getTweet()); } - + const user& getUser() const; user& getUser() { return const_cast(static_cast(*this).getUser()); } - + const list& getList() const; list& getList() { return const_cast(static_cast(*this).getList()); } - + tweet_id getTweetID() const; void setTweetID(tweet_id _arg); - + user_id getUserID() const; void setUserID(user_id _arg); - + const std::vector& getCountries() const; std::vector& getCountries() { return const_cast&>(static_cast(*this).getCountries()); } - + disconnect_code getDisconnectCode() const; void setDisconnectCode(disconnect_code _arg); - + const std::set& getFriends() const; std::set& getFriends() { return const_cast&>(static_cast(*this).getFriends()); } - + const direct_message& getDirectMessage() const; direct_message& getDirectMessage() { return const_cast(static_cast(*this).getDirectMessage()); } - + int getLimit() const; void setLimit(int _arg); - + const std::string& getWarning() const; std::string& getWarning() { return const_cast(static_cast(*this).getWarning()); } - + private: union { tweet _tweet; @@ -186,7 +186,7 @@ namespace twitter { }; type _type = type::invalid; }; - + }; #endif /* end of include guard: NOTIFICATION_H_69AEF4CC */ diff --git a/src/request.cpp b/src/request.cpp new file mode 100644 index 0000000..a79c7f0 --- /dev/null +++ b/src/request.cpp @@ -0,0 +1,264 @@ +#include "request.h" +#include +#include "codes.h" + +// 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; +} + +namespace twitter { + + request::request( + std::string url) try : + ios_(output_), + conn_(ios_) + { + conn_.add(url.c_str()); + } catch (const curl::curl_easy_exception& error) + { + std::throw_with_nested(connection_error()); + } + + std::string request::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; + } + + get::get( + const auth& tauth, + std::string url) try : + request(url) + { + std::string oauthHeader = + tauth.getClient().getFormattedHttpHeader(OAuth::Http::Get, url, ""); + + if (!oauthHeader.empty()) + { + headers_.add(std::move(oauthHeader)); + } + + conn_.add(headers_.get()); + } catch (const OAuth::ParseError& error) + { + std::throw_with_nested(connection_error()); + } catch (const curl::curl_easy_exception& error) + { + std::throw_with_nested(connection_error()); + } + + post::post( + const auth& tauth, + std::string url, + std::string datastr) try : + request(url) + { + std::string oauthHeader = + tauth.getClient().getFormattedHttpHeader(OAuth::Http::Post, url, datastr); + + if (!oauthHeader.empty()) + { + headers_.add(std::move(oauthHeader)); + } + + conn_.add(headers_.get()); + conn_.add(datastr.c_str()); + } catch (const OAuth::ParseError& error) + { + std::throw_with_nested(connection_error()); + } catch (const curl::curl_easy_exception& error) + { + std::throw_with_nested(connection_error()); + } + + multipost::multipost( + const auth& tauth, + std::string url, + const curl_httppost* fields) try : + request(url) + { + std::string oauthHeader = + tauth.getClient().getFormattedHttpHeader(OAuth::Http::Post, url, ""); + + if (!oauthHeader.empty()) + { + headers_.add(std::move(oauthHeader)); + } + + conn_.add(headers_.get()); + conn_.add(fields); + } catch (const OAuth::ParseError& error) + { + std::throw_with_nested(connection_error()); + } catch (const curl::curl_easy_exception& error) + { + std::throw_with_nested(connection_error()); + } + +} diff --git a/src/request.h b/src/request.h new file mode 100644 index 0000000..1672d71 --- /dev/null +++ b/src/request.h @@ -0,0 +1,73 @@ +#ifndef REQUEST_H_9D3C30E2 +#define REQUEST_H_9D3C30E2 + +#include +#include +#include +#include +#include "auth.h" + +namespace twitter { + + class request + { + public: + + explicit request(std::string url); + + std::string perform(); + + private: + + std::ostringstream output_; + curl::curl_ios ios_; + + protected: + + curl::curl_easy conn_; + }; + + class get : public request + { + public: + + get( + const auth& tauth, + std::string url); + + private: + + curl::curl_header headers_; + }; + + class post : public request + { + public: + + post( + const auth& tauth, + std::string url, + std::string datastr); + + private: + + curl::curl_header headers_; + }; + + class multipost : public request + { + public: + + multipost( + const auth& tauth, + std::string url, + const curl_httppost* fields); + + private: + + curl::curl_header headers_; + }; + +} + +#endif /* end of include guard: REQUEST_H_9D3C30E2 */ diff --git a/src/stream.cpp b/src/stream.cpp index cb55ee8..86d177c 100644 --- a/src/stream.cpp +++ b/src/stream.cpp @@ -4,23 +4,27 @@ #include #include "util.h" #include "notification.h" -#include "client.h" +#include "request.h" namespace twitter { stream::stream( - const client& tclient, + const auth& tauth, notify_callback callback, bool with_followings, bool receive_all_replies, std::list track, std::list locations) : - _client(tclient), + _auth(tauth), _notify(callback), + _currentUser(get( + _auth, + "https://api.twitter.com/1.1/account/verify_credentials.json") + .perform()), _thread(&stream::run, this, generateUrl(with_followings, receive_all_replies, track, locations)) { } - + stream::~stream() { if (_thread.joinable()) @@ -29,7 +33,7 @@ namespace twitter { _thread.join(); } } - + std::string stream::generateUrl( bool with_followings, bool receive_all_replies, @@ -37,68 +41,68 @@ namespace twitter { std::list locations) { std::list arguments; - + if (receive_all_replies) { arguments.push_back("replies=all"); } - + if (!with_followings) { arguments.push_back("with=user"); } - + if (!track.empty()) { std::ostringstream trackstr; trackstr << "track="; - + for (auto it = std::begin(track); it != std::end(track); it++) { if (it != std::begin(track)) { trackstr << ","; } - + trackstr << OAuth::HttpEncodeQueryValue(*it); } - + arguments.push_back(trackstr.str()); } - + if (!locations.empty()) { std::ostringstream localstr; localstr << "locations="; - + for (auto it = std::begin(locations); it != std::end(locations); it++) { if (it != std::begin(locations)) { localstr << ","; } - + localstr << (double)it->getSouthWestLongitude() << ","; localstr << (double)it->getSouthWestLatitude() << ","; localstr << (double)it->getNorthEastLongitude() << ","; localstr << (double)it->getNorthEastLatitude(); } - + arguments.push_back(localstr.str()); } - + std::ostringstream urlstr; urlstr << "https://userstream.twitter.com/1.1/user.json"; - + if (!arguments.empty()) { urlstr << "?"; urlstr << implode(std::begin(arguments), std::end(arguments), "&"); } - + return urlstr.str(); } - + void stream::run(std::string url) { _backoff_type = backoff::none; @@ -108,15 +112,15 @@ namespace twitter { curl::curl_ios ios(this, [] (void* contents, size_t size, size_t nmemb, void* userp) { return static_cast(userp)->write(static_cast(contents), size, nmemb); }); - + curl::curl_easy conn(ios); curl::curl_header headers; std::string oauth_header; - + try { - oauth_header = _client._oauth_client->getFormattedHttpHeader(OAuth::Http::Get, url, ""); - + oauth_header = _auth.getClient().getFormattedHttpHeader(OAuth::Http::Get, url, ""); + if (!oauth_header.empty()) { headers.add(oauth_header); @@ -126,10 +130,10 @@ namespace twitter { 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); } - + try { conn.add(nullptr); @@ -146,10 +150,10 @@ namespace twitter { } catch (const curl::curl_exception& error) { error.print_traceback(); - + assert(false); } - + bool failure = false; try { @@ -169,7 +173,7 @@ namespace twitter { } } } - + if (!failure) { long response_code = conn.get_info().get(); @@ -198,7 +202,7 @@ namespace twitter { } } } - + std::this_thread::sleep_for(_backoff_amount); switch (_backoff_type) @@ -209,35 +213,35 @@ namespace twitter { { _backoff_amount += std::chrono::milliseconds(250); } - + break; } - + case backoff::http: { if (_backoff_amount < std::chrono::seconds(320)) { _backoff_amount *= 2; } - + break; } - + case backoff::rate_limit: { _backoff_amount *= 2; - + break; } - + case backoff::none: - { + { break; } } } } - + size_t stream::write(char* ptr, size_t size, size_t nmemb) { for (size_t i = 0; i < size*nmemb; i++) @@ -245,38 +249,38 @@ namespace twitter { if (ptr[i] == '\r') { i++; // Skip the \n - + if (!_buffer.empty()) { - notification n(_client, _buffer); + notification n(_currentUser, _buffer); if (n.getType() == notification::type::friends) { _established = true; _backoff_type = backoff::none; _backoff_amount = std::chrono::milliseconds(0); } - + _notify(std::move(n)); - + _buffer = ""; } } else { _buffer.push_back(ptr[i]); } } - + time(&_last_write); - + return size*nmemb; } - + int stream::progress() { if (_stop) { return 1; } - + if (_established) { if (difftime(time(NULL), _last_write) >= 90) @@ -284,8 +288,8 @@ namespace twitter { return 1; } } - + return 0; } - + } diff --git a/src/stream.h b/src/stream.h index b682ce2..f6ce91e 100644 --- a/src/stream.h +++ b/src/stream.h @@ -7,32 +7,35 @@ #include #include #include "bounding_box.h" +#include "auth.h" +#include "user.h" namespace twitter { - - class client; + class notification; - - class stream { + + class + [[deprecated("The Twitter streaming API will sunset on August 16th, 2018")]] + stream { public: - + typedef std::function notify_callback; - + stream( - const client& tclient, + const auth& tauth, notify_callback callback, bool with_followings = true, bool receive_all_replies = false, std::list track = {}, std::list locations = {}); - + ~stream(); - + stream(const stream& other) = delete; stream(stream&& other) = delete; stream& operator=(const stream& other) = delete; stream& operator=(stream&& other) = delete; - + private: enum class backoff { none, @@ -40,18 +43,18 @@ namespace twitter { http, rate_limit }; - + static std::string generateUrl( bool with_followings, bool receive_all_replies, std::list track, std::list locations); - + void run(std::string url); int progress(); size_t write(char* ptr, size_t size, size_t nmemb); - - const client& _client; + + const auth& _auth; notify_callback _notify; bool _stop = false; std::string _buffer; @@ -59,9 +62,10 @@ namespace twitter { bool _established = false; backoff _backoff_type = backoff::none; std::chrono::milliseconds _backoff_amount; + user _currentUser; std::thread _thread; }; - + } #endif /* end of include guard: STREAM_H_E9146952 */ diff --git a/src/timeline.cpp b/src/timeline.cpp new file mode 100644 index 0000000..fd75a69 --- /dev/null +++ b/src/timeline.cpp @@ -0,0 +1,82 @@ +#include "timeline.h" +#include +#include +#include "codes.h" +#include "util.h" +#include "request.h" + +namespace twitter { + + timeline::timeline( + const auth& tauth, + std::string url) : + auth_(tauth), + url_(std::move(url)) + { + } + + std::list timeline::poll() + { + tweet_id maxId; + std::list result; + + for (int i = 0; i < 5; i++) + { + std::ostringstream urlstr; + urlstr << url_; + + std::list arguments; + + if (i > 0) + { + arguments.push_back("max_id=" + std::to_string(maxId)); + } + + if (hasSince_) + { + arguments.push_back("since_id=" + std::to_string(sinceId_)); + } + + if (!arguments.empty()) + { + urlstr << "?"; + urlstr << implode(std::begin(arguments), std::end(arguments), "&"); + } + + std::string theUrl = urlstr.str(); + std::string response = get(auth_, theUrl).perform(); + + try + { + nlohmann::json rjs = nlohmann::json::parse(response); + + if (rjs.empty()) + { + break; + } + + for (auto& single : rjs) + { + result.emplace_back(single.dump()); + } + } catch (const std::invalid_argument& error) + { + std::throw_with_nested(invalid_response(response)); + } catch (const std::domain_error& error) + { + std::throw_with_nested(invalid_response(response)); + } + + maxId = result.back().getID() - 1; + } + + if (!result.empty()) + { + sinceId_ = result.front().getID(); + hasSince_ = true; + } + + return result; + } + +}; diff --git a/src/timeline.h b/src/timeline.h new file mode 100644 index 0000000..60ce78d --- /dev/null +++ b/src/timeline.h @@ -0,0 +1,31 @@ +#ifndef TIMELINE_H_D359681C +#define TIMELINE_H_D359681C + +#include +#include +#include +#include "auth.h" +#include "tweet.h" + +namespace twitter { + + class timeline { + public: + + timeline( + const auth& tauth, + std::string url); + + std::list poll(); + + private: + + const auth& auth_; + std::string url_; + bool hasSince_ = false; + tweet_id sinceId_; + }; + +} + +#endif /* end of include guard: TIMELINE_H_D359681C */ diff --git a/src/tweet.cpp b/src/tweet.cpp index 864bcd8..1c869f9 100644 --- a/src/tweet.cpp +++ b/src/tweet.cpp @@ -12,13 +12,13 @@ namespace twitter { auto json = nlohmann::json::parse(data); _id = json["id"].get(); _text = json["text"].get(); - _author = make_unique(json["user"].dump()); + _author = std::make_unique(json["user"].dump()); if (!json["retweeted_status"].is_null()) { _is_retweet = true; - _retweeted_status = make_unique(json["retweeted_status"].dump()); + _retweeted_status = std::make_unique(json["retweeted_status"].dump()); } if (!json["entities"].is_null()) @@ -45,12 +45,12 @@ namespace twitter { _valid = other._valid; _id = other._id; _text = other._text; - _author = make_unique(*other._author); + _author = std::make_unique(*other._author); _is_retweet = other._is_retweet; if (_is_retweet) { - _retweeted_status = make_unique(*other._retweeted_status); + _retweeted_status = std::make_unique(*other._retweeted_status); } _mentions = other._mentions; diff --git a/src/twitter.h b/src/twitter.h index 1ba4394..a4f336e 100644 --- a/src/twitter.h +++ b/src/twitter.h @@ -11,6 +11,7 @@ namespace twitter { #include "util.h" #include "auth.h" #include "client.h" +#include "timeline.h" #include "stream.h" #include "tweet.h" #include "user.h" diff --git a/src/util.h b/src/util.h index 9084e81..8cbe054 100644 --- a/src/util.h +++ b/src/util.h @@ -6,31 +6,25 @@ #include namespace twitter { - + template std::string implode(InputIterator first, InputIterator last, std::string delimiter) { std::stringstream result; - + for (InputIterator it = first; it != last; it++) { if (it != first) { result << delimiter; } - + result << *it; } - + return result.str(); } - - template - std::unique_ptr make_unique(Args&&... args) - { - return std::unique_ptr(new T(std::forward(args)...)); - } - + }; #endif /* end of include guard: UTIL_H_440DEAA0 */ -- cgit 1.4.1