about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules2
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/auth.cpp2
-rw-r--r--src/client.cpp311
-rw-r--r--src/client.h15
-rw-r--r--src/codes.cpp24
-rw-r--r--src/codes.h31
-rw-r--r--src/tweet.cpp2
-rw-r--r--src/tweet.h4
-rw-r--r--src/twitter.h24
-rw-r--r--src/util.h29
m---------vendor/liboauthcpp0
12 files changed, 404 insertions, 42 deletions
diff --git a/.gitmodules b/.gitmodules index 6d8effa..b701c21 100644 --- a/.gitmodules +++ b/.gitmodules
@@ -1,6 +1,6 @@
1[submodule "vendor/liboauthcpp"] 1[submodule "vendor/liboauthcpp"]
2 path = vendor/liboauthcpp 2 path = vendor/liboauthcpp
3 url = https://github.com/sirikata/liboauthcpp 3 url = https://github.com/hatkirby/liboauthcpp
4[submodule "vendor/json"] 4[submodule "vendor/json"]
5 path = vendor/json 5 path = vendor/json
6 url = https://github.com/nlohmann/json 6 url = https://github.com/nlohmann/json
diff --git a/CMakeLists.txt b/CMakeLists.txt index 7507e25..9f290c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -9,7 +9,7 @@ include_directories(vendor/liboauthcpp/include)
9add_subdirectory(vendor/curlcpp) 9add_subdirectory(vendor/curlcpp)
10include_directories(${CURLCPP_SOURCE_DIR}/include) 10include_directories(${CURLCPP_SOURCE_DIR}/include)
11 11
12add_library(twitter++ src/client.cpp src/auth.cpp src/tweet.cpp) 12add_library(twitter++ src/client.cpp src/auth.cpp src/tweet.cpp src/codes.cpp)
13set_property(TARGET twitter++ PROPERTY CXX_STANDARD 11) 13set_property(TARGET twitter++ PROPERTY CXX_STANDARD 11)
14set_property(TARGET twitter++ PROPERTY CXX_STANDARD_REQUIRED ON) 14set_property(TARGET twitter++ PROPERTY CXX_STANDARD_REQUIRED ON)
15target_link_libraries(twitter++ oauthcpp curlcpp) 15target_link_libraries(twitter++ oauthcpp curlcpp)
diff --git a/src/auth.cpp b/src/auth.cpp index 325c521..f0f17e0 100644 --- a/src/auth.cpp +++ b/src/auth.cpp
@@ -1,4 +1,4 @@
1#include "twitter.h" 1#include "auth.h"
2 2
3namespace twitter { 3namespace twitter {
4 4
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;
diff --git a/src/client.h b/src/client.h index 1ab5c70..3a133e4 100644 --- a/src/client.h +++ b/src/client.h
@@ -1,7 +1,12 @@
1#ifndef TWITTER_H_ABFF6A12 1#ifndef TWITTER_H_ABFF6A12
2#define TWITTER_H_ABFF6A12 2#define TWITTER_H_ABFF6A12
3 3
4#include "twitter.h" 4#include "codes.h"
5#include "tweet.h"
6#include "auth.h"
7#include <list>
8#include <curl_easy.h>
9#include <curl_header.h>
5 10
6namespace OAuth { 11namespace OAuth {
7 class Consumer; 12 class Consumer;
@@ -16,12 +21,18 @@ namespace twitter {
16 client(const auth& _auth); 21 client(const auth& _auth);
17 ~client(); 22 ~client();
18 23
19 response updateStatus(std::string msg, tweet& result); 24 response updateStatus(std::string msg, tweet& result, std::list<long> media_ids = {});
25 response uploadMedia(std::string media_type, const char* data, long data_length, long& media_id);
20 26
21 private: 27 private:
22 OAuth::Consumer* _oauth_consumer; 28 OAuth::Consumer* _oauth_consumer;
23 OAuth::Token* _oauth_token; 29 OAuth::Token* _oauth_token;
24 OAuth::Client* _oauth_client; 30 OAuth::Client* _oauth_client;
31
32 bool performGet(std::string url, long& response_code, json& result);
33 bool performPost(std::string url, std::string dataStr, long& response_code, json& result);
34 bool performMultiPost(std::string url, const curl_httppost* fields, long& response_code, json& result);
35 response codeForError(int httpcode, json errors) const;
25 }; 36 };
26 37
27}; 38};
diff --git a/src/codes.cpp b/src/codes.cpp new file mode 100644 index 0000000..9639d5d --- /dev/null +++ b/src/codes.cpp
@@ -0,0 +1,24 @@
1#include "codes.h"
2
3std::ostream& operator<<(std::ostream& os, twitter::response r)
4{
5 switch (r)
6 {
7 case twitter::response::ok: return os << "OK";
8 case twitter::response::curl_error: return os << "Curl Error";
9 case twitter::response::bad_auth: return os << "Bad Auth";
10 case twitter::response::limited: return os << "Rate Limit Exceeded";
11 case twitter::response::server_error: return os << "Twitter Server Error";
12 case twitter::response::server_unavailable: return os << "Twitter Is Down";
13 case twitter::response::server_overloaded: return os << "Twitter Is Over Capacity";
14 case twitter::response::server_timeout: return os << "Twitter Connection Timed Out";
15 case twitter::response::suspended: return os << "Authenticated User Is Suspended";
16 case twitter::response::bad_token: return os << "Invalid Or Expired Access Token";
17 case twitter::response::duplicate_status: return os << "Duplicate Status";
18 case twitter::response::suspected_spam: return os << "Request Looks Automated";
19 case twitter::response::write_restricted: return os << "Cannot Perform Write";
20 case twitter::response::bad_length: return os << "Message Body Too Long";
21 case twitter::response::unknown_error: return os << "Unknown Error";
22 case twitter::response::invalid_media: return os << "Invalid Media";
23 }
24}
diff --git a/src/codes.h b/src/codes.h new file mode 100644 index 0000000..334f0ce --- /dev/null +++ b/src/codes.h
@@ -0,0 +1,31 @@
1#ifndef CODES_H_05838D39
2#define CODES_H_05838D39
3
4#include <ostream>
5
6namespace twitter {
7
8 enum class response {
9 ok,
10 curl_error,
11 bad_auth,
12 limited,
13 server_error,
14 server_unavailable,
15 server_overloaded,
16 server_timeout,
17 suspended,
18 bad_token,
19 duplicate_status,
20 suspected_spam,
21 write_restricted,
22 bad_length,
23 unknown_error,
24 invalid_media
25 };
26
27};
28
29std::ostream& operator<<(std::ostream& os, twitter::response r);
30
31#endif /* end of include guard: CODES_H_05838D39 */
diff --git a/src/tweet.cpp b/src/tweet.cpp index 165187e..3ba3aa3 100644 --- a/src/tweet.cpp +++ b/src/tweet.cpp
@@ -1,4 +1,4 @@
1#include "twitter.h" 1#include "tweet.h"
2 2
3namespace twitter { 3namespace twitter {
4 4
diff --git a/src/tweet.h b/src/tweet.h index 1d83aae..e099579 100644 --- a/src/tweet.h +++ b/src/tweet.h
@@ -1,6 +1,10 @@
1#ifndef TWEET_H_CE980721 1#ifndef TWEET_H_CE980721
2#define TWEET_H_CE980721 2#define TWEET_H_CE980721
3 3
4#include <json.hpp>
5
6using nlohmann::json;
7
4namespace twitter { 8namespace twitter {
5 9
6 class tweet { 10 class tweet {
diff --git a/src/twitter.h b/src/twitter.h index 39618c9..f9534c6 100644 --- a/src/twitter.h +++ b/src/twitter.h
@@ -3,32 +3,12 @@
3 3
4namespace twitter { 4namespace twitter {
5 5
6 enum class response {
7 ok,
8 curl_error,
9 bad_auth,
10 limited,
11 server_error,
12 server_unavailable,
13 server_overloaded,
14 server_timeout,
15 suspended,
16 bad_token,
17 duplicate_status,
18 suspected_spam,
19 write_restricted,
20 bad_length,
21 unknown_error
22 };
23
24 class tweet; 6 class tweet;
25 7
26}; 8};
27 9
28#include <json.hpp> 10#include "codes.h"
29 11#include "util.h"
30using nlohmann::json;
31
32#include "auth.h" 12#include "auth.h"
33#include "client.h" 13#include "client.h"
34#include "tweet.h" 14#include "tweet.h"
diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..e1c9370 --- /dev/null +++ b/src/util.h
@@ -0,0 +1,29 @@
1#ifndef UTIL_H_440DEAA0
2#define UTIL_H_440DEAA0
3
4#include <string>
5#include <sstream>
6
7namespace twitter {
8
9 template <class InputIterator>
10 std::string implode(InputIterator first, InputIterator last, std::string delimiter)
11 {
12 std::stringstream result;
13
14 for (InputIterator it = first; it != last; it++)
15 {
16 if (it != first)
17 {
18 result << delimiter;
19 }
20
21 result << *it;
22 }
23
24 return result.str();
25 }
26
27};
28
29#endif /* end of include guard: UTIL_H_440DEAA0 */
diff --git a/vendor/liboauthcpp b/vendor/liboauthcpp
Subproject 4219b6576c53545d5f6b6f18a28ec93ec796968 Subproject b460cdfadb599c5cea88cbaff0539b7ba9fb17b