summary refs log tree commit diff stats
path: root/grunge.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'grunge.cpp')
-rw-r--r--grunge.cpp355
1 files changed, 355 insertions, 0 deletions
diff --git a/grunge.cpp b/grunge.cpp new file mode 100644 index 0000000..de9061e --- /dev/null +++ b/grunge.cpp
@@ -0,0 +1,355 @@
1#include "grunge.h"
2#include <yaml-cpp/yaml.h>
3#include <iostream>
4#include <thread>
5#include <chrono>
6#include <curl_easy.h>
7#include <curl_header.h>
8#include <deque>
9#include "palette.h"
10
11grunge::grunge(
12 std::string configFile,
13 std::mt19937& rng) :
14 rng_(rng)
15{
16 // Load the config file.
17 YAML::Node config = YAML::LoadFile(configFile);
18
19 // Set up the Twitter client.
20 twitter::auth auth;
21 auth.setConsumerKey(config["consumer_key"].as<std::string>());
22 auth.setConsumerSecret(config["consumer_secret"].as<std::string>());
23 auth.setAccessKey(config["access_key"].as<std::string>());
24 auth.setAccessSecret(config["access_secret"].as<std::string>());
25
26 client_ = std::unique_ptr<twitter::client>(new twitter::client(auth));
27
28 // Set up the verbly database.
29 database_ = std::unique_ptr<verbly::database>(
30 new verbly::database(config["verbly_datafile"].as<std::string>()));
31}
32
33void grunge::run() const
34{
35 for (;;)
36 {
37 std::cout << "Generating tweet..." << std::endl;
38
39 try
40 {
41 // Find a noun to use as the pictured item.
42 std::cout << "Choosing pictured noun..." << std::endl;
43
44 verbly::word pictured = getPicturedNoun();
45
46 std::cout << "Noun: " << pictured.getBaseForm().getText() << std::endl;
47
48 // Choose a picture of that noun.
49 std::cout << "Finding an image..." << std::endl;
50
51 Magick::Image image = getImageForNoun(pictured);
52
53 // Pixelate the image.
54 std::cout << "Pixelating image..." << std::endl;
55
56 image = pixelateImage(std::move(image));
57
58 // Pastelize the image.
59 std::cout << "Pastelizing image..." << std::endl;
60
61 image = pastelizeImage(std::move(image));
62
63 // Generate the tweet text.
64 std::cout << "Generating text..." << std::endl;
65
66 std::string text = generateTweetText(pictured);
67
68 std::cout << "Tweet text: " << text << std::endl;
69
70 // Send the tweet.
71 std::cout << "Sending tweet..." << std::endl;
72
73 sendTweet(std::move(text), std::move(image));
74
75 std::cout << "Tweeted!" << std::endl;
76
77 // Wait.
78 std::this_thread::sleep_for(std::chrono::hours(1));
79 } catch (const could_not_get_images& ex)
80 {
81 std::cout << ex.what() << std::endl;
82 } catch (const Magick::ErrorImage& ex)
83 {
84 std::cout << "Image error: " << ex.what() << std::endl;
85 } catch (const Magick::ErrorCorruptImage& ex)
86 {
87 std::cout << "Corrupt image: " << ex.what() << std::endl;
88 } catch (const twitter::twitter_error& ex)
89 {
90 std::cout << "Twitter error: " << ex.what() << std::endl;
91
92 std::this_thread::sleep_for(std::chrono::hours(1));
93 }
94
95 std::cout << std::endl;
96 }
97}
98
99verbly::word grunge::getPicturedNoun() const
100{
101 verbly::filter whitelist =
102 (verbly::notion::wnid == 109287968) // Geological formations
103 || (verbly::notion::wnid == 109208496) // Asterisms (collections of stars)
104 || (verbly::notion::wnid == 109239740) // Celestial bodies
105 || (verbly::notion::wnid == 109277686) // Exterrestrial objects (comets and meteroids)
106 || (verbly::notion::wnid == 109403211) // Radiators (supposedly natural radiators but actually these are just pictures of radiators)
107 || (verbly::notion::wnid == 109416076) // Rocks
108 || (verbly::notion::wnid == 105442131) // Chromosomes
109 || (verbly::notion::wnid == 100324978) // Tightrope walking
110 || (verbly::notion::wnid == 100326094) // Rock climbing
111 || (verbly::notion::wnid == 100433458) // Contact sports
112 || (verbly::notion::wnid == 100433802) // Gymnastics
113 || (verbly::notion::wnid == 100439826) // Track and field
114 || (verbly::notion::wnid == 100440747) // Skiing
115 || (verbly::notion::wnid == 100441824) // Water sport
116 || (verbly::notion::wnid == 100445351) // Rowing
117 || (verbly::notion::wnid == 100446980) // Archery
118 // TODO: add more sports
119 || (verbly::notion::wnid == 100021939) // Artifacts
120 || (verbly::notion::wnid == 101471682) // Vertebrates
121 ;
122
123 verbly::filter blacklist =
124 (verbly::notion::wnid == 106883725) // swastika
125 || (verbly::notion::wnid == 104416901) // tetraskele
126 || (verbly::notion::wnid == 102512053) // fish
127 || (verbly::notion::wnid == 103575691) // instrument of execution
128 || (verbly::notion::wnid == 103829563) // noose
129 ;
130
131 verbly::query<verbly::word> pictureQuery = database_->words(
132 (verbly::notion::fullHypernyms %= whitelist)
133 && !(verbly::notion::fullHypernyms %= blacklist)
134 && (verbly::notion::partOfSpeech == verbly::part_of_speech::noun)
135 && (verbly::notion::numOfImages >= 1));
136
137 verbly::word pictured = pictureQuery.first();
138
139 return pictured;
140}
141
142Magick::Image grunge::getImageForNoun(verbly::word pictured) const
143{
144 // Accept string from Google Chrome
145 std::string accept = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
146 curl::curl_header headers;
147 headers.add(accept);
148
149 int backoff = 0;
150
151 std::cout << "Getting URLs..." << std::endl;
152
153 std::string lstdata;
154 while (lstdata.empty())
155 {
156 std::ostringstream lstbuf;
157 curl::curl_ios<std::ostringstream> lstios(lstbuf);
158 curl::curl_easy lsthandle(lstios);
159 std::string lsturl = pictured.getNotion().getImageNetUrl();
160 lsthandle.add<CURLOPT_URL>(lsturl.c_str());
161
162 try
163 {
164 lsthandle.perform();
165 } catch (const curl::curl_easy_exception& e)
166 {
167 e.print_traceback();
168
169 backoff++;
170 std::cout << "Waiting for " << backoff << " seconds..." << std::endl;
171
172 std::this_thread::sleep_for(std::chrono::seconds(backoff));
173
174 continue;
175 }
176
177 backoff = 0;
178
179 if (lsthandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
180 {
181 throw could_not_get_images();
182 }
183
184 std::cout << "Got URLs." << std::endl;
185 lstdata = lstbuf.str();
186 }
187
188 std::vector<std::string> lstvec = verbly::split<std::vector<std::string>>(lstdata, "\r\n");
189 if (lstvec.empty())
190 {
191 throw could_not_get_images();
192 }
193
194 std::shuffle(std::begin(lstvec), std::end(lstvec), rng_);
195
196 std::deque<std::string> urls;
197 for (std::string& url : lstvec)
198 {
199 urls.push_back(url);
200 }
201
202 bool found = false;
203 Magick::Blob img;
204 Magick::Image pic;
205
206 while (!found && !urls.empty())
207 {
208 std::string url = urls.front();
209 urls.pop_front();
210
211 std::ostringstream imgbuf;
212 curl::curl_ios<std::ostringstream> imgios(imgbuf);
213 curl::curl_easy imghandle(imgios);
214
215 imghandle.add<CURLOPT_HTTPHEADER>(headers.get());
216 imghandle.add<CURLOPT_URL>(url.c_str());
217 imghandle.add<CURLOPT_CONNECTTIMEOUT>(30);
218
219 try
220 {
221 imghandle.perform();
222 } catch (curl::curl_easy_exception error) {
223 error.print_traceback();
224
225 continue;
226 }
227
228 if (imghandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
229 {
230 continue;
231 }
232
233 std::string content_type = imghandle.get_info<CURLINFO_CONTENT_TYPE>().get();
234 if (content_type.substr(0, 6) != "image/")
235 {
236 continue;
237 }
238
239 std::string imgstr = imgbuf.str();
240 img = Magick::Blob(imgstr.c_str(), imgstr.length());
241
242 try
243 {
244 pic.read(img);
245
246 if ((pic.rows() > 0) && (pic.columns() >= 800))
247 {
248 std::cout << url << std::endl;
249 found = true;
250 }
251 } catch (const Magick::ErrorOption& e)
252 {
253 // Occurs when the the data downloaded from the server is malformed
254 std::cout << "Magick: " << e.what() << std::endl;
255 }
256 }
257
258 if (!found)
259 {
260 throw could_not_get_images();
261 }
262
263 return pic;
264}
265
266Magick::Image grunge::pixelateImage(Magick::Image image) const
267{
268 // Check that the image dimensions are a multiple of four.
269 if ((image.rows() % 4 != 0) || (image.columns() % 4 != 0))
270 {
271 Magick::Geometry cropped(
272 image.columns() - (image.columns() % 4),
273 image.rows() - (image.rows() % 4));
274
275 image.crop(cropped);
276 }
277
278 // Downscale the image.
279 Magick::Geometry originalSize = image.size();
280 Magick::Geometry pixelatedSize(
281 originalSize.width() / 4,
282 originalSize.height() / 4);
283
284 image.scale(pixelatedSize);
285
286 // Scale the image back up.
287 image.scale(originalSize);
288
289 return image;
290}
291
292Magick::Image grunge::pastelizeImage(Magick::Image input) const
293{
294 input.quantizeColorSpace(Magick::GRAYColorspace);
295 input.quantizeColors(256);
296 input.quantize();
297
298 palette pastelPalette = palette::randomPalette(rng_);
299 Magick::Geometry size = input.size();
300 Magick::Image pastelized(size, "white");
301
302 for (int y=0; y<size.height(); y++)
303 {
304 for (int x=0; x<size.width(); x++)
305 {
306 Magick::ColorGray grade =
307 static_cast<Magick::ColorGray>(input.pixelColor(x, y));
308 Magick::Color mapped = pastelPalette.getColor(grade.shade());
309
310 pastelized.pixelColor(x, y, mapped);
311 }
312 }
313
314 return pastelized;
315}
316
317std::string grunge::generateTweetText(verbly::word pictured) const
318{
319 verbly::word simpler = database_->words(
320 (verbly::notion::partOfSpeech == verbly::part_of_speech::noun)
321 && (verbly::notion::fullHyponyms %= pictured)
322 && (verbly::form::proper == false)).first();
323
324 std::vector<std::string> symbols = {"☯","✡","☨","✞","✝","☮","☥","☦","☪","✌"};
325 std::string prefix;
326 std::string suffix;
327 int length = std::geometric_distribution<int>(0.5)(rng_) + 1;
328 for (int i=0; i<length; i++)
329 {
330 std::string choice = symbols[
331 std::uniform_int_distribution<int>(0, symbols.size()-1)(rng_)];
332
333 prefix += choice;
334 suffix = choice + suffix;
335 }
336
337 verbly::token action = {
338 prefix,
339 "follow for more soft grunge",
340 simpler,
341 suffix
342 };
343
344 return action.compile();
345}
346
347void grunge::sendTweet(std::string text, Magick::Image image) const
348{
349 Magick::Blob outputBlob;
350 image.magick("jpg");
351 image.write(&outputBlob);
352
353 long media_id = client_->uploadMedia("image/jpeg", (const char*) outputBlob.data(), outputBlob.length());
354 client_->updateStatus(std::move(text), {media_id});
355}