diff options
Diffstat (limited to 'src/client.cpp')
| -rw-r--r-- | src/client.cpp | 368 |
1 files changed, 23 insertions, 345 deletions
| diff --git a/src/client.cpp b/src/client.cpp index 6fea80a..b7eb9d1 100644 --- a/src/client.cpp +++ b/src/client.cpp | |||
| @@ -2,342 +2,22 @@ | |||
| 2 | #include <sstream> | 2 | #include <sstream> |
| 3 | #include <set> | 3 | #include <set> |
| 4 | #include <algorithm> | 4 | #include <algorithm> |
| 5 | #include <liboauthcpp/liboauthcpp.h> | ||
| 6 | #include <curl_easy.h> | ||
| 7 | #include <curl_header.h> | ||
| 8 | #include <json.hpp> | 5 | #include <json.hpp> |
| 9 | #include <thread> | 6 | #include <thread> |
| 10 | 7 | #include "request.h" | |
| 11 | // These are here for debugging curl stuff | ||
| 12 | |||
| 13 | static | ||
| 14 | void dump(const char *text, | ||
| 15 | FILE *stream, unsigned char *ptr, size_t size) | ||
| 16 | { | ||
| 17 | size_t i; | ||
| 18 | size_t c; | ||
| 19 | unsigned int width=80; | ||
| 20 | |||
| 21 | fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n", | ||
| 22 | text, (long)size, (long)size); | ||
| 23 | |||
| 24 | for(i=0; i<size; i+= width) { | ||
| 25 | fprintf(stream, "%4.4lx: ", (long)i); | ||
| 26 | |||
| 27 | /* show hex to the left | ||
| 28 | for(c = 0; c < width; c++) { | ||
| 29 | if(i+c < size) | ||
| 30 | fprintf(stream, "%02x ", ptr[i+c]); | ||
| 31 | else | ||
| 32 | fputs(" ", stream); | ||
| 33 | }*/ | ||
| 34 | |||
| 35 | /* show data on the right */ | ||
| 36 | for(c = 0; (c < width) && (i+c < size); c++) { | ||
| 37 | char x = (ptr[i+c] >= 0x20 && ptr[i+c] < 0x80) ? ptr[i+c] : '.'; | ||
| 38 | fputc(x, stream); | ||
| 39 | } | ||
| 40 | |||
| 41 | fputc('\n', stream); /* newline */ | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | static | ||
| 46 | int my_trace(CURL *handle, curl_infotype type, | ||
| 47 | char *data, size_t size, | ||
| 48 | void *userp) | ||
| 49 | { | ||
| 50 | const char *text; | ||
| 51 | (void)handle; /* prevent compiler warning */ | ||
| 52 | |||
| 53 | switch (type) { | ||
| 54 | case CURLINFO_TEXT: | ||
| 55 | fprintf(stderr, "== Info: %s", data); | ||
| 56 | default: /* in case a new one is introduced to shock us */ | ||
| 57 | return 0; | ||
| 58 | |||
| 59 | case CURLINFO_HEADER_OUT: | ||
| 60 | text = "=> Send header"; | ||
| 61 | break; | ||
| 62 | case CURLINFO_DATA_OUT: | ||
| 63 | text = "=> Send data"; | ||
| 64 | break; | ||
| 65 | case CURLINFO_SSL_DATA_OUT: | ||
| 66 | text = "=> Send SSL data"; | ||
| 67 | break; | ||
| 68 | case CURLINFO_HEADER_IN: | ||
| 69 | text = "<= Recv header"; | ||
| 70 | break; | ||
| 71 | case CURLINFO_DATA_IN: | ||
| 72 | text = "<= Recv data"; | ||
| 73 | break; | ||
| 74 | case CURLINFO_SSL_DATA_IN: | ||
| 75 | text = "<= Recv SSL data"; | ||
| 76 | break; | ||
| 77 | } | ||
| 78 | |||
| 79 | dump(text, stderr, (unsigned char *)data, size); | ||
| 80 | return 0; | ||
| 81 | } | ||
| 82 | 8 | ||
| 83 | namespace twitter { | 9 | namespace twitter { |
| 84 | 10 | ||
| 85 | class request | 11 | client::client( |
| 86 | { | 12 | const auth& _arg) : |
| 87 | public: | 13 | auth_(_arg), |
| 88 | 14 | currentUser_( | |
| 89 | explicit request(std::string url) try | 15 | get(auth_, |
| 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 | }; | ||
| 213 | |||
| 214 | class get : public request | ||
| 215 | { | ||
| 216 | public: | ||
| 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 | } | ||
| 226 | |||
| 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 | ||
| 248 | { | ||
| 249 | public: | ||
| 250 | |||
| 251 | post(const OAuth::Client& oauth_client, std::string url, std::string datastr) try | ||
| 252 | : request(url) | ||
| 253 | { | ||
| 254 | std::string oauth_header = oauth_client.getFormattedHttpHeader(OAuth::Http::Post, url, datastr); | ||
| 255 | if (!oauth_header.empty()) | ||
| 256 | { | ||
| 257 | _headers.add(std::move(oauth_header)); | ||
| 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); | ||
| 274 | } | ||
| 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(); | ||
| 306 | |||
| 307 | assert(false); | ||
| 308 | } | ||
| 309 | |||
| 310 | private: | ||
| 311 | |||
| 312 | curl::curl_header _headers; | ||
| 313 | }; | ||
| 314 | |||
| 315 | client::client(const auth& _arg) | ||
| 316 | { | ||
| 317 | _oauth_consumer = | ||
| 318 | make_unique<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>( | ||
| 334 | get(*_oauth_client, | ||
| 335 | "https://api.twitter.com/1.1/account/verify_credentials.json") | 16 | "https://api.twitter.com/1.1/account/verify_credentials.json") |
| 336 | .perform()); | 17 | .perform()) |
| 18 | { | ||
| 337 | } | 19 | } |
| 338 | 20 | ||
| 339 | client::~client() = default; | ||
| 340 | |||
| 341 | tweet client::updateStatus(std::string msg, std::list<long> media_ids) const | 21 | tweet client::updateStatus(std::string msg, std::list<long> media_ids) const |
| 342 | { | 22 | { |
| 343 | std::stringstream datastrstream; | 23 | std::stringstream datastrstream; |
| @@ -350,7 +30,7 @@ namespace twitter { | |||
| 350 | } | 30 | } |
| 351 | 31 | ||
| 352 | return tweet( | 32 | return tweet( |
| 353 | post(*_oauth_client, | 33 | post(auth_, |
| 354 | "https://api.twitter.com/1.1/statuses/update.json", | 34 | "https://api.twitter.com/1.1/statuses/update.json", |
| 355 | datastrstream.str()) | 35 | datastrstream.str()) |
| 356 | .perform()); | 36 | .perform()); |
| @@ -370,7 +50,7 @@ namespace twitter { | |||
| 370 | } | 50 | } |
| 371 | 51 | ||
| 372 | return tweet( | 52 | return tweet( |
| 373 | post(*_oauth_client, | 53 | post(auth_, |
| 374 | "https://api.twitter.com/1.1/statuses/update.json", | 54 | "https://api.twitter.com/1.1/statuses/update.json", |
| 375 | datastrstream.str()) | 55 | datastrstream.str()) |
| 376 | .perform()); | 56 | .perform()); |
| @@ -404,7 +84,7 @@ namespace twitter { | |||
| 404 | } | 84 | } |
| 405 | 85 | ||
| 406 | std::string init_response = | 86 | std::string init_response = |
| 407 | multipost(*_oauth_client, | 87 | multipost(auth_, |
| 408 | "https://upload.twitter.com/1.1/media/upload.json", | 88 | "https://upload.twitter.com/1.1/media/upload.json", |
| 409 | form.get()) | 89 | form.get()) |
| 410 | .perform(); | 90 | .perform(); |
| @@ -435,7 +115,7 @@ namespace twitter { | |||
| 435 | assert(false); | 115 | assert(false); |
| 436 | } | 116 | } |
| 437 | 117 | ||
| 438 | multipost(*_oauth_client, "https://upload.twitter.com/1.1/media/upload.json", append_form_post).perform(); | 118 | multipost(auth_, "https://upload.twitter.com/1.1/media/upload.json", append_form_post).perform(); |
| 439 | 119 | ||
| 440 | curl_formfree(append_form_post); | 120 | curl_formfree(append_form_post); |
| 441 | 121 | ||
| @@ -450,7 +130,7 @@ namespace twitter { | |||
| 450 | finalize_form.add(media_id_name, media_id_cont); | 130 | finalize_form.add(media_id_name, media_id_cont); |
| 451 | 131 | ||
| 452 | std::string finalize_response = | 132 | std::string finalize_response = |
| 453 | multipost(*_oauth_client, | 133 | multipost(auth_, |
| 454 | "https://upload.twitter.com/1.1/media/upload.json", | 134 | "https://upload.twitter.com/1.1/media/upload.json", |
| 455 | finalize_form.get()) | 135 | finalize_form.get()) |
| 456 | .perform(); | 136 | .perform(); |
| @@ -472,7 +152,7 @@ namespace twitter { | |||
| 472 | 152 | ||
| 473 | for (;;) | 153 | for (;;) |
| 474 | { | 154 | { |
| 475 | std::string status_response = get(*_oauth_client, datastr.str()).perform(); | 155 | std::string status_response = get(auth_, datastr.str()).perform(); |
| 476 | 156 | ||
| 477 | try | 157 | try |
| 478 | { | 158 | { |
| @@ -499,9 +179,7 @@ namespace twitter { | |||
| 499 | return media_id; | 179 | return media_id; |
| 500 | } catch (const curl::curl_exception& error) | 180 | } catch (const curl::curl_exception& error) |
| 501 | { | 181 | { |
| 502 | error.print_traceback(); | 182 | std::throw_with_nested(connection_error()); |
| 503 | |||
| 504 | assert(false); | ||
| 505 | } | 183 | } |
| 506 | 184 | ||
| 507 | std::set<user_id> client::getFriends(user_id id) const | 185 | std::set<user_id> client::getFriends(user_id id) const |
| @@ -518,7 +196,7 @@ namespace twitter { | |||
| 518 | urlstream << cursor; | 196 | urlstream << cursor; |
| 519 | 197 | ||
| 520 | std::string url = urlstream.str(); | 198 | std::string url = urlstream.str(); |
| 521 | std::string response_data = get(*_oauth_client, url).perform(); | 199 | std::string response_data = get(auth_, url).perform(); |
| 522 | 200 | ||
| 523 | try | 201 | try |
| 524 | { | 202 | { |
| @@ -562,7 +240,7 @@ namespace twitter { | |||
| 562 | urlstream << cursor; | 240 | urlstream << cursor; |
| 563 | 241 | ||
| 564 | std::string url = urlstream.str(); | 242 | std::string url = urlstream.str(); |
| 565 | std::string response_data = get(*_oauth_client, url).perform(); | 243 | std::string response_data = get(auth_, url).perform(); |
| 566 | 244 | ||
| 567 | try | 245 | try |
| 568 | { | 246 | { |
| @@ -604,7 +282,7 @@ namespace twitter { | |||
| 604 | urlstream << cursor; | 282 | urlstream << cursor; |
| 605 | 283 | ||
| 606 | std::string url = urlstream.str(); | 284 | std::string url = urlstream.str(); |
| 607 | std::string response_data = get(*_oauth_client, url).perform(); | 285 | std::string response_data = get(auth_, url).perform(); |
| 608 | 286 | ||
| 609 | try | 287 | try |
| 610 | { | 288 | { |
| @@ -630,7 +308,7 @@ namespace twitter { | |||
| 630 | datastrstream << "follow=true&user_id="; | 308 | datastrstream << "follow=true&user_id="; |
| 631 | datastrstream << toFollow; | 309 | datastrstream << toFollow; |
| 632 | 310 | ||
| 633 | post(*_oauth_client, "https://api.twitter.com/1.1/friendships/create.json", datastrstream.str()).perform(); | 311 | post(auth_, "https://api.twitter.com/1.1/friendships/create.json", datastrstream.str()).perform(); |
| 634 | } | 312 | } |
| 635 | 313 | ||
| 636 | void client::follow(const user& toFollow) const | 314 | void client::follow(const user& toFollow) const |
| @@ -644,7 +322,7 @@ namespace twitter { | |||
| 644 | datastrstream << "user_id="; | 322 | datastrstream << "user_id="; |
| 645 | datastrstream << toUnfollow; | 323 | datastrstream << toUnfollow; |
| 646 | 324 | ||
| 647 | post(*_oauth_client, "https://api.twitter.com/1.1/friendships/destroy.json", datastrstream.str()).perform(); | 325 | post(auth_, "https://api.twitter.com/1.1/friendships/destroy.json", datastrstream.str()).perform(); |
| 648 | } | 326 | } |
| 649 | 327 | ||
| 650 | void client::unfollow(const user& toUnfollow) const | 328 | void client::unfollow(const user& toUnfollow) const |
| @@ -654,7 +332,7 @@ namespace twitter { | |||
| 654 | 332 | ||
| 655 | const user& client::getUser() const | 333 | const user& client::getUser() const |
| 656 | { | 334 | { |
| 657 | return *_current_user; | 335 | return currentUser_; |
| 658 | } | 336 | } |
| 659 | 337 | ||
| 660 | const configuration& client::getConfiguration() const | 338 | const configuration& client::getConfiguration() const |
| @@ -662,8 +340,8 @@ namespace twitter { | |||
| 662 | if (!_configuration || (difftime(time(NULL), _last_configuration_update) > 60*60*24)) | 340 | if (!_configuration || (difftime(time(NULL), _last_configuration_update) > 60*60*24)) |
| 663 | { | 341 | { |
| 664 | _configuration = | 342 | _configuration = |
| 665 | make_unique<configuration>( | 343 | std::make_unique<configuration>( |
| 666 | get(*_oauth_client, | 344 | get(auth_, |
| 667 | "https://api.twitter.com/1.1/help/configuration.json") | 345 | "https://api.twitter.com/1.1/help/configuration.json") |
| 668 | .perform()); | 346 | .perform()); |
| 669 | 347 | ||
