diff options
author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2016-04-13 11:24:24 -0400 |
---|---|---|
committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2016-04-13 11:24:24 -0400 |
commit | 7e85b35d7d1714e3f85434b891a1050ad584e337 (patch) | |
tree | 1ca626fe9551734d6d98e50b841b7365d49ba088 /src/client.cpp | |
parent | 891d57d200f55b91f80b6d3b4dd0c30479be6109 (diff) | |
download | libtwittercpp-7e85b35d7d1714e3f85434b891a1050ad584e337.tar.gz libtwittercpp-7e85b35d7d1714e3f85434b891a1050ad584e337.tar.bz2 libtwittercpp-7e85b35d7d1714e3f85434b891a1050ad584e337.zip |
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.
Diffstat (limited to 'src/client.cpp')
-rw-r--r-- | src/client.cpp | 311 |
1 files changed, 297 insertions, 14 deletions
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 @@ | |||
1 | #include "client.h" | 1 | #include "client.h" |
2 | #include <curl_easy.h> | ||
3 | #include <curl_header.h> | ||
4 | #include <sstream> | 2 | #include <sstream> |
5 | #include <set> | 3 | #include <set> |
6 | #include <algorithm> | 4 | #include <algorithm> |
7 | #include <liboauthcpp/liboauthcpp.h> | 5 | #include <liboauthcpp/liboauthcpp.h> |
6 | #include "util.h" | ||
7 | |||
8 | // These are here for debugging curl stuff | ||
9 | |||
10 | static | ||
11 | void dump(const char *text, | ||
12 | FILE *stream, unsigned char *ptr, size_t size) | ||
13 | { | ||
14 | size_t i; | ||
15 | size_t c; | ||
16 | unsigned int width=80; | ||
17 | |||
18 | fprintf(stream, "%s, %10.10ld bytes (0x%8.8lx)\n", | ||
19 | text, (long)size, (long)size); | ||
20 | |||
21 | for(i=0; i<size; i+= width) { | ||
22 | fprintf(stream, "%4.4lx: ", (long)i); | ||
23 | |||
24 | /* show hex to the left | ||
25 | for(c = 0; c < width; c++) { | ||
26 | if(i+c < size) | ||
27 | fprintf(stream, "%02x ", ptr[i+c]); | ||
28 | else | ||
29 | fputs(" ", stream); | ||
30 | }*/ | ||
31 | |||
32 | /* show data on the right */ | ||
33 | for(c = 0; (c < width) && (i+c < size); c++) { | ||
34 | char x = (ptr[i+c] >= 0x20 && ptr[i+c] < 0x80) ? ptr[i+c] : '.'; | ||
35 | fputc(x, stream); | ||
36 | } | ||
37 | |||
38 | fputc('\n', stream); /* newline */ | ||
39 | } | ||
40 | } | ||
41 | |||
42 | static | ||
43 | int my_trace(CURL *handle, curl_infotype type, | ||
44 | char *data, size_t size, | ||
45 | void *userp) | ||
46 | { | ||
47 | const char *text; | ||
48 | (void)handle; /* prevent compiler warning */ | ||
49 | |||
50 | switch (type) { | ||
51 | case CURLINFO_TEXT: | ||
52 | fprintf(stderr, "== Info: %s", data); | ||
53 | default: /* in case a new one is introduced to shock us */ | ||
54 | return 0; | ||
55 | |||
56 | case CURLINFO_HEADER_OUT: | ||
57 | text = "=> Send header"; | ||
58 | break; | ||
59 | case CURLINFO_DATA_OUT: | ||
60 | text = "=> Send data"; | ||
61 | break; | ||
62 | case CURLINFO_SSL_DATA_OUT: | ||
63 | text = "=> Send SSL data"; | ||
64 | break; | ||
65 | case CURLINFO_HEADER_IN: | ||
66 | text = "<= Recv header"; | ||
67 | break; | ||
68 | case CURLINFO_DATA_IN: | ||
69 | text = "<= Recv data"; | ||
70 | break; | ||
71 | case CURLINFO_SSL_DATA_IN: | ||
72 | text = "<= Recv SSL data"; | ||
73 | break; | ||
74 | } | ||
75 | |||
76 | dump(text, stderr, (unsigned char *)data, size); | ||
77 | return 0; | ||
78 | } | ||
8 | 79 | ||
9 | namespace twitter { | 80 | namespace twitter { |
10 | 81 | ||
@@ -22,18 +93,181 @@ namespace twitter { | |||
22 | delete _oauth_consumer; | 93 | delete _oauth_consumer; |
23 | } | 94 | } |
24 | 95 | ||
25 | response client::updateStatus(std::string msg, tweet& result) | 96 | response client::updateStatus(std::string msg, tweet& result, std::list<long> media_ids) |
26 | { | 97 | { |
27 | std::ostringstream output; | ||
28 | curl::curl_ios<std::ostringstream> ios(output); | ||
29 | curl::curl_easy conn(ios); | ||
30 | |||
31 | std::stringstream datastrstream; | 98 | std::stringstream datastrstream; |
32 | datastrstream << "status=" << OAuth::PercentEncode(msg); | 99 | datastrstream << "status=" << OAuth::PercentEncode(msg); |
33 | 100 | ||
101 | if (!media_ids.empty()) | ||
102 | { | ||
103 | datastrstream << "&media_ids="; | ||
104 | datastrstream << twitter::implode(std::begin(media_ids), std::end(media_ids), ","); | ||
105 | } | ||
106 | |||
34 | std::string datastr = datastrstream.str(); | 107 | std::string datastr = datastrstream.str(); |
35 | std::string url = "https://api.twitter.com/1.1/statuses/update.json"; | 108 | std::string url = "https://api.twitter.com/1.1/statuses/update.json"; |
36 | 109 | ||
110 | long response_code; | ||
111 | json response_data; | ||
112 | if (!performPost(url, datastr, response_code, response_data)) | ||
113 | { | ||
114 | return response::curl_error; | ||
115 | } | ||
116 | |||
117 | if (response_code == 200) | ||
118 | { | ||
119 | result = tweet(response_data); | ||
120 | return response::ok; | ||
121 | } else { | ||
122 | return codeForError(response_code, response_data); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | response client::uploadMedia(std::string media_type, const char* data, long data_length, long& media_id) | ||
127 | { | ||
128 | curl::curl_form form; | ||
129 | |||
130 | curl::curl_pair<CURLformoption, std::string> command_name(CURLFORM_COPYNAME, "command"); | ||
131 | curl::curl_pair<CURLformoption, std::string> command_cont(CURLFORM_COPYCONTENTS, "INIT"); | ||
132 | curl::curl_pair<CURLformoption, std::string> bytes_name(CURLFORM_COPYNAME, "total_bytes"); | ||
133 | curl::curl_pair<CURLformoption, std::string> bytes_cont(CURLFORM_COPYCONTENTS, std::to_string(data_length)); | ||
134 | curl::curl_pair<CURLformoption, std::string> type_name(CURLFORM_COPYNAME, "media_type"); | ||
135 | curl::curl_pair<CURLformoption, std::string> type_cont(CURLFORM_COPYCONTENTS, media_type); | ||
136 | form.add(command_name, command_cont); | ||
137 | form.add(bytes_name, bytes_cont); | ||
138 | form.add(type_name, type_cont); | ||
139 | |||
140 | if (media_type == "image/gif") | ||
141 | { | ||
142 | curl::curl_pair<CURLformoption, std::string> category_name(CURLFORM_COPYNAME, "media_category"); | ||
143 | curl::curl_pair<CURLformoption, std::string> category_cont(CURLFORM_COPYCONTENTS, "tweet_gif"); | ||
144 | form.add(category_name, category_cont); | ||
145 | } | ||
146 | |||
147 | long response_code; | ||
148 | json response_data; | ||
149 | if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", form.get(), response_code, response_data)) | ||
150 | { | ||
151 | return response::curl_error; | ||
152 | } | ||
153 | |||
154 | if (response_code / 100 != 2) | ||
155 | { | ||
156 | return codeForError(response_code, response_data); | ||
157 | } | ||
158 | |||
159 | media_id = response_data["media_id"].get<long>(); | ||
160 | |||
161 | curl_httppost* append_form_post = nullptr; | ||
162 | curl_httppost* append_form_last = nullptr; | ||
163 | curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "command", CURLFORM_COPYCONTENTS, "APPEND", CURLFORM_END); | ||
164 | curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "media_id", CURLFORM_COPYCONTENTS, std::to_string(media_id).c_str(), CURLFORM_END); | ||
165 | 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); | ||
166 | curl_formadd(&append_form_post, &append_form_last, CURLFORM_COPYNAME, "segment_index", CURLFORM_COPYCONTENTS, std::to_string(0).c_str(), CURLFORM_END); | ||
167 | if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", append_form_post, response_code, response_data)) | ||
168 | { | ||
169 | return response::curl_error; | ||
170 | } | ||
171 | |||
172 | curl_formfree(append_form_post); | ||
173 | |||
174 | if (response_code / 100 != 2) | ||
175 | { | ||
176 | return codeForError(response_code, response_data); | ||
177 | } | ||
178 | |||
179 | curl::curl_form finalize_form; | ||
180 | curl::curl_pair<CURLformoption, std::string> command3_name(CURLFORM_COPYNAME, "command"); | ||
181 | curl::curl_pair<CURLformoption, std::string> command3_cont(CURLFORM_COPYCONTENTS, "FINALIZE"); | ||
182 | curl::curl_pair<CURLformoption, std::string> media_id_name(CURLFORM_COPYNAME, "media_id"); | ||
183 | curl::curl_pair<CURLformoption, std::string> media_id_cont(CURLFORM_COPYCONTENTS, std::to_string(media_id)); | ||
184 | finalize_form.add(command3_name, command3_cont); | ||
185 | finalize_form.add(media_id_name, media_id_cont); | ||
186 | |||
187 | if (!performMultiPost("https://upload.twitter.com/1.1/media/upload.json", finalize_form.get(), response_code, response_data)) | ||
188 | { | ||
189 | return response::curl_error; | ||
190 | } | ||
191 | |||
192 | if (response_code / 100 != 2) | ||
193 | { | ||
194 | return codeForError(response_code, response_data); | ||
195 | } | ||
196 | |||
197 | if (response_data.find("processing_info") != response_data.end()) | ||
198 | { | ||
199 | std::stringstream datastr; | ||
200 | datastr << "https://upload.twitter.com/1.1/media/upload.json?command=STATUS&media_id=" << media_id; | ||
201 | |||
202 | for (;;) | ||
203 | { | ||
204 | if (!performGet(datastr.str(), response_code, response_data)) | ||
205 | { | ||
206 | return response::curl_error; | ||
207 | } | ||
208 | |||
209 | if (response_code / 100 != 2) | ||
210 | { | ||
211 | return codeForError(response_code, response_data); | ||
212 | } | ||
213 | |||
214 | if (response_data["processing_info"]["state"] == "succeeded") | ||
215 | { | ||
216 | break; | ||
217 | } | ||
218 | |||
219 | int ttw = response_data["processing_info"]["check_after_secs"].get<int>(); | ||
220 | sleep(ttw); | ||
221 | } | ||
222 | } | ||
223 | |||
224 | return response::ok; | ||
225 | } | ||
226 | |||
227 | bool client::performGet(std::string url, long& response_code, json& result) | ||
228 | { | ||
229 | std::ostringstream output; | ||
230 | curl::curl_ios<std::ostringstream> ios(output); | ||
231 | curl::curl_easy conn(ios); | ||
232 | |||
233 | curl::curl_header headers; | ||
234 | std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Get, url, ""); | ||
235 | if (!oauth_header.empty()) | ||
236 | { | ||
237 | headers.add(oauth_header); | ||
238 | } | ||
239 | |||
240 | try { | ||
241 | //conn.add<CURLOPT_VERBOSE>(1); | ||
242 | //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace); | ||
243 | conn.add<CURLOPT_URL>(url.c_str()); | ||
244 | conn.add<CURLOPT_HTTPHEADER>(headers.get()); | ||
245 | |||
246 | conn.perform(); | ||
247 | } catch (curl::curl_easy_exception error) | ||
248 | { | ||
249 | error.print_traceback(); | ||
250 | |||
251 | return false; | ||
252 | } | ||
253 | |||
254 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); | ||
255 | if (output.str().empty()) | ||
256 | { | ||
257 | result = json(); | ||
258 | } else { | ||
259 | result = json::parse(output.str()); | ||
260 | } | ||
261 | |||
262 | return true; | ||
263 | } | ||
264 | |||
265 | bool client::performPost(std::string url, std::string datastr, long& response_code, json& result) | ||
266 | { | ||
267 | std::ostringstream output; | ||
268 | curl::curl_ios<std::ostringstream> ios(output); | ||
269 | curl::curl_easy conn(ios); | ||
270 | |||
37 | curl::curl_header headers; | 271 | curl::curl_header headers; |
38 | std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Post, url, datastr); | 272 | std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Post, url, datastr); |
39 | if (!oauth_header.empty()) | 273 | if (!oauth_header.empty()) |
@@ -42,9 +276,10 @@ namespace twitter { | |||
42 | } | 276 | } |
43 | 277 | ||
44 | try { | 278 | try { |
279 | //conn.add<CURLOPT_VERBOSE>(1); | ||
280 | //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace); | ||
45 | conn.add<CURLOPT_URL>(url.c_str()); | 281 | conn.add<CURLOPT_URL>(url.c_str()); |
46 | conn.add<CURLOPT_COPYPOSTFIELDS>(datastr.c_str()); | 282 | conn.add<CURLOPT_COPYPOSTFIELDS>(datastr.c_str()); |
47 | conn.add<CURLOPT_POST>(1); | ||
48 | conn.add<CURLOPT_HTTPHEADER>(headers.get()); | 283 | conn.add<CURLOPT_HTTPHEADER>(headers.get()); |
49 | 284 | ||
50 | conn.perform(); | 285 | conn.perform(); |
@@ -52,17 +287,62 @@ namespace twitter { | |||
52 | { | 287 | { |
53 | error.print_traceback(); | 288 | error.print_traceback(); |
54 | 289 | ||
55 | return response::curl_error; | 290 | return false; |
56 | } | 291 | } |
57 | 292 | ||
58 | long response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); | 293 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); |
59 | json response_data = json::parse(output.str()); | 294 | if (output.str().empty()) |
60 | if (response_code == 200) | ||
61 | { | 295 | { |
62 | result = tweet(response_data); | 296 | result = json(); |
63 | return response::ok; | 297 | } else { |
298 | result = json::parse(output.str()); | ||
64 | } | 299 | } |
65 | 300 | ||
301 | return true; | ||
302 | } | ||
303 | |||
304 | bool client::performMultiPost(std::string url, const curl_httppost* fields, long& response_code, json& result) | ||
305 | { | ||
306 | std::ostringstream output; | ||
307 | curl::curl_ios<std::ostringstream> ios(output); | ||
308 | curl::curl_easy conn(ios); | ||
309 | |||
310 | curl::curl_header headers; | ||
311 | std::string oauth_header = _oauth_client->getFormattedHttpHeader(OAuth::Http::Post, url, ""); | ||
312 | if (!oauth_header.empty()) | ||
313 | { | ||
314 | headers.add(oauth_header); | ||
315 | } | ||
316 | |||
317 | try { | ||
318 | //conn.add<CURLOPT_VERBOSE>(1); | ||
319 | //conn.add<CURLOPT_DEBUGFUNCTION>(my_trace); | ||
320 | conn.add<CURLOPT_HTTPHEADER>(headers.get()); | ||
321 | conn.add<CURLOPT_URL>(url.c_str()); | ||
322 | conn.add<CURLOPT_HTTPPOST>(fields); | ||
323 | |||
324 | conn.perform(); | ||
325 | } catch (curl::curl_easy_exception error) | ||
326 | { | ||
327 | error.print_traceback(); | ||
328 | |||
329 | return false; | ||
330 | } | ||
331 | |||
332 | response_code = conn.get_info<CURLINFO_RESPONSE_CODE>().get(); | ||
333 | |||
334 | if (output.str().empty()) | ||
335 | { | ||
336 | result = json(); | ||
337 | } else { | ||
338 | result = json::parse(output.str()); | ||
339 | } | ||
340 | |||
341 | return true; | ||
342 | } | ||
343 | |||
344 | response client::codeForError(int response_code, json response_data) const | ||
345 | { | ||
66 | std::set<int> error_codes; | 346 | std::set<int> error_codes; |
67 | if (response_data.find("errors") != response_data.end()) | 347 | if (response_data.find("errors") != response_data.end()) |
68 | { | 348 | { |
@@ -101,6 +381,9 @@ namespace twitter { | |||
101 | } else if (error_codes.count(261) == 1) | 381 | } else if (error_codes.count(261) == 1) |
102 | { | 382 | { |
103 | return response::write_restricted; | 383 | return response::write_restricted; |
384 | } else if (error_codes.count(44) == 1) | ||
385 | { | ||
386 | return response::invalid_media; | ||
104 | } else if (response_code == 429) | 387 | } else if (response_code == 429) |
105 | { | 388 | { |
106 | return response::limited; | 389 | return response::limited; |