diff options
author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2016-05-20 15:34:44 -0400 |
---|---|---|
committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2016-05-20 15:34:44 -0400 |
commit | 0ccac89815ee92c69fefc148cfb272faf7309136 (patch) | |
tree | 228775a433018c8a5fd20f0ebb0f8446057b2112 /src | |
parent | f465dce27cf0f07039e29d8975ad98939f0df3a9 (diff) | |
download | libtwittercpp-0ccac89815ee92c69fefc148cfb272faf7309136.tar.gz libtwittercpp-0ccac89815ee92c69fefc148cfb272faf7309136.tar.bz2 libtwittercpp-0ccac89815ee92c69fefc148cfb272faf7309136.zip |
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.
Diffstat (limited to 'src')
-rw-r--r-- | src/client.cpp | 449 | ||||
-rw-r--r-- | src/client.h | 77 | ||||
-rw-r--r-- | src/direct_message.cpp | 10 | ||||
-rw-r--r-- | src/direct_message.h | 15 | ||||
-rw-r--r-- | src/list.cpp | 15 | ||||
-rw-r--r-- | src/list.h | 16 | ||||
-rw-r--r-- | src/notification.cpp | 848 | ||||
-rw-r--r-- | src/notification.h | 145 | ||||
-rw-r--r-- | src/tweet.cpp | 34 | ||||
-rw-r--r-- | src/tweet.h | 18 | ||||
-rw-r--r-- | src/twitter.h | 4 | ||||
-rw-r--r-- | src/user.cpp | 46 | ||||
-rw-r--r-- | src/user.h | 31 |
13 files changed, 1661 insertions, 47 deletions
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 @@ | |||
4 | #include <algorithm> | 4 | #include <algorithm> |
5 | #include <liboauthcpp/liboauthcpp.h> | 5 | #include <liboauthcpp/liboauthcpp.h> |
6 | #include "util.h" | 6 | #include "util.h" |
7 | #include <unistd.h> | 7 | #include <json.hpp> |
8 | |||
9 | using nlohmann::json; | ||
8 | 10 | ||
9 | // These are here for debugging curl stuff | 11 | // These are here for debugging curl stuff |
10 | 12 | ||
@@ -80,11 +82,29 @@ int my_trace(CURL *handle, curl_infotype type, | |||
80 | 82 | ||
81 | namespace twitter { | 83 | namespace twitter { |
82 | 84 | ||
85 | int client_stream_progress_callback_wrapper(void* cdata, curl_off_t, curl_off_t, curl_off_t, curl_off_t) | ||
86 | { | ||
87 | return static_cast<client::stream*>(cdata)->progress(); | ||
88 | } | ||
89 | |||
90 | size_t client_stream_write_callback_wrapper(void* ptr, size_t size, size_t nmemb, void* cdata) | ||
91 | { | ||
92 | return static_cast<client::stream*>(cdata)->write(static_cast<char*>(ptr), size, nmemb); | ||
93 | } | ||
94 | |||
83 | client::client(const auth& _arg) | 95 | client::client(const auth& _arg) |
84 | { | 96 | { |
85 | _oauth_consumer = new OAuth::Consumer(_arg.getConsumerKey(), _arg.getConsumerSecret()); | 97 | _oauth_consumer = new OAuth::Consumer(_arg.getConsumerKey(), _arg.getConsumerSecret()); |
86 | _oauth_token = new OAuth::Token(_arg.getAccessKey(), _arg.getAccessSecret()); | 98 | _oauth_token = new OAuth::Token(_arg.getAccessKey(), _arg.getAccessSecret()); |
87 | _oauth_client = new OAuth::Client(_oauth_consumer, _oauth_token); | 99 | _oauth_client = new OAuth::Client(_oauth_consumer, _oauth_token); |
100 | |||
101 | std::string url = "https://api.twitter.com/1.1/account/verify_credentials.json"; | ||
102 | long response_code; | ||
103 | std::string response_data; | ||
104 | if (performGet(url, response_code, response_data) && (response_code == 200)) | ||
105 | { | ||
106 | _current_user = user(response_data); | ||
107 | } | ||
88 | } | 108 | } |
89 | 109 | ||
90 | client::~client() | 110 | client::~client() |
@@ -94,11 +114,17 @@ namespace twitter { | |||
94 | delete _oauth_consumer; | 114 | delete _oauth_consumer; |
95 | } | 115 | } |
96 | 116 | ||
97 | response client::updateStatus(std::string msg, tweet& result, std::list<long> media_ids) | 117 | response client::updateStatus(std::string msg, tweet& result, tweet in_response_to, std::list<long> media_ids) |
98 | { | 118 | { |
99 | std::stringstream datastrstream; | 119 | std::stringstream datastrstream; |
100 | datastrstream << "status=" << OAuth::PercentEncode(msg); | 120 | datastrstream << "status=" << OAuth::PercentEncode(msg); |
101 | 121 | ||
122 | if (in_response_to) | ||
123 | { | ||
124 | datastrstream << "&in_reply_to_status_id="; | ||
125 | datastrstream << in_response_to.getID(); | ||
126 | } | ||
127 | |||
102 | if (!media_ids.empty()) | 128 | if (!media_ids.empty()) |
103 | { | 129 | { |
104 | datastrstream << "&media_ids="; | 130 | datastrstream << "&media_ids="; |
@@ -109,7 +135,7 @@ namespace twitter { | |||
109 | std::string url = "https://api.twitter.com/1.1/statuses/update.json"; | 135 | std::string url = "https://api.twitter.com/1.1/statuses/update.json"; |
110 | 136 | ||
111 | long response_code; | 137 | long response_code; |
112 | json response_data; | 138 | std::string response_data; |
113 | if (!performPost(url, datastr, response_code, response_data)) | 139 | if (!performPost(url, datastr, response_code, response_data)) |
114 | { | 140 | { |
115 | return response::curl_error; | 141 | return response::curl_error; |
@@ -147,7 +173,7 @@ namespace twitter { | |||
147 | } | 173 | } |
148 | 174 | ||
149 | long response_code; | 175 | long response_code; |
150 | json response_data; | 176 | std::string response_data; |
151 | if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", form.get(), response_code, response_data)) | 177 | if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", form.get(), response_code, response_data)) |
152 | { | 178 | { |
153 | return response::curl_error; | 179 | return response::curl_error; |
@@ -158,7 +184,8 @@ namespace twitter { | |||
158 | return codeForError(response_code, response_data); | 184 | return codeForError(response_code, response_data); |
159 | } | 185 | } |
160 | 186 | ||
161 | media_id = response_data["media_id"].get<long>(); | 187 | auto response_json = json::parse(response_data); |
188 | media_id = response_json["media_id"].get<long>(); | ||
162 | 189 | ||
163 | curl_httppost* append_form_post = nullptr; | 190 | curl_httppost* append_form_post = nullptr; |
164 | curl_httppost* append_form_last = nullptr; | 191 | curl_httppost* append_form_last = nullptr; |
@@ -197,7 +224,8 @@ namespace twitter { | |||
197 | return codeForError(response_code, response_data); | 224 | return codeForError(response_code, response_data); |
198 | } | 225 | } |
199 | 226 | ||
200 | if (response_data.find("processing_info") != response_data.end()) | 227 | response_json = json::parse(response_data); |
228 | if (response_json.find("processing_info") != response_json.end()) | ||
201 | { | 229 | { |
202 | std::stringstream datastr; | 230 | std::stringstream datastr; |
203 | datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id; | 231 | datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id; |
@@ -214,20 +242,183 @@ namespace twitter { | |||
214 | return codeForError(response_code, response_data); | 242 | return codeForError(response_code, response_data); |
215 | } | 243 | } |
216 | 244 | ||
217 | if (response_data["processing_info"]["state"] == "succeeded") | 245 | response_json = json::parse(response_data); |
246 | if (response_json["processing_info"]["state"] == "succeeded") | ||
218 | { | 247 | { |
219 | break; | 248 | break; |
220 | } | 249 | } |
221 | 250 | ||
222 | int ttw = response_data["processing_info"]["check_after_secs"].get<int>(); | 251 | int ttw = response_json["processing_info"]["check_after_secs"].get<int>(); |
223 | sleep(ttw); | 252 | std::this_thread::sleep_for(std::chrono::seconds(ttw)); |
224 | } | 253 | } |
225 | } | 254 | } |
226 | 255 | ||
227 | return response::ok; | 256 | return response::ok; |
228 | } | 257 | } |
229 | 258 | ||
230 | bool client::performGet(std::string url, long& response_code, json& result) | 259 | response client::follow(user_id toFollow) |
260 | { | ||
261 | std::stringstream datastrstream; | ||
262 | datastrstream << "follow=true&user_id="; | ||
263 | datastrstream << toFollow; | ||
264 | |||
265 | std::string datastr = datastrstream.str(); | ||
266 | std::string url = "https://api.twitter.com/1.1/friendships/create.json"; | ||
267 | |||
268 | long response_code; | ||
269 | std::string response_data; | ||
270 | if (!performPost(url, datastr, response_code, response_data)) | ||
271 | { | ||
272 | return response::curl_error; | ||
273 | } | ||
274 | |||
275 | if (response_code == 200) | ||
276 | { | ||
277 | return response::ok; | ||
278 | } else { | ||
279 | return codeForError(response_code, response_data); | ||
280 | } | ||
281 | } | ||
282 | |||
283 | response client::follow(user toFollow) | ||
284 | { | ||
285 | return follow(toFollow.getID()); | ||
286 | } | ||
287 | |||
288 | response client::unfollow(user_id toUnfollow) | ||
289 | { | ||
290 | std::stringstream datastrstream; | ||
291 | datastrstream << "user_id="; | ||
292 | datastrstream << toUnfollow; | ||
293 | |||
294 | std::string datastr = datastrstream.str(); | ||
295 | std::string url = "https://api.twitter.com/1.1/friendships/destroy.json"; | ||
296 | |||
297 | long response_code; | ||
298 | std::string response_data; | ||
299 | if (!performPost(url, datastr, response_code, response_data)) | ||
300 | { | ||
301 | return response::curl_error; | ||
302 | } | ||
303 | |||
304 | if (response_code == 200) | ||
305 | { | ||
306 | return response::ok; | ||
307 | } else { | ||
308 | return codeForError(response_code, response_data); | ||
309 | } | ||
310 | } | ||
311 | |||
312 | response client::unfollow(user toUnfollow) | ||
313 | { | ||
314 | return unfollow(toUnfollow.getID()); | ||
315 | } | ||
316 | |||
317 | const user& client::getUser() const | ||
318 | { | ||
319 | return _current_user; | ||
320 | } | ||
321 | |||
322 | response client::getFriends(std::set<user_id>& _ret) | ||
323 | { | ||
324 | if (!_current_user) | ||
325 | { | ||
326 | return response::unknown_error; | ||
327 | } | ||
328 | |||
329 | long long cursor = -1; | ||
330 | std::set<user_id> result; | ||
331 | |||
332 | while (cursor != 0) | ||
333 | { | ||
334 | std::stringstream urlstream; | ||
335 | urlstream << "https://api.twitter.com/1.1/friends/ids.json?user_id="; | ||
336 | urlstream << _current_user.getID(); | ||
337 | urlstream << "&cursor="; | ||
338 | urlstream << cursor; | ||
339 | |||
340 | std::string url = urlstream.str(); | ||
341 | |||
342 | long response_code; | ||
343 | std::string response_data; | ||
344 | if (!performGet(url, response_code, response_data)) | ||
345 | { | ||
346 | return response::curl_error; | ||
347 | } | ||
348 | |||
349 | if (response_code == 200) | ||
350 | { | ||
351 | json rjs = json::parse(response_data); | ||
352 | cursor = rjs.at("next_cursor"); | ||
353 | result.insert(std::begin(rjs.at("ids")), std::end(rjs.at("ids"))); | ||
354 | } else { | ||
355 | return codeForError(response_code, response_data); | ||
356 | } | ||
357 | } | ||
358 | |||
359 | _ret = result; | ||
360 | |||
361 | return response::ok; | ||
362 | } | ||
363 | |||
364 | response client::getFollowers(std::set<user_id>& _ret) | ||
365 | { | ||
366 | if (!_current_user) | ||
367 | { | ||
368 | return response::unknown_error; | ||
369 | } | ||
370 | |||
371 | long long cursor = -1; | ||
372 | std::set<user_id> result; | ||
373 | |||
374 | while (cursor != 0) | ||
375 | { | ||
376 | std::stringstream urlstream; | ||
377 | urlstream << "https://api.twitter.com/1.1/followers/ids.json?user_id="; | ||
378 | urlstream << _current_user.getID(); | ||
379 | urlstream << "&cursor="; | ||
380 | urlstream << cursor; | ||
381 | |||
382 | std::string url = urlstream.str(); | ||
383 | |||
384 | long response_code; | ||
385 | std::string response_data; | ||
386 | if (!performGet(url, response_code, response_data)) | ||
387 | { | ||
388 | return response::curl_error; | ||
389 | } | ||
390 | |||
391 | if (response_code == 200) | ||
392 | { | ||
393 | json rjs = json::parse(response_data); | ||
394 | cursor = rjs.at("next_cursor"); | ||
395 | result.insert(std::begin(rjs.at("ids")), std::end(rjs.at("ids"))); | ||
396 | } else { | ||
397 | return codeForError(response_code, response_data); | ||
398 | } | ||
399 | } | ||
400 | |||
401 | _ret = result; | ||
402 | |||
403 | return response::ok; | ||
404 | } | ||
405 | |||
406 | void client::setUserStreamNotifyCallback(stream::notify_callback callback) | ||
407 | { | ||
408 | _user_stream.setNotifyCallback(callback); | ||
409 | } | ||
410 | |||
411 | void client::startUserStream() | ||
412 | { | ||
413 | _user_stream.start(); | ||
414 | } | ||
415 | |||
416 | void client::stopUserStream() | ||
417 | { | ||
418 | _user_stream.stop(); | ||
419 | } | ||
420 | |||
421 | bool client::performGet(std::string url, long& response_code, std::string& result) | ||
231 | { | 422 | { |
232 | std::ostringstream output; | 423 | std::ostringstream output; |
233 | curl::curl_ios<std::ostringstream> ios(output); | 424 | curl::curl_ios<std::ostringstream> ios(output); |
@@ -255,17 +446,12 @@ namespace twitter { | |||
255 | } | 446 | } |
256 | 447 | ||
257 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); | 448 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); |
258 | if (output.str().empty()) | 449 | result = output.str(); |
259 | { | ||
260 | result = json(); | ||
261 | } else { | ||
262 | result = json::parse(output.str()); | ||
263 | } | ||
264 | 450 | ||
265 | return true; | 451 | return true; |
266 | } | 452 | } |
267 | 453 | ||
268 | bool client::performPost(std::string url, std::string datastr, long& response_code, json& result) | 454 | bool client::performPost(std::string url, std::string datastr, long& response_code, std::string& result) |
269 | { | 455 | { |
270 | std::ostringstream output; | 456 | std::ostringstream output; |
271 | curl::curl_ios<std::ostringstream> ios(output); | 457 | curl::curl_ios<std::ostringstream> ios(output); |
@@ -294,17 +480,12 @@ namespace twitter { | |||
294 | } | 480 | } |
295 | 481 | ||
296 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); | 482 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); |
297 | if (output.str().empty()) | 483 | result = output.str(); |
298 | { | ||
299 | result = json(); | ||
300 | } else { | ||
301 | result = json::parse(output.str()); | ||
302 | } | ||
303 | 484 | ||
304 | return true; | 485 | return true; |
305 | } | 486 | } |
306 | 487 | ||
307 | bool client::performMultiPost(std::string url, const curl_httppost* fields, long& response_code, json& result) | 488 | bool client::performMultiPost(std::string url, const curl_httppost* fields, long& response_code, std::string& result) |
308 | { | 489 | { |
309 | std::ostringstream output; | 490 | std::ostringstream output; |
310 | curl::curl_ios<std::ostringstream> ios(output); | 491 | curl::curl_ios<std::ostringstream> ios(output); |
@@ -333,23 +514,19 @@ namespace twitter { | |||
333 | } | 514 | } |
334 | 515 | ||
335 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); | 516 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); |
336 | 517 | result = output.str(); | |
337 | if (output.str().empty()) | ||
338 | { | ||
339 | result = json(); | ||
340 | } else { | ||
341 | result = json::parse(output.str()); | ||
342 | } | ||
343 | 518 | ||
344 | return true; | 519 | return true; |
345 | } | 520 | } |
346 | 521 | ||
347 | response client::codeForError(int response_code, json response_data) const | 522 | response client::codeForError(int response_code, std::string response_data) const |
348 | { | 523 | { |
524 | auto response_json = json::parse(response_data); | ||
525 | |||
349 | std::set<int> error_codes; | 526 | std::set<int> error_codes; |
350 | if (response_data.find("errors") != response_data.end()) | 527 | if (response_json.find("errors") != response_json.end()) |
351 | { | 528 | { |
352 | std::transform(std::begin(response_data["errors"]), std::end(response_data["errors"]), std::inserter(error_codes, std::begin(error_codes)), [] (const json& error) { | 529 | std::transform(std::begin(response_json["errors"]), std::end(response_json["errors"]), std::inserter(error_codes, std::begin(error_codes)), [] (const json& error) { |
353 | return error["code"].get<int>(); | 530 | return error["code"].get<int>(); |
354 | }); | 531 | }); |
355 | } | 532 | } |
@@ -407,4 +584,208 @@ namespace twitter { | |||
407 | } | 584 | } |
408 | } | 585 | } |
409 | 586 | ||
587 | client::stream::stream(client& _client) : _client(_client) | ||
588 | { | ||
589 | |||
590 | } | ||
591 | |||
592 | bool client::stream::isRunning() const | ||
593 | { | ||
594 | return _thread.joinable(); | ||
595 | } | ||
596 | |||
597 | void client::stream::setNotifyCallback(notify_callback _n) | ||
598 | { | ||
599 | std::lock_guard<std::mutex> _notify_lock(_notify_mutex); | ||
600 | _notify = _n; | ||
601 | } | ||
602 | |||
603 | void client::stream::start() | ||
604 | { | ||
605 | std::lock_guard<std::mutex> _running_lock(_running_mutex); | ||
606 | |||
607 | if (!_thread.joinable()) | ||
608 | { | ||
609 | _thread = std::thread(&stream::run, this); | ||
610 | } | ||
611 | } | ||
612 | |||
613 | void client::stream::stop() | ||
614 | { | ||
615 | std::lock_guard<std::mutex> _running_lock(_running_mutex); | ||
616 | |||
617 | if (_thread.joinable()) | ||
618 | { | ||
619 | _stop = true; | ||
620 | _thread.join(); | ||
621 | _stop = false; | ||
622 | } | ||
623 | } | ||
624 | |||
625 | void client::stream::run() | ||
626 | { | ||
627 | curl::curl_easy conn; | ||
628 | std::string url = "https://userstream.twitter.com/1.1/user.json"; | ||
629 | |||
630 | curl::curl_header headers; | ||
631 | std::string oauth_header = _client._oauth_client->getFormattedHttpHeader(OAuth::Http::Get, url, ""); | ||
632 | if (!oauth_header.empty()) | ||
633 | { | ||
634 | headers.add(oauth_header); | ||
635 | } | ||
636 | |||
637 | conn.add<CURLOPT_WRITEFUNCTION>(client_stream_write_callback_wrapper); | ||
638 | conn.add<CURLOPT_WRITEDATA>(this); | ||
639 | conn.add<CURLOPT_HEADERFUNCTION>(nullptr); | ||
640 | conn.add<CURLOPT_HEADERDATA>(nullptr); | ||
641 | conn.add<CURLOPT_XFERINFOFUNCTION>(client_stream_progress_callback_wrapper); | ||
642 | conn.add<CURLOPT_XFERINFODATA>(this); | ||
643 | conn.add<CURLOPT_NOPROGRESS>(0); | ||
644 | //conn.add<CURLOPT_VERBOSE>(1); | ||
645 | //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace); | ||
646 | conn.add<CURLOPT_URL>(url.c_str()); | ||
647 | conn.add<CURLOPT_HTTPHEADER>(headers.get()); | ||
648 | |||
649 | _backoff_type = backoff::none; | ||
650 | _backoff_amount = std::chrono::milliseconds(0); | ||
651 | for (;;) | ||
652 | { | ||
653 | bool failure = false; | ||
654 | try { | ||
655 | conn.perform(); | ||
656 | } catch (curl::curl_easy_exception error) | ||
657 | { | ||
658 | failure = true; | ||
659 | if ((error.get_code() == CURLE_ABORTED_BY_CALLBACK) && _stop) | ||
660 | { | ||
661 | break; | ||
662 | } else { | ||
663 | if (_backoff_type == backoff::none) | ||
664 | { | ||
665 | _established = false; | ||
666 | _backoff_type = backoff::network; | ||
667 | _backoff_amount = std::chrono::milliseconds(0); | ||
668 | } | ||
669 | } | ||
670 | } | ||
671 | |||
672 | if (!failure) | ||
673 | { | ||
674 | long response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); | ||
675 | if (response_code == 420) | ||
676 | { | ||
677 | if (_backoff_type == backoff::none) | ||
678 | { | ||
679 | _established = false; | ||
680 | _backoff_type = backoff::rate_limit; | ||
681 | _backoff_amount = std::chrono::minutes(1); | ||
682 | } | ||
683 | } else if (response_code != 200) | ||
684 | { | ||
685 | if (_backoff_type == backoff::none) | ||
686 | { | ||
687 | _established = false; | ||
688 | _backoff_type = backoff::http; | ||
689 | _backoff_amount = std::chrono::seconds(5); | ||
690 | } | ||
691 | } else { | ||
692 | break; | ||
693 | } | ||
694 | } | ||
695 | |||
696 | std::this_thread::sleep_for(_backoff_amount); | ||
697 | |||
698 | switch (_backoff_type) | ||
699 | { | ||
700 | case backoff::network: | ||
701 | { | ||
702 | if (_backoff_amount < std::chrono::seconds(16)) | ||
703 | { | ||
704 | _backoff_amount += std::chrono::milliseconds(250); | ||
705 | } | ||
706 | |||
707 | break; | ||
708 | } | ||
709 | |||
710 | case backoff::http: | ||
711 | { | ||
712 | if (_backoff_amount < std::chrono::seconds(320)) | ||
713 | { | ||
714 | _backoff_amount *= 2; | ||
715 | } | ||
716 | |||
717 | break; | ||
718 | } | ||
719 | |||
720 | case backoff::rate_limit: | ||
721 | { | ||
722 | _backoff_amount *= 2; | ||
723 | |||
724 | break; | ||
725 | } | ||
726 | } | ||
727 | } | ||
728 | } | ||
729 | |||
730 | int client::stream::write(char* ptr, size_t size, size_t nmemb) | ||
731 | { | ||
732 | for (size_t i = 0; i < size*nmemb; i++) | ||
733 | { | ||
734 | if (ptr[i] == '\r') | ||
735 | { | ||
736 | i++; // Skip the \n | ||
737 | |||
738 | if (!_buffer.empty()) | ||
739 | { | ||
740 | notification n(_buffer, _client._current_user); | ||
741 | if (n.getType() == notification::type::friends) | ||
742 | { | ||
743 | _established = true; | ||
744 | _backoff_type = backoff::none; | ||
745 | _backoff_amount = std::chrono::milliseconds(0); | ||
746 | } | ||
747 | |||
748 | { | ||
749 | std::lock_guard<std::mutex> _notify_lock(_notify_mutex); | ||
750 | |||
751 | if (_notify) | ||
752 | { | ||
753 | _notify(n); | ||
754 | } | ||
755 | } | ||
756 | |||
757 | _buffer = ""; | ||
758 | } | ||
759 | } else { | ||
760 | _buffer.push_back(ptr[i]); | ||
761 | } | ||
762 | } | ||
763 | |||
764 | { | ||
765 | std::lock_guard<std::mutex> _stall_lock(_stall_mutex); | ||
766 | time(&_last_write); | ||
767 | } | ||
768 | |||
769 | return size*nmemb; | ||
770 | } | ||
771 | |||
772 | int client::stream::progress() | ||
773 | { | ||
774 | if (_stop) | ||
775 | { | ||
776 | return 1; | ||
777 | } | ||
778 | |||
779 | if (_established) | ||
780 | { | ||
781 | std::lock_guard<std::mutex> _stall_lock(_stall_mutex); | ||
782 | if (difftime(time(NULL), _last_write) >= 90) | ||
783 | { | ||
784 | return 1; | ||
785 | } | ||
786 | } | ||
787 | |||
788 | return 0; | ||
789 | } | ||
790 | |||
410 | }; | 791 | }; |
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 @@ | |||
7 | #include <list> | 7 | #include <list> |
8 | #include <curl_easy.h> | 8 | #include <curl_easy.h> |
9 | #include <curl_header.h> | 9 | #include <curl_header.h> |
10 | #include <thread> | ||
11 | #include <mutex> | ||
12 | #include "notification.h" | ||
13 | #include <set> | ||
14 | #include <ctime> | ||
15 | #include <chrono> | ||
10 | 16 | ||
11 | namespace OAuth { | 17 | namespace OAuth { |
12 | class Consumer; | 18 | class Consumer; |
@@ -18,21 +24,82 @@ namespace twitter { | |||
18 | 24 | ||
19 | class client { | 25 | class client { |
20 | public: | 26 | public: |
27 | class stream { | ||
28 | public: | ||
29 | typedef std::function<void(notification _notification)> notify_callback; | ||
30 | |||
31 | stream(client& _client); | ||
32 | |||
33 | void setNotifyCallback(notify_callback _n); | ||
34 | |||
35 | bool isRunning() const; | ||
36 | void start(); | ||
37 | void stop(); | ||
38 | |||
39 | private: | ||
40 | enum class backoff { | ||
41 | none, | ||
42 | network, | ||
43 | http, | ||
44 | rate_limit | ||
45 | }; | ||
46 | |||
47 | void run(); | ||
48 | int progress(); | ||
49 | int write(char* ptr, size_t size, size_t nmemb); | ||
50 | |||
51 | friend int client_stream_progress_callback_wrapper(void* stream, curl_off_t, curl_off_t, curl_off_t, curl_off_t); | ||
52 | friend size_t client_stream_write_callback_wrapper(void* ptr, size_t size, size_t nmemb, void* stream); | ||
53 | |||
54 | client& _client; | ||
55 | notify_callback _notify; | ||
56 | bool _stop = false; | ||
57 | std::thread _thread; | ||
58 | std::mutex _running_mutex; | ||
59 | std::mutex _notify_mutex; | ||
60 | std::mutex _stall_mutex; | ||
61 | std::string _buffer; | ||
62 | time_t _last_write; | ||
63 | bool _established = false; | ||
64 | backoff _backoff_type = backoff::none; | ||
65 | std::chrono::milliseconds _backoff_amount; | ||
66 | }; | ||
67 | |||
21 | client(const auth& _auth); | 68 | client(const auth& _auth); |
22 | ~client(); | 69 | ~client(); |
23 | 70 | ||
24 | response updateStatus(std::string msg, tweet& result, std::list<long> media_ids = {}); | 71 | response updateStatus(std::string msg, tweet& result, tweet in_response_to = tweet(), std::list<long> media_ids = {}); |
25 | response uploadMedia(std::string media_type, const char* data, long data_length, long& media_id); | 72 | response uploadMedia(std::string media_type, const char* data, long data_length, long& media_id); |
26 | 73 | ||
74 | response follow(user_id toFollow); | ||
75 | response follow(user toFollow); | ||
76 | |||
77 | response unfollow(user_id toUnfollow); | ||
78 | response unfollow(user toUnfollow); | ||
79 | |||
80 | response getFriends(std::set<user_id>& result); | ||
81 | response getFollowers(std::set<user_id>& result); | ||
82 | |||
83 | const user& getUser() const; | ||
84 | |||
85 | void setUserStreamNotifyCallback(stream::notify_callback callback); | ||
86 | void startUserStream(); | ||
87 | void stopUserStream(); | ||
88 | |||
27 | private: | 89 | private: |
90 | friend class stream; | ||
91 | |||
28 | OAuth::Consumer* _oauth_consumer; | 92 | OAuth::Consumer* _oauth_consumer; |
29 | OAuth::Token* _oauth_token; | 93 | OAuth::Token* _oauth_token; |
30 | OAuth::Client* _oauth_client; | 94 | OAuth::Client* _oauth_client; |
31 | 95 | ||
32 | bool performGet(std::string url, long& response_code, json& result); | 96 | user _current_user; |
33 | bool performPost(std::string url, std::string dataStr, long& response_code, json& result); | 97 | stream _user_stream{*this}; |
34 | bool performMultiPost(std::string url, const curl_httppost* fields, long& response_code, json& result); | 98 | |
35 | response codeForError(int httpcode, json errors) const; | 99 | bool performGet(std::string url, long& response_code, std::string& result); |
100 | bool performPost(std::string url, std::string dataStr, long& response_code, std::string& result); | ||
101 | bool performMultiPost(std::string url, const curl_httppost* fields, long& response_code, std::string& result); | ||
102 | response codeForError(int httpcode, std::string errors) const; | ||
36 | }; | 103 | }; |
37 | 104 | ||
38 | }; | 105 | }; |
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 @@ | |||
1 | #include "direct_message.h" | ||
2 | |||
3 | namespace twitter { | ||
4 | |||
5 | direct_message::direct_message(std::string data) | ||
6 | { | ||
7 | |||
8 | } | ||
9 | |||
10 | }; | ||
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 @@ | |||
1 | #ifndef DIRECT_MESSAGE_H_2B2AE3F8 | ||
2 | #define DIRECT_MESSAGE_H_2B2AE3F8 | ||
3 | |||
4 | #include <string> | ||
5 | |||
6 | namespace twitter { | ||
7 | |||
8 | class direct_message { | ||
9 | public: | ||
10 | direct_message(std::string data); | ||
11 | }; | ||
12 | |||
13 | }; | ||
14 | |||
15 | #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 @@ | |||
1 | #include "list.h" | ||
2 | |||
3 | namespace twitter { | ||
4 | |||
5 | list::list() | ||
6 | { | ||
7 | |||
8 | } | ||
9 | |||
10 | list::list(std::string data) | ||
11 | { | ||
12 | |||
13 | } | ||
14 | |||
15 | }; | ||
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 @@ | |||
1 | #ifndef LIST_H_D7DEA7D8 | ||
2 | #define LIST_H_D7DEA7D8 | ||
3 | |||
4 | #include <string> | ||
5 | |||
6 | namespace twitter { | ||
7 | |||
8 | class list { | ||
9 | public: | ||
10 | list(); | ||
11 | list(std::string data); | ||
12 | }; | ||
13 | |||
14 | }; | ||
15 | |||
16 | #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 @@ | |||
1 | #include "notification.h" | ||
2 | #include <cassert> | ||
3 | #include <new> | ||
4 | #include <json.hpp> | ||
5 | |||
6 | using nlohmann::json; | ||
7 | |||
8 | namespace twitter { | ||
9 | |||
10 | notification::type notification::getType() const | ||
11 | { | ||
12 | return _type; | ||
13 | } | ||
14 | |||
15 | notification::notification() : _type(type::invalid) | ||
16 | { | ||
17 | |||
18 | } | ||
19 | |||
20 | notification::notification(std::string data, const user& current_user) | ||
21 | { | ||
22 | auto _data = json::parse(data); | ||
23 | |||
24 | if (_data.find("in_reply_to_status_id") != _data.end()) | ||
25 | { | ||
26 | _type = type::tweet; | ||
27 | |||
28 | new(&_tweet) tweet(data); | ||
29 | } else if (_data.find("event") != _data.end()) | ||
30 | { | ||
31 | std::string event = _data.at("event"); | ||
32 | user source(_data.at("source").dump()); | ||
33 | user target(_data.at("target").dump()); | ||
34 | |||
35 | if (event == "user_update") | ||
36 | { | ||
37 | _type = type::update_user; | ||
38 | |||
39 | new(&_user) user(source); | ||
40 | } else if (event == "block") | ||
41 | { | ||
42 | _type = type::block; | ||
43 | |||
44 | new(&_user) user(target); | ||
45 | } else if (event == "unblock") | ||
46 | { | ||
47 | _type = type::unblock; | ||
48 | |||
49 | new(&_user) user(target); | ||
50 | } else if (event == "favorite") | ||
51 | { | ||
52 | new(&_user_and_tweet._tweet) tweet(_data.at("target_object").dump()); | ||
53 | |||
54 | if (current_user == source) | ||
55 | { | ||
56 | _type = type::favorite; | ||
57 | |||
58 | new(&_user_and_tweet._user) user(target); | ||
59 | } else { | ||
60 | _type = type::favorited; | ||
61 | |||
62 | new(&_user_and_tweet._user) user(source); | ||
63 | } | ||
64 | } else if (event == "unfavorite") | ||
65 | { | ||
66 | new(&_user_and_tweet._tweet) tweet(_data.at("target_object").dump()); | ||
67 | |||
68 | if (current_user == source) | ||
69 | { | ||
70 | _type = type::unfavorite; | ||
71 | |||
72 | new(&_user_and_tweet._user) user(target); | ||
73 | } else { | ||
74 | _type = type::unfavorited; | ||
75 | |||
76 | new(&_user_and_tweet._user) user(source); | ||
77 | } | ||
78 | } else if (event == "follow") | ||
79 | { | ||
80 | if (current_user == source) | ||
81 | { | ||
82 | _type = type::follow; | ||
83 | |||
84 | new(&_user) user(target); | ||
85 | } else { | ||
86 | _type = type::followed; | ||
87 | |||
88 | new(&_user) user(source); | ||
89 | } | ||
90 | } else if (event == "unfollow") | ||
91 | { | ||
92 | _type = type::unfollow; | ||
93 | |||
94 | new(&_user) user(target); | ||
95 | } else if (event == "list_created") | ||
96 | { | ||
97 | _type = type::list_created; | ||
98 | |||
99 | new(&_list) list(_data.at("target_object").dump()); | ||
100 | } else if (event == "list_destroyed") | ||
101 | { | ||
102 | _type = type::list_destroyed; | ||
103 | |||
104 | new(&_list) list(_data.at("target_object").dump()); | ||
105 | } else if (event == "list_updated") | ||
106 | { | ||
107 | _type = type::list_updated; | ||
108 | |||
109 | new(&_list) list(_data.at("target_object").dump()); | ||
110 | } else if (event == "list_member_added") | ||
111 | { | ||
112 | new(&_user_and_list._list) list(_data.at("target_object").dump()); | ||
113 | |||
114 | if (current_user == source) | ||
115 | { | ||
116 | _type = type::list_add; | ||
117 | |||
118 | new(&_user_and_list._user) user(target); | ||
119 | } else { | ||
120 | _type = type::list_added; | ||
121 | |||
122 | new(&_user_and_list._user) user(source); | ||
123 | } | ||
124 | } else if (event == "list_member_removed") | ||
125 | { | ||
126 | new(&_user_and_list._list) list(_data.at("target_object").dump()); | ||
127 | |||
128 | if (current_user == source) | ||
129 | { | ||
130 | _type = type::list_remove; | ||
131 | |||
132 | new(&_user_and_list._user) user(target); | ||
133 | } else { | ||
134 | _type = type::list_removed; | ||
135 | |||
136 | new(&_user_and_list._user) user(source); | ||
137 | } | ||
138 | } else if (event == "list_member_subscribe") | ||
139 | { | ||
140 | new(&_user_and_list._list) list(_data.at("target_object").dump()); | ||
141 | |||
142 | if (current_user == source) | ||
143 | { | ||
144 | _type = type::list_subscribe; | ||
145 | |||
146 | new(&_user_and_list._user) user(target); | ||
147 | } else { | ||
148 | _type = type::list_subscribed; | ||
149 | |||
150 | new(&_user_and_list._user) user(source); | ||
151 | } | ||
152 | } else if (event == "list_member_unsubscribe") | ||
153 | { | ||
154 | new(&_user_and_list._list) list(_data.at("target_object").dump()); | ||
155 | |||
156 | if (current_user == source) | ||
157 | { | ||
158 | _type = type::list_unsubscribe; | ||
159 | |||
160 | new(&_user_and_list._user) user(target); | ||
161 | } else { | ||
162 | _type = type::list_unsubscribed; | ||
163 | |||
164 | new(&_user_and_list._user) user(source); | ||
165 | } | ||
166 | } else if (event == "quoted_tweet") | ||
167 | { | ||
168 | _type = type::quoted; | ||
169 | |||
170 | new(&_user_and_tweet._user) user(source); | ||
171 | new(&_user_and_tweet._tweet) tweet(_data.at("target_object").dump()); | ||
172 | } | ||
173 | } else if (_data.find("warning") != _data.end()) | ||
174 | { | ||
175 | new(&_warning) std::string(_data.at("warning").at("message").get<std::string>()); | ||
176 | |||
177 | if (_data.at("warning").at("code") == "FALLING_BEHIND") | ||
178 | { | ||
179 | _type = type::stall; | ||
180 | } else if (_data.at("warning").at("code") == "FOLLOWS_OVER_LIMIT") | ||
181 | { | ||
182 | _type = type::follow_limit; | ||
183 | } else { | ||
184 | _type = type::unknown_warning; | ||
185 | } | ||
186 | } else if (_data.find("delete") != _data.end()) | ||
187 | { | ||
188 | _type = type::deletion; | ||
189 | |||
190 | _user_id_and_tweet_id._tweet_id = _data.at("delete").at("status").at("id"); | ||
191 | _user_id_and_tweet_id._user_id = _data.at("delete").at("status").at("user_id"); | ||
192 | } else if (_data.find("scrub_geo") != _data.end()) | ||
193 | { | ||
194 | _type = type::scrub_location; | ||
195 | |||
196 | _user_id_and_tweet_id._tweet_id = _data.at("scrub_geo").at("up_to_status_id"); | ||
197 | _user_id_and_tweet_id._user_id = _data.at("scrub_geo").at("user_id"); | ||
198 | } else if (_data.find("limit") != _data.end()) | ||
199 | { | ||
200 | _type = type::limit; | ||
201 | |||
202 | _limit = _data.at("limit").at("track"); | ||
203 | } else if (_data.find("status_withheld") != _data.end()) | ||
204 | { | ||
205 | _type = type::withhold_status; | ||
206 | |||
207 | _withhold_status._user_id = _data.at("status_withheld").at("user_id"); | ||
208 | _withhold_status._tweet_id = _data.at("status_withheld").at("id"); | ||
209 | |||
210 | new(&_withhold_status._countries) std::vector<std::string>(); | ||
211 | for (auto s : _data.at("status_withheld").at("withheld_in_countries")) | ||
212 | { | ||
213 | _withhold_status._countries.push_back(s); | ||
214 | } | ||
215 | } else if (_data.find("user_withheld") != _data.end()) | ||
216 | { | ||
217 | _type = type::withhold_user; | ||
218 | |||
219 | _withhold_user._user_id = _data.at("user_withheld").at("id"); | ||
220 | |||
221 | new(&_withhold_user._countries) std::vector<std::string>(); | ||
222 | for (auto s : _data.at("user_withheld").at("withheld_in_countries")) | ||
223 | { | ||
224 | _withhold_user._countries.push_back(s); | ||
225 | } | ||
226 | } else if (_data.find("disconnect") != _data.end()) | ||
227 | { | ||
228 | _type = type::disconnect; | ||
229 | |||
230 | switch (_data.at("disconnect").at("code").get<int>()) | ||
231 | { | ||
232 | case 1: _disconnect = disconnect_code::shutdown; break; | ||
233 | case 2: _disconnect = disconnect_code::duplicate; break; | ||
234 | case 4: _disconnect = disconnect_code::stall; break; | ||
235 | case 5: _disconnect = disconnect_code::normal; break; | ||
236 | case 6: _disconnect = disconnect_code::token_revoked; break; | ||
237 | case 7: _disconnect = disconnect_code::admin_logout; break; | ||
238 | case 9: _disconnect = disconnect_code::limit; break; | ||
239 | case 10: _disconnect = disconnect_code::exception; break; | ||
240 | case 11: _disconnect = disconnect_code::broker; break; | ||
241 | case 12: _disconnect = disconnect_code::load; break; | ||
242 | default: _disconnect = disconnect_code::unknown; | ||
243 | } | ||
244 | } else if (_data.find("friends") != _data.end()) | ||
245 | { | ||
246 | _type = type::friends; | ||
247 | |||
248 | new(&_friends) std::set<user_id>(_data.at("friends").begin(), _data.at("friends").end()); | ||
249 | } else if (_data.find("direct_message") != _data.end()) | ||
250 | { | ||
251 | _type = type::direct; | ||
252 | |||
253 | new(&_direct_message) direct_message(_data.at("direct_message").dump()); | ||
254 | } else { | ||
255 | _type = type::unknown; | ||
256 | } | ||
257 | } | ||
258 | |||
259 | notification::notification(const notification& other) | ||
260 | { | ||
261 | _type = other._type; | ||
262 | |||
263 | switch (_type) | ||
264 | { | ||
265 | case type::tweet: | ||
266 | { | ||
267 | new(&_tweet) tweet(other._tweet); | ||
268 | |||
269 | break; | ||
270 | } | ||
271 | |||
272 | case type::update_user: | ||
273 | case type::block: | ||
274 | case type::unblock: | ||
275 | case type::follow: | ||
276 | case type::followed: | ||
277 | case type::unfollow: | ||
278 | { | ||
279 | new(&_user) user(other._user); | ||
280 | |||
281 | break; | ||
282 | } | ||
283 | |||
284 | case type::favorite: | ||
285 | case type::favorited: | ||
286 | case type::unfavorite: | ||
287 | case type::unfavorited: | ||
288 | case type::quoted: | ||
289 | { | ||
290 | new(&_user_and_tweet._user) user(other._user_and_tweet._user); | ||
291 | new(&_user_and_tweet._tweet) tweet(other._user_and_tweet._tweet); | ||
292 | |||
293 | break; | ||
294 | } | ||
295 | |||
296 | case type::list_created: | ||
297 | case type::list_destroyed: | ||
298 | case type::list_updated: | ||
299 | { | ||
300 | new(&_list) list(other._list); | ||
301 | |||
302 | break; | ||
303 | } | ||
304 | |||
305 | case type::list_add: | ||
306 | case type::list_added: | ||
307 | case type::list_remove: | ||
308 | case type::list_removed: | ||
309 | case type::list_subscribe: | ||
310 | case type::list_subscribed: | ||
311 | case type::list_unsubscribe: | ||
312 | case type::list_unsubscribed: | ||
313 | { | ||
314 | new(&_user_and_list._user) user(other._user_and_list._user); | ||
315 | new(&_user_and_list._list) list(other._user_and_list._list); | ||
316 | |||
317 | break; | ||
318 | } | ||
319 | |||
320 | case type::stall: | ||
321 | case type::follow_limit: | ||
322 | case type::unknown_warning: | ||
323 | { | ||
324 | new(&_warning) std::string(other._warning); | ||
325 | |||
326 | break; | ||
327 | } | ||
328 | |||
329 | case type::deletion: | ||
330 | case type::scrub_location: | ||
331 | { | ||
332 | _user_id_and_tweet_id._user_id = other._user_id_and_tweet_id._user_id; | ||
333 | _user_id_and_tweet_id._tweet_id = other._user_id_and_tweet_id._tweet_id; | ||
334 | |||
335 | break; | ||
336 | } | ||
337 | |||
338 | case type::limit: | ||
339 | { | ||
340 | _limit = other._limit; | ||
341 | |||
342 | break; | ||
343 | } | ||
344 | |||
345 | case type::withhold_status: | ||
346 | { | ||
347 | _withhold_status._user_id = other._withhold_status._user_id; | ||
348 | _withhold_status._tweet_id = other._withhold_status._tweet_id; | ||
349 | new(&_withhold_status._countries) std::vector<std::string>(other._withhold_status._countries); | ||
350 | |||
351 | break; | ||
352 | } | ||
353 | |||
354 | case type::withhold_user: | ||
355 | { | ||
356 | _withhold_user._user_id = other._withhold_user._user_id; | ||
357 | new(&_withhold_user._countries) std::vector<std::string>(other._withhold_user._countries); | ||
358 | |||
359 | break; | ||
360 | } | ||
361 | |||
362 | case type::disconnect: | ||
363 | { | ||
364 | _disconnect = other._disconnect; | ||
365 | |||
366 | break; | ||
367 | } | ||
368 | |||
369 | case type::friends: | ||
370 | { | ||
371 | new(&_friends) std::set<user_id>(other._friends); | ||
372 | |||
373 | break; | ||
374 | } | ||
375 | |||
376 | case type::direct: | ||
377 | { | ||
378 | new(&_direct_message) direct_message(other._direct_message); | ||
379 | |||
380 | break; | ||
381 | } | ||
382 | } | ||
383 | } | ||
384 | |||
385 | notification& notification::operator=(const notification& other) | ||
386 | { | ||
387 | this->~notification(); | ||
388 | |||
389 | _type = other._type; | ||
390 | |||
391 | switch (_type) | ||
392 | { | ||
393 | case type::tweet: | ||
394 | { | ||
395 | new(&_tweet) tweet(other._tweet); | ||
396 | |||
397 | break; | ||
398 | } | ||
399 | |||
400 | case type::update_user: | ||
401 | case type::block: | ||
402 | case type::unblock: | ||
403 | case type::follow: | ||
404 | case type::followed: | ||
405 | case type::unfollow: | ||
406 | { | ||
407 | new(&_user) user(other._user); | ||
408 | |||
409 | break; | ||
410 | } | ||
411 | |||
412 | case type::favorite: | ||
413 | case type::favorited: | ||
414 | case type::unfavorite: | ||
415 | case type::unfavorited: | ||
416 | case type::quoted: | ||
417 | { | ||
418 | new(&_user_and_tweet._user) user(other._user_and_tweet._user); | ||
419 | new(&_user_and_tweet._tweet) tweet(other._user_and_tweet._tweet); | ||
420 | |||
421 | break; | ||
422 | } | ||
423 | |||
424 | case type::list_created: | ||
425 | case type::list_destroyed: | ||
426 | case type::list_updated: | ||
427 | { | ||
428 | new(&_list) list(other._list); | ||
429 | |||
430 | break; | ||
431 | } | ||
432 | |||
433 | case type::list_add: | ||
434 | case type::list_added: | ||
435 | case type::list_remove: | ||
436 | case type::list_removed: | ||
437 | case type::list_subscribe: | ||
438 | case type::list_subscribed: | ||
439 | case type::list_unsubscribe: | ||
440 | case type::list_unsubscribed: | ||
441 | { | ||
442 | new(&_user_and_list._user) user(other._user_and_list._user); | ||
443 | new(&_user_and_list._list) list(other._user_and_list._list); | ||
444 | |||
445 | break; | ||
446 | } | ||
447 | |||
448 | case type::stall: | ||
449 | case type::follow_limit: | ||
450 | case type::unknown_warning: | ||
451 | { | ||
452 | new(&_warning) std::string(other._warning); | ||
453 | |||
454 | break; | ||
455 | } | ||
456 | |||
457 | case type::deletion: | ||
458 | case type::scrub_location: | ||
459 | { | ||
460 | _user_id_and_tweet_id._user_id = other._user_id_and_tweet_id._user_id; | ||
461 | _user_id_and_tweet_id._tweet_id = other._user_id_and_tweet_id._tweet_id; | ||
462 | |||
463 | break; | ||
464 | } | ||
465 | |||
466 | case type::limit: | ||
467 | { | ||
468 | _limit = other._limit; | ||
469 | |||
470 | break; | ||
471 | } | ||
472 | |||
473 | case type::withhold_status: | ||
474 | { | ||
475 | _withhold_status._user_id = other._withhold_status._user_id; | ||
476 | _withhold_status._tweet_id = other._withhold_status._tweet_id; | ||
477 | new(&_withhold_status._countries) std::vector<std::string>(other._withhold_status._countries); | ||
478 | |||
479 | break; | ||
480 | } | ||
481 | |||
482 | case type::withhold_user: | ||
483 | { | ||
484 | _withhold_user._user_id = other._withhold_user._user_id; | ||
485 | new(&_withhold_user._countries) std::vector<std::string>(other._withhold_user._countries); | ||
486 | |||
487 | break; | ||
488 | } | ||
489 | |||
490 | case type::disconnect: | ||
491 | { | ||
492 | _disconnect = other._disconnect; | ||
493 | |||
494 | break; | ||
495 | } | ||
496 | |||
497 | case type::friends: | ||
498 | { | ||
499 | new(&_friends) std::set<user_id>(other._friends); | ||
500 | |||
501 | break; | ||
502 | } | ||
503 | |||
504 | case type::direct: | ||
505 | { | ||
506 | new(&_direct_message) direct_message(other._direct_message); | ||
507 | |||
508 | break; | ||
509 | } | ||
510 | } | ||
511 | |||
512 | return *this; | ||
513 | } | ||
514 | |||
515 | notification::~notification() | ||
516 | { | ||
517 | switch (_type) | ||
518 | { | ||
519 | case type::tweet: | ||
520 | { | ||
521 | _tweet.~tweet(); | ||
522 | |||
523 | break; | ||
524 | } | ||
525 | |||
526 | case type::update_user: | ||
527 | case type::block: | ||
528 | case type::unblock: | ||
529 | case type::follow: | ||
530 | case type::followed: | ||
531 | case type::unfollow: | ||
532 | { | ||
533 | _user.~user(); | ||
534 | |||
535 | break; | ||
536 | } | ||
537 | |||
538 | case type::favorite: | ||
539 | case type::favorited: | ||
540 | case type::unfavorite: | ||
541 | case type::unfavorited: | ||
542 | case type::quoted: | ||
543 | { | ||
544 | _user_and_tweet._user.~user(); | ||
545 | _user_and_tweet._tweet.~tweet(); | ||
546 | |||
547 | break; | ||
548 | } | ||
549 | |||
550 | case type::list_created: | ||
551 | case type::list_destroyed: | ||
552 | case type::list_updated: | ||
553 | { | ||
554 | _list.~list(); | ||
555 | |||
556 | break; | ||
557 | } | ||
558 | |||
559 | case type::list_add: | ||
560 | case type::list_added: | ||
561 | case type::list_remove: | ||
562 | case type::list_removed: | ||
563 | case type::list_subscribe: | ||
564 | case type::list_subscribed: | ||
565 | case type::list_unsubscribe: | ||
566 | case type::list_unsubscribed: | ||
567 | { | ||
568 | _user_and_list._user.~user(); | ||
569 | _user_and_list._list.~list(); | ||
570 | |||
571 | break; | ||
572 | } | ||
573 | |||
574 | case type::stall: | ||
575 | case type::follow_limit: | ||
576 | { | ||
577 | using string_type = std::string; | ||
578 | _warning.~string_type(); | ||
579 | |||
580 | break; | ||
581 | } | ||
582 | |||
583 | case type::withhold_status: | ||
584 | { | ||
585 | using list_type = std::vector<std::string>; | ||
586 | _withhold_status._countries.~list_type(); | ||
587 | |||
588 | break; | ||
589 | } | ||
590 | |||
591 | case type::withhold_user: | ||
592 | { | ||
593 | using list_type = std::vector<std::string>; | ||
594 | _withhold_user._countries.~list_type(); | ||
595 | |||
596 | break; | ||
597 | } | ||
598 | |||
599 | case type::friends: | ||
600 | { | ||
601 | using list_type = std::set<user_id>; | ||
602 | _friends.~list_type(); | ||
603 | |||
604 | break; | ||
605 | } | ||
606 | |||
607 | case type::direct: | ||
608 | { | ||
609 | _direct_message.~direct_message(); | ||
610 | |||
611 | break; | ||
612 | } | ||
613 | } | ||
614 | } | ||
615 | |||
616 | tweet notification::getTweet() const | ||
617 | { | ||
618 | switch (_type) | ||
619 | { | ||
620 | case type::tweet: | ||
621 | { | ||
622 | return _tweet; | ||
623 | } | ||
624 | |||
625 | case type::favorite: | ||
626 | case type::favorited: | ||
627 | case type::unfavorite: | ||
628 | case type::unfavorited: | ||
629 | case type::quoted: | ||
630 | { | ||
631 | return _user_and_tweet._tweet; | ||
632 | } | ||
633 | |||
634 | default: | ||
635 | { | ||
636 | assert(false); | ||
637 | |||
638 | return tweet(); | ||
639 | } | ||
640 | } | ||
641 | } | ||
642 | |||
643 | user notification::getUser() const | ||
644 | { | ||
645 | switch (_type) | ||
646 | { | ||
647 | case type::update_user: | ||
648 | case type::block: | ||
649 | case type::unblock: | ||
650 | case type::follow: | ||
651 | case type::followed: | ||
652 | case type::unfollow: | ||
653 | { | ||
654 | return _user; | ||
655 | } | ||
656 | |||
657 | case type::favorite: | ||
658 | case type::favorited: | ||
659 | case type::unfavorite: | ||
660 | case type::unfavorited: | ||
661 | case type::quoted: | ||
662 | { | ||
663 | return _user_and_tweet._user; | ||
664 | } | ||
665 | |||
666 | case type::list_add: | ||
667 | case type::list_added: | ||
668 | case type::list_remove: | ||
669 | case type::list_removed: | ||
670 | case type::list_subscribe: | ||
671 | case type::list_subscribed: | ||
672 | case type::list_unsubscribe: | ||
673 | case type::list_unsubscribed: | ||
674 | { | ||
675 | return _user_and_list._user; | ||
676 | } | ||
677 | |||
678 | default: | ||
679 | { | ||
680 | assert(false); | ||
681 | |||
682 | return user(); | ||
683 | } | ||
684 | } | ||
685 | } | ||
686 | |||
687 | list notification::getList() const | ||
688 | { | ||
689 | switch (_type) | ||
690 | { | ||
691 | case type::list_created: | ||
692 | case type::list_destroyed: | ||
693 | case type::list_updated: | ||
694 | { | ||
695 | return _list; | ||
696 | } | ||
697 | |||
698 | case type::list_add: | ||
699 | case type::list_added: | ||
700 | case type::list_remove: | ||
701 | case type::list_removed: | ||
702 | case type::list_subscribe: | ||
703 | case type::list_subscribed: | ||
704 | case type::list_unsubscribe: | ||
705 | case type::list_unsubscribed: | ||
706 | { | ||
707 | return _user_and_list._list; | ||
708 | } | ||
709 | |||
710 | default: | ||
711 | { | ||
712 | assert(false); | ||
713 | |||
714 | return list(); | ||
715 | } | ||
716 | } | ||
717 | } | ||
718 | |||
719 | tweet_id notification::getTweetID() const | ||
720 | { | ||
721 | switch (_type) | ||
722 | { | ||
723 | case type::deletion: | ||
724 | case type::scrub_location: | ||
725 | { | ||
726 | return _user_id_and_tweet_id._tweet_id; | ||
727 | } | ||
728 | |||
729 | case type::withhold_status: | ||
730 | { | ||
731 | return _withhold_status._tweet_id; | ||
732 | } | ||
733 | |||
734 | default: | ||
735 | { | ||
736 | assert(false); | ||
737 | |||
738 | return 0; | ||
739 | } | ||
740 | } | ||
741 | } | ||
742 | |||
743 | user_id notification::getUserID() const | ||
744 | { | ||
745 | switch (_type) | ||
746 | { | ||
747 | case type::deletion: | ||
748 | case type::scrub_location: | ||
749 | { | ||
750 | return _user_id_and_tweet_id._user_id; | ||
751 | } | ||
752 | |||
753 | case type::withhold_status: | ||
754 | { | ||
755 | return _withhold_status._user_id; | ||
756 | } | ||
757 | |||
758 | case type::withhold_user: | ||
759 | { | ||
760 | return _withhold_user._user_id; | ||
761 | } | ||
762 | |||
763 | default: | ||
764 | { | ||
765 | assert(false); | ||
766 | |||
767 | return 0; | ||
768 | } | ||
769 | } | ||
770 | } | ||
771 | |||
772 | std::vector<std::string> notification::getCountries() const | ||
773 | { | ||
774 | switch (_type) | ||
775 | { | ||
776 | case type::withhold_status: | ||
777 | { | ||
778 | return _withhold_status._countries; | ||
779 | } | ||
780 | |||
781 | case type::withhold_user: | ||
782 | { | ||
783 | return _withhold_user._countries; | ||
784 | } | ||
785 | |||
786 | default: | ||
787 | { | ||
788 | assert(false); | ||
789 | |||
790 | return std::vector<std::string>(); | ||
791 | } | ||
792 | } | ||
793 | } | ||
794 | |||
795 | disconnect_code notification::getDisconnectCode() const | ||
796 | { | ||
797 | assert(_type == type::disconnect); | ||
798 | |||
799 | return _disconnect; | ||
800 | } | ||
801 | |||
802 | std::set<user_id> notification::getFriends() const | ||
803 | { | ||
804 | assert(_type == type::friends); | ||
805 | |||
806 | return _friends; | ||
807 | } | ||
808 | |||
809 | direct_message notification::getDirectMessage() const | ||
810 | { | ||
811 | assert(_type == type::direct); | ||
812 | |||
813 | return _direct_message; | ||
814 | } | ||
815 | |||
816 | int notification::getLimit() const | ||
817 | { | ||
818 | assert(_type == type::limit); | ||
819 | |||
820 | return _limit; | ||
821 | } | ||
822 | |||
823 | std::string notification::getWarning() const | ||
824 | { | ||
825 | switch (_type) | ||
826 | { | ||
827 | case type::stall: | ||
828 | case type::follow_limit: | ||
829 | case type::unknown_warning: | ||
830 | { | ||
831 | return _warning; | ||
832 | } | ||
833 | |||
834 | default: | ||
835 | { | ||
836 | assert(false); | ||
837 | |||
838 | return ""; | ||
839 | } | ||
840 | } | ||
841 | } | ||
842 | |||
843 | notification::operator bool() const | ||
844 | { | ||
845 | return _type != type::invalid; | ||
846 | } | ||
847 | |||
848 | }; | ||
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 @@ | |||
1 | #ifndef NOTIFICATION_H_69AEF4CC | ||
2 | #define NOTIFICATION_H_69AEF4CC | ||
3 | |||
4 | #include <string> | ||
5 | #include <vector> | ||
6 | #include <set> | ||
7 | #include "tweet.h" | ||
8 | #include "user.h" | ||
9 | #include "list.h" | ||
10 | #include "direct_message.h" | ||
11 | |||
12 | namespace twitter { | ||
13 | |||
14 | enum class disconnect_code { | ||
15 | shutdown, | ||
16 | duplicate, | ||
17 | stall, | ||
18 | normal, | ||
19 | token_revoked, | ||
20 | admin_logout, | ||
21 | limit, | ||
22 | exception, | ||
23 | broker, | ||
24 | load, | ||
25 | unknown | ||
26 | }; | ||
27 | |||
28 | class notification { | ||
29 | public: | ||
30 | enum class type { | ||
31 | // Tweet object | ||
32 | tweet, | ||
33 | |||
34 | // User object | ||
35 | update_user, | ||
36 | block, | ||
37 | unblock, | ||
38 | follow, | ||
39 | followed, | ||
40 | unfollow, | ||
41 | |||
42 | // User and tweet | ||
43 | favorite, | ||
44 | favorited, | ||
45 | unfavorite, | ||
46 | unfavorited, | ||
47 | quoted, | ||
48 | |||
49 | // List | ||
50 | list_created, | ||
51 | list_destroyed, | ||
52 | list_updated, | ||
53 | |||
54 | // User and list | ||
55 | list_add, | ||
56 | list_added, | ||
57 | list_remove, | ||
58 | list_removed, | ||
59 | list_subscribe, | ||
60 | list_subscribed, | ||
61 | list_unsubscribe, | ||
62 | list_unsubscribed, | ||
63 | |||
64 | // Warning | ||
65 | stall, | ||
66 | follow_limit, | ||
67 | unknown_warning, | ||
68 | |||
69 | // User ID and tweet ID | ||
70 | deletion, | ||
71 | scrub_location, | ||
72 | |||
73 | // Special | ||
74 | limit, | ||
75 | withhold_status, | ||
76 | withhold_user, | ||
77 | disconnect, | ||
78 | friends, | ||
79 | direct, | ||
80 | |||
81 | // Nothing | ||
82 | unknown, | ||
83 | invalid | ||
84 | }; | ||
85 | |||
86 | type getType() const; | ||
87 | |||
88 | notification(); | ||
89 | notification(std::string data, const user& current_user); | ||
90 | notification(const notification& other); | ||
91 | notification& operator=(const notification& other); | ||
92 | ~notification(); | ||
93 | |||
94 | tweet getTweet() const; | ||
95 | user getUser() const; | ||
96 | list getList() const; | ||
97 | tweet_id getTweetID() const; | ||
98 | user_id getUserID() const; | ||
99 | std::vector<std::string> getCountries() const; | ||
100 | disconnect_code getDisconnectCode() const; | ||
101 | std::set<user_id> getFriends() const; | ||
102 | direct_message getDirectMessage() const; | ||
103 | int getLimit() const; | ||
104 | std::string getWarning() const; | ||
105 | |||
106 | operator bool() const; | ||
107 | |||
108 | private: | ||
109 | union { | ||
110 | tweet _tweet; | ||
111 | user _user; | ||
112 | list _list; | ||
113 | struct { | ||
114 | user _user; | ||
115 | tweet _tweet; | ||
116 | } _user_and_tweet; | ||
117 | struct { | ||
118 | user _user; | ||
119 | list _list; | ||
120 | } _user_and_list; | ||
121 | std::string _warning; | ||
122 | struct { | ||
123 | user_id _user_id; | ||
124 | tweet_id _tweet_id; | ||
125 | } _user_id_and_tweet_id; | ||
126 | int _limit; | ||
127 | struct { | ||
128 | user_id _user_id; | ||
129 | tweet_id _tweet_id; | ||
130 | std::vector<std::string> _countries; | ||
131 | } _withhold_status; | ||
132 | struct { | ||
133 | user_id _user_id; | ||
134 | std::vector<std::string> _countries; | ||
135 | } _withhold_user; | ||
136 | disconnect_code _disconnect; | ||
137 | std::set<user_id> _friends; | ||
138 | direct_message _direct_message; | ||
139 | }; | ||
140 | type _type; | ||
141 | }; | ||
142 | |||
143 | }; | ||
144 | |||
145 | #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 @@ | |||
1 | #include "tweet.h" | 1 | #include "tweet.h" |
2 | #include <json.hpp> | ||
3 | |||
4 | using nlohmann::json; | ||
2 | 5 | ||
3 | namespace twitter { | 6 | namespace twitter { |
4 | 7 | ||
5 | tweet::tweet() | 8 | tweet::tweet() : _valid(false) |
9 | { | ||
10 | |||
11 | } | ||
12 | |||
13 | tweet::tweet(std::string data) : _valid(true) | ||
14 | { | ||
15 | auto _data = json::parse(data); | ||
16 | _id = _data.at("id"); | ||
17 | _text = _data.at("text"); | ||
18 | _author = user(_data.at("user").dump()); | ||
19 | } | ||
20 | |||
21 | tweet_id tweet::getID() const | ||
22 | { | ||
23 | return _id; | ||
24 | } | ||
25 | |||
26 | std::string tweet::getText() const | ||
27 | { | ||
28 | return _text; | ||
29 | } | ||
30 | |||
31 | const user& tweet::getAuthor() const | ||
6 | { | 32 | { |
7 | _valid = false; | 33 | return _author; |
8 | } | 34 | } |
9 | 35 | ||
10 | tweet::tweet(const json& data) | 36 | tweet::operator bool() const |
11 | { | 37 | { |
12 | _valid = true; | 38 | return _valid; |
13 | } | 39 | } |
14 | 40 | ||
15 | }; | 41 | }; |
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 @@ | |||
1 | #ifndef TWEET_H_CE980721 | 1 | #ifndef TWEET_H_CE980721 |
2 | #define TWEET_H_CE980721 | 2 | #define TWEET_H_CE980721 |
3 | 3 | ||
4 | #include <json.hpp> | 4 | #include <string> |
5 | 5 | #include "user.h" | |
6 | using nlohmann::json; | ||
7 | 6 | ||
8 | namespace twitter { | 7 | namespace twitter { |
9 | 8 | ||
9 | typedef unsigned long long tweet_id; | ||
10 | |||
10 | class tweet { | 11 | class tweet { |
11 | public: | 12 | public: |
12 | tweet(); | 13 | tweet(); |
13 | tweet(const json& _data); | 14 | tweet(std::string data); |
15 | |||
16 | tweet_id getID() const; | ||
17 | std::string getText() const; | ||
18 | const user& getAuthor() const; | ||
19 | |||
20 | operator bool() const; | ||
14 | 21 | ||
15 | private: | 22 | private: |
16 | bool _valid; | 23 | bool _valid; |
24 | tweet_id _id; | ||
25 | std::string _text; | ||
26 | user _author; | ||
17 | }; | 27 | }; |
18 | 28 | ||
19 | }; | 29 | }; |
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 { | |||
12 | #include "auth.h" | 12 | #include "auth.h" |
13 | #include "client.h" | 13 | #include "client.h" |
14 | #include "tweet.h" | 14 | #include "tweet.h" |
15 | #include "user.h" | ||
16 | #include "notification.h" | ||
17 | #include "list.h" | ||
18 | #include "direct_message.h" | ||
15 | 19 | ||
16 | #endif /* end of include guard: TWITTER_H_AC7A7666 */ | 20 | #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 @@ | |||
1 | #include "user.h" | ||
2 | #include <json.hpp> | ||
3 | |||
4 | using nlohmann::json; | ||
5 | |||
6 | namespace twitter { | ||
7 | |||
8 | user::user() : _valid(false) | ||
9 | { | ||
10 | |||
11 | } | ||
12 | |||
13 | user::user(std::string data) : _valid(true) | ||
14 | { | ||
15 | auto _data = json::parse(data); | ||
16 | _id = _data.at("id"); | ||
17 | _screen_name = _data.at("screen_name"); | ||
18 | _name = _data.at("name"); | ||
19 | } | ||
20 | |||
21 | user_id user::getID() const | ||
22 | { | ||
23 | return _id; | ||
24 | } | ||
25 | |||
26 | std::string user::getScreenName() const | ||
27 | { | ||
28 | return _screen_name; | ||
29 | } | ||
30 | |||
31 | std::string user::getName() const | ||
32 | { | ||
33 | return _name; | ||
34 | } | ||
35 | |||
36 | user::operator bool() const | ||
37 | { | ||
38 | return _valid; | ||
39 | } | ||
40 | |||
41 | bool user::operator==(const user& other) const | ||
42 | { | ||
43 | return _id == other._id; | ||
44 | } | ||
45 | |||
46 | }; | ||
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 @@ | |||
1 | #ifndef USER_H_BF3AB38C | ||
2 | #define USER_H_BF3AB38C | ||
3 | |||
4 | #include <string> | ||
5 | |||
6 | namespace twitter { | ||
7 | |||
8 | typedef unsigned long long user_id; | ||
9 | |||
10 | class user { | ||
11 | public: | ||
12 | user(); | ||
13 | user(std::string data); | ||
14 | |||
15 | user_id getID() const; | ||
16 | std::string getScreenName() const; | ||
17 | std::string getName() const; | ||
18 | |||
19 | operator bool() const; | ||
20 | bool operator==(const user& other) const; | ||
21 | |||
22 | private: | ||
23 | bool _valid = false; | ||
24 | user_id _id; | ||
25 | std::string _screen_name; | ||
26 | std::string _name; | ||
27 | }; | ||
28 | |||
29 | }; | ||
30 | |||
31 | #endif /* end of include guard: USER_H_BF3AB38C */ | ||