about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/bounding_box.h88
-rw-r--r--src/client.cpp1066
-rw-r--r--src/client.h129
-rw-r--r--src/codes.cpp33
-rw-r--r--src/codes.h193
-rw-r--r--src/configuration.cpp101
-rw-r--r--src/configuration.h101
-rw-r--r--src/direct_message.h5
-rw-r--r--src/list.cpp5
-rw-r--r--src/list.h6
-rw-r--r--src/notification.cpp269
-rw-r--r--src/notification.h213
-rw-r--r--src/stream.cpp291
-rw-r--r--src/stream.h67
-rw-r--r--src/tweet.cpp141
-rw-r--r--src/tweet.h77
-rw-r--r--src/twitter.h1
-rw-r--r--src/user.cpp48
-rw-r--r--src/user.h44
-rw-r--r--src/util.h6
m---------vendor/liboauthcpp0
22 files changed, 1541 insertions, 1345 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index a0a1b0b..74f1f64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -10,7 +10,7 @@ include_directories(vendor/liboauthcpp/include)
10add_subdirectory(vendor/curlcpp) 10add_subdirectory(vendor/curlcpp)
11include_directories(${CURLCPP_SOURCE_DIR}/include) 11include_directories(${CURLCPP_SOURCE_DIR}/include)
12 12
13add_library(twitter++ src/client.cpp src/auth.cpp src/tweet.cpp src/codes.cpp src/notification.cpp src/direct_message.cpp src/list.cpp src/user.cpp src/configuration.cpp) 13add_library(twitter++ src/client.cpp src/stream.cpp src/auth.cpp src/tweet.cpp src/codes.cpp src/notification.cpp src/direct_message.cpp src/list.cpp src/user.cpp src/configuration.cpp)
14set_property(TARGET twitter++ PROPERTY CXX_STANDARD 11) 14set_property(TARGET twitter++ PROPERTY CXX_STANDARD 11)
15set_property(TARGET twitter++ PROPERTY CXX_STANDARD_REQUIRED ON) 15set_property(TARGET twitter++ PROPERTY CXX_STANDARD_REQUIRED ON)
16target_link_libraries(twitter++ oauthcpp curlcpp curl pthread) 16target_link_libraries(twitter++ oauthcpp curlcpp curl pthread)
diff --git a/src/bounding_box.h b/src/bounding_box.h new file mode 100644 index 0000000..25c7790 --- /dev/null +++ b/src/bounding_box.h
@@ -0,0 +1,88 @@
1#ifndef BOUNDING_BOX_H_75D2077D
2#define BOUNDING_BOX_H_75D2077D
3
4namespace twitter {
5
6 class coordinate {
7 public:
8
9 coordinate(int degrees, int minutes = 0, int seconds = 0) :
10 _degrees(degrees),
11 _minutes(minutes),
12 _seconds(seconds)
13 {
14 }
15
16 int getDegrees() const
17 {
18 return _degrees;
19 }
20
21 int getMinutes() const
22 {
23 return _minutes;
24 }
25
26 int getSeconds() const
27 {
28 return _seconds;
29 }
30
31 operator double() const
32 {
33 return (double)_degrees + ((double)_minutes / (double)60) + ((double)_seconds / (double)3600);
34 }
35
36 private:
37
38 int _degrees;
39 int _minutes;
40 int _seconds;
41 };
42
43 class bounding_box {
44 public:
45
46 bounding_box(
47 coordinate south_west_long,
48 coordinate south_west_lat,
49 coordinate north_east_long,
50 coordinate north_east_lat) :
51 _south_west_long(south_west_long),
52 _south_west_lat(south_west_lat),
53 _north_east_long(north_east_long),
54 _north_east_lat(north_east_lat)
55 {
56 }
57
58 const coordinate& getSouthWestLongitude() const
59 {
60 return _south_west_long;
61 }
62
63 const coordinate& getSouthWestLatitude() const
64 {
65 return _south_west_lat;
66 }
67
68 const coordinate& getNorthEastLongitude() const
69 {
70 return _north_east_long;
71 }
72
73 const coordinate& getNorthEastLatitude() const
74 {
75 return _north_east_lat;
76 }
77
78 private:
79
80 coordinate _south_west_long;
81 coordinate _south_west_lat;
82 coordinate _north_east_long;
83 coordinate _north_east_lat;
84 };
85
86}
87
88#endif /* end of include guard: BOUNDING_BOX_H_75D2077D */
diff --git a/src/client.cpp b/src/client.cpp index 2c655e2..f8908fd 100644 --- a/src/client.cpp +++ b/src/client.cpp
@@ -5,10 +5,8 @@
5#include <liboauthcpp/liboauthcpp.h> 5#include <liboauthcpp/liboauthcpp.h>
6#include <curl_easy.h> 6#include <curl_easy.h>
7#include <curl_header.h> 7#include <curl_header.h>
8#include "util.h"
9#include <json.hpp> 8#include <json.hpp>
10 9#include <thread>
11using nlohmann::json;
12 10
13// These are here for debugging curl stuff 11// These are here for debugging curl stuff
14 12
@@ -84,99 +82,315 @@ int my_trace(CURL *handle, curl_infotype type,
84 82
85namespace twitter { 83namespace twitter {
86 84
87 int client_stream_progress_callback_wrapper(void* cdata, curl_off_t, curl_off_t, curl_off_t, curl_off_t) 85 class request
88 { 86 {
89 return static_cast<client::stream*>(cdata)->progress(); 87 public:
90 } 88
89 explicit request(std::string url) try
90 : _ios(_output), _conn(_ios)
91 {
92 _conn.add<CURLOPT_URL>(url.c_str());
93 } catch (const curl::curl_easy_exception& error)
94 {
95 error.print_traceback();
96
97 assert(false);
98 }
99
100 std::string perform()
101 {
102 try
103 {
104 _conn.perform();
105 } catch (const curl::curl_easy_exception& error)
106 {
107 std::throw_with_nested(connection_error());
108 }
109
110 int response_code = _conn.get_info<CURLINFO_RESPONSE_CODE>().get();
111 std::string result = _output.str();
112
113 if (response_code / 100 != 2)
114 {
115 nlohmann::json response_json;
116
117 try
118 {
119 response_json = nlohmann::json::parse(result);
120 } catch (const std::invalid_argument& e)
121 {
122 std::throw_with_nested(invalid_response(result));
123 }
124
125 for (nlohmann::json& error : response_json["errors"])
126 {
127 int error_code;
128 std::string error_message;
129
130 try
131 {
132 error_code = error["code"].get<int>();
133 error_message = error["message"].get<std::string>();
134 } catch (const std::domain_error& e)
135 {
136 std::throw_with_nested(invalid_response(result));
137 }
138
139 switch (error_code)
140 {
141 case 32:
142 case 135:
143 case 215:
144 throw bad_auth(error_message);
145
146 case 44:
147 throw invalid_media(error_message);
148
149 case 64:
150 throw account_suspended(error_message);
151
152 case 88:
153 throw rate_limit_exceeded(error_message);
154
155 case 89:
156 throw bad_token(error_message);
157
158 case 130:
159 throw server_overloaded(error_message);
160
161 case 131:
162 throw server_error(error_message);
163
164 case 185:
165 throw update_limit_exceeded(error_message);
166
167 case 186:
168 throw bad_length(error_message);
169
170 case 187:
171 throw duplicate_status(error_message);
172
173 case 226:
174 throw suspected_spam(error_message);
175
176 case 261:
177 throw write_restricted(error_message);
178 }
179 }
180
181 if (response_code == 429)
182 {
183 throw rate_limit_exceeded("HTTP 429 Too Many Requests");
184 } else if (response_code == 500)
185 {
186 throw server_error("HTTP 500 Internal Server Error");
187 } else if (response_code == 502)
188 {
189 throw server_unavailable("HTTP 502 Bad Gateway");
190 } else if (response_code == 503)
191 {
192 throw server_overloaded("HTTP 503 Service Unavailable");
193 } else if (response_code == 504)
194 {
195 throw server_timeout("HTTP 504 Gateway Timeout");
196 }
197
198 throw unknown_error(response_code, result);
199 }
200
201 return result;
202 }
203
204 private:
205
206 std::ostringstream _output;
207 curl::curl_ios<std::ostringstream> _ios;
208
209 protected:
210
211 curl::curl_easy _conn;
212 };
91 213
92 size_t client_stream_write_callback_wrapper(void* ptr, size_t size, size_t nmemb, void* cdata) 214 class get : public request
93 { 215 {
94 return static_cast<client::stream*>(cdata)->write(static_cast<char*>(ptr), size, nmemb); 216 public:
95 } 217
218 get(const OAuth::Client& oauth_client, std::string url) try
219 : request(url)
220 {
221 std::string oauth_header = oauth_client.getFormattedHttpHeader(OAuth::Http::Get, url, "");
222 if (!oauth_header.empty())
223 {
224 _headers.add(std::move(oauth_header));
225 }
96 226
97 client::client(const auth& _arg) 227 _conn.add<CURLOPT_HTTPHEADER>(_headers.get());
228 } catch (const OAuth::ParseError& error)
229 {
230 std::cout << "Error generating OAuth header:" << std::endl;
231 std::cout << error.what() << std::endl;
232 std::cout << "This is likely due to a malformed URL." << std::endl;
233
234 assert(false);
235 } catch (const curl::curl_easy_exception& error)
236 {
237 error.print_traceback();
238
239 assert(false);
240 }
241
242 private:
243
244 curl::curl_header _headers;
245 };
246
247 class post : public request
98 { 248 {
99 _oauth_consumer = new OAuth::Consumer(_arg.getConsumerKey(), _arg.getConsumerSecret()); 249 public:
100 _oauth_token = new OAuth::Token(_arg.getAccessKey(), _arg.getAccessSecret()); 250
101 _oauth_client = new OAuth::Client(_oauth_consumer, _oauth_token); 251 post(const OAuth::Client& oauth_client, std::string url, std::string datastr) try
102 252 : request(url)
103 std::string url = "https://api.twitter.com/1.1/account/verify_credentials.json";
104 long response_code;
105 std::string response_data;
106 if (performGet(url, response_code, response_data) && (response_code == 200))
107 { 253 {
108 try { 254 std::string oauth_header = oauth_client.getFormattedHttpHeader(OAuth::Http::Post, url, datastr);
109 _current_user = user(response_data); 255 if (!oauth_header.empty())
110 } catch (std::invalid_argument e)
111 { 256 {
112 // Ignore 257 _headers.add(std::move(oauth_header));
113 } 258 }
259
260 _conn.add<CURLOPT_HTTPHEADER>(_headers.get());
261 _conn.add<CURLOPT_COPYPOSTFIELDS>(datastr.c_str());
262 } catch (const OAuth::ParseError& error)
263 {
264 std::cout << "Error generating OAuth header:" << std::endl;
265 std::cout << error.what() << std::endl;
266 std::cout << "This is likely due to a malformed URL." << std::endl;
267
268 assert(false);
269 } catch (const curl::curl_easy_exception& error)
270 {
271 error.print_traceback();
272
273 assert(false);
114 } 274 }
115 } 275
276 private:
277
278 curl::curl_header _headers;
279 };
280
281 class multipost : public request
282 {
283 public:
284
285 multipost(const OAuth::Client& oauth_client, std::string url, const curl_httppost* fields) try
286 : request(url)
287 {
288 std::string oauth_header = oauth_client.getFormattedHttpHeader(OAuth::Http::Post, url, "");
289 if (!oauth_header.empty())
290 {
291 _headers.add(std::move(oauth_header));
292 }
293
294 _conn.add<CURLOPT_HTTPHEADER>(_headers.get());
295 _conn.add<CURLOPT_HTTPPOST>(fields);
296 } catch (const OAuth::ParseError& error)
297 {
298 std::cout << "Error generating OAuth header:" << std::endl;
299 std::cout << error.what() << std::endl;
300 std::cout << "This is likely due to a malformed URL." << std::endl;
301
302 assert(false);
303 } catch (const curl::curl_easy_exception& error)
304 {
305 error.print_traceback();
116 306
117 client::~client() 307 assert(false);
308 }
309
310 private:
311
312 curl::curl_header _headers;
313 };
314
315 client::client(const auth& _arg)
118 { 316 {
119 delete _oauth_client; 317 _oauth_consumer =
120 delete _oauth_token; 318 make_unique<OAuth::Consumer>(
121 delete _oauth_consumer; 319 _arg.getConsumerKey(),
320 _arg.getConsumerSecret());
321
322 _oauth_token =
323 make_unique<OAuth::Token>(
324 _arg.getAccessKey(),
325 _arg.getAccessSecret());
326
327 _oauth_client =
328 make_unique<OAuth::Client>(
329 _oauth_consumer.get(),
330 _oauth_token.get());
331
332 _current_user =
333 make_unique<user>(*this,
334 get(*_oauth_client,
335 "https://api.twitter.com/1.1/account/verify_credentials.json")
336 .perform());
122 } 337 }
123 338
124 response client::updateStatus(std::string msg, tweet& result, tweet in_response_to, std::list<long> media_ids) 339 client::~client() = default;
340
341 tweet client::updateStatus(std::string msg, std::list<long> media_ids) const
125 { 342 {
126 std::stringstream datastrstream; 343 std::stringstream datastrstream;
127 datastrstream << "status=" << OAuth::PercentEncode(msg); 344 datastrstream << "status=" << OAuth::PercentEncode(msg);
128 345
129 if (in_response_to)
130 {
131 datastrstream << "&in_reply_to_status_id=";
132 datastrstream << in_response_to.getID();
133 }
134
135 if (!media_ids.empty()) 346 if (!media_ids.empty())
136 { 347 {
137 datastrstream << "&media_ids="; 348 datastrstream << "&media_ids=";
138 datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ","); 349 datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ",");
139 } 350 }
140 351
141 std::string datastr = datastrstream.str(); 352 return tweet(*this,
142 std::string url = "https://api.twitter.com/1.1/statuses/update.json"; 353 post(*_oauth_client,
354 "https://api.twitter.com/1.1/statuses/update.json",
355 datastrstream.str())
356 .perform());
357 }
358
359 tweet client::replyToTweet(std::string msg, tweet_id in_response_to, std::list<long> media_ids) const
360 {
361 std::stringstream datastrstream;
362 datastrstream << "status=" << OAuth::PercentEncode(msg);
363 datastrstream << "&in_reply_to_status_id=";
364 datastrstream << in_response_to;
143 365
144 long response_code; 366 if (!media_ids.empty())
145 std::string response_data;
146 if (!performPost(url, datastr, response_code, response_data))
147 { 367 {
148 return response::curl_error; 368 datastrstream << "&media_ids=";
369 datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ",");
149 } 370 }
150 371
151 if (response_code == 200) 372 return tweet(*this,
152 { 373 post(*_oauth_client,
153 try { 374 "https://api.twitter.com/1.1/statuses/update.json",
154 result = tweet(response_data); 375 datastrstream.str())
155 return response::ok; 376 .perform());
156 } catch (std::invalid_argument e)
157 {
158 return response::invalid_response;
159 }
160 } else {
161 return codeForError(response_code, response_data);
162 }
163 } 377 }
164 378
165 response client::uploadMedia(std::string media_type, const char* data, long data_length, long& media_id) 379 long client::uploadMedia(std::string media_type, const char* data, long data_length) const try
166 { 380 {
167 curl::curl_form form; 381 curl::curl_form form;
382 std::string str_data_length = std::to_string(data_length);
168 383
169 curl::curl_pair<CURLformoption, std::string> command_name(CURLFORM_COPYNAME, "command"); 384 curl::curl_pair<CURLformoption, std::string> command_name(CURLFORM_COPYNAME, "command");
170 curl::curl_pair<CURLformoption, std::string> command_cont(CURLFORM_COPYCONTENTS, "INIT"); 385 curl::curl_pair<CURLformoption, std::string> command_cont(CURLFORM_COPYCONTENTS, "INIT");
171 curl::curl_pair<CURLformoption, std::string> bytes_name(CURLFORM_COPYNAME, "total_bytes"); 386 curl::curl_pair<CURLformoption, std::string> bytes_name(CURLFORM_COPYNAME, "total_bytes");
172 std::string str_data_length = std::to_string(data_length);
173 curl::curl_pair<CURLformoption, std::string> bytes_cont(CURLFORM_COPYCONTENTS, str_data_length); 387 curl::curl_pair<CURLformoption, std::string> bytes_cont(CURLFORM_COPYCONTENTS, str_data_length);
174 curl::curl_pair<CURLformoption, std::string> type_name(CURLFORM_COPYNAME, "media_type"); 388 curl::curl_pair<CURLformoption, std::string> type_name(CURLFORM_COPYNAME, "media_type");
175 curl::curl_pair<CURLformoption, std::string> type_cont(CURLFORM_COPYCONTENTS, media_type); 389 curl::curl_pair<CURLformoption, std::string> type_cont(CURLFORM_COPYCONTENTS, media_type);
176 form.add(command_name, command_cont); 390 form.add(command_name, command_cont);
177 form.add(bytes_name, bytes_cont); 391 form.add(bytes_name, bytes_cont);
178 form.add(type_name, type_cont); 392 form.add(type_name, type_cont);
179 393
180 if (media_type == "image/gif") 394 if (media_type == "image/gif")
181 { 395 {
182 curl::curl_pair<CURLformoption, std::string> category_name(CURLFORM_COPYNAME, "media_category"); 396 curl::curl_pair<CURLformoption, std::string> category_name(CURLFORM_COPYNAME, "media_category");
@@ -184,212 +398,109 @@ namespace twitter {
184 form.add(category_name, category_cont); 398 form.add(category_name, category_cont);
185 } 399 }
186 400
187 long response_code; 401 std::string init_response =
188 std::string response_data; 402 multipost(*_oauth_client,
189 if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", form.get(), response_code, response_data)) 403 "https://upload.twitter.com/1.1/media/upload.json",
190 { 404 form.get())
191 return response::curl_error; 405 .perform();
192 } 406
407 long media_id;
193 408
194 if (response_code / 100 != 2) 409 try
195 { 410 {
196 return codeForError(response_code, response_data); 411 nlohmann::json response_json = nlohmann::json::parse(init_response);
197 } 412 media_id = response_json["media_id"].get<long>();
198 413 } catch (const std::invalid_argument& error)
199 json response_json; 414 {
200 try { 415 std::throw_with_nested(invalid_response(init_response));
201 response_json = json::parse(response_data); 416 } catch (const std::domain_error& error)
202 } catch (std::invalid_argument e)
203 { 417 {
204 return response::invalid_response; 418 std::throw_with_nested(invalid_response(init_response));
205 } 419 }
206
207 media_id = response_json["media_id"].get<long>();
208 420
421 // TODO: Currently have to use the C libcurl API to create this form because it uses a buffer and
422 // libcurlcpp currently messes that up.
209 curl_httppost* append_form_post = nullptr; 423 curl_httppost* append_form_post = nullptr;
210 curl_httppost* append_form_last = nullptr; 424 curl_httppost* append_form_last = nullptr;
211 curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "command", CURLFORM_COPYCONTENTS, "APPEND", CURLFORM_END); 425 if ( curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "command", CURLFORM_COPYCONTENTS, "APPEND", CURLFORM_END)
212 curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "media_id", CURLFORM_COPYCONTENTS, std::to_string(media_id).c_str(), CURLFORM_END); 426 || curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "media_id", CURLFORM_COPYCONTENTS, std::to_string(media_id).c_str(), CURLFORM_END)
213 curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "media", CURLFORM_BUFFER, "media", CURLFORM_BUFFERPTR, data, CURLFORM_BUFFERLENGTH, data_length, CURLFORM_CONTENTTYPE, "application/octet-stream", CURLFORM_END); 427 || curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "media", CURLFORM_BUFFER, "media", CURLFORM_BUFFERPTR, data, CURLFORM_BUFFERLENGTH, data_length, CURLFORM_CONTENTTYPE, "application/octet-stream", CURLFORM_END)
214 curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "segment_index", CURLFORM_COPYCONTENTS, std::to_string(0).c_str(), CURLFORM_END); 428 || curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "segment_index", CURLFORM_COPYCONTENTS, std::to_string(0).c_str(), CURLFORM_END))
215 if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", append_form_post, response_code, response_data))
216 { 429 {
217 return response::curl_error; 430 assert(false);
218 } 431 }
219 432
220 curl_formfree(append_form_post); 433 multipost(*_oauth_client, "https://upload.twitter.com/1.1/media/upload.json", append_form_post).perform();
221 434
222 if (response_code / 100 != 2) 435 curl_formfree(append_form_post);
223 {
224 return codeForError(response_code, response_data);
225 }
226 436
227 curl::curl_form finalize_form; 437 curl::curl_form finalize_form;
438 std::string str_media_id = std::to_string(media_id);
439
228 curl::curl_pair<CURLformoption, std::string> command3_name(CURLFORM_COPYNAME, "command"); 440 curl::curl_pair<CURLformoption, std::string> command3_name(CURLFORM_COPYNAME, "command");
229 curl::curl_pair<CURLformoption, std::string> command3_cont(CURLFORM_COPYCONTENTS, "FINALIZE"); 441 curl::curl_pair<CURLformoption, std::string> command3_cont(CURLFORM_COPYCONTENTS, "FINALIZE");
230 curl::curl_pair<CURLformoption, std::string> media_id_name(CURLFORM_COPYNAME, "media_id"); 442 curl::curl_pair<CURLformoption, std::string> media_id_name(CURLFORM_COPYNAME, "media_id");
231 std::string str_media_id = std::to_string(media_id);
232 curl::curl_pair<CURLformoption, std::string> media_id_cont(CURLFORM_COPYCONTENTS, str_media_id); 443 curl::curl_pair<CURLformoption, std::string> media_id_cont(CURLFORM_COPYCONTENTS, str_media_id);
233 finalize_form.add(command3_name, command3_cont); 444 finalize_form.add(command3_name, command3_cont);
234 finalize_form.add(media_id_name, media_id_cont); 445 finalize_form.add(media_id_name, media_id_cont);
235 446
236 if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", finalize_form.get(), response_code, response_data)) 447 std::string finalize_response =
237 { 448 multipost(*_oauth_client,
238 return response::curl_error; 449 "https://upload.twitter.com/1.1/media/upload.json",
239 } 450 finalize_form.get())
451 .perform();
452
453 nlohmann::json finalize_json;
240 454
241 if (response_code / 100 != 2) 455 try
242 { 456 {
243 return codeForError(response_code, response_data); 457 finalize_json = nlohmann::json::parse(finalize_response);
244 } 458 } catch (const std::invalid_argument& error)
245
246 try {
247 response_json = json::parse(response_data);
248 } catch (std::invalid_argument e)
249 { 459 {
250 return response::invalid_response; 460 std::throw_with_nested(invalid_response(finalize_response));
251 } 461 }
252 462
253 if (response_json.find("processing_info") != response_json.end()) 463 if (finalize_json.find("processing_info") != finalize_json.end())
254 { 464 {
255 std::stringstream datastr; 465 std::stringstream datastr;
256 datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id; 466 datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id;
257 467
258 for (;;) 468 for (;;)
259 { 469 {
260 if (!performGet(datastr.str(), response_code, response_data)) 470 std::string status_response = get(*_oauth_client, datastr.str()).perform();
261 {
262 return response::curl_error;
263 }
264 471
265 if (response_code / 100 != 2) 472 try
266 { 473 {
267 return codeForError(response_code, response_data); 474 nlohmann::json status_json = nlohmann::json::parse(status_response);
268 } 475 std::string state = status_json["processing_info"]["state"].get<std::string>();
269 476
270 try { 477 if (state == "succeeded")
271 response_json = json::parse(response_data); 478 {
272 } catch (std::invalid_argument e) 479 break;
480 }
481
482 int ttw = status_json["processing_info"]["check_after_secs"].get<int>();
483 std::this_thread::sleep_for(std::chrono::seconds(ttw));
484 } catch (const std::invalid_argument& error)
273 { 485 {
274 return response::invalid_response; 486 std::throw_with_nested(invalid_response(status_response));
275 } 487 } catch (const std::domain_error& error)
276
277 if (response_json["processing_info"]["state"] == "succeeded")
278 { 488 {
279 break; 489 std::throw_with_nested(invalid_response(status_response));
280 } 490 }
281
282 int ttw = response_json["processing_info"]["check_after_secs"].get<int>();
283 std::this_thread::sleep_for(std::chrono::seconds(ttw));
284 } 491 }
285 } 492 }
286 493
287 return response::ok; 494 return media_id;
288 } 495 } catch (const curl::curl_exception& error)
289
290 response client::follow(user_id toFollow)
291 { 496 {
292 std::stringstream datastrstream; 497 error.print_traceback();
293 datastrstream << "follow=true&user_id=";
294 datastrstream << toFollow;
295 498
296 std::string datastr = datastrstream.str(); 499 assert(false);
297 std::string url = "https://api.twitter.com/1.1/friendships/create.json";
298
299 long response_code;
300 std::string response_data;
301 if (!performPost(url, datastr, response_code, response_data))
302 {
303 return response::curl_error;
304 }
305
306 if (response_code == 200)
307 {
308 return response::ok;
309 } else {
310 return codeForError(response_code, response_data);
311 }
312 }
313
314 response client::follow(user toFollow)
315 {
316 return follow(toFollow.getID());
317 }
318
319 response client::unfollow(user_id toUnfollow)
320 {
321 std::stringstream datastrstream;
322 datastrstream << "user_id=";
323 datastrstream << toUnfollow;
324
325 std::string datastr = datastrstream.str();
326 std::string url = "https://api.twitter.com/1.1/friendships/destroy.json";
327
328 long response_code;
329 std::string response_data;
330 if (!performPost(url, datastr, response_code, response_data))
331 {
332 return response::curl_error;
333 }
334
335 if (response_code == 200)
336 {
337 return response::ok;
338 } else {
339 return codeForError(response_code, response_data);
340 }
341 }
342
343 response client::unfollow(user toUnfollow)
344 {
345 return unfollow(toUnfollow.getID());
346 }
347
348 response client::getUser(user& result)
349 {
350 if (!_current_user)
351 {
352 std::string url = "https://api.twitter.com/1.1/account/verify_credentials.json";
353 long response_code;
354 std::string response_data;
355 if (performGet(url, response_code, response_data) && (response_code == 200))
356 {
357 try {
358 _current_user = user(response_data);
359 } catch (std::invalid_argument e)
360 {
361 return response::invalid_response;
362 }
363 }
364 }
365
366 result = _current_user;
367 return response::ok;
368 }
369
370 configuration client::getConfiguration()
371 {
372 if (!_configuration || (difftime(time(NULL), _last_configuration_update) > 60*60*24))
373 {
374 long response_code;
375 std::string response_data;
376 if (performGet("https://api.twitter.com/1.1/help/configuration.json", response_code, response_data))
377 {
378 _configuration = configuration(response_data);
379 _last_configuration_update = time(NULL);
380 }
381 }
382
383 return _configuration;
384 } 500 }
385 501
386 response client::getFriends(std::set<user_id>& _ret) 502 std::set<user_id> client::getFriends(user_id id) const
387 { 503 {
388 if (!_current_user)
389 {
390 return response::unknown_error;
391 }
392
393 long long cursor = -1; 504 long long cursor = -1;
394 std::set<user_id> result; 505 std::set<user_id> result;
395 506
@@ -397,48 +508,33 @@ namespace twitter {
397 { 508 {
398 std::stringstream urlstream; 509 std::stringstream urlstream;
399 urlstream << "https://api.twitter.com/1.1/friends/ids.json?user_id="; 510 urlstream << "https://api.twitter.com/1.1/friends/ids.json?user_id=";
400 urlstream << _current_user.getID(); 511 urlstream << id;
401 urlstream << "&cursor="; 512 urlstream << "&cursor=";
402 urlstream << cursor; 513 urlstream << cursor;
403 514
404 std::string url = urlstream.str(); 515 std::string url = urlstream.str();
405 516 std::string response_data = get(*_oauth_client, url).perform();
406 long response_code; 517
407 std::string response_data; 518 try
408 if (!performGet(url, response_code, response_data))
409 {
410 return response::curl_error;
411 }
412
413 if (response_code == 200)
414 { 519 {
415 json rjs; 520 nlohmann::json rjs = nlohmann::json::parse(response_data);
416 try {
417 rjs = json::parse(response_data);
418 } catch (std::invalid_argument e)
419 {
420 return response::invalid_response;
421 }
422 521
423 cursor = rjs.at("next_cursor"); 522 cursor = rjs["next_cursor"].get<long long>();
424 result.insert(std::begin(rjs.at("ids")), std::end(rjs.at("ids"))); 523 result.insert(std::begin(rjs["ids"]), std::end(rjs["ids"]));
425 } else { 524 } catch (const std::invalid_argument& error)
426 return codeForError(response_code, response_data); 525 {
526 std::throw_with_nested(invalid_response(response_data));
527 } catch (const std::domain_error& error)
528 {
529 std::throw_with_nested(invalid_response(response_data));
427 } 530 }
428 } 531 }
429 532
430 _ret = result; 533 return result;
431
432 return response::ok;
433 } 534 }
434 535
435 response client::getFollowers(std::set<user_id>& _ret) 536 std::set<user_id> client::getFollowers(user_id id) const
436 { 537 {
437 if (!_current_user)
438 {
439 return response::unknown_error;
440 }
441
442 long long cursor = -1; 538 long long cursor = -1;
443 std::set<user_id> result; 539 std::set<user_id> result;
444 540
@@ -446,474 +542,68 @@ namespace twitter {
446 { 542 {
447 std::stringstream urlstream; 543 std::stringstream urlstream;
448 urlstream << "https://api.twitter.com/1.1/followers/ids.json?user_id="; 544 urlstream << "https://api.twitter.com/1.1/followers/ids.json?user_id=";
449 urlstream << _current_user.getID(); 545 urlstream << id;
450 urlstream << "&cursor="; 546 urlstream << "&cursor=";
451 urlstream << cursor; 547 urlstream << cursor;
452 548
453 std::string url = urlstream.str(); 549 std::string url = urlstream.str();
454 550 std::string response_data = get(*_oauth_client, url).perform();
455 long response_code; 551
456 std::string response_data; 552 try
457 if (!performGet(url, response_code, response_data))
458 {
459 return response::curl_error;
460 }
461
462 if (response_code == 200)
463 { 553 {
464 json rjs; 554 nlohmann::json rjs = nlohmann::json::parse(response_data);
465 try {
466 rjs = json::parse(response_data);
467 } catch (std::invalid_argument e)
468 {
469 return response::invalid_response;
470 }
471 555
472 cursor = rjs.at("next_cursor"); 556 cursor = rjs["next_cursor"].get<long long>();
473 result.insert(std::begin(rjs.at("ids")), std::end(rjs.at("ids"))); 557 result.insert(std::begin(rjs["ids"]), std::end(rjs["ids"]));
474 } else { 558 } catch (const std::invalid_argument& error)
475 return codeForError(response_code, response_data); 559 {
476 } 560 std::throw_with_nested(invalid_response(response_data));
477 } 561 } catch (const std::domain_error& error)
478
479 _ret = result;
480
481 return response::ok;
482 }
483
484 void client::setUserStreamNotifyCallback(stream::notify_callback callback)
485 {
486 _user_stream.setNotifyCallback(callback);
487 }
488
489 void client::setUserStreamReceiveAllReplies(bool _arg)
490 {
491 _user_stream.setReceiveAllReplies(_arg);
492 }
493
494 void client::startUserStream()
495 {
496 _user_stream.start();
497 }
498
499 void client::stopUserStream()
500 {
501 _user_stream.stop();
502 }
503
504 std::string client::generateReplyPrefill(tweet _tweet) const
505 {
506 std::ostringstream output;
507 output << "@" << _tweet.getAuthor().getScreenName() << " ";
508
509 for (auto mention : _tweet.getMentions())
510 {
511 if ((mention.first != _tweet.getAuthor().getID()) && (mention.first != _current_user.getID()))
512 { 562 {
513 output << "@" << mention.second << " "; 563 std::throw_with_nested(invalid_response(response_data));
514 } 564 }
515 } 565 }
516 566
517 return output.str(); 567 return result;
518 }
519
520 bool client::performGet(std::string url, long& response_code, std::string& result)
521 {
522 std::ostringstream output;
523 curl::curl_ios<std::ostringstream> ios(output);
524 curl::curl_easy conn(ios);
525
526 curl::curl_header headers;
527 std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Get, url, "");
528 if (!oauth_header.empty())
529 {
530 headers.add(oauth_header);
531 }
532
533 try {
534 //conn.add<CURLOPT_VERBOSE>(1);
535 //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace);
536 conn.add<CURLOPT_URL>(url.c_str());
537 conn.add<CURLOPT_HTTPHEADER>(headers.get());
538
539 conn.perform();
540 } catch (curl::curl_easy_exception error)
541 {
542 error.print_traceback();
543
544 return false;
545 }
546
547 response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get();
548 result = output.str();
549
550 return true;
551 }
552
553 bool client::performPost(std::string url, std::string datastr, long& response_code, std::string& result)
554 {
555 std::ostringstream output;
556 curl::curl_ios<std::ostringstream> ios(output);
557 curl::curl_easy conn(ios);
558
559 curl::curl_header headers;
560 std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Post, url, datastr);
561 if (!oauth_header.empty())
562 {
563 headers.add(oauth_header);
564 }
565
566 try {
567 //conn.add<CURLOPT_VERBOSE>(1);
568 //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace);
569 conn.add<CURLOPT_URL>(url.c_str());
570 conn.add<CURLOPT_COPYPOSTFIELDS>(datastr.c_str());
571 conn.add<CURLOPT_HTTPHEADER>(headers.get());
572
573 conn.perform();
574 } catch (curl::curl_easy_exception error)
575 {
576 error.print_traceback();
577
578 return false;
579 }
580
581 response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get();
582 result = output.str();
583
584 return true;
585 }
586
587 bool client::performMultiPost(std::string url, const curl_httppost* fields, long& response_code, std::string& result)
588 {
589 std::ostringstream output;
590 curl::curl_ios<std::ostringstream> ios(output);
591 curl::curl_easy conn(ios);
592
593 curl::curl_header headers;
594 std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Post, url, "");
595 if (!oauth_header.empty())
596 {
597 headers.add(oauth_header);
598 }
599
600 try {
601 //conn.add<CURLOPT_VERBOSE>(1);
602 //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace);
603 conn.add<CURLOPT_HTTPHEADER>(headers.get());
604 conn.add<CURLOPT_URL>(url.c_str());
605 conn.add<CURLOPT_HTTPPOST>(fields);
606
607 conn.perform();
608 } catch (curl::curl_easy_exception error)
609 {
610 error.print_traceback();
611
612 return false;
613 }
614
615 response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get();
616 result = output.str();
617
618 return true;
619 }
620
621 response client::codeForError(int response_code, std::string response_data) const
622 {
623 json response_json;
624 try {
625 response_json = json::parse(response_data);
626 } catch (std::invalid_argument e)
627 {
628 return response::invalid_response;
629 }
630
631 std::set<int> error_codes;
632 if (response_json.find("errors") != response_json.end())
633 {
634 std::transform(std::begin(response_json["errors"]), std::end(response_json["errors"]), std::inserter(error_codes, std::begin(error_codes)), [] (const json& error) {
635 return error["code"].get<int>();
636 });
637 }
638
639 if (error_codes.count(32) == 1 || error_codes.count(135) == 1 || error_codes.count(215) == 1)
640 {
641 return response::bad_auth;
642 } else if (error_codes.count(64) == 1)
643 {
644 return response::suspended;
645 } else if (error_codes.count(88) == 1 || error_codes.count(185) == 1)
646 {
647 return response::limited;
648 } else if (error_codes.count(89) == 1)
649 {
650 return response::bad_token;
651 } else if (error_codes.count(130) == 1)
652 {
653 return response::server_overloaded;
654 } else if (error_codes.count(131) == 1)
655 {
656 return response::server_error;
657 } else if (error_codes.count(186) == 1)
658 {
659 return response::bad_length;
660 } else if (error_codes.count(187) == 1)
661 {
662 return response::duplicate_status;
663 } else if (error_codes.count(226) == 1)
664 {
665 return response::suspected_spam;
666 } else if (error_codes.count(261) == 1)
667 {
668 return response::write_restricted;
669 } else if (error_codes.count(44) == 1)
670 {
671 return response::invalid_media;
672 } else if (response_code == 429)
673 {
674 return response::limited;
675 } else if (response_code == 500)
676 {
677 return response::server_error;
678 } else if (response_code == 502)
679 {
680 return response::server_unavailable;
681 } else if (response_code == 503)
682 {
683 return response::server_overloaded;
684 } else if (response_code == 504)
685 {
686 return response::server_timeout;
687 } else {
688 return response::unknown_error;
689 }
690 }
691
692 client::stream::stream(client& _client) : _client(_client)
693 {
694
695 }
696
697 bool client::stream::isRunning() const
698 {
699 return _thread.joinable();
700 } 568 }
701 569
702 void client::stream::setNotifyCallback(notify_callback _n) 570 void client::follow(user_id toFollow) const
703 { 571 {
704 std::lock_guard<std::mutex> _running_lock(_running_mutex); 572 std::stringstream datastrstream;
573 datastrstream << "follow=true&user_id=";
574 datastrstream << toFollow;
705 575
706 if (!_thread.joinable()) 576 post(*_oauth_client, "https://api.twitter.com/1.1/friendships/create.json", datastrstream.str()).perform();
707 {
708 _notify = _n;
709 }
710 } 577 }
711 578
712 void client::stream::setReceiveAllReplies(bool _arg) 579 void client::unfollow(user_id toUnfollow) const
713 { 580 {
714 std::lock_guard<std::mutex> _running_lock(_running_mutex); 581 std::stringstream datastrstream;
715 582 datastrstream << "user_id=";
716 if (!_thread.joinable()) 583 datastrstream << toUnfollow;
717 {
718 _receive_all_replies = _arg;
719 }
720 }
721
722 void client::stream::start()
723 {
724 std::lock_guard<std::mutex> _running_lock(_running_mutex);
725 584
726 if (!_thread.joinable()) 585 post(*_oauth_client, "https://api.twitter.com/1.1/friendships/destroy.json", datastrstream.str()).perform();
727 {
728 _thread = std::thread(&stream::run, this);
729 }
730 } 586 }
731
732 void client::stream::stop()
733 {
734 std::lock_guard<std::mutex> _running_lock(_running_mutex);
735
736 if (_thread.joinable())
737 {
738 _stop = true;
739 _thread.join();
740 _stop = false;
741 }
742 }
743
744 void client::stream::run()
745 {
746 std::ostringstream urlstr;
747 urlstr << "https://userstream.twitter.com/1.1/user.json";
748
749 if (_receive_all_replies)
750 {
751 urlstr << "?replies=all";
752 }
753
754 std::string url = urlstr.str();
755
756 _backoff_type = backoff::none;
757 _backoff_amount = std::chrono::milliseconds(0);
758 for (;;)
759 {
760 curl::curl_easy conn;
761 curl::curl_header headers;
762 std::string oauth_header = _client._oauth_client->getFormattedHttpHeader(OAuth::Http::Get, url, "");
763 if (!oauth_header.empty())
764 {
765 headers.add(oauth_header);
766 }
767
768 conn.add<CURLOPT_WRITEFUNCTION>(client_stream_write_callback_wrapper);
769 conn.add<CURLOPT_WRITEDATA>(this);
770 conn.add<CURLOPT_HEADERFUNCTION>(nullptr);
771 conn.add<CURLOPT_HEADERDATA>(nullptr);
772 conn.add<CURLOPT_XFERINFOFUNCTION>(client_stream_progress_callback_wrapper);
773 conn.add<CURLOPT_XFERINFODATA>(this);
774 conn.add<CURLOPT_NOPROGRESS>(0);
775 //conn.add<CURLOPT_VERBOSE>(1);
776 //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace);
777 conn.add<CURLOPT_URL>(url.c_str());
778 conn.add<CURLOPT_HTTPHEADER>(headers.get());
779
780 bool failure = false;
781 try {
782 conn.perform();
783 } catch (curl::curl_easy_exception error)
784 {
785 failure = true;
786 if ((error.get_code() == CURLE_ABORTED_BY_CALLBACK) && _stop)
787 {
788 break;
789 } else {
790 if (_backoff_type == backoff::none)
791 {
792 _established = false;
793 _backoff_type = backoff::network;
794 _backoff_amount = std::chrono::milliseconds(0);
795 }
796 }
797 }
798
799 if (!failure)
800 {
801 long response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get();
802 if (response_code == 420)
803 {
804 if (_backoff_type == backoff::none)
805 {
806 _established = false;
807 _backoff_type = backoff::rate_limit;
808 _backoff_amount = std::chrono::minutes(1);
809 }
810 } else if (response_code != 200)
811 {
812 if (_backoff_type == backoff::none)
813 {
814 _established = false;
815 _backoff_type = backoff::http;
816 _backoff_amount = std::chrono::seconds(5);
817 }
818 } else {
819 if (_backoff_type == backoff::none)
820 {
821 _established = false;
822 _backoff_type = backoff::network;
823 _backoff_amount = std::chrono::milliseconds(0);
824 }
825 }
826 }
827
828 std::this_thread::sleep_for(_backoff_amount);
829 587
830 switch (_backoff_type) 588 const user& client::getUser() const
831 {
832 case backoff::network:
833 {
834 if (_backoff_amount < std::chrono::seconds(16))
835 {
836 _backoff_amount += std::chrono::milliseconds(250);
837 }
838
839 break;
840 }
841
842 case backoff::http:
843 {
844 if (_backoff_amount < std::chrono::seconds(320))
845 {
846 _backoff_amount *= 2;
847 }
848
849 break;
850 }
851
852 case backoff::rate_limit:
853 {
854 _backoff_amount *= 2;
855
856 break;
857 }
858 }
859 }
860 }
861
862 size_t client::stream::write(char* ptr, size_t size, size_t nmemb)
863 { 589 {
864 for (size_t i = 0; i < size*nmemb; i++) 590 return *_current_user;
865 {
866 if (ptr[i] == '\r')
867 {
868 i++; // Skip the \n
869
870 if (!_buffer.empty())
871 {
872 notification n(_buffer, _client._current_user);
873 if (n.getType() == notification::type::friends)
874 {
875 _established = true;
876 _backoff_type = backoff::none;
877 _backoff_amount = std::chrono::milliseconds(0);
878 }
879
880 if (_notify)
881 {
882 _notify(n);
883 }
884
885 _buffer = "";
886 }
887 } else {
888 _buffer.push_back(ptr[i]);
889 }
890 }
891
892 {
893 std::lock_guard<std::mutex> _stall_lock(_stall_mutex);
894 time(&_last_write);
895 }
896
897 return size*nmemb;
898 } 591 }
899 592
900 int client::stream::progress() 593 const configuration& client::getConfiguration() const
901 { 594 {
902 if (_stop) 595 if (!_configuration || (difftime(time(NULL), _last_configuration_update) > 60*60*24))
903 {
904 return 1;
905 }
906
907 if (_established)
908 { 596 {
909 std::lock_guard<std::mutex> _stall_lock(_stall_mutex); 597 _configuration =
910 if (difftime(time(NULL), _last_write) >= 90) 598 make_unique<configuration>(
911 { 599 get(*_oauth_client,
912 return 1; 600 "https://api.twitter.com/1.1/help/configuration.json")
913 } 601 .perform());
602
603 _last_configuration_update = time(NULL);
914 } 604 }
915 605
916 return 0; 606 return *_configuration;
917 } 607 }
918 608
919}; 609};
diff --git a/src/client.h b/src/client.h index 6963412..37081ff 100644 --- a/src/client.h +++ b/src/client.h
@@ -1,17 +1,15 @@
1#ifndef TWITTER_H_ABFF6A12 1#ifndef TWITTER_H_ABFF6A12
2#define TWITTER_H_ABFF6A12 2#define TWITTER_H_ABFF6A12
3 3
4#include "codes.h"
5#include "tweet.h"
6#include "auth.h"
7#include <list> 4#include <list>
8#include <thread>
9#include <mutex>
10#include "notification.h"
11#include <set> 5#include <set>
12#include <ctime> 6#include <ctime>
13#include <chrono> 7#include <memory>
8#include "codes.h"
9#include "tweet.h"
10#include "auth.h"
14#include "configuration.h" 11#include "configuration.h"
12#include "util.h"
15 13
16namespace OAuth { 14namespace OAuth {
17 class Consumer; 15 class Consumer;
@@ -19,96 +17,39 @@ namespace OAuth {
19 class Client; 17 class Client;
20}; 18};
21 19
22class curl_httppost;
23
24namespace twitter { 20namespace twitter {
25 21
26 class client { 22 class client {
27 public: 23 public:
28 class stream { 24
29 public: 25 client(const auth& _auth);
30 typedef std::function<void(notification _notification)> notify_callback; 26 ~client();
31 27
32 stream(client& _client); 28 tweet updateStatus(std::string msg, std::list<long> media_ids = {}) const;
33 29 long uploadMedia(std::string media_type, const char* data, long data_length) const;
34 void setNotifyCallback(notify_callback _n); 30
35 void setReceiveAllReplies(bool _arg); 31 tweet replyToTweet(std::string msg, tweet_id in_response_to, std::list<long> media_ids = {}) const;
36 32 std::set<user_id> getFriends(user_id id) const;
37 bool isRunning() const; 33 std::set<user_id> getFollowers(user_id id) const;
38 void start(); 34 void follow(user_id toFollow) const;
39 void stop(); 35 void unfollow(user_id toUnfollow) const;
40 36
41 int progress(); 37 const user& getUser() const;
42 size_t write(char* ptr, size_t size, size_t nmemb); 38
43 39 const configuration& getConfiguration() const;
44 private: 40
45 enum class backoff { 41 private:
46 none, 42
47 network, 43 friend class stream;
48 http, 44
49 rate_limit 45 std::unique_ptr<OAuth::Consumer> _oauth_consumer;
50 }; 46 std::unique_ptr<OAuth::Token> _oauth_token;
51 47 std::unique_ptr<OAuth::Client> _oauth_client;
52 void run(); 48
53 49 std::unique_ptr<user> _current_user;
54 client& _client; 50
55 notify_callback _notify; 51 mutable std::unique_ptr<configuration> _configuration;
56 bool _stop = false; 52 mutable time_t _last_configuration_update;
57 std::thread _thread;
58 std::mutex _running_mutex;
59 std::mutex _stall_mutex;
60 std::string _buffer;
61 time_t _last_write;
62 bool _established = false;
63 backoff _backoff_type = backoff::none;
64 std::chrono::milliseconds _backoff_amount;
65 bool _receive_all_replies = false;
66 };
67
68 client(const auth& _auth);
69 ~client();
70
71 response updateStatus(std::string msg, tweet& result, tweet in_response_to = tweet(), std::list<long> media_ids = {});
72 response uploadMedia(std::string media_type, const char* data, long data_length, long& media_id);
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 response getUser(user& result);
84
85 configuration getConfiguration();
86
87 // NOTE: stream setting function calls will fail silently when stream is running
88 void setUserStreamNotifyCallback(stream::notify_callback callback);
89 void setUserStreamReceiveAllReplies(bool _arg);
90 void startUserStream();
91 void stopUserStream();
92
93 std::string generateReplyPrefill(tweet t) const;
94
95 private:
96 friend class stream;
97
98 OAuth::Consumer* _oauth_consumer;
99 OAuth::Token* _oauth_token;
100 OAuth::Client* _oauth_client;
101
102 user _current_user;
103 stream _user_stream{*this};
104
105 configuration _configuration;
106 time_t _last_configuration_update;
107
108 bool performGet(std::string url, long& response_code, std::string& result);
109 bool performPost(std::string url, std::string dataStr, long& response_code, std::string& result);
110 bool performMultiPost(std::string url, const curl_httppost* fields, long& response_code, std::string& result);
111 response codeForError(int httpcode, std::string errors) const;
112 }; 53 };
113 54
114}; 55};
diff --git a/src/codes.cpp b/src/codes.cpp index 9639d5d..7805c7a 100644 --- a/src/codes.cpp +++ b/src/codes.cpp
@@ -1,24 +1,17 @@
1#include "codes.h" 1#include "codes.h"
2#include <sstream>
2 3
3std::ostream& operator<<(std::ostream& os, twitter::response r) 4namespace twitter {
4{ 5
5 switch (r) 6 const char* invalid_response::WHAT_TEXT = "Invalid response data received from Twitter";
7 const char* connection_error::WHAT_TEXT = "Error connecting to Twitter";
8
9 std::string unknown_error::generateMessage(int response_code)
6 { 10 {
7 case twitter::response::ok: return os << "OK"; 11 std::ostringstream msgbuilder;
8 case twitter::response::curl_error: return os << "Curl Error"; 12 msgbuilder << "Unknown error (HTTP " << response_code << ")";
9 case twitter::response::bad_auth: return os << "Bad Auth"; 13
10 case twitter::response::limited: return os << "Rate Limit Exceeded"; 14 return msgbuilder.str();
11 case twitter::response::server_error: return os << "Twitter Server Error";
12 case twitter::response::server_unavailable: return os << "Twitter Is Down";
13 case twitter::response::server_overloaded: return os << "Twitter Is Over Capacity";
14 case twitter::response::server_timeout: return os << "Twitter Connection Timed Out";
15 case twitter::response::suspended: return os << "Authenticated User Is Suspended";
16 case twitter::response::bad_token: return os << "Invalid Or Expired Access Token";
17 case twitter::response::duplicate_status: return os << "Duplicate Status";
18 case twitter::response::suspected_spam: return os << "Request Looks Automated";
19 case twitter::response::write_restricted: return os << "Cannot Perform Write";
20 case twitter::response::bad_length: return os << "Message Body Too Long";
21 case twitter::response::unknown_error: return os << "Unknown Error";
22 case twitter::response::invalid_media: return os << "Invalid Media";
23 } 15 }
24} 16
17};
diff --git a/src/codes.h b/src/codes.h index 6767805..baf4f8f 100644 --- a/src/codes.h +++ b/src/codes.h
@@ -1,32 +1,183 @@
1#ifndef CODES_H_05838D39 1#ifndef CODES_H_05838D39
2#define CODES_H_05838D39 2#define CODES_H_05838D39
3 3
4#include <ostream> 4#include <stdexcept>
5#include <string>
5 6
6namespace twitter { 7namespace twitter {
7 8
8 enum class response { 9 class twitter_error : public std::runtime_error {
9 ok, 10 public:
10 curl_error, 11
11 bad_auth, 12 using std::runtime_error::runtime_error;
12 limited, 13 };
13 server_error, 14
14 server_unavailable, 15 class bad_auth : public twitter_error {
15 server_overloaded, 16 public:
16 server_timeout, 17
17 suspended, 18 using twitter_error::twitter_error;
18 bad_token, 19 };
19 duplicate_status, 20
20 suspected_spam, 21 class invalid_response : public twitter_error {
21 write_restricted, 22 public:
22 bad_length, 23
23 unknown_error, 24 static const char* WHAT_TEXT;
24 invalid_media, 25
25 invalid_response 26 explicit invalid_response(std::string response) noexcept
27 : twitter_error(WHAT_TEXT), _response(std::move(response))
28 {
29 }
30
31 const std::string& getResponse() const noexcept
32 {
33 return _response;
34 }
35
36 private:
37
38 std::string _response;
39 };
40
41 class account_suspended : public twitter_error {
42 public:
43
44 using twitter_error::twitter_error;
45 };
46
47 class rate_limit_exceeded : public twitter_error {
48 public:
49
50 using twitter_error::twitter_error;
51 };
52
53 class update_limit_exceeded : public twitter_error {
54 public:
55
56 using twitter_error::twitter_error;
57 };
58
59 class bad_token : public twitter_error {
60 public:
61
62 using twitter_error::twitter_error;
63 };
64
65 class server_overloaded : public twitter_error {
66 public:
67
68 using twitter_error::twitter_error;
69 };
70
71 class server_error : public twitter_error {
72 public:
73
74 using twitter_error::twitter_error;
75 };
76
77 class bad_length : public twitter_error {
78 public:
79
80 using twitter_error::twitter_error;
81 };
82
83 class duplicate_status : public twitter_error {
84 public:
85
86 using twitter_error::twitter_error;
87 };
88
89 class suspected_spam : public twitter_error {
90 public:
91
92 using twitter_error::twitter_error;
93 };
94
95 class write_restricted : public twitter_error {
96 public:
97
98 using twitter_error::twitter_error;
99 };
100
101 class invalid_media : public twitter_error {
102 public:
103
104 using twitter_error::twitter_error;
105 };
106
107 class server_unavailable : public twitter_error {
108 public:
109
110 using twitter_error::twitter_error;
111 };
112
113 class server_timeout : public twitter_error {
114 public:
115
116 using twitter_error::twitter_error;
117 };
118
119 class unknown_error : public twitter_error {
120 public:
121
122 unknown_error(int response_code, std::string response_data)
123 : twitter_error(generateMessage(response_code)),
124 _response_code(response_code),
125 _response_data(std::move(response_data))
126 {
127 }
128
129 int getResponseCode() const noexcept
130 {
131 return _response_code;
132 }
133
134 const std::string& getResponse() const noexcept
135 {
136 return _response_data;
137 }
138
139 private:
140
141 static std::string generateMessage(int response_code);
142
143 int _response_code;
144 std::string _response_data;
145 };
146
147 class connection_error : public twitter_error {
148 public:
149
150 static const char* WHAT_TEXT;
151
152 connection_error() noexcept : twitter_error(WHAT_TEXT)
153 {
154 }
155 };
156
157 class invalid_member : public std::domain_error {
158 public:
159
160 using std::domain_error::domain_error;
161 };
162
163 class malformed_object : public invalid_response {
164 public:
165
166 malformed_object(std::string type, std::string data) noexcept
167 : invalid_response(std::move(data)), _type(std::move(type))
168 {
169 }
170
171 const std::string& getType() const noexcept
172 {
173 return _type;
174 }
175
176 private:
177
178 std::string _type;
26 }; 179 };
27 180
28}; 181};
29 182
30std::ostream& operator<<(std::ostream& os, twitter::response r);
31
32#endif /* end of include guard: CODES_H_05838D39 */ 183#endif /* end of include guard: CODES_H_05838D39 */
diff --git a/src/configuration.cpp b/src/configuration.cpp index 63464ed..0010a40 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp
@@ -1,35 +1,27 @@
1#include "configuration.h" 1#include "configuration.h"
2#include <json.hpp> 2#include <json.hpp>
3#include <cassert> 3#include <cassert>
4 4#include "codes.h"
5using nlohmann::json;
6 5
7namespace twitter { 6namespace twitter {
8 7
9 configuration::configuration() 8 configuration::configuration(std::string data) try
10 { 9 {
11 _valid = false; 10 auto json_data = nlohmann::json::parse(data);
12 }
13
14 configuration::configuration(std::string data)
15 {
16 _valid = true;
17
18 auto _data = json::parse(data);
19 11
20 _characters_reserved_per_media = _data.at("characters_reserved_per_media"); 12 _characters_reserved_per_media = json_data["characters_reserved_per_media"].get<size_t>();
21 _dm_text_character_limit = _data.at("dm_text_character_limit"); 13 _dm_text_character_limit = json_data["dm_text_character_limit"].get<size_t>();
22 _max_media_per_upload = _data.at("max_media_per_upload"); 14 _max_media_per_upload = json_data["max_media_per_upload"].get<size_t>();
23 _photo_size_limit = _data.at("photo_size_limit"); 15 _photo_size_limit = json_data["photo_size_limit"].get<size_t>();
24 _short_url_length = _data.at("short_url_length"); 16 _short_url_length = json_data["short_url_length"].get<size_t>();
25 _short_https_url_length = _data.at("short_url_length_https"); 17 _short_https_url_length = json_data["short_url_length_https"].get<size_t>();
26 18
27 for (json::iterator sizedata = _data.at("photo_sizes").begin(); sizedata != _data.at("photo_sizes").end(); ++sizedata) 19 for (auto sizedata = std::begin(json_data["photo_sizes"]); sizedata != std::end(json_data["photo_sizes"]); ++sizedata)
28 { 20 {
29 photosize size; 21 photosize size;
30 size.height = sizedata.value().at("h"); 22 size.height = sizedata.value()["h"].get<size_t>();
31 size.width = sizedata.value().at("w"); 23 size.width = sizedata.value()["w"].get<size_t>();
32 if (sizedata.value().at("resize") == "fit") 24 if (sizedata.value()["resize"].get<std::string>() == "fit")
33 { 25 {
34 size.resize = resizetype::fit; 26 size.resize = resizetype::fit;
35 } else { 27 } else {
@@ -39,71 +31,16 @@ namespace twitter {
39 _photo_sizes[sizedata.key()] = size; 31 _photo_sizes[sizedata.key()] = size;
40 } 32 }
41 33
42 for (auto path : _data.at("non_username_paths")) 34 for (auto path : json_data["non_username_paths"])
43 { 35 {
44 _non_username_paths.insert(path.get<std::string>()); 36 _non_username_paths.insert(path.get<std::string>());
45 } 37 }
46 } 38 } catch (const std::invalid_argument& error)
47
48 size_t configuration::getCharactersReservedPerMedia() const
49 {
50 assert(_valid);
51
52 return _characters_reserved_per_media;
53 }
54
55 size_t configuration::getDirectMessageCharacterLimit() const
56 {
57 assert(_valid);
58
59 return _dm_text_character_limit;
60 }
61
62 size_t configuration::getMaxMediaPerUpload() const
63 {
64 assert(_valid);
65
66 return _max_media_per_upload;
67 }
68
69 size_t configuration::getPhotoSizeLimit() const
70 {
71 assert(_valid);
72
73 return _photo_size_limit;
74 }
75
76 std::map<std::string, configuration::photosize> configuration::getPhotoSizes() const
77 {
78 assert(_valid);
79
80 return _photo_sizes;
81 }
82
83 size_t configuration::getShortUrlLength() const
84 {
85 assert(_valid);
86
87 return _short_url_length;
88 }
89
90 size_t configuration::getShortHttpsUrlLength() const
91 {
92 assert(_valid);
93
94 return _short_https_url_length;
95 }
96
97 std::set<std::string> configuration::getNonUsernamePaths() const
98 { 39 {
99 assert(_valid); 40 std::throw_with_nested(malformed_object("configuration", data));
100 41 } catch (const std::domain_error& error)
101 return _non_username_paths;
102 }
103
104 configuration::operator bool() const
105 { 42 {
106 return _valid; 43 std::throw_with_nested(malformed_object("configuration", data));
107 } 44 }
108 45
109}; 46};
diff --git a/src/configuration.h b/src/configuration.h index a15be3d..543e306 100644 --- a/src/configuration.h +++ b/src/configuration.h
@@ -8,43 +8,70 @@
8namespace twitter { 8namespace twitter {
9 9
10 class configuration { 10 class configuration {
11 public: 11 public:
12 enum class resizetype { 12 enum class resizetype {
13 fit, 13 fit,
14 crop 14 crop
15 }; 15 };
16 16
17 struct photosize { 17 struct photosize {
18 size_t height; 18 size_t height;
19 size_t width; 19 size_t width;
20 resizetype resize; 20 resizetype resize;
21 }; 21 };
22 22
23 configuration(); 23 explicit configuration(std::string data);
24 configuration(std::string data); 24
25 25 size_t getCharactersReservedPerMedia() const
26 size_t getCharactersReservedPerMedia() const; 26 {
27 size_t getDirectMessageCharacterLimit() const; 27 return _characters_reserved_per_media;
28 size_t getMaxMediaPerUpload() const; 28 }
29 size_t getPhotoSizeLimit() const; 29
30 std::map<std::string, photosize> getPhotoSizes() const; 30 size_t getDirectMessageCharacterLimit() const
31 size_t getShortUrlLength() const; 31 {
32 size_t getShortHttpsUrlLength() const; 32 return _dm_text_character_limit;
33 std::set<std::string> getNonUsernamePaths() const; 33 }
34 34
35 operator bool() const; 35 size_t getMaxMediaPerUpload() const
36 36 {
37 private: 37 return _max_media_per_upload;
38 bool _valid = false; 38 }
39 39
40 size_t _characters_reserved_per_media; 40 size_t getPhotoSizeLimit() const
41 size_t _dm_text_character_limit; 41 {
42 size_t _max_media_per_upload; 42 return _photo_size_limit;
43 size_t _photo_size_limit; 43 }
44 std::map<std::string, photosize> _photo_sizes; 44
45 size_t _short_url_length; 45 const std::map<std::string, photosize>& getPhotoSizes() const
46 size_t _short_https_url_length; 46 {
47 std::set<std::string> _non_username_paths; 47 return _photo_sizes;
48 }
49
50 size_t getShortUrlLength() const
51 {
52 return _short_url_length;
53 }
54
55 size_t getShortHttpsUrlLength() const
56 {
57 return _short_https_url_length;
58 }
59
60 const std::set<std::string>& getNonUsernamePaths() const
61 {
62 return _non_username_paths;
63 }
64
65 private:
66
67 size_t _characters_reserved_per_media;
68 size_t _dm_text_character_limit;
69 size_t _max_media_per_upload;
70 size_t _photo_size_limit;
71 std::map<std::string, photosize> _photo_sizes;
72 size_t _short_url_length;
73 size_t _short_https_url_length;
74 std::set<std::string> _non_username_paths;
48 }; 75 };
49 76
50}; 77};
diff --git a/src/direct_message.h b/src/direct_message.h index 4b5e285..12e254c 100644 --- a/src/direct_message.h +++ b/src/direct_message.h
@@ -6,8 +6,9 @@
6namespace twitter { 6namespace twitter {
7 7
8 class direct_message { 8 class direct_message {
9 public: 9 public:
10 direct_message(std::string data); 10
11 explicit direct_message(std::string data);
11 }; 12 };
12 13
13}; 14};
diff --git a/src/list.cpp b/src/list.cpp index 49405d0..d2b61cc 100644 --- a/src/list.cpp +++ b/src/list.cpp
@@ -2,11 +2,6 @@
2 2
3namespace twitter { 3namespace twitter {
4 4
5 list::list()
6 {
7
8 }
9
10 list::list(std::string data) 5 list::list(std::string data)
11 { 6 {
12 7
diff --git a/src/list.h b/src/list.h index 3876f2d..69085b4 100644 --- a/src/list.h +++ b/src/list.h
@@ -6,9 +6,9 @@
6namespace twitter { 6namespace twitter {
7 7
8 class list { 8 class list {
9 public: 9 public:
10 list(); 10
11 list(std::string data); 11 explicit list(std::string data);
12 }; 12 };
13 13
14}; 14};
diff --git a/src/notification.cpp b/src/notification.cpp index 0397ce5..3269a90 100644 --- a/src/notification.cpp +++ b/src/notification.cpp
@@ -2,8 +2,8 @@
2#include <cassert> 2#include <cassert>
3#include <new> 3#include <new>
4#include <json.hpp> 4#include <json.hpp>
5 5#include "codes.h"
6using nlohmann::json; 6#include "client.h"
7 7
8namespace twitter { 8namespace twitter {
9 9
@@ -12,26 +12,32 @@ namespace twitter {
12 return _type; 12 return _type;
13 } 13 }
14 14
15 notification::notification() : _type(type::invalid) 15 notification::notification(const client& tclient, std::string data)
16 { 16 {
17 const user& current_user = tclient.getUser();
17 18
18 } 19 nlohmann::json json;
19
20 notification::notification(std::string data, const user& current_user)
21 {
22 try {
23 auto _data = json::parse(data);
24 20
25 if (_data.find("in_reply_to_status_id") != _data.end()) 21 try
22 {
23 json = nlohmann::json::parse(data);
24 } catch (const std::invalid_argument& error)
25 {
26 std::throw_with_nested(invalid_response(data));
27 }
28
29 try
30 {
31 if (!json["in_reply_to_status_id"].is_null())
26 { 32 {
27 _type = type::tweet; 33 _type = type::tweet;
28 34
29 new(&_tweet) tweet(data); 35 new(&_tweet) tweet(tclient, data);
30 } else if (_data.find("event") != _data.end()) 36 } else if (!json["event"].is_null())
31 { 37 {
32 std::string event = _data.at("event"); 38 std::string event = json["event"];
33 user source(_data.at("source").dump()); 39 user source(tclient, json["source"].dump());
34 user target(_data.at("target").dump()); 40 user target(tclient, json["target"].dump());
35 41
36 if (event == "user_update") 42 if (event == "user_update")
37 { 43 {
@@ -50,7 +56,7 @@ namespace twitter {
50 new(&_user) user(target); 56 new(&_user) user(target);
51 } else if (event == "favorite") 57 } else if (event == "favorite")
52 { 58 {
53 new(&_user_and_tweet._tweet) tweet(_data.at("target_object").dump()); 59 new(&_user_and_tweet._tweet) tweet(tclient, json["target_object"].dump());
54 60
55 if (current_user == source) 61 if (current_user == source)
56 { 62 {
@@ -64,7 +70,7 @@ namespace twitter {
64 } 70 }
65 } else if (event == "unfavorite") 71 } else if (event == "unfavorite")
66 { 72 {
67 new(&_user_and_tweet._tweet) tweet(_data.at("target_object").dump()); 73 new(&_user_and_tweet._tweet) tweet(tclient, json["target_object"].dump());
68 74
69 if (current_user == source) 75 if (current_user == source)
70 { 76 {
@@ -97,20 +103,20 @@ namespace twitter {
97 { 103 {
98 _type = type::list_created; 104 _type = type::list_created;
99 105
100 new(&_list) list(_data.at("target_object").dump()); 106 new(&_list) list(json["target_object"].dump());
101 } else if (event == "list_destroyed") 107 } else if (event == "list_destroyed")
102 { 108 {
103 _type = type::list_destroyed; 109 _type = type::list_destroyed;
104 110
105 new(&_list) list(_data.at("target_object").dump()); 111 new(&_list) list(json["target_object"].dump());
106 } else if (event == "list_updated") 112 } else if (event == "list_updated")
107 { 113 {
108 _type = type::list_updated; 114 _type = type::list_updated;
109 115
110 new(&_list) list(_data.at("target_object").dump()); 116 new(&_list) list(json["target_object"].dump());
111 } else if (event == "list_member_added") 117 } else if (event == "list_member_added")
112 { 118 {
113 new(&_user_and_list._list) list(_data.at("target_object").dump()); 119 new(&_user_and_list._list) list(json["target_object"].dump());
114 120
115 if (current_user == source) 121 if (current_user == source)
116 { 122 {
@@ -124,7 +130,7 @@ namespace twitter {
124 } 130 }
125 } else if (event == "list_member_removed") 131 } else if (event == "list_member_removed")
126 { 132 {
127 new(&_user_and_list._list) list(_data.at("target_object").dump()); 133 new(&_user_and_list._list) list(json["target_object"].dump());
128 134
129 if (current_user == source) 135 if (current_user == source)
130 { 136 {
@@ -138,7 +144,7 @@ namespace twitter {
138 } 144 }
139 } else if (event == "list_member_subscribe") 145 } else if (event == "list_member_subscribe")
140 { 146 {
141 new(&_user_and_list._list) list(_data.at("target_object").dump()); 147 new(&_user_and_list._list) list(json["target_object"].dump());
142 148
143 if (current_user == source) 149 if (current_user == source)
144 { 150 {
@@ -152,7 +158,7 @@ namespace twitter {
152 } 158 }
153 } else if (event == "list_member_unsubscribe") 159 } else if (event == "list_member_unsubscribe")
154 { 160 {
155 new(&_user_and_list._list) list(_data.at("target_object").dump()); 161 new(&_user_and_list._list) list(json["target_object"].dump());
156 162
157 if (current_user == source) 163 if (current_user == source)
158 { 164 {
@@ -169,66 +175,67 @@ namespace twitter {
169 _type = type::quoted; 175 _type = type::quoted;
170 176
171 new(&_user_and_tweet._user) user(source); 177 new(&_user_and_tweet._user) user(source);
172 new(&_user_and_tweet._tweet) tweet(_data.at("target_object").dump()); 178 new(&_user_and_tweet._tweet) tweet(tclient, json["target_object"].dump());
173 } 179 }
174 } else if (_data.find("warning") != _data.end()) 180 } else if (!json["warning"].is_null())
175 { 181 {
176 new(&_warning) std::string(_data.at("warning").at("message").get<std::string>()); 182 new(&_warning) std::string(json["warning"]["message"].get<std::string>());
177 183
178 if (_data.at("warning").at("code") == "FALLING_BEHIND") 184 auto warning_code = json["warning"]["code"].get<std::string>();
185 if (warning_code == "FALLING_BEHIND")
179 { 186 {
180 _type = type::stall; 187 _type = type::stall;
181 } else if (_data.at("warning").at("code") == "FOLLOWS_OVER_LIMIT") 188 } else if (warning_code == "FOLLOWS_OVER_LIMIT")
182 { 189 {
183 _type = type::follow_limit; 190 _type = type::follow_limit;
184 } else { 191 } else {
185 _type = type::unknown_warning; 192 _type = type::unknown_warning;
186 } 193 }
187 } else if (_data.find("delete") != _data.end()) 194 } else if (!json["delete"].is_null())
188 { 195 {
189 _type = type::deletion; 196 _type = type::deletion;
190 197
191 _user_id_and_tweet_id._tweet_id = _data.at("delete").at("status").at("id"); 198 _user_id_and_tweet_id._tweet_id = json["delete"]["status"]["id"].get<tweet_id>();
192 _user_id_and_tweet_id._user_id = _data.at("delete").at("status").at("user_id"); 199 _user_id_and_tweet_id._user_id = json["delete"]["status"]["user_id"].get<user_id>();
193 } else if (_data.find("scrub_geo") != _data.end()) 200 } else if (!json["scrub_geo"].is_null())
194 { 201 {
195 _type = type::scrub_location; 202 _type = type::scrub_location;
196 203
197 _user_id_and_tweet_id._tweet_id = _data.at("scrub_geo").at("up_to_status_id"); 204 _user_id_and_tweet_id._tweet_id = json["scrub_geo"]["up_to_status_id"].get<tweet_id>();
198 _user_id_and_tweet_id._user_id = _data.at("scrub_geo").at("user_id"); 205 _user_id_and_tweet_id._user_id = json["scrub_geo"]["user_id"].get<user_id>();
199 } else if (_data.find("limit") != _data.end()) 206 } else if (!json["limit"].is_null())
200 { 207 {
201 _type = type::limit; 208 _type = type::limit;
202 209
203 _limit = _data.at("limit").at("track"); 210 _limit = json["limit"]["track"].get<int>();
204 } else if (_data.find("status_withheld") != _data.end()) 211 } else if (!json["status_withheld"].is_null())
205 { 212 {
206 _type = type::withhold_status; 213 _type = type::withhold_status;
207 214
208 _withhold_status._user_id = _data.at("status_withheld").at("user_id"); 215 _withhold_status._user_id = json["status_withheld"]["user_id"].get<user_id>();
209 _withhold_status._tweet_id = _data.at("status_withheld").at("id"); 216 _withhold_status._tweet_id = json["status_withheld"]["id"].get<tweet_id>();
210 217
211 new(&_withhold_status._countries) std::vector<std::string>(); 218 new(&_withhold_status._countries) std::vector<std::string>();
212 for (auto s : _data.at("status_withheld").at("withheld_in_countries")) 219 for (auto s : json["status_withheld"]["withheld_in_countries"])
213 { 220 {
214 _withhold_status._countries.push_back(s); 221 _withhold_status._countries.push_back(s);
215 } 222 }
216 } else if (_data.find("user_withheld") != _data.end()) 223 } else if (!json["user_withheld"].is_null())
217 { 224 {
218 _type = type::withhold_user; 225 _type = type::withhold_user;
219 226
220 _withhold_user._user_id = _data.at("user_withheld").at("id"); 227 _withhold_user._user_id = json["user_withheld"]["id"].get<user_id>();
221 228
222 new(&_withhold_user._countries) std::vector<std::string>(); 229 new(&_withhold_user._countries) std::vector<std::string>();
223 for (auto s : _data.at("user_withheld").at("withheld_in_countries")) 230 for (auto s : json["user_withheld"]["withheld_in_countries"])
224 { 231 {
225 _withhold_user._countries.push_back(s); 232 _withhold_user._countries.push_back(s);
226 } 233 }
227 } else if (_data.find("disconnect") != _data.end()) 234 } else if (!json["disconnect"].is_null())
228 { 235 {
229 _type = type::disconnect; 236 _type = type::disconnect;
230 237
231 switch (_data.at("disconnect").at("code").get<int>()) 238 switch (json["disconnect"]["code"].get<int>())
232 { 239 {
233 case 1: _disconnect = disconnect_code::shutdown; break; 240 case 1: _disconnect = disconnect_code::shutdown; break;
234 case 2: _disconnect = disconnect_code::duplicate; break; 241 case 2: _disconnect = disconnect_code::duplicate; break;
@@ -242,26 +249,26 @@ namespace twitter {
242 case 12: _disconnect = disconnect_code::load; break; 249 case 12: _disconnect = disconnect_code::load; break;
243 default: _disconnect = disconnect_code::unknown; 250 default: _disconnect = disconnect_code::unknown;
244 } 251 }
245 } else if (_data.find("friends") != _data.end()) 252 } else if (!json["friends"].is_null())
246 { 253 {
247 _type = type::friends; 254 _type = type::friends;
248 255
249 new(&_friends) std::set<user_id>(_data.at("friends").begin(), _data.at("friends").end()); 256 new(&_friends) std::set<user_id>(std::begin(json["friends"]), std::end(json["friends"]));
250 } else if (_data.find("direct_message") != _data.end()) 257 } else if (!json["direct_message"].is_null())
251 { 258 {
252 _type = type::direct; 259 _type = type::direct;
253 260
254 new(&_direct_message) direct_message(_data.at("direct_message").dump()); 261 new(&_direct_message) direct_message(json["direct_message"].dump());
255 } else { 262 } else {
256 _type = type::unknown; 263 _type = type::unknown;
257 } 264 }
258 } catch (std::invalid_argument e) 265 } catch (const std::domain_error& error)
259 { 266 {
260 _type = type::invalid; 267 std::throw_with_nested(invalid_response(data));
261 } 268 }
262 } 269 }
263 270
264 notification::notification(const notification& other) 271 notification::notification(notification&& other)
265 { 272 {
266 _type = other._type; 273 _type = other._type;
267 274
@@ -269,11 +276,11 @@ namespace twitter {
269 { 276 {
270 case type::tweet: 277 case type::tweet:
271 { 278 {
272 new(&_tweet) tweet(other._tweet); 279 new(&_tweet) tweet(std::move(other._tweet));
273 280
274 break; 281 break;
275 } 282 }
276 283
277 case type::update_user: 284 case type::update_user:
278 case type::block: 285 case type::block:
279 case type::unblock: 286 case type::unblock:
@@ -281,32 +288,32 @@ namespace twitter {
281 case type::followed: 288 case type::followed:
282 case type::unfollow: 289 case type::unfollow:
283 { 290 {
284 new(&_user) user(other._user); 291 new(&_user) user(std::move(other._user));
285 292
286 break; 293 break;
287 } 294 }
288 295
289 case type::favorite: 296 case type::favorite:
290 case type::favorited: 297 case type::favorited:
291 case type::unfavorite: 298 case type::unfavorite:
292 case type::unfavorited: 299 case type::unfavorited:
293 case type::quoted: 300 case type::quoted:
294 { 301 {
295 new(&_user_and_tweet._user) user(other._user_and_tweet._user); 302 new(&_user_and_tweet._user) user(std::move(other._user_and_tweet._user));
296 new(&_user_and_tweet._tweet) tweet(other._user_and_tweet._tweet); 303 new(&_user_and_tweet._tweet) tweet(std::move(other._user_and_tweet._tweet));
297 304
298 break; 305 break;
299 } 306 }
300 307
301 case type::list_created: 308 case type::list_created:
302 case type::list_destroyed: 309 case type::list_destroyed:
303 case type::list_updated: 310 case type::list_updated:
304 { 311 {
305 new(&_list) list(other._list); 312 new(&_list) list(std::move(other._list));
306 313
307 break; 314 break;
308 } 315 }
309 316
310 case type::list_add: 317 case type::list_add:
311 case type::list_added: 318 case type::list_added:
312 case type::list_remove: 319 case type::list_remove:
@@ -316,78 +323,84 @@ namespace twitter {
316 case type::list_unsubscribe: 323 case type::list_unsubscribe:
317 case type::list_unsubscribed: 324 case type::list_unsubscribed:
318 { 325 {
319 new(&_user_and_list._user) user(other._user_and_list._user); 326 new(&_user_and_list._user) user(std::move(other._user_and_list._user));
320 new(&_user_and_list._list) list(other._user_and_list._list); 327 new(&_user_and_list._list) list(std::move(other._user_and_list._list));
321 328
322 break; 329 break;
323 } 330 }
324 331
325 case type::stall: 332 case type::stall:
326 case type::follow_limit: 333 case type::follow_limit:
327 case type::unknown_warning: 334 case type::unknown_warning:
328 { 335 {
329 new(&_warning) std::string(other._warning); 336 new(&_warning) std::string(std::move(other._warning));
330 337
331 break; 338 break;
332 } 339 }
333 340
334 case type::deletion: 341 case type::deletion:
335 case type::scrub_location: 342 case type::scrub_location:
336 { 343 {
337 _user_id_and_tweet_id._user_id = other._user_id_and_tweet_id._user_id; 344 _user_id_and_tweet_id._user_id = other._user_id_and_tweet_id._user_id;
338 _user_id_and_tweet_id._tweet_id = other._user_id_and_tweet_id._tweet_id; 345 _user_id_and_tweet_id._tweet_id = other._user_id_and_tweet_id._tweet_id;
339 346
340 break; 347 break;
341 } 348 }
342 349
343 case type::limit: 350 case type::limit:
344 { 351 {
345 _limit = other._limit; 352 _limit = other._limit;
346 353
347 break; 354 break;
348 } 355 }
349 356
350 case type::withhold_status: 357 case type::withhold_status:
351 { 358 {
352 _withhold_status._user_id = other._withhold_status._user_id; 359 _withhold_status._user_id = other._withhold_status._user_id;
353 _withhold_status._tweet_id = other._withhold_status._tweet_id; 360 _withhold_status._tweet_id = other._withhold_status._tweet_id;
354 new(&_withhold_status._countries) std::vector<std::string>(other._withhold_status._countries); 361 new(&_withhold_status._countries) std::vector<std::string>(std::move(other._withhold_status._countries));
355 362
356 break; 363 break;
357 } 364 }
358 365
359 case type::withhold_user: 366 case type::withhold_user:
360 { 367 {
361 _withhold_user._user_id = other._withhold_user._user_id; 368 _withhold_user._user_id = other._withhold_user._user_id;
362 new(&_withhold_user._countries) std::vector<std::string>(other._withhold_user._countries); 369 new(&_withhold_user._countries) std::vector<std::string>(std::move(other._withhold_user._countries));
363 370
364 break; 371 break;
365 } 372 }
366 373
367 case type::disconnect: 374 case type::disconnect:
368 { 375 {
369 _disconnect = other._disconnect; 376 _disconnect = other._disconnect;
370 377
371 break; 378 break;
372 } 379 }
373 380
374 case type::friends: 381 case type::friends:
375 { 382 {
376 new(&_friends) std::set<user_id>(other._friends); 383 new(&_friends) std::set<user_id>(std::move(other._friends));
377 384
378 break; 385 break;
379 } 386 }
380 387
381 case type::direct: 388 case type::direct:
382 { 389 {
383 new(&_direct_message) direct_message(other._direct_message); 390 new(&_direct_message) direct_message(std::move(other._direct_message));
384 391
392 break;
393 }
394
395 case type::unknown:
396 case type::invalid:
397 {
385 break; 398 break;
386 } 399 }
387 } 400 }
388 } 401 }
389 402
390 notification& notification::operator=(const notification& other) 403 notification& notification::operator=(notification&& other)
391 { 404 {
392 this->~notification(); 405 this->~notification();
393 406
@@ -397,7 +410,7 @@ namespace twitter {
397 { 410 {
398 case type::tweet: 411 case type::tweet:
399 { 412 {
400 new(&_tweet) tweet(other._tweet); 413 new(&_tweet) tweet(std::move(other._tweet));
401 414
402 break; 415 break;
403 } 416 }
@@ -409,7 +422,7 @@ namespace twitter {
409 case type::followed: 422 case type::followed:
410 case type::unfollow: 423 case type::unfollow:
411 { 424 {
412 new(&_user) user(other._user); 425 new(&_user) user(std::move(other._user));
413 426
414 break; 427 break;
415 } 428 }
@@ -420,8 +433,8 @@ namespace twitter {
420 case type::unfavorited: 433 case type::unfavorited:
421 case type::quoted: 434 case type::quoted:
422 { 435 {
423 new(&_user_and_tweet._user) user(other._user_and_tweet._user); 436 new(&_user_and_tweet._user) user(std::move(other._user_and_tweet._user));
424 new(&_user_and_tweet._tweet) tweet(other._user_and_tweet._tweet); 437 new(&_user_and_tweet._tweet) tweet(std::move(other._user_and_tweet._tweet));
425 438
426 break; 439 break;
427 } 440 }
@@ -430,7 +443,7 @@ namespace twitter {
430 case type::list_destroyed: 443 case type::list_destroyed:
431 case type::list_updated: 444 case type::list_updated:
432 { 445 {
433 new(&_list) list(other._list); 446 new(&_list) list(std::move(other._list));
434 447
435 break; 448 break;
436 } 449 }
@@ -444,8 +457,8 @@ namespace twitter {
444 case type::list_unsubscribe: 457 case type::list_unsubscribe:
445 case type::list_unsubscribed: 458 case type::list_unsubscribed:
446 { 459 {
447 new(&_user_and_list._user) user(other._user_and_list._user); 460 new(&_user_and_list._user) user(std::move(other._user_and_list._user));
448 new(&_user_and_list._list) list(other._user_and_list._list); 461 new(&_user_and_list._list) list(std::move(other._user_and_list._list));
449 462
450 break; 463 break;
451 } 464 }
@@ -454,7 +467,7 @@ namespace twitter {
454 case type::follow_limit: 467 case type::follow_limit:
455 case type::unknown_warning: 468 case type::unknown_warning:
456 { 469 {
457 new(&_warning) std::string(other._warning); 470 new(&_warning) std::string(std::move(other._warning));
458 471
459 break; 472 break;
460 } 473 }
@@ -479,7 +492,7 @@ namespace twitter {
479 { 492 {
480 _withhold_status._user_id = other._withhold_status._user_id; 493 _withhold_status._user_id = other._withhold_status._user_id;
481 _withhold_status._tweet_id = other._withhold_status._tweet_id; 494 _withhold_status._tweet_id = other._withhold_status._tweet_id;
482 new(&_withhold_status._countries) std::vector<std::string>(other._withhold_status._countries); 495 new(&_withhold_status._countries) std::vector<std::string>(std::move(other._withhold_status._countries));
483 496
484 break; 497 break;
485 } 498 }
@@ -487,7 +500,7 @@ namespace twitter {
487 case type::withhold_user: 500 case type::withhold_user:
488 { 501 {
489 _withhold_user._user_id = other._withhold_user._user_id; 502 _withhold_user._user_id = other._withhold_user._user_id;
490 new(&_withhold_user._countries) std::vector<std::string>(other._withhold_user._countries); 503 new(&_withhold_user._countries) std::vector<std::string>(std::move(other._withhold_user._countries));
491 504
492 break; 505 break;
493 } 506 }
@@ -501,17 +514,23 @@ namespace twitter {
501 514
502 case type::friends: 515 case type::friends:
503 { 516 {
504 new(&_friends) std::set<user_id>(other._friends); 517 new(&_friends) std::set<user_id>(std::move(other._friends));
505 518
506 break; 519 break;
507 } 520 }
508 521
509 case type::direct: 522 case type::direct:
510 { 523 {
511 new(&_direct_message) direct_message(other._direct_message); 524 new(&_direct_message) direct_message(std::move(other._direct_message));
512 525
513 break; 526 break;
514 } 527 }
528
529 case type::invalid:
530 case type::unknown:
531 {
532 break;
533 }
515 } 534 }
516 535
517 return *this; 536 return *this;
@@ -578,6 +597,7 @@ namespace twitter {
578 597
579 case type::stall: 598 case type::stall:
580 case type::follow_limit: 599 case type::follow_limit:
600 case type::unknown_warning:
581 { 601 {
582 using string_type = std::string; 602 using string_type = std::string;
583 _warning.~string_type(); 603 _warning.~string_type();
@@ -615,10 +635,20 @@ namespace twitter {
615 635
616 break; 636 break;
617 } 637 }
638
639 case type::deletion:
640 case type::scrub_location:
641 case type::limit:
642 case type::disconnect:
643 case type::unknown:
644 case type::invalid:
645 {
646 break;
647 }
618 } 648 }
619 } 649 }
620 650
621 tweet notification::getTweet() const 651 const tweet& notification::getTweet() const
622 { 652 {
623 switch (_type) 653 switch (_type)
624 { 654 {
@@ -639,13 +669,11 @@ namespace twitter {
639 default: 669 default:
640 { 670 {
641 assert(false); 671 assert(false);
642
643 return tweet();
644 } 672 }
645 } 673 }
646 } 674 }
647 675
648 user notification::getUser() const 676 const user& notification::getUser() const
649 { 677 {
650 switch (_type) 678 switch (_type)
651 { 679 {
@@ -683,13 +711,11 @@ namespace twitter {
683 default: 711 default:
684 { 712 {
685 assert(false); 713 assert(false);
686
687 return user();
688 } 714 }
689 } 715 }
690 } 716 }
691 717
692 list notification::getList() const 718 const list& notification::getList() const
693 { 719 {
694 switch (_type) 720 switch (_type)
695 { 721 {
@@ -715,8 +741,6 @@ namespace twitter {
715 default: 741 default:
716 { 742 {
717 assert(false); 743 assert(false);
718
719 return list();
720 } 744 }
721 } 745 }
722 } 746 }
@@ -739,8 +763,6 @@ namespace twitter {
739 default: 763 default:
740 { 764 {
741 assert(false); 765 assert(false);
742
743 return 0;
744 } 766 }
745 } 767 }
746 } 768 }
@@ -768,13 +790,11 @@ namespace twitter {
768 default: 790 default:
769 { 791 {
770 assert(false); 792 assert(false);
771
772 return 0;
773 } 793 }
774 } 794 }
775 } 795 }
776 796
777 std::vector<std::string> notification::getCountries() const 797 const std::vector<std::string>& notification::getCountries() const
778 { 798 {
779 switch (_type) 799 switch (_type)
780 { 800 {
@@ -791,8 +811,6 @@ namespace twitter {
791 default: 811 default:
792 { 812 {
793 assert(false); 813 assert(false);
794
795 return std::vector<std::string>();
796 } 814 }
797 } 815 }
798 } 816 }
@@ -804,14 +822,14 @@ namespace twitter {
804 return _disconnect; 822 return _disconnect;
805 } 823 }
806 824
807 std::set<user_id> notification::getFriends() const 825 const std::set<user_id>& notification::getFriends() const
808 { 826 {
809 assert(_type == type::friends); 827 assert(_type == type::friends);
810 828
811 return _friends; 829 return _friends;
812 } 830 }
813 831
814 direct_message notification::getDirectMessage() const 832 const direct_message& notification::getDirectMessage() const
815 { 833 {
816 assert(_type == type::direct); 834 assert(_type == type::direct);
817 835
@@ -825,7 +843,7 @@ namespace twitter {
825 return _limit; 843 return _limit;
826 } 844 }
827 845
828 std::string notification::getWarning() const 846 const std::string& notification::getWarning() const
829 { 847 {
830 switch (_type) 848 switch (_type)
831 { 849 {
@@ -839,15 +857,8 @@ namespace twitter {
839 default: 857 default:
840 { 858 {
841 assert(false); 859 assert(false);
842
843 return "";
844 } 860 }
845 } 861 }
846 } 862 }
847 863
848 notification::operator bool() const
849 {
850 return _type != type::invalid;
851 }
852
853}; 864};
diff --git a/src/notification.h b/src/notification.h index da83b0f..b5ecd49 100644 --- a/src/notification.h +++ b/src/notification.h
@@ -11,7 +11,10 @@
11 11
12namespace twitter { 12namespace twitter {
13 13
14 enum class disconnect_code { 14 class client;
15
16 enum class disconnect_code
17 {
15 shutdown, 18 shutdown,
16 duplicate, 19 duplicate,
17 stall, 20 stall,
@@ -26,118 +29,118 @@ namespace twitter {
26 }; 29 };
27 30
28 class notification { 31 class notification {
29 public: 32 public:
30 enum class type { 33 enum class type {
31 // Tweet object 34 // Tweet object
32 tweet, 35 tweet,
33 36
34 // User object 37 // User object
35 update_user, 38 update_user,
36 block, 39 block,
37 unblock, 40 unblock,
38 follow, 41 follow,
39 followed, 42 followed,
40 unfollow, 43 unfollow,
41 44
42 // User and tweet 45 // User and tweet
43 favorite, 46 favorite,
44 favorited, 47 favorited,
45 unfavorite, 48 unfavorite,
46 unfavorited, 49 unfavorited,
47 quoted, 50 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 51
86 type getType() const; 52 // List
53 list_created,
54 list_destroyed,
55 list_updated,
87 56
88 notification(); 57 // User and list
89 notification(std::string data, const user& current_user); 58 list_add,
90 notification(const notification& other); 59 list_added,
91 notification& operator=(const notification& other); 60 list_remove,
92 ~notification(); 61 list_removed,
62 list_subscribe,
63 list_subscribed,
64 list_unsubscribe,
65 list_unsubscribed,
93 66
94 tweet getTweet() const; 67 // Warning
95 user getUser() const; 68 stall,
96 list getList() const; 69 follow_limit,
97 tweet_id getTweetID() const; 70 unknown_warning,
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 71
106 operator bool() const; 72 // User ID and tweet ID
73 deletion,
74 scrub_location,
107 75
108 private: 76 // Special
109 union { 77 limit,
78 withhold_status,
79 withhold_user,
80 disconnect,
81 friends,
82 direct,
83
84 // Nothing
85 unknown,
86 invalid
87 };
88
89 type getType() const;
90
91 notification(const client& tclient, std::string data);
92 notification(notification&& other);
93 notification& operator=(notification&& other);
94 ~notification();
95
96 notification(const notification& other) = delete;
97 notification& operator=(const notification& other) = delete;
98
99 const tweet& getTweet() const;
100 const user& getUser() const;
101 const list& getList() const;
102 tweet_id getTweetID() const;
103 user_id getUserID() const;
104 const std::vector<std::string>& getCountries() const;
105 disconnect_code getDisconnectCode() const;
106 const std::set<user_id>& getFriends() const;
107 const direct_message& getDirectMessage() const;
108 int getLimit() const;
109 const std::string& getWarning() const;
110
111 private:
112 union {
113 tweet _tweet;
114 user _user;
115 list _list;
116 struct {
117 user _user;
110 tweet _tweet; 118 tweet _tweet;
119 } _user_and_tweet;
120 struct {
111 user _user; 121 user _user;
112 list _list; 122 list _list;
113 struct { 123 } _user_and_list;
114 user _user; 124 std::string _warning;
115 tweet _tweet; 125 struct {
116 } _user_and_tweet; 126 user_id _user_id;
117 struct { 127 tweet_id _tweet_id;
118 user _user; 128 } _user_id_and_tweet_id;
119 list _list; 129 int _limit;
120 } _user_and_list; 130 struct {
121 std::string _warning; 131 user_id _user_id;
122 struct { 132 tweet_id _tweet_id;
123 user_id _user_id; 133 std::vector<std::string> _countries;
124 tweet_id _tweet_id; 134 } _withhold_status;
125 } _user_id_and_tweet_id; 135 struct {
126 int _limit; 136 user_id _user_id;
127 struct { 137 std::vector<std::string> _countries;
128 user_id _user_id; 138 } _withhold_user;
129 tweet_id _tweet_id; 139 disconnect_code _disconnect;
130 std::vector<std::string> _countries; 140 std::set<user_id> _friends;
131 } _withhold_status; 141 direct_message _direct_message;
132 struct { 142 };
133 user_id _user_id; 143 type _type;
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 }; 144 };
142 145
143}; 146};
diff --git a/src/stream.cpp b/src/stream.cpp new file mode 100644 index 0000000..d2b3afe --- /dev/null +++ b/src/stream.cpp
@@ -0,0 +1,291 @@
1#include "stream.h"
2#include <liboauthcpp/liboauthcpp.h>
3#include <curl_easy.h>
4#include <curl_header.h>
5#include "util.h"
6#include "notification.h"
7#include "client.h"
8
9namespace twitter {
10
11 stream::stream(
12 const client& tclient,
13 notify_callback callback,
14 bool with_followings,
15 bool receive_all_replies,
16 std::list<std::string> track,
17 std::list<bounding_box> locations) :
18 _client(tclient),
19 _notify(callback),
20 _thread(&stream::run, this, generateUrl(with_followings, receive_all_replies, track, locations))
21 {
22 }
23
24 stream::~stream()
25 {
26 if (_thread.joinable())
27 {
28 _stop = true;
29 _thread.join();
30 }
31 }
32
33 std::string stream::generateUrl(
34 bool with_followings,
35 bool receive_all_replies,
36 std::list<std::string> track,
37 std::list<bounding_box> locations)
38 {
39 std::list<std::string> arguments;
40
41 if (receive_all_replies)
42 {
43 arguments.push_back("replies=all");
44 }
45
46 if (!with_followings)
47 {
48 arguments.push_back("with=user");
49 }
50
51 if (!track.empty())
52 {
53 std::ostringstream trackstr;
54 trackstr << "track=";
55
56 for (auto it = std::begin(track); it != std::end(track); it++)
57 {
58 if (it != std::begin(track))
59 {
60 trackstr << ",";
61 }
62
63 trackstr << OAuth::HttpEncodeQueryValue(*it);
64 }
65
66 arguments.push_back(trackstr.str());
67 }
68
69 if (!locations.empty())
70 {
71 std::ostringstream localstr;
72 localstr << "locations=";
73
74 for (auto it = std::begin(locations); it != std::end(locations); it++)
75 {
76 if (it != std::begin(locations))
77 {
78 localstr << ",";
79 }
80
81 localstr << (double)it->getSouthWestLongitude() << ",";
82 localstr << (double)it->getSouthWestLatitude() << ",";
83 localstr << (double)it->getNorthEastLongitude() << ",";
84 localstr << (double)it->getNorthEastLatitude();
85 }
86
87 arguments.push_back(localstr.str());
88 }
89
90 std::ostringstream urlstr;
91 urlstr << "https://userstream.twitter.com/1.1/user.json";
92
93 if (!arguments.empty())
94 {
95 urlstr << "?";
96 urlstr << implode(std::begin(arguments), std::end(arguments), "&");
97 }
98
99 return urlstr.str();
100 }
101
102 void stream::run(std::string url)
103 {
104 _backoff_type = backoff::none;
105 _backoff_amount = std::chrono::milliseconds(0);
106 for (;;)
107 {
108 curl::curl_ios<stream> ios(this, [] (void* contents, size_t size, size_t nmemb, void* userp) {
109 return static_cast<stream*>(userp)->write(static_cast<char*>(contents), size, nmemb);
110 });
111
112 curl::curl_easy conn(ios);
113 curl::curl_header headers;
114 std::string oauth_header;
115
116 try
117 {
118 oauth_header = _client._oauth_client->getFormattedHttpHeader(OAuth::Http::Get, url, "");
119
120 if (!oauth_header.empty())
121 {
122 headers.add(oauth_header);
123 }
124 } catch (const OAuth::ParseError& error)
125 {
126 std::cout << "Error generating OAuth header:" << std::endl;
127 std::cout << error.what() << std::endl;
128 std::cout << "This is likely due to a malformed URL." << std::endl;
129
130 assert(false);
131 }
132
133 try
134 {
135 conn.add<CURLOPT_HEADERFUNCTION>(nullptr);
136 conn.add<CURLOPT_HEADERDATA>(nullptr);
137 conn.add<CURLOPT_XFERINFOFUNCTION>([] (void* cdata, curl_off_t, curl_off_t, curl_off_t, curl_off_t) {
138 return static_cast<stream*>(cdata)->progress();
139 });
140 conn.add<CURLOPT_XFERINFODATA>(this);
141 conn.add<CURLOPT_NOPROGRESS>(0);
142 //conn.add<CURLOPT_VERBOSE>(1);
143 //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace);
144 conn.add<CURLOPT_URL>(url.c_str());
145 conn.add<CURLOPT_HTTPHEADER>(headers.get());
146 } catch (const curl::curl_exception& error)
147 {
148 error.print_traceback();
149
150 assert(false);
151 }
152
153 bool failure = false;
154 try
155 {
156 conn.perform();
157 } catch (const curl::curl_easy_exception& error)
158 {
159 failure = true;
160 if ((error.get_code() == CURLE_ABORTED_BY_CALLBACK) && _stop)
161 {
162 break;
163 } else {
164 if (_backoff_type == backoff::none)
165 {
166 _established = false;
167 _backoff_type = backoff::network;
168 _backoff_amount = std::chrono::milliseconds(0);
169 }
170 }
171 }
172
173 if (!failure)
174 {
175 long response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get();
176 if (response_code == 420)
177 {
178 if (_backoff_type == backoff::none)
179 {
180 _established = false;
181 _backoff_type = backoff::rate_limit;
182 _backoff_amount = std::chrono::minutes(1);
183 }
184 } else if (response_code != 200)
185 {
186 if (_backoff_type == backoff::none)
187 {
188 _established = false;
189 _backoff_type = backoff::http;
190 _backoff_amount = std::chrono::seconds(5);
191 }
192 } else {
193 if (_backoff_type == backoff::none)
194 {
195 _established = false;
196 _backoff_type = backoff::network;
197 _backoff_amount = std::chrono::milliseconds(0);
198 }
199 }
200 }
201
202 std::this_thread::sleep_for(_backoff_amount);
203
204 switch (_backoff_type)
205 {
206 case backoff::network:
207 {
208 if (_backoff_amount < std::chrono::seconds(16))
209 {
210 _backoff_amount += std::chrono::milliseconds(250);
211 }
212
213 break;
214 }
215
216 case backoff::http:
217 {
218 if (_backoff_amount < std::chrono::seconds(320))
219 {
220 _backoff_amount *= 2;
221 }
222
223 break;
224 }
225
226 case backoff::rate_limit:
227 {
228 _backoff_amount *= 2;
229
230 break;
231 }
232
233 case backoff::none:
234 {
235 break;
236 }
237 }
238 }
239 }
240
241 size_t stream::write(char* ptr, size_t size, size_t nmemb)
242 {
243 for (size_t i = 0; i < size*nmemb; i++)
244 {
245 if (ptr[i] == '\r')
246 {
247 i++; // Skip the \n
248
249 if (!_buffer.empty())
250 {
251 notification n(_client, _buffer);
252 if (n.getType() == notification::type::friends)
253 {
254 _established = true;
255 _backoff_type = backoff::none;
256 _backoff_amount = std::chrono::milliseconds(0);
257 }
258
259 _notify(n);
260
261 _buffer = "";
262 }
263 } else {
264 _buffer.push_back(ptr[i]);
265 }
266 }
267
268 time(&_last_write);
269
270 return size*nmemb;
271 }
272
273 int stream::progress()
274 {
275 if (_stop)
276 {
277 return 1;
278 }
279
280 if (_established)
281 {
282 if (difftime(time(NULL), _last_write) >= 90)
283 {
284 return 1;
285 }
286 }
287
288 return 0;
289 }
290
291}
diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 0000000..3391912 --- /dev/null +++ b/src/stream.h
@@ -0,0 +1,67 @@
1#ifndef STREAM_H_E9146952
2#define STREAM_H_E9146952
3
4#include <functional>
5#include <list>
6#include <string>
7#include <chrono>
8#include <thread>
9#include "bounding_box.h"
10
11namespace twitter {
12
13 class client;
14 class notification;
15
16 class stream {
17 public:
18
19 typedef std::function<void(const notification& _notification)> notify_callback;
20
21 stream(
22 const client& tclient,
23 notify_callback callback,
24 bool with_followings = true,
25 bool receive_all_replies = false,
26 std::list<std::string> track = {},
27 std::list<bounding_box> locations = {});
28
29 ~stream();
30
31 stream(const stream& other) = delete;
32 stream(stream&& other) = delete;
33 stream& operator=(const stream& other) = delete;
34 stream& operator=(stream&& other) = delete;
35
36 private:
37 enum class backoff {
38 none,
39 network,
40 http,
41 rate_limit
42 };
43
44 static std::string generateUrl(
45 bool with_followings,
46 bool receive_all_replies,
47 std::list<std::string> track,
48 std::list<bounding_box> locations);
49
50 void run(std::string url);
51 int progress();
52 size_t write(char* ptr, size_t size, size_t nmemb);
53
54 const client& _client;
55 notify_callback _notify;
56 bool _stop = false;
57 std::string _buffer;
58 time_t _last_write;
59 bool _established = false;
60 backoff _backoff_type = backoff::none;
61 std::chrono::milliseconds _backoff_amount;
62 std::thread _thread;
63 };
64
65}
66
67#endif /* end of include guard: STREAM_H_E9146952 */
diff --git a/src/tweet.cpp b/src/tweet.cpp index 193df6e..4abe2f8 100644 --- a/src/tweet.cpp +++ b/src/tweet.cpp
@@ -1,139 +1,80 @@
1#include "tweet.h" 1#include "tweet.h"
2#include <json.hpp> 2#include <json.hpp>
3#include <cassert> 3#include "util.h"
4 4#include "codes.h"
5using nlohmann::json; 5#include "client.h"
6 6
7namespace twitter { 7namespace twitter {
8 8
9 tweet::tweet() : _valid(false) 9 tweet::tweet(const client& tclient, std::string data) try
10 { 10 : _client(tclient)
11
12 }
13
14 tweet::tweet(std::string data) : _valid(true)
15 { 11 {
16 auto _data = json::parse(data); 12 auto json = nlohmann::json::parse(data);
17 _id = _data.at("id"); 13 _id = json["id"].get<tweet_id>();
18 _text = _data.at("text"); 14 _text = json["text"].get<std::string>();
19 _author = user(_data.at("user").dump()); 15 _author = make_unique<user>(_client, json["user"].dump());
20 16
21 if (_data.find("retweeted_status") != _data.end()) 17 if (!json["retweeted_status"].is_null())
22 { 18 {
23 _is_retweet = true; 19 _is_retweet = true;
24 20
25 std::string retweet = _data.at("retweeted_status").dump(); 21 _retweeted_status = make_unique<tweet>(_client, json["retweeted_status"].dump());
26 _retweeted_status = new tweet(retweet);
27 } 22 }
28 23
29 if (_data.find("entities") != _data.end()) 24 if (!json["entities"].is_null())
30 { 25 {
31 auto _entities = _data.at("entities"); 26 auto entities = json["entities"];
32 if (_entities.find("user_mentions") != _entities.end()) 27 if (!entities["user_mentions"].is_null())
33 { 28 {
34 for (auto _mention : _entities.at("user_mentions")) 29 for (auto mention : entities["user_mentions"])
35 { 30 {
36 _mentions.push_back(std::make_pair(_mention.at("id"), _mention.at("screen_name").get<std::string>())); 31 _mentions.push_back(std::make_pair(mention["id"].get<user_id>(), mention["screen_name"].get<std::string>()));
37 } 32 }
38 } 33 }
39 } 34 }
40 } 35 } catch (const std::invalid_argument& error)
41
42 tweet::tweet(const tweet& other)
43 { 36 {
44 _valid = other._valid; 37 std::throw_with_nested(malformed_object("tweet", data));
45 _id = other._id; 38 } catch (const std::domain_error& error)
46 _text = other._text;
47 _author = other._author;
48 _is_retweet = other._is_retweet;
49
50 if (_is_retweet)
51 {
52 _retweeted_status = new tweet(*other._retweeted_status);
53 }
54
55 _mentions = other._mentions;
56 }
57
58 tweet::tweet(tweet&& other) : tweet()
59 { 39 {
60 swap(*this, other); 40 std::throw_with_nested(malformed_object("tweet", data));
61 } 41 }
62 42
63 tweet::~tweet() 43 std::string tweet::generateReplyPrefill() const
64 { 44 {
65 if (_is_retweet) 45 std::ostringstream output;
46 output << "@" << _author->getScreenName() << " ";
47
48 for (auto mention : _mentions)
66 { 49 {
67 delete _retweeted_status; 50 if ((mention.first != _author->getID()) && (mention.first != _client.getUser().getID()))
51 {
52 output << "@" << mention.second << " ";
53 }
68 } 54 }
69 }
70
71 tweet& tweet::operator=(tweet other)
72 {
73 swap(*this, other);
74 55
75 return *this; 56 return output.str();
76 }
77
78 void swap(tweet& first, tweet& second)
79 {
80 std::swap(first._valid, second._valid);
81 std::swap(first._id, second._id);
82 std::swap(first._text, second._text);
83 std::swap(first._author, second._author);
84 std::swap(first._is_retweet, second._is_retweet);
85 std::swap(first._retweeted_status, second._retweeted_status);
86 std::swap(first._mentions, second._mentions);
87 } 57 }
88 58
89 tweet_id tweet::getID() const 59 tweet tweet::reply(std::string message, std::list<long> media_ids) const
90 { 60 {
91 assert(_valid); 61 return _client.replyToTweet(message, _id, media_ids);
92
93 return _id;
94 }
95
96 std::string tweet::getText() const
97 {
98 assert(_valid);
99
100 return _text;
101 } 62 }
102 63
103 const user& tweet::getAuthor() const 64 bool tweet::isMyTweet() const
104 { 65 {
105 assert(_valid); 66 return *_author == _client.getUser();
106
107 return _author;
108 }
109
110 bool tweet::isRetweet() const
111 {
112 assert(_valid);
113
114 return _is_retweet;
115 }
116
117 tweet tweet::getRetweet() const
118 {
119 assert(_valid && _is_retweet);
120
121 return *_retweeted_status;
122 }
123
124 std::vector<std::pair<user_id, std::string>> tweet::getMentions() const
125 {
126 return _mentions;
127 } 67 }
128 68
129 std::string tweet::getURL() const 69 std::string tweet::getURL() const
130 { 70 {
131 return "https://twitter.com/" + _author.getScreenName() + "/statuses/" + std::to_string(_id); 71 std::ostringstream urlstr;
132 } 72 urlstr << "https://twitter.com";
133 73 urlstr << _author->getScreenName();
134 tweet::operator bool() const 74 urlstr << "/statuses/";
135 { 75 urlstr << _id;
136 return _valid; 76
77 return urlstr.str();
137 } 78 }
138 79
139}; 80};
diff --git a/src/tweet.h b/src/tweet.h index 74d73d2..a29e45c 100644 --- a/src/tweet.h +++ b/src/tweet.h
@@ -2,42 +2,77 @@
2#define TWEET_H_CE980721 2#define TWEET_H_CE980721
3 3
4#include <string> 4#include <string>
5#include "user.h"
6#include <vector> 5#include <vector>
7#include <utility> 6#include <utility>
7#include <cassert>
8#include <list>
9#include "user.h"
8 10
9namespace twitter { 11namespace twitter {
10 12
13 class client;
14
11 typedef unsigned long long tweet_id; 15 typedef unsigned long long tweet_id;
12 16
13 class tweet { 17 class tweet {
14 public: 18 public:
15 tweet();
16 tweet(std::string data);
17 tweet(const tweet& other);
18 tweet(tweet&& other);
19 ~tweet();
20
21 tweet& operator=(tweet other);
22 friend void swap(tweet& first, tweet& second);
23
24 tweet_id getID() const;
25 std::string getText() const;
26 const user& getAuthor() const;
27 bool isRetweet() const;
28 tweet getRetweet() const;
29 std::vector<std::pair<user_id, std::string>> getMentions() const;
30 std::string getURL() const;
31 19
32 operator bool() const; 20 tweet(const client& tclient, std::string data);
21
22 tweet(const tweet& other) = delete;
23 tweet& operator=(const tweet& other) = delete;
24
25 tweet(tweet&& other) = default;
26 tweet& operator=(tweet&& other) = default;
27
28 tweet_id getID() const
29 {
30 return _id;
31 }
32
33 std::string getText() const
34 {
35 return _text;
36 }
37
38 const user& getAuthor() const
39 {
40 return *_author;
41 }
42
43 bool isRetweet() const
44 {
45 return _is_retweet;
46 }
47
48 const tweet& getRetweet() const
49 {
50 assert(_is_retweet);
51
52 return *_retweeted_status;
53 }
54
55 const std::vector<std::pair<user_id, std::string>>& getMentions() const
56 {
57 return _mentions;
58 }
59
60 std::string generateReplyPrefill() const;
61
62 tweet reply(std::string message, std::list<long> media_ids = {}) const;
63
64 bool isMyTweet() const;
65
66 std::string getURL() const;
33 67
34 private: 68 private:
35 bool _valid; 69
70 const client& _client;
36 tweet_id _id; 71 tweet_id _id;
37 std::string _text; 72 std::string _text;
38 user _author; 73 std::unique_ptr<user> _author;
39 bool _is_retweet = false; 74 bool _is_retweet = false;
40 tweet* _retweeted_status = nullptr; 75 std::unique_ptr<tweet> _retweeted_status;
41 std::vector<std::pair<user_id, std::string>> _mentions; 76 std::vector<std::pair<user_id, std::string>> _mentions;
42 }; 77 };
43 78
diff --git a/src/twitter.h b/src/twitter.h index 90925df..1ba4394 100644 --- a/src/twitter.h +++ b/src/twitter.h
@@ -11,6 +11,7 @@ namespace twitter {
11#include "util.h" 11#include "util.h"
12#include "auth.h" 12#include "auth.h"
13#include "client.h" 13#include "client.h"
14#include "stream.h"
14#include "tweet.h" 15#include "tweet.h"
15#include "user.h" 16#include "user.h"
16#include "notification.h" 17#include "notification.h"
diff --git a/src/user.cpp b/src/user.cpp index 0fa1e39..0b6e93a 100644 --- a/src/user.cpp +++ b/src/user.cpp
@@ -1,51 +1,43 @@
1#include "user.h" 1#include "user.h"
2#include <json.hpp> 2#include <json.hpp>
3 3#include "codes.h"
4using nlohmann::json; 4#include "client.h"
5 5
6namespace twitter { 6namespace twitter {
7 7
8 user::user() : _valid(false) 8 user::user(const client& tclient, std::string data) try
9 { 9 : _client(tclient)
10
11 }
12
13 user::user(std::string data) : _valid(true)
14 { 10 {
15 auto _data = json::parse(data); 11 auto json = nlohmann::json::parse(data);
16 _id = _data.at("id"); 12 _id = json["id"].get<user_id>();
17 _screen_name = _data.at("screen_name"); 13 _screen_name = json["screen_name"].get<std::string>();
18 _name = _data.at("name"); 14 _name = json["name"].get<std::string>();
19 } 15 } catch (const std::invalid_argument& error)
20
21 user_id user::getID() const
22 { 16 {
23 return _id; 17 std::throw_with_nested(malformed_object("user", data));
24 } 18 } catch (const std::domain_error& error)
25
26 std::string user::getScreenName() const
27 { 19 {
28 return _screen_name; 20 std::throw_with_nested(malformed_object("user", data));
29 } 21 }
30 22
31 std::string user::getName() const 23 std::set<user_id> user::getFriends() const
32 { 24 {
33 return _name; 25 return _client.getFriends(_id);
34 } 26 }
35 27
36 user::operator bool() const 28 std::set<user_id> user::getFollowers() const
37 { 29 {
38 return _valid; 30 return _client.getFollowers(_id);
39 } 31 }
40 32
41 bool user::operator==(const user& other) const 33 void user::follow() const
42 { 34 {
43 return _id == other._id; 35 _client.follow(_id);
44 } 36 }
45 37
46 bool user::operator!=(const user& other) const 38 void user::unfollow() const
47 { 39 {
48 return _id != other._id; 40 _client.unfollow(_id);
49 } 41 }
50 42
51}; 43};
diff --git a/src/user.h b/src/user.h index 1d8be99..f08840b 100644 --- a/src/user.h +++ b/src/user.h
@@ -2,26 +2,52 @@
2#define USER_H_BF3AB38C 2#define USER_H_BF3AB38C
3 3
4#include <string> 4#include <string>
5#include <set>
5 6
6namespace twitter { 7namespace twitter {
7 8
9 class client;
10
8 typedef unsigned long long user_id; 11 typedef unsigned long long user_id;
9 12
10 class user { 13 class user {
11 public: 14 public:
12 user();
13 user(std::string data);
14 15
15 user_id getID() const; 16 user(const client& tclient, std::string data);
16 std::string getScreenName() const; 17
17 std::string getName() const; 18 user_id getID() const
19 {
20 return _id;
21 }
22
23 std::string getScreenName() const
24 {
25 return _screen_name;
26 }
27
28 std::string getName() const
29 {
30 return _name;
31 }
18 32
19 operator bool() const; 33 bool operator==(const user& other) const
20 bool operator==(const user& other) const; 34 {
21 bool operator!=(const user& other) const; 35 return _id == other._id;
36 }
37
38 bool operator!=(const user& other) const
39 {
40 return _id != other._id;
41 }
42
43 std::set<user_id> getFriends() const;
44 std::set<user_id> getFollowers() const;
45 void follow() const;
46 void unfollow() const;
22 47
23 private: 48 private:
24 bool _valid = false; 49
50 const client& _client;
25 user_id _id; 51 user_id _id;
26 std::string _screen_name; 52 std::string _screen_name;
27 std::string _name; 53 std::string _name;
diff --git a/src/util.h b/src/util.h index e1c9370..2f4b9a3 100644 --- a/src/util.h +++ b/src/util.h
@@ -24,6 +24,12 @@ namespace twitter {
24 return result.str(); 24 return result.str();
25 } 25 }
26 26
27 template<typename T, typename... Args>
28 std::unique_ptr<T> make_unique(Args&&... args)
29 {
30 return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
31 }
32
27}; 33};
28 34
29#endif /* end of include guard: UTIL_H_440DEAA0 */ 35#endif /* end of include guard: UTIL_H_440DEAA0 */
diff --git a/vendor/liboauthcpp b/vendor/liboauthcpp
Subproject b460cdfadb599c5cea88cbaff0539b7ba9fb17b Subproject 9ebd39b8be7ccb846c13cc50d995b666500d495