about summary refs log tree commit diff stats
path: root/src/client.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/client.cpp')
-rw-r--r--src/client.cpp368
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
13static
14void 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
45static
46int 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
83namespace twitter { 9namespace 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