From 7e85b35d7d1714e3f85434b891a1050ad584e337 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Wed, 13 Apr 2016 11:24:24 -0400 Subject: Added ability to upload media and tweet it Images (static and animated) and videos have been tested. Currently all media uploads occur in one large chunk; support to break down chunks will be added later. --- src/client.cpp | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 297 insertions(+), 14 deletions(-) (limited to 'src/client.cpp') diff --git a/src/client.cpp b/src/client.cpp index b71ff70..39d6b5d 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1,10 +1,81 @@ #include "client.h" -#include -#include #include #include #include #include +#include "util.h" + +// These are here for debugging curl stuff + +static +void dump(const char *text, + FILE *stream, unsigned char *ptr, size_t size) +{ + size_t i; + size_t c; + unsigned int width=80; + + fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n", + text, (long)size, (long)size); + + for(i=0; i= 0x20 && ptr[i+c] < 0x80) ? ptr[i+c] : '.'; + fputc(x, stream); + } + + fputc('\n', stream); /* newline */ + } +} + +static +int my_trace(CURL *handle, curl_infotype type, + char *data, size_t size, + void *userp) +{ + const char *text; + (void)handle; /* prevent compiler warning */ + + switch (type) { + case CURLINFO_TEXT: + fprintf(stderr, "== Info: %s", data); + default: /* in case a new one is introduced to shock us */ + return 0; + + case CURLINFO_HEADER_OUT: + text = "=> Send header"; + break; + case CURLINFO_DATA_OUT: + text = "=> Send data"; + break; + case CURLINFO_SSL_DATA_OUT: + text = "=> Send SSL data"; + break; + case CURLINFO_HEADER_IN: + text = "<= Recv header"; + break; + case CURLINFO_DATA_IN: + text = "<= Recv data"; + break; + case CURLINFO_SSL_DATA_IN: + text = "<= Recv SSL data"; + break; + } + + dump(text, stderr, (unsigned char *)data, size); + return 0; +} namespace twitter { @@ -22,18 +93,181 @@ namespace twitter { delete _oauth_consumer; } - response client::updateStatus(std::string msg, tweet& result) + response client::updateStatus(std::string msg, tweet& result, std::list media_ids) { - std::ostringstream output; - curl::curl_ios ios(output); - curl::curl_easy conn(ios); - std::stringstream datastrstream; datastrstream << "status=" << OAuth::PercentEncode(msg); + if (!media_ids.empty()) + { + datastrstream << "&media_ids="; + datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ","); + } + std::string datastr = datastrstream.str(); std::string url = "https://api.twitter.com/1.1/statuses/update.json"; + long response_code; + json response_data; + if (!performPost(url, datastr, response_code, response_data)) + { + return response::curl_error; + } + + if (response_code == 200) + { + result = tweet(response_data); + return response::ok; + } else { + return codeForError(response_code, response_data); + } + } + + response client::uploadMedia(std::string media_type, const char* data, long data_length, long& media_id) + { + curl::curl_form form; + + curl::curl_pair command_name(CURLFORM_COPYNAME, "command"); + curl::curl_pair command_cont(CURLFORM_COPYCONTENTS, "INIT"); + curl::curl_pair bytes_name(CURLFORM_COPYNAME, "total_bytes"); + curl::curl_pair bytes_cont(CURLFORM_COPYCONTENTS, std::to_string(data_length)); + curl::curl_pair type_name(CURLFORM_COPYNAME, "media_type"); + curl::curl_pair type_cont(CURLFORM_COPYCONTENTS, media_type); + form.add(command_name, command_cont); + form.add(bytes_name, bytes_cont); + form.add(type_name, type_cont); + + if (media_type == "image/gif") + { + curl::curl_pair category_name(CURLFORM_COPYNAME, "media_category"); + curl::curl_pair category_cont(CURLFORM_COPYCONTENTS, "tweet_gif"); + form.add(category_name, category_cont); + } + + long response_code; + json response_data; + if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", form.get(), response_code, response_data)) + { + return response::curl_error; + } + + if (response_code / 100 != 2) + { + return codeForError(response_code, response_data); + } + + media_id = response_data["media_id"].get(); + + curl_httppost* append_form_post = nullptr; + curl_httppost* append_form_last = nullptr; + curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "command", CURLFORM_COPYCONTENTS, "APPEND", CURLFORM_END); + curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "media_id", CURLFORM_COPYCONTENTS, std::to_string(media_id).c_str(), CURLFORM_END); + 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); + curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "segment_index", CURLFORM_COPYCONTENTS, std::to_string(0).c_str(), CURLFORM_END); + if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", append_form_post, response_code, response_data)) + { + return response::curl_error; + } + + curl_formfree(append_form_post); + + if (response_code / 100 != 2) + { + return codeForError(response_code, response_data); + } + + curl::curl_form finalize_form; + curl::curl_pair command3_name(CURLFORM_COPYNAME, "command"); + curl::curl_pair command3_cont(CURLFORM_COPYCONTENTS, "FINALIZE"); + curl::curl_pair media_id_name(CURLFORM_COPYNAME, "media_id"); + curl::curl_pair media_id_cont(CURLFORM_COPYCONTENTS, std::to_string(media_id)); + finalize_form.add(command3_name, command3_cont); + finalize_form.add(media_id_name, media_id_cont); + + if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", finalize_form.get(), response_code, response_data)) + { + return response::curl_error; + } + + if (response_code / 100 != 2) + { + return codeForError(response_code, response_data); + } + + if (response_data.find("processing_info") != response_data.end()) + { + std::stringstream datastr; + datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id; + + for (;;) + { + if (!performGet(datastr.str(), response_code, response_data)) + { + return response::curl_error; + } + + if (response_code / 100 != 2) + { + return codeForError(response_code, response_data); + } + + if (response_data["processing_info"]["state"] == "succeeded") + { + break; + } + + int ttw = response_data["processing_info"]["check_after_secs"].get(); + sleep(ttw); + } + } + + return response::ok; + } + + bool client::performGet(std::string url, long& response_code, json& result) + { + std::ostringstream output; + curl::curl_ios ios(output); + curl::curl_easy conn(ios); + + curl::curl_header headers; + std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Get, url, ""); + if (!oauth_header.empty()) + { + headers.add(oauth_header); + } + + try { + //conn.add(1); + //conn.add(my_trace); + conn.add(url.c_str()); + conn.add(headers.get()); + + conn.perform(); + } catch (curl::curl_easy_exception error) + { + error.print_traceback(); + + return false; + } + + response_code = conn.get_info().get(); + if (output.str().empty()) + { + result = json(); + } else { + result = json::parse(output.str()); + } + + return true; + } + + bool client::performPost(std::string url, std::string datastr, long& response_code, json& result) + { + std::ostringstream output; + curl::curl_ios ios(output); + curl::curl_easy conn(ios); + curl::curl_header headers; std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Post, url, datastr); if (!oauth_header.empty()) @@ -42,9 +276,10 @@ namespace twitter { } try { + //conn.add(1); + //conn.add(my_trace); conn.add(url.c_str()); conn.add(datastr.c_str()); - conn.add(1); conn.add(headers.get()); conn.perform(); @@ -52,17 +287,62 @@ namespace twitter { { error.print_traceback(); - return response::curl_error; + return false; } - long response_code = conn.get_info().get(); - json response_data = json::parse(output.str()); - if (response_code == 200) + response_code = conn.get_info().get(); + if (output.str().empty()) { - result = tweet(response_data); - return response::ok; + result = json(); + } else { + result = json::parse(output.str()); } + return true; + } + + bool client::performMultiPost(std::string url, const curl_httppost* fields, long& response_code, json& result) + { + std::ostringstream output; + curl::curl_ios ios(output); + curl::curl_easy conn(ios); + + curl::curl_header headers; + std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Post, url, ""); + if (!oauth_header.empty()) + { + headers.add(oauth_header); + } + + try { + //conn.add(1); + //conn.add(my_trace); + conn.add(headers.get()); + conn.add(url.c_str()); + conn.add(fields); + + conn.perform(); + } catch (curl::curl_easy_exception error) + { + error.print_traceback(); + + return false; + } + + response_code = conn.get_info().get(); + + if (output.str().empty()) + { + result = json(); + } else { + result = json::parse(output.str()); + } + + return true; + } + + response client::codeForError(int response_code, json response_data) const + { std::set error_codes; if (response_data.find("errors") != response_data.end()) { @@ -101,6 +381,9 @@ namespace twitter { } else if (error_codes.count(261) == 1) { return response::write_restricted; + } else if (error_codes.count(44) == 1) + { + return response::invalid_media; } else if (response_code == 429) { return response::limited; -- cgit 1.4.1