about summary refs log tree commit diff stats
path: root/src/client.cpp
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2016-04-13 11:24:24 -0400
committerKelly Rauchenberger <fefferburbia@gmail.com>2016-04-13 11:24:24 -0400
commit7e85b35d7d1714e3f85434b891a1050ad584e337 (patch)
tree1ca626fe9551734d6d98e50b841b7365d49ba088 /src/client.cpp
parent891d57d200f55b91f80b6d3b4dd0c30479be6109 (diff)
downloadlibtwittercpp-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.cpp311
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
10static
11void 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
42static
43int 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
9namespace twitter { 80namespace 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;