about summary refs log tree commit diff stats
path: root/difference.cpp
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2022-12-23 11:09:58 +0000
committerStar Rauchenberger <fefferburbia@gmail.com>2022-12-23 11:09:58 +0000
commita76e81ee8bcbad87c9eb58cdb25452cae65bd0c7 (patch)
tree4f677cd679bb7374b022238d55d49dba81b7313f /difference.cpp
parent31770500a918bccf7b624262bc7d444cc2dd8a1d (diff)
downloaddifference-a76e81ee8bcbad87c9eb58cdb25452cae65bd0c7.tar.gz
difference-a76e81ee8bcbad87c9eb58cdb25452cae65bd0c7.tar.bz2
difference-a76e81ee8bcbad87c9eb58cdb25452cae65bd0c7.zip
Bot is now a Mastodon bot
Diffstat (limited to 'difference.cpp')
-rw-r--r--difference.cpp263
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
11difference::difference( 13difference::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
35void difference::run() const 39void 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
153std::pair<Magick::Image, Magick::Image> 157std::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
262Magick::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
327std::pair<verbly::word, verbly::word> difference::getOppositeIdentifiers() const 178std::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
422std::string difference::generateTweetText( 273std::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
435void difference::sendTweet(std::string text, Magick::Image image) const 286void 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}