diff options
Diffstat (limited to 'src/client.cpp')
| -rw-r--r-- | src/client.cpp | 1066 |
1 files changed, 378 insertions, 688 deletions
| 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> | |
| 11 | using 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 | ||
| 85 | namespace twitter { | 83 | namespace 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 | }; |
