From 0ccac89815ee92c69fefc148cfb272faf7309136 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Fri, 20 May 2016 15:34:44 -0400 Subject: Started implementing user streams You can now start a user stream and end it yourself. If it disconnects abnormally, it will reconnect with a backoff as described by Twitter. Some data structures have some fields parsed now; tweets have IDs, text, and authors. Users have IDs, screen names, and names. Notifications from the stream are parsed completely. The ability to follow and unfollow users has also been added, as well as the ability to get a list of friends and followers, and to reply to a tweet. --- src/client.cpp | 449 ++++++++++++++++++++++++-- src/client.h | 77 ++++- src/direct_message.cpp | 10 + src/direct_message.h | 15 + src/list.cpp | 15 + src/list.h | 16 + src/notification.cpp | 848 +++++++++++++++++++++++++++++++++++++++++++++++++ src/notification.h | 145 +++++++++ src/tweet.cpp | 34 +- src/tweet.h | 18 +- src/twitter.h | 4 + src/user.cpp | 46 +++ src/user.h | 31 ++ 13 files changed, 1661 insertions(+), 47 deletions(-) create mode 100644 src/direct_message.cpp create mode 100644 src/direct_message.h create mode 100644 src/list.cpp create mode 100644 src/list.h create mode 100644 src/notification.cpp create mode 100644 src/notification.h create mode 100644 src/user.cpp create mode 100644 src/user.h (limited to 'src') diff --git a/src/client.cpp b/src/client.cpp index 26f3289..ffb651b 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -4,7 +4,9 @@ #include #include #include "util.h" -#include +#include + +using nlohmann::json; // These are here for debugging curl stuff @@ -80,11 +82,29 @@ int my_trace(CURL *handle, curl_infotype type, namespace twitter { + int client_stream_progress_callback_wrapper(void* cdata, curl_off_t, curl_off_t, curl_off_t, curl_off_t) + { + return static_cast(cdata)->progress(); + } + + size_t client_stream_write_callback_wrapper(void* ptr, size_t size, size_t nmemb, void* cdata) + { + return static_cast(cdata)->write(static_cast(ptr), size, nmemb); + } + client::client(const auth& _arg) { _oauth_consumer = new OAuth::Consumer(_arg.getConsumerKey(), _arg.getConsumerSecret()); _oauth_token = new OAuth::Token(_arg.getAccessKey(), _arg.getAccessSecret()); _oauth_client = new OAuth::Client(_oauth_consumer, _oauth_token); + + std::string url = "https://api.twitter.com/1.1/account/verify_credentials.json"; + long response_code; + std::string response_data; + if (performGet(url, response_code, response_data) && (response_code == 200)) + { + _current_user = user(response_data); + } } client::~client() @@ -94,11 +114,17 @@ namespace twitter { delete _oauth_consumer; } - response client::updateStatus(std::string msg, tweet& result, std::list media_ids) + response client::updateStatus(std::string msg, tweet& result, tweet in_response_to, std::list media_ids) { std::stringstream datastrstream; datastrstream << "status=" << OAuth::PercentEncode(msg); + if (in_response_to) + { + datastrstream << "&in_reply_to_status_id="; + datastrstream << in_response_to.getID(); + } + if (!media_ids.empty()) { datastrstream << "&media_ids="; @@ -109,7 +135,7 @@ namespace twitter { std::string url = "https://api.twitter.com/1.1/statuses/update.json"; long response_code; - json response_data; + std::string response_data; if (!performPost(url, datastr, response_code, response_data)) { return response::curl_error; @@ -147,7 +173,7 @@ namespace twitter { } long response_code; - json response_data; + std::string response_data; if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", form.get(), response_code, response_data)) { return response::curl_error; @@ -158,7 +184,8 @@ namespace twitter { return codeForError(response_code, response_data); } - media_id = response_data["media_id"].get(); + auto response_json = json::parse(response_data); + media_id = response_json["media_id"].get(); curl_httppost* append_form_post = nullptr; curl_httppost* append_form_last = nullptr; @@ -197,7 +224,8 @@ namespace twitter { return codeForError(response_code, response_data); } - if (response_data.find("processing_info") != response_data.end()) + response_json = json::parse(response_data); + if (response_json.find("processing_info") != response_json.end()) { std::stringstream datastr; datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id; @@ -214,20 +242,183 @@ namespace twitter { return codeForError(response_code, response_data); } - if (response_data["processing_info"]["state"] == "succeeded") + response_json = json::parse(response_data); + if (response_json["processing_info"]["state"] == "succeeded") { break; } - int ttw = response_data["processing_info"]["check_after_secs"].get(); - sleep(ttw); + int ttw = response_json["processing_info"]["check_after_secs"].get(); + std::this_thread::sleep_for(std::chrono::seconds(ttw)); } } return response::ok; } - bool client::performGet(std::string url, long& response_code, json& result) + response client::follow(user_id toFollow) + { + std::stringstream datastrstream; + datastrstream << "follow=true&user_id="; + datastrstream << toFollow; + + std::string datastr = datastrstream.str(); + std::string url = "https://api.twitter.com/1.1/friendships/create.json"; + + long response_code; + std::string response_data; + if (!performPost(url, datastr, response_code, response_data)) + { + return response::curl_error; + } + + if (response_code == 200) + { + return response::ok; + } else { + return codeForError(response_code, response_data); + } + } + + response client::follow(user toFollow) + { + return follow(toFollow.getID()); + } + + response client::unfollow(user_id toUnfollow) + { + std::stringstream datastrstream; + datastrstream << "user_id="; + datastrstream << toUnfollow; + + std::string datastr = datastrstream.str(); + std::string url = "https://api.twitter.com/1.1/friendships/destroy.json"; + + long response_code; + std::string response_data; + if (!performPost(url, datastr, response_code, response_data)) + { + return response::curl_error; + } + + if (response_code == 200) + { + return response::ok; + } else { + return codeForError(response_code, response_data); + } + } + + response client::unfollow(user toUnfollow) + { + return unfollow(toUnfollow.getID()); + } + + const user& client::getUser() const + { + return _current_user; + } + + response client::getFriends(std::set& _ret) + { + if (!_current_user) + { + return response::unknown_error; + } + + long long cursor = -1; + std::set result; + + while (cursor != 0) + { + std::stringstream urlstream; + urlstream << "https://api.twitter.com/1.1/friends/ids.json?user_id="; + urlstream << _current_user.getID(); + urlstream << "&cursor="; + urlstream << cursor; + + std::string url = urlstream.str(); + + long response_code; + std::string response_data; + if (!performGet(url, response_code, response_data)) + { + return response::curl_error; + } + + if (response_code == 200) + { + json rjs = json::parse(response_data); + cursor = rjs.at("next_cursor"); + result.insert(std::begin(rjs.at("ids")), std::end(rjs.at("ids"))); + } else { + return codeForError(response_code, response_data); + } + } + + _ret = result; + + return response::ok; + } + + response client::getFollowers(std::set& _ret) + { + if (!_current_user) + { + return response::unknown_error; + } + + long long cursor = -1; + std::set result; + + while (cursor != 0) + { + std::stringstream urlstream; + urlstream << "https://api.twitter.com/1.1/followers/ids.json?user_id="; + urlstream << _current_user.getID(); + urlstream << "&cursor="; + urlstream << cursor; + + std::string url = urlstream.str(); + + long response_code; + std::string response_data; + if (!performGet(url, response_code, response_data)) + { + return response::curl_error; + } + + if (response_code == 200) + { + json rjs = json::parse(response_data); + cursor = rjs.at("next_cursor"); + result.insert(std::begin(rjs.at("ids")), std::end(rjs.at("ids"))); + } else { + return codeForError(response_code, response_data); + } + } + + _ret = result; + + return response::ok; + } + + void client::setUserStreamNotifyCallback(stream::notify_callback callback) + { + _user_stream.setNotifyCallback(callback); + } + + void client::startUserStream() + { + _user_stream.start(); + } + + void client::stopUserStream() + { + _user_stream.stop(); + } + + bool client::performGet(std::string url, long& response_code, std::string& result) { std::ostringstream output; curl::curl_ios ios(output); @@ -255,17 +446,12 @@ namespace twitter { } response_code = conn.get_info().get(); - if (output.str().empty()) - { - result = json(); - } else { - result = json::parse(output.str()); - } + result = output.str(); return true; } - bool client::performPost(std::string url, std::string datastr, long& response_code, json& result) + bool client::performPost(std::string url, std::string datastr, long& response_code, std::string& result) { std::ostringstream output; curl::curl_ios ios(output); @@ -294,17 +480,12 @@ namespace twitter { } response_code = conn.get_info().get(); - if (output.str().empty()) - { - result = json(); - } else { - result = json::parse(output.str()); - } + result = output.str(); return true; } - bool client::performMultiPost(std::string url, const curl_httppost* fields, long& response_code, json& result) + bool client::performMultiPost(std::string url, const curl_httppost* fields, long& response_code, std::string& result) { std::ostringstream output; curl::curl_ios ios(output); @@ -333,23 +514,19 @@ namespace twitter { } response_code = conn.get_info().get(); - - if (output.str().empty()) - { - result = json(); - } else { - result = json::parse(output.str()); - } + result = output.str(); return true; } - response client::codeForError(int response_code, json response_data) const + response client::codeForError(int response_code, std::string response_data) const { + auto response_json = json::parse(response_data); + std::set error_codes; - if (response_data.find("errors") != response_data.end()) + if (response_json.find("errors") != response_json.end()) { - std::transform(std::begin(response_data["errors"]), std::end(response_data["errors"]), std::inserter(error_codes, std::begin(error_codes)), [] (const json& error) { + std::transform(std::begin(response_json["errors"]), std::end(response_json["errors"]), std::inserter(error_codes, std::begin(error_codes)), [] (const json& error) { return error["code"].get(); }); } @@ -407,4 +584,208 @@ namespace twitter { } } + client::stream::stream(client& _client) : _client(_client) + { + + } + + bool client::stream::isRunning() const + { + return _thread.joinable(); + } + + void client::stream::setNotifyCallback(notify_callback _n) + { + std::lock_guard _notify_lock(_notify_mutex); + _notify = _n; + } + + void client::stream::start() + { + std::lock_guard _running_lock(_running_mutex); + + if (!_thread.joinable()) + { + _thread = std::thread(&stream::run, this); + } + } + + void client::stream::stop() + { + std::lock_guard _running_lock(_running_mutex); + + if (_thread.joinable()) + { + _stop = true; + _thread.join(); + _stop = false; + } + } + + void client::stream::run() + { + curl::curl_easy conn; + std::string url = "https://userstream.twitter.com/1.1/user.json"; + + curl::curl_header headers; + std::string oauth_header = _client._oauth_client->getFormattedHttpHeader(OAuth::Http::Get, url, ""); + if (!oauth_header.empty()) + { + headers.add(oauth_header); + } + + conn.add(client_stream_write_callback_wrapper); + conn.add(this); + conn.add(nullptr); + conn.add(nullptr); + conn.add(client_stream_progress_callback_wrapper); + conn.add(this); + conn.add(0); + //conn.add(1); + //conn.add(my_trace); + conn.add(url.c_str()); + conn.add(headers.get()); + + _backoff_type = backoff::none; + _backoff_amount = std::chrono::milliseconds(0); + for (;;) + { + bool failure = false; + try { + conn.perform(); + } catch (curl::curl_easy_exception error) + { + failure = true; + if ((error.get_code() == CURLE_ABORTED_BY_CALLBACK) && _stop) + { + break; + } else { + if (_backoff_type == backoff::none) + { + _established = false; + _backoff_type = backoff::network; + _backoff_amount = std::chrono::milliseconds(0); + } + } + } + + if (!failure) + { + long response_code = conn.get_info().get(); + if (response_code == 420) + { + if (_backoff_type == backoff::none) + { + _established = false; + _backoff_type = backoff::rate_limit; + _backoff_amount = std::chrono::minutes(1); + } + } else if (response_code != 200) + { + if (_backoff_type == backoff::none) + { + _established = false; + _backoff_type = backoff::http; + _backoff_amount = std::chrono::seconds(5); + } + } else { + break; + } + } + + std::this_thread::sleep_for(_backoff_amount); + + switch (_backoff_type) + { + case backoff::network: + { + if (_backoff_amount < std::chrono::seconds(16)) + { + _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; + } + } + } + } + + int client::stream::write(char* ptr, size_t size, size_t nmemb) + { + for (size_t i = 0; i < size*nmemb; i++) + { + if (ptr[i] == '\r') + { + i++; // Skip the \n + + if (!_buffer.empty()) + { + notification n(_buffer, _client._current_user); + if (n.getType() == notification::type::friends) + { + _established = true; + _backoff_type = backoff::none; + _backoff_amount = std::chrono::milliseconds(0); + } + + { + std::lock_guard _notify_lock(_notify_mutex); + + if (_notify) + { + _notify(n); + } + } + + _buffer = ""; + } + } else { + _buffer.push_back(ptr[i]); + } + } + + { + std::lock_guard _stall_lock(_stall_mutex); + time(&_last_write); + } + + return size*nmemb; + } + + int client::stream::progress() + { + if (_stop) + { + return 1; + } + + if (_established) + { + std::lock_guard _stall_lock(_stall_mutex); + if (difftime(time(NULL), _last_write) >= 90) + { + return 1; + } + } + + return 0; + } + }; diff --git a/src/client.h b/src/client.h index 3a133e4..ae80ed9 100644 --- a/src/client.h +++ b/src/client.h @@ -7,6 +7,12 @@ #include #include #include +#include +#include +#include "notification.h" +#include +#include +#include namespace OAuth { class Consumer; @@ -18,21 +24,82 @@ namespace twitter { class client { public: + class stream { + public: + typedef std::function notify_callback; + + stream(client& _client); + + void setNotifyCallback(notify_callback _n); + + bool isRunning() const; + void start(); + void stop(); + + private: + enum class backoff { + none, + network, + http, + rate_limit + }; + + void run(); + int progress(); + int write(char* ptr, size_t size, size_t nmemb); + + friend int client_stream_progress_callback_wrapper(void* stream, curl_off_t, curl_off_t, curl_off_t, curl_off_t); + friend size_t client_stream_write_callback_wrapper(void* ptr, size_t size, size_t nmemb, void* stream); + + client& _client; + notify_callback _notify; + bool _stop = false; + std::thread _thread; + std::mutex _running_mutex; + std::mutex _notify_mutex; + std::mutex _stall_mutex; + std::string _buffer; + time_t _last_write; + bool _established = false; + backoff _backoff_type = backoff::none; + std::chrono::milliseconds _backoff_amount; + }; + client(const auth& _auth); ~client(); - response updateStatus(std::string msg, tweet& result, std::list media_ids = {}); + response updateStatus(std::string msg, tweet& result, tweet in_response_to = tweet(), std::list media_ids = {}); response uploadMedia(std::string media_type, const char* data, long data_length, long& media_id); + response follow(user_id toFollow); + response follow(user toFollow); + + response unfollow(user_id toUnfollow); + response unfollow(user toUnfollow); + + response getFriends(std::set& result); + response getFollowers(std::set& result); + + const user& getUser() const; + + void setUserStreamNotifyCallback(stream::notify_callback callback); + void startUserStream(); + void stopUserStream(); + private: + friend class stream; + OAuth::Consumer* _oauth_consumer; OAuth::Token* _oauth_token; OAuth::Client* _oauth_client; - bool performGet(std::string url, long& response_code, json& result); - bool performPost(std::string url, std::string dataStr, long& response_code, json& result); - bool performMultiPost(std::string url, const curl_httppost* fields, long& response_code, json& result); - response codeForError(int httpcode, json errors) const; + user _current_user; + stream _user_stream{*this}; + + bool performGet(std::string url, long& response_code, std::string& result); + bool performPost(std::string url, std::string dataStr, long& response_code, std::string& result); + bool performMultiPost(std::string url, const curl_httppost* fields, long& response_code, std::string& result); + response codeForError(int httpcode, std::string errors) const; }; }; diff --git a/src/direct_message.cpp b/src/direct_message.cpp new file mode 100644 index 0000000..d5b6f36 --- /dev/null +++ b/src/direct_message.cpp @@ -0,0 +1,10 @@ +#include "direct_message.h" + +namespace twitter { + + direct_message::direct_message(std::string data) + { + + } + +}; diff --git a/src/direct_message.h b/src/direct_message.h new file mode 100644 index 0000000..4b5e285 --- /dev/null +++ b/src/direct_message.h @@ -0,0 +1,15 @@ +#ifndef DIRECT_MESSAGE_H_2B2AE3F8 +#define DIRECT_MESSAGE_H_2B2AE3F8 + +#include + +namespace twitter { + + class direct_message { + public: + direct_message(std::string data); + }; + +}; + +#endif /* end of include guard: DIRECT_MESSAGE_H_2B2AE3F8 */ diff --git a/src/list.cpp b/src/list.cpp new file mode 100644 index 0000000..49405d0 --- /dev/null +++ b/src/list.cpp @@ -0,0 +1,15 @@ +#include "list.h" + +namespace twitter { + + list::list() + { + + } + + list::list(std::string data) + { + + } + +}; diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..3876f2d --- /dev/null +++ b/src/list.h @@ -0,0 +1,16 @@ +#ifndef LIST_H_D7DEA7D8 +#define LIST_H_D7DEA7D8 + +#include + +namespace twitter { + + class list { + public: + list(); + list(std::string data); + }; + +}; + +#endif /* end of include guard: LIST_H_D7DEA7D8 */ diff --git a/src/notification.cpp b/src/notification.cpp new file mode 100644 index 0000000..3dcdd90 --- /dev/null +++ b/src/notification.cpp @@ -0,0 +1,848 @@ +#include "notification.h" +#include +#include +#include + +using nlohmann::json; + +namespace twitter { + + notification::type notification::getType() const + { + return _type; + } + + notification::notification() : _type(type::invalid) + { + + } + + notification::notification(std::string data, const user& current_user) + { + auto _data = json::parse(data); + + if (_data.find("in_reply_to_status_id") != _data.end()) + { + _type = type::tweet; + + new(&_tweet) tweet(data); + } else if (_data.find("event") != _data.end()) + { + std::string event = _data.at("event"); + user source(_data.at("source").dump()); + user target(_data.at("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(_data.at("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(_data.at("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") + { + 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(_data.at("target_object").dump()); + } else if (event == "list_destroyed") + { + _type = type::list_destroyed; + + new(&_list) list(_data.at("target_object").dump()); + } else if (event == "list_updated") + { + _type = type::list_updated; + + new(&_list) list(_data.at("target_object").dump()); + } else if (event == "list_member_added") + { + new(&_user_and_list._list) list(_data.at("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(_data.at("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(_data.at("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(_data.at("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(_data.at("target_object").dump()); + } + } else if (_data.find("warning") != _data.end()) + { + new(&_warning) std::string(_data.at("warning").at("message").get()); + + if (_data.at("warning").at("code") == "FALLING_BEHIND") + { + _type = type::stall; + } else if (_data.at("warning").at("code") == "FOLLOWS_OVER_LIMIT") + { + _type = type::follow_limit; + } else { + _type = type::unknown_warning; + } + } else if (_data.find("delete") != _data.end()) + { + _type = type::deletion; + + _user_id_and_tweet_id._tweet_id = _data.at("delete").at("status").at("id"); + _user_id_and_tweet_id._user_id = _data.at("delete").at("status").at("user_id"); + } else if (_data.find("scrub_geo") != _data.end()) + { + _type = type::scrub_location; + + _user_id_and_tweet_id._tweet_id = _data.at("scrub_geo").at("up_to_status_id"); + _user_id_and_tweet_id._user_id = _data.at("scrub_geo").at("user_id"); + } else if (_data.find("limit") != _data.end()) + { + _type = type::limit; + + _limit = _data.at("limit").at("track"); + } else if (_data.find("status_withheld") != _data.end()) + { + _type = type::withhold_status; + + _withhold_status._user_id = _data.at("status_withheld").at("user_id"); + _withhold_status._tweet_id = _data.at("status_withheld").at("id"); + + new(&_withhold_status._countries) std::vector(); + for (auto s : _data.at("status_withheld").at("withheld_in_countries")) + { + _withhold_status._countries.push_back(s); + } + } else if (_data.find("user_withheld") != _data.end()) + { + _type = type::withhold_user; + + _withhold_user._user_id = _data.at("user_withheld").at("id"); + + new(&_withhold_user._countries) std::vector(); + for (auto s : _data.at("user_withheld").at("withheld_in_countries")) + { + _withhold_user._countries.push_back(s); + } + } else if (_data.find("disconnect") != _data.end()) + { + _type = type::disconnect; + + switch (_data.at("disconnect").at("code").get()) + { + case 1: _disconnect = disconnect_code::shutdown; break; + case 2: _disconnect = disconnect_code::duplicate; break; + case 4: _disconnect = disconnect_code::stall; break; + case 5: _disconnect = disconnect_code::normal; break; + case 6: _disconnect = disconnect_code::token_revoked; break; + case 7: _disconnect = disconnect_code::admin_logout; break; + case 9: _disconnect = disconnect_code::limit; break; + case 10: _disconnect = disconnect_code::exception; break; + case 11: _disconnect = disconnect_code::broker; break; + case 12: _disconnect = disconnect_code::load; break; + default: _disconnect = disconnect_code::unknown; + } + } else if (_data.find("friends") != _data.end()) + { + _type = type::friends; + + new(&_friends) std::set(_data.at("friends").begin(), _data.at("friends").end()); + } else if (_data.find("direct_message") != _data.end()) + { + _type = type::direct; + + new(&_direct_message) direct_message(_data.at("direct_message").dump()); + } else { + _type = type::unknown; + } + } + + 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: + case type::follow: + case type::followed: + case type::unfollow: + { + new(&_user) user(other._user); + + break; + } + + case type::favorite: + case type::favorited: + case type::unfavorite: + case type::unfavorited: + case type::quoted: + { + 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: + case type::list_removed: + case type::list_subscribe: + case type::list_subscribed: + case type::list_unsubscribe: + case type::list_unsubscribed: + { + 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; + } + } + } + + notification& notification::operator=(const notification& other) + { + this->~notification(); + + _type = other._type; + + switch (_type) + { + case type::tweet: + { + new(&_tweet) tweet(other._tweet); + + break; + } + + case type::update_user: + case type::block: + case type::unblock: + case type::follow: + case type::followed: + case type::unfollow: + { + new(&_user) user(other._user); + + break; + } + + case type::favorite: + case type::favorited: + case type::unfavorite: + case type::unfavorited: + case type::quoted: + { + 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: + case type::list_removed: + case type::list_subscribe: + case type::list_subscribed: + case type::list_unsubscribe: + case type::list_unsubscribed: + { + 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; + } + } + + return *this; + } + + notification::~notification() + { + switch (_type) + { + case type::tweet: + { + _tweet.~tweet(); + + break; + } + + case type::update_user: + case type::block: + case type::unblock: + case type::follow: + case type::followed: + case type::unfollow: + { + _user.~user(); + + break; + } + + case type::favorite: + case type::favorited: + case type::unfavorite: + case type::unfavorited: + case type::quoted: + { + _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: + case type::list_removed: + case type::list_subscribe: + case type::list_subscribed: + case type::list_unsubscribe: + case type::list_unsubscribed: + { + _user_and_list._user.~user(); + _user_and_list._list.~list(); + + break; + } + + case type::stall: + case type::follow_limit: + { + 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; + } + } + } + + tweet notification::getTweet() const + { + switch (_type) + { + case type::tweet: + { + return _tweet; + } + + case type::favorite: + case type::favorited: + case type::unfavorite: + case type::unfavorited: + case type::quoted: + { + return _user_and_tweet._tweet; + } + + default: + { + assert(false); + + return tweet(); + } + } + } + + user notification::getUser() const + { + switch (_type) + { + case type::update_user: + case type::block: + case type::unblock: + case type::follow: + case type::followed: + case type::unfollow: + { + return _user; + } + + case type::favorite: + case type::favorited: + case type::unfavorite: + case type::unfavorited: + case type::quoted: + { + return _user_and_tweet._user; + } + + case type::list_add: + case type::list_added: + case type::list_remove: + case type::list_removed: + case type::list_subscribe: + case type::list_subscribed: + case type::list_unsubscribe: + case type::list_unsubscribed: + { + return _user_and_list._user; + } + + default: + { + assert(false); + + return user(); + } + } + } + + 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: + case type::list_removed: + case type::list_subscribe: + case type::list_subscribed: + case type::list_unsubscribe: + case type::list_unsubscribed: + { + return _user_and_list._list; + } + + default: + { + assert(false); + + return list(); + } + } + } + + tweet_id notification::getTweetID() const + { + switch (_type) + { + case type::deletion: + case type::scrub_location: + { + return _user_id_and_tweet_id._tweet_id; + } + + case type::withhold_status: + { + return _withhold_status._tweet_id; + } + + default: + { + assert(false); + + return 0; + } + } + } + + 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; + } + + default: + { + assert(false); + + return 0; + } + } + } + + std::vector notification::getCountries() const + { + switch (_type) + { + case type::withhold_status: + { + return _withhold_status._countries; + } + + case type::withhold_user: + { + return _withhold_user._countries; + } + + default: + { + assert(false); + + return std::vector(); + } + } + } + + disconnect_code notification::getDisconnectCode() const + { + assert(_type == type::disconnect); + + return _disconnect; + } + + std::set notification::getFriends() const + { + assert(_type == type::friends); + + return _friends; + } + + direct_message notification::getDirectMessage() const + { + assert(_type == type::direct); + + return _direct_message; + } + + int notification::getLimit() const + { + assert(_type == type::limit); + + return _limit; + } + + std::string notification::getWarning() const + { + switch (_type) + { + case type::stall: + case type::follow_limit: + case type::unknown_warning: + { + return _warning; + } + + default: + { + assert(false); + + return ""; + } + } + } + + notification::operator bool() const + { + return _type != type::invalid; + } + +}; diff --git a/src/notification.h b/src/notification.h new file mode 100644 index 0000000..da83b0f --- /dev/null +++ b/src/notification.h @@ -0,0 +1,145 @@ +#ifndef NOTIFICATION_H_69AEF4CC +#define NOTIFICATION_H_69AEF4CC + +#include +#include +#include +#include "tweet.h" +#include "user.h" +#include "list.h" +#include "direct_message.h" + +namespace twitter { + + enum class disconnect_code { + shutdown, + duplicate, + stall, + normal, + token_revoked, + admin_logout, + limit, + exception, + broker, + load, + unknown + }; + + class notification { + public: + enum class type { + // Tweet object + tweet, + + // User object + update_user, + block, + unblock, + 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, + list_remove, + list_removed, + list_subscribe, + list_subscribed, + list_unsubscribe, + list_unsubscribed, + + // Warning + stall, + follow_limit, + unknown_warning, + + // User ID and tweet ID + deletion, + scrub_location, + + // Special + limit, + withhold_status, + withhold_user, + disconnect, + friends, + direct, + + // Nothing + unknown, + invalid + }; + + type getType() const; + + notification(); + notification(std::string data, const user& current_user); + notification(const notification& other); + notification& operator=(const notification& other); + ~notification(); + + tweet getTweet() const; + user getUser() const; + list getList() const; + tweet_id getTweetID() const; + user_id getUserID() const; + std::vector getCountries() const; + disconnect_code getDisconnectCode() const; + std::set getFriends() const; + direct_message getDirectMessage() const; + int getLimit() const; + std::string getWarning() const; + + operator bool() const; + + private: + union { + tweet _tweet; + user _user; + list _list; + struct { + user _user; + tweet _tweet; + } _user_and_tweet; + struct { + user _user; + list _list; + } _user_and_list; + std::string _warning; + struct { + user_id _user_id; + tweet_id _tweet_id; + } _user_id_and_tweet_id; + int _limit; + struct { + user_id _user_id; + tweet_id _tweet_id; + std::vector _countries; + } _withhold_status; + struct { + user_id _user_id; + std::vector _countries; + } _withhold_user; + disconnect_code _disconnect; + std::set _friends; + direct_message _direct_message; + }; + type _type; + }; + +}; + +#endif /* end of include guard: NOTIFICATION_H_69AEF4CC */ diff --git a/src/tweet.cpp b/src/tweet.cpp index 3ba3aa3..e515595 100644 --- a/src/tweet.cpp +++ b/src/tweet.cpp @@ -1,15 +1,41 @@ #include "tweet.h" +#include + +using nlohmann::json; namespace twitter { - tweet::tweet() + tweet::tweet() : _valid(false) + { + + } + + tweet::tweet(std::string data) : _valid(true) + { + auto _data = json::parse(data); + _id = _data.at("id"); + _text = _data.at("text"); + _author = user(_data.at("user").dump()); + } + + tweet_id tweet::getID() const + { + return _id; + } + + std::string tweet::getText() const + { + return _text; + } + + const user& tweet::getAuthor() const { - _valid = false; + return _author; } - tweet::tweet(const json& data) + tweet::operator bool() const { - _valid = true; + return _valid; } }; diff --git a/src/tweet.h b/src/tweet.h index e099579..137776c 100644 --- a/src/tweet.h +++ b/src/tweet.h @@ -1,19 +1,29 @@ #ifndef TWEET_H_CE980721 #define TWEET_H_CE980721 -#include - -using nlohmann::json; +#include +#include "user.h" namespace twitter { + typedef unsigned long long tweet_id; + class tweet { public: tweet(); - tweet(const json& _data); + tweet(std::string data); + + tweet_id getID() const; + std::string getText() const; + const user& getAuthor() const; + + operator bool() const; private: bool _valid; + tweet_id _id; + std::string _text; + user _author; }; }; diff --git a/src/twitter.h b/src/twitter.h index f9534c6..d0b469e 100644 --- a/src/twitter.h +++ b/src/twitter.h @@ -12,5 +12,9 @@ namespace twitter { #include "auth.h" #include "client.h" #include "tweet.h" +#include "user.h" +#include "notification.h" +#include "list.h" +#include "direct_message.h" #endif /* end of include guard: TWITTER_H_AC7A7666 */ diff --git a/src/user.cpp b/src/user.cpp new file mode 100644 index 0000000..9352938 --- /dev/null +++ b/src/user.cpp @@ -0,0 +1,46 @@ +#include "user.h" +#include + +using nlohmann::json; + +namespace twitter { + + user::user() : _valid(false) + { + + } + + user::user(std::string data) : _valid(true) + { + auto _data = json::parse(data); + _id = _data.at("id"); + _screen_name = _data.at("screen_name"); + _name = _data.at("name"); + } + + user_id user::getID() const + { + return _id; + } + + std::string user::getScreenName() const + { + return _screen_name; + } + + std::string user::getName() const + { + return _name; + } + + user::operator bool() const + { + return _valid; + } + + bool user::operator==(const user& other) const + { + return _id == other._id; + } + +}; diff --git a/src/user.h b/src/user.h new file mode 100644 index 0000000..0af40d6 --- /dev/null +++ b/src/user.h @@ -0,0 +1,31 @@ +#ifndef USER_H_BF3AB38C +#define USER_H_BF3AB38C + +#include + +namespace twitter { + + typedef unsigned long long user_id; + + class user { + public: + user(); + user(std::string data); + + user_id getID() const; + std::string getScreenName() const; + std::string getName() const; + + operator bool() const; + bool operator==(const user& other) const; + + private: + bool _valid = false; + user_id _id; + std::string _screen_name; + std::string _name; + }; + +}; + +#endif /* end of include guard: USER_H_BF3AB38C */ -- cgit 1.4.1