diff options
Diffstat (limited to 'difference.cpp')
-rw-r--r-- | difference.cpp | 263 |
1 files changed, 73 insertions, 190 deletions
diff --git a/difference.cpp b/difference.cpp index 9c060d4..86a0779 100644 --- a/difference.cpp +++ b/difference.cpp | |||
@@ -1,12 +1,14 @@ | |||
1 | #include "difference.h" | 1 | #include "difference.h" |
2 | #include <curl_easy.h> | ||
3 | #include <curl_header.h> | ||
4 | #include <vector> | 2 | #include <vector> |
5 | #include <iostream> | 3 | #include <iostream> |
6 | #include <yaml-cpp/yaml.h> | 4 | #include <yaml-cpp/yaml.h> |
7 | #include <chrono> | 5 | #include <chrono> |
8 | #include <thread> | 6 | #include <thread> |
9 | #include <deque> | 7 | #include <deque> |
8 | #include <json.hpp> | ||
9 | #include "imagenet.h" | ||
10 | |||
11 | #define ENABLE_BOT | ||
10 | 12 | ||
11 | difference::difference( | 13 | difference::difference( |
12 | std::string configFile, | 14 | std::string configFile, |
@@ -16,20 +18,22 @@ difference::difference( | |||
16 | // Load the config file. | 18 | // Load the config file. |
17 | YAML::Node config = YAML::LoadFile(configFile); | 19 | YAML::Node config = YAML::LoadFile(configFile); |
18 | 20 | ||
19 | // Set up the Twitter client. | 21 | #ifdef ENABLE_BOT |
20 | twitter::auth auth; | 22 | // Set up the Mastodon client. |
21 | auth.setConsumerKey(config["consumer_key"].as<std::string>()); | 23 | instance_ = std::make_unique<mastodonpp::Instance>( |
22 | auth.setConsumerSecret(config["consumer_secret"].as<std::string>()); | 24 | config["mastodon_instance"].as<std::string>(), |
23 | auth.setAccessKey(config["access_key"].as<std::string>()); | 25 | config["mastodon_token"].as<std::string>()); |
24 | auth.setAccessSecret(config["access_secret"].as<std::string>()); | 26 | connection_ = std::make_unique<mastodonpp::Connection>(*instance_); |
25 | 27 | #endif | |
26 | client_ = std::unique_ptr<twitter::client>(new twitter::client(auth)); | 28 | |
27 | |||
28 | // Set up the verbly database. | 29 | // Set up the verbly database. |
29 | database_ = std::unique_ptr<verbly::database>( | 30 | database_ = std::unique_ptr<verbly::database>( |
30 | new verbly::database(config["verbly_datafile"].as<std::string>())); | 31 | new verbly::database(config["verbly_datafile"].as<std::string>())); |
31 | 32 | ||
33 | imagenet_ = std::make_unique<imagenet>(config["imagenet"].as<std::string>()); | ||
34 | |||
32 | fontfile_ = "@" + config["font"].as<std::string>(); | 35 | fontfile_ = "@" + config["font"].as<std::string>(); |
36 | tempfile_ = config["tempfile"].as<std::string>(); | ||
33 | } | 37 | } |
34 | 38 | ||
35 | void difference::run() const | 39 | void difference::run() const |
@@ -73,19 +77,19 @@ void difference::run() const | |||
73 | // Generate the tweet text. | 77 | // Generate the tweet text. |
74 | std::cout << "Generating text..." << std::endl; | 78 | std::cout << "Generating text..." << std::endl; |
75 | 79 | ||
76 | std::string text = generateTweetText(std::move(word1), std::move(word2)); | 80 | std::string text = generatePostText(std::move(word1), std::move(word2)); |
77 | 81 | ||
78 | std::cout << "Tweet text: " << text << std::endl; | 82 | std::cout << "Post text: " << text << std::endl; |
79 | 83 | ||
80 | // Send the tweet. | 84 | // Send the post. |
81 | std::cout << "Sending tweet..." << std::endl; | 85 | std::cout << "Sending post..." << std::endl; |
82 | 86 | ||
83 | sendTweet(std::move(text), std::move(image)); | 87 | sendPost(std::move(text), std::move(image)); |
84 | 88 | ||
85 | std::cout << "Tweeted!" << std::endl; | 89 | std::cout << "Posted!" << std::endl; |
86 | 90 | ||
87 | // Wait. | 91 | // Wait. |
88 | std::this_thread::sleep_for(std::chrono::hours(1)); | 92 | std::this_thread::sleep_for(std::chrono::hours(9)); |
89 | } catch (const could_not_get_images& ex) | 93 | } catch (const could_not_get_images& ex) |
90 | { | 94 | { |
91 | std::cout << ex.what() << std::endl; | 95 | std::cout << ex.what() << std::endl; |
@@ -95,9 +99,9 @@ void difference::run() const | |||
95 | } catch (const Magick::ErrorCorruptImage& ex) | 99 | } catch (const Magick::ErrorCorruptImage& ex) |
96 | { | 100 | { |
97 | std::cout << "Corrupt image: " << ex.what() << std::endl; | 101 | std::cout << "Corrupt image: " << ex.what() << std::endl; |
98 | } catch (const twitter::twitter_error& ex) | 102 | } catch (const std::exception& ex) |
99 | { | 103 | { |
100 | std::cout << "Twitter error: " << ex.what() << std::endl; | 104 | std::cout << "Other error: " << ex.what() << std::endl; |
101 | 105 | ||
102 | std::this_thread::sleep_for(std::chrono::hours(1)); | 106 | std::this_thread::sleep_for(std::chrono::hours(1)); |
103 | } | 107 | } |
@@ -153,175 +157,22 @@ verbly::word difference::getPicturedNoun() const | |||
153 | std::pair<Magick::Image, Magick::Image> | 157 | std::pair<Magick::Image, Magick::Image> |
154 | difference::getImagesForNoun(verbly::word pictured) const | 158 | difference::getImagesForNoun(verbly::word pictured) const |
155 | { | 159 | { |
156 | int backoff = 0; | ||
157 | |||
158 | std::cout << "Getting URLs..." << std::endl; | 160 | std::cout << "Getting URLs..." << std::endl; |
159 | 161 | ||
160 | std::string lstdata; | 162 | std::vector<std::tuple<std::string, std::string>> images = |
161 | while (lstdata.empty()) | 163 | imagenet_->getImagesForNotion(pictured.getNotion().getId(), rng_, 2); |
162 | { | ||
163 | std::ostringstream lstbuf; | ||
164 | curl::curl_ios<std::ostringstream> lstios(lstbuf); | ||
165 | curl::curl_easy lsthandle(lstios); | ||
166 | std::string lsturl = pictured.getNotion().getImageNetUrl(); | ||
167 | lsthandle.add<CURLOPT_URL>(lsturl.c_str()); | ||
168 | lsthandle.add<CURLOPT_CONNECTTIMEOUT>(30); | ||
169 | lsthandle.add<CURLOPT_TIMEOUT>(300); | ||
170 | |||
171 | try | ||
172 | { | ||
173 | lsthandle.perform(); | ||
174 | } catch (const curl::curl_easy_exception& e) | ||
175 | { | ||
176 | e.print_traceback(); | ||
177 | |||
178 | backoff++; | ||
179 | std::cout << "Waiting for " << backoff << " seconds..." << std::endl; | ||
180 | |||
181 | std::this_thread::sleep_for(std::chrono::seconds(backoff)); | ||
182 | |||
183 | continue; | ||
184 | } | ||
185 | |||
186 | backoff = 0; | ||
187 | |||
188 | if (lsthandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200) | ||
189 | { | ||
190 | throw could_not_get_images(); | ||
191 | } | ||
192 | |||
193 | std::cout << "Got URLs." << std::endl; | ||
194 | lstdata = lstbuf.str(); | ||
195 | } | ||
196 | |||
197 | std::vector<std::string> lstvec = verbly::split<std::vector<std::string>>(lstdata, "\r\n"); | ||
198 | if (lstvec.size() < 2) | ||
199 | { | ||
200 | throw could_not_get_images(); | ||
201 | } | ||
202 | |||
203 | std::shuffle(std::begin(lstvec), std::end(lstvec), rng_); | ||
204 | |||
205 | std::deque<std::string> urls; | ||
206 | for (std::string& url : lstvec) | ||
207 | { | ||
208 | urls.push_back(url); | ||
209 | } | ||
210 | |||
211 | Magick::Image image1; | ||
212 | bool success = false; | ||
213 | while (!urls.empty()) | ||
214 | { | ||
215 | std::string url = urls.front(); | ||
216 | urls.pop_front(); | ||
217 | |||
218 | try | ||
219 | { | ||
220 | image1 = getImageAtUrl(url); | ||
221 | |||
222 | success = true; | ||
223 | break; | ||
224 | } catch (const could_not_get_images& ex) | ||
225 | { | ||
226 | // Just try the next one. | ||
227 | } | ||
228 | } | ||
229 | |||
230 | if (!success) | ||
231 | { | ||
232 | throw could_not_get_images(); | ||
233 | } | ||
234 | 164 | ||
235 | Magick::Image image2; | 165 | const std::string& imgdata1 = std::get<0>(images[0]); |
236 | success = false; | 166 | Magick::Blob image1(imgdata1.c_str(), imgdata1.length()); |
237 | while (!urls.empty()) | 167 | Magick::Image pic1; |
238 | { | 168 | pic1.read(image1); |
239 | std::string url = urls.front(); | ||
240 | urls.pop_front(); | ||
241 | 169 | ||
242 | try | 170 | const std::string& imgdata2 = std::get<0>(images[1]); |
243 | { | 171 | Magick::Blob image2(imgdata2.c_str(), imgdata2.length()); |
244 | image2 = getImageAtUrl(url); | 172 | Magick::Image pic2; |
173 | pic2.read(image2); | ||
245 | 174 | ||
246 | success = true; | 175 | return {std::move(pic1), std::move(pic2)}; |
247 | break; | ||
248 | } catch (const could_not_get_images& ex) | ||
249 | { | ||
250 | // Just try the next one. | ||
251 | } | ||
252 | } | ||
253 | |||
254 | if (!success) | ||
255 | { | ||
256 | throw could_not_get_images(); | ||
257 | } | ||
258 | |||
259 | return {std::move(image1), std::move(image2)}; | ||
260 | } | ||
261 | |||
262 | Magick::Image difference::getImageAtUrl(std::string url) const | ||
263 | { | ||
264 | // willyfogg.com is a thumbnail generator known to return 200 even if the target image no longer exists | ||
265 | if (url.find("willyfogg.com/thumb.php") != std::string::npos) | ||
266 | { | ||
267 | throw could_not_get_images(); | ||
268 | } | ||
269 | |||
270 | // Accept string from Google Chrome | ||
271 | std::string accept = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"; | ||
272 | curl::curl_header headers; | ||
273 | headers.add(accept); | ||
274 | |||
275 | std::ostringstream imgbuf; | ||
276 | curl::curl_ios<std::ostringstream> imgios(imgbuf); | ||
277 | curl::curl_easy imghandle(imgios); | ||
278 | |||
279 | imghandle.add<CURLOPT_HTTPHEADER>(headers.get()); | ||
280 | imghandle.add<CURLOPT_URL>(url.c_str()); | ||
281 | imghandle.add<CURLOPT_CONNECTTIMEOUT>(30); | ||
282 | imghandle.add<CURLOPT_TIMEOUT>(300); | ||
283 | |||
284 | try | ||
285 | { | ||
286 | imghandle.perform(); | ||
287 | } catch (const curl::curl_easy_exception& error) { | ||
288 | error.print_traceback(); | ||
289 | |||
290 | throw could_not_get_images(); | ||
291 | } | ||
292 | |||
293 | if (imghandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200) | ||
294 | { | ||
295 | throw could_not_get_images(); | ||
296 | } | ||
297 | |||
298 | std::string content_type = imghandle.get_info<CURLINFO_CONTENT_TYPE>().get(); | ||
299 | if (content_type.substr(0, 6) != "image/") | ||
300 | { | ||
301 | throw could_not_get_images(); | ||
302 | } | ||
303 | |||
304 | std::string imgstr = imgbuf.str(); | ||
305 | Magick::Blob img(imgstr.c_str(), imgstr.length()); | ||
306 | Magick::Image pic; | ||
307 | |||
308 | try | ||
309 | { | ||
310 | pic.read(img); | ||
311 | |||
312 | if ((pic.rows() > 0) && (pic.columns() >= 800)) | ||
313 | { | ||
314 | std::cout << url << std::endl; | ||
315 | } | ||
316 | } catch (const Magick::ErrorOption& e) | ||
317 | { | ||
318 | // Occurs when the the data downloaded from the server is malformed | ||
319 | std::cout << "Magick: " << e.what() << std::endl; | ||
320 | |||
321 | throw could_not_get_images(); | ||
322 | } | ||
323 | |||
324 | return pic; | ||
325 | } | 176 | } |
326 | 177 | ||
327 | std::pair<verbly::word, verbly::word> difference::getOppositeIdentifiers() const | 178 | std::pair<verbly::word, verbly::word> difference::getOppositeIdentifiers() const |
@@ -419,7 +270,7 @@ Magick::Image difference::composeImage( | |||
419 | return composite; | 270 | return composite; |
420 | } | 271 | } |
421 | 272 | ||
422 | std::string difference::generateTweetText( | 273 | std::string difference::generatePostText( |
423 | verbly::word word1, | 274 | verbly::word word1, |
424 | verbly::word word2) const | 275 | verbly::word word2) const |
425 | { | 276 | { |
@@ -432,12 +283,44 @@ std::string difference::generateTweetText( | |||
432 | return result.compile(); | 283 | return result.compile(); |
433 | } | 284 | } |
434 | 285 | ||
435 | void difference::sendTweet(std::string text, Magick::Image image) const | 286 | void difference::sendPost(std::string text, Magick::Image image) const |
436 | { | 287 | { |
437 | Magick::Blob outputBlob; | ||
438 | image.magick("png"); | 288 | image.magick("png"); |
439 | image.write(&outputBlob); | 289 | image.write(tempfile_); |
440 | 290 | ||
441 | long media_id = client_->uploadMedia("image/png", (const char*) outputBlob.data(), outputBlob.length()); | 291 | #ifdef ENABLE_BOT |
442 | client_->updateStatus(std::move(text), {media_id}); | 292 | auto answer{connection_->post(mastodonpp::API::v1::media, |
293 | {{"file", std::string("@file:") + tempfile_}, {"description", text}})}; | ||
294 | if (!answer) | ||
295 | { | ||
296 | if (answer.curl_error_code == 0) | ||
297 | { | ||
298 | std::cout << "HTTP status: " << answer.http_status << std::endl; | ||
299 | } | ||
300 | else | ||
301 | { | ||
302 | std::cout << "libcurl error " << std::to_string(answer.curl_error_code) | ||
303 | << ": " << answer.error_message << std::endl; | ||
304 | } | ||
305 | return; | ||
306 | } | ||
307 | |||
308 | nlohmann::json response_json = nlohmann::json::parse(answer.body); | ||
309 | answer = connection_->post(mastodonpp::API::v1::statuses, | ||
310 | {{"status", ""}, {"media_ids", | ||
311 | std::vector<std::string_view>{response_json["id"].get<std::string>()}}}); | ||
312 | |||
313 | if (!answer) | ||
314 | { | ||
315 | if (answer.curl_error_code == 0) | ||
316 | { | ||
317 | std::cout << "HTTP status: " << answer.http_status << std::endl; | ||
318 | } | ||
319 | else | ||
320 | { | ||
321 | std::cout << "libcurl error " << std::to_string(answer.curl_error_code) | ||
322 | << ": " << answer.error_message << std::endl; | ||
323 | } | ||
324 | } | ||
325 | #endif | ||
443 | } | 326 | } |