summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--.gitmodules9
-rw-r--r--CMakeLists.txt16
-rw-r--r--capital.cpp292
-rw-r--r--capital.h45
-rw-r--r--main.cpp33
m---------vendor/libtwittercpp0
m---------vendor/verbly0
m---------vendor/yaml-cpp0
9 files changed, 400 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e89877 --- /dev/null +++ b/.gitignore
@@ -0,0 +1,5 @@
1CMakeCache.txt
2CMakeFiles
3Makefile
4cmake_install.cmake
5yaml-cpp.pc \ No newline at end of file
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b8daec7 --- /dev/null +++ b/.gitmodules
@@ -0,0 +1,9 @@
1[submodule "vendor/verbly"]
2 path = vendor/verbly
3 url = git@github.com:hatkirby/verbly
4[submodule "vendor/libtwittercpp"]
5 path = vendor/libtwittercpp
6 url = git@github.com:hatkirby/libtwittercpp.git
7[submodule "vendor/yaml-cpp"]
8 path = vendor/yaml-cpp
9 url = git@github.com:jbeder/yaml-cpp.git
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0462950 --- /dev/null +++ b/CMakeLists.txt
@@ -0,0 +1,16 @@
1cmake_minimum_required (VERSION 3.1)
2project (capital)
3
4add_subdirectory(vendor/libtwittercpp)
5add_subdirectory(vendor/verbly)
6add_subdirectory(vendor/yaml-cpp EXCLUDE_FROM_ALL)
7
8find_package(PkgConfig)
9pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED)
10
11include_directories(${GraphicsMagick_INCLUDE_DIRS} vendor/verbly/lib vendor/libtwittercpp/src vendor/yaml-cpp/include vendor/libtwittercpp/vendor/curlcpp/include)
12link_directories(${GraphicsMagick_LIBRARY_DIRS})
13add_executable(capital capital.cpp main.cpp)
14set_property(TARGET capital PROPERTY CXX_STANDARD 11)
15set_property(TARGET capital PROPERTY CXX_STANDARD_REQUIRED ON)
16target_link_libraries(capital ${GraphicsMagick_LIBRARIES} verbly yaml-cpp twitter++)
diff --git a/capital.cpp b/capital.cpp new file mode 100644 index 0000000..526e655 --- /dev/null +++ b/capital.cpp
@@ -0,0 +1,292 @@
1#include "capital.h"
2#include <vector>
3#include <yaml-cpp/yaml.h>
4#include <curl_easy.h>
5#include <curl_header.h>
6#include <iostream>
7#include <deque>
8
9capital::capital(
10 std::string configFile,
11 std::mt19937& rng) :
12 rng_(rng)
13{
14 // Load the config file.
15 YAML::Node config = YAML::LoadFile(configFile);
16
17 // Set up the Twitter client.
18 twitter::auth auth;
19 auth.setConsumerKey(config["consumer_key"].as<std::string>());
20 auth.setConsumerSecret(config["consumer_secret"].as<std::string>());
21 auth.setAccessKey(config["access_key"].as<std::string>());
22 auth.setAccessSecret(config["access_secret"].as<std::string>());
23
24 client_ = std::unique_ptr<twitter::client>(new twitter::client(auth));
25
26 // Set up the verbly database.
27 database_ = std::unique_ptr<verbly::database>(
28 new verbly::database(config["verbly_datafile"].as<std::string>()));
29}
30
31void capital::run() const
32{
33 for (;;)
34 {
35 std::cout << "Generating tweet..." << std::endl;
36
37 try
38 {
39 // Find a noun to use as the pictured item.
40 std::cout << "Choosing pictured noun..." << std::endl;
41
42 verbly::word pictured = getPicturedNoun();
43
44 std::cout << "Noun: " << pictured.getBaseForm().getText() << std::endl;
45
46 // Choose a picture of that noun.
47 std::cout << "Finding an image..." << std::endl;
48
49 Magick::Image image = getImageForNoun(pictured);
50
51 // Generate the tweet text.
52 std::cout << "Generating text..." << std::endl;
53
54 std::string text = generateTweetText(pictured);
55
56 std::cout << "Tweet text: " << text << std::endl;
57
58 // Send the tweet.
59 std::cout << "Sending tweet..." << std::endl;
60
61 sendTweet(std::move(text), std::move(image));
62
63 std::cout << "Tweeted!" << std::endl;
64
65 // Wait.
66 std::this_thread::sleep_for(std::chrono::hours(1));
67 } catch (const could_not_get_image& ex)
68 {
69 std::cout << ex.what() << std::endl;
70 } catch (const Magick::ErrorImage& ex)
71 {
72 std::cout << "Image error: " << ex.what() << std::endl;
73 } catch (const Magick::ErrorCorruptImage& ex)
74 {
75 std::cout << "Corrupt image: " << ex.what() << std::endl;
76 } catch (const twitter::twitter_error& ex)
77 {
78 std::cout << "Twitter error: " << ex.what() << std::endl;
79
80 std::this_thread::sleep_for(std::chrono::hours(1));
81 }
82
83 std::cout << std::endl;
84 }
85}
86
87verbly::word capital::getPicturedNoun() const
88{
89 verbly::filter whitelist = (verbly::notion::wnid == 100021939); // Artifacts
90
91 verbly::filter blacklist =
92 (verbly::notion::wnid == 106883725) // swastika
93 || (verbly::notion::wnid == 104416901) // tetraskele
94 || (verbly::notion::wnid == 103575691) // instrument of execution
95 || (verbly::notion::wnid == 103829563) // noose
96 ;
97
98 verbly::query<verbly::word> pictureQuery = database_->words(
99 (verbly::notion::fullHypernyms %= whitelist)
100 && !(verbly::notion::fullHypernyms %= blacklist)
101 && (verbly::notion::partOfSpeech == verbly::part_of_speech::noun)
102 && (verbly::notion::numOfImages >= 1)
103 // Blacklist ethnic slurs
104 && !(verbly::word::usageDomains %= (verbly::notion::wnid == 106718862))
105 );
106
107 verbly::word pictured = pictureQuery.first();
108
109 return pictured;
110}
111
112Magick::Image capital::getImageForNoun(verbly::word pictured) const
113{
114 // Accept string from Google Chrome
115 std::string accept = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
116 curl::curl_header headers;
117 headers.add(accept);
118
119 int backoff = 0;
120
121 std::cout << "Getting URLs..." << std::endl;
122
123 std::string lstdata;
124 while (lstdata.empty())
125 {
126 std::ostringstream lstbuf;
127 curl::curl_ios<std::ostringstream> lstios(lstbuf);
128 curl::curl_easy lsthandle(lstios);
129 std::string lsturl = pictured.getNotion().getImageNetUrl();
130 lsthandle.add<CURLOPT_URL>(lsturl.c_str());
131
132 try
133 {
134 lsthandle.perform();
135 } catch (const curl::curl_easy_exception& e)
136 {
137 e.print_traceback();
138
139 backoff++;
140 std::cout << "Waiting for " << backoff << " seconds..." << std::endl;
141
142 std::this_thread::sleep_for(std::chrono::seconds(backoff));
143
144 continue;
145 }
146
147 backoff = 0;
148
149 if (lsthandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
150 {
151 throw could_not_get_image();
152 }
153
154 std::cout << "Got URLs." << std::endl;
155 lstdata = lstbuf.str();
156 }
157
158 std::vector<std::string> lstvec =
159 verbly::split<std::vector<std::string>>(lstdata, "\r\n");
160 if (lstvec.empty())
161 {
162 throw could_not_get_image();
163 }
164
165 std::shuffle(std::begin(lstvec), std::end(lstvec), rng_);
166
167 std::deque<std::string> urls;
168 for (std::string& url : lstvec)
169 {
170 urls.push_back(url);
171 }
172
173 bool found = false;
174 Magick::Blob img;
175 Magick::Image pic;
176
177 while (!found && !urls.empty())
178 {
179 std::string url = urls.front();
180 urls.pop_front();
181
182 std::ostringstream imgbuf;
183 curl::curl_ios<std::ostringstream> imgios(imgbuf);
184 curl::curl_easy imghandle(imgios);
185
186 imghandle.add<CURLOPT_HTTPHEADER>(headers.get());
187 imghandle.add<CURLOPT_URL>(url.c_str());
188 imghandle.add<CURLOPT_CONNECTTIMEOUT>(30);
189
190 try
191 {
192 imghandle.perform();
193 } catch (curl::curl_easy_exception error) {
194 error.print_traceback();
195
196 continue;
197 }
198
199 if (imghandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
200 {
201 continue;
202 }
203
204 std::string content_type =
205 imghandle.get_info<CURLINFO_CONTENT_TYPE>().get();
206 if (content_type.substr(0, 6) != "image/")
207 {
208 continue;
209 }
210
211 std::string imgstr = imgbuf.str();
212 img = Magick::Blob(imgstr.c_str(), imgstr.length());
213
214 try
215 {
216 pic.read(img);
217
218 if (pic.rows() > 0)
219 {
220 std::cout << url << std::endl;
221 found = true;
222 }
223 } catch (const Magick::ErrorOption& e)
224 {
225 // Occurs when the the data downloaded from the server is malformed
226 std::cout << "Magick: " << e.what() << std::endl;
227 }
228 }
229
230 if (!found)
231 {
232 throw could_not_get_image();
233 }
234
235 return pic;
236}
237
238std::string capital::generateTweetText(verbly::word pictured) const
239{
240 int msd = std::uniform_int_distribution<int>(1, 9)(rng_);
241 int mag = std::uniform_int_distribution<int>(2, 9)(rng_);
242
243 std::string money;
244 for (int i=0; i<mag; i++)
245 {
246 money.insert(0, "0");
247
248 if ((i % 3 == 2) && (mag > 3))
249 {
250 money.insert(0, ",");
251 }
252 }
253
254 money.insert(0, std::to_string(msd));
255 money.insert(0, "$");
256
257 verbly::token nounTok = verbly::token::capitalize(
258 verbly::token::casing::title_case,
259 pictured);
260
261 int aci = std::uniform_int_distribution<int>(0, 7)(rng_);
262 verbly::token action;
263
264 switch (aci)
265 {
266 case 0: action = { "No One Will Buy This", money, nounTok }; break;
267 case 1: action = { "This", nounTok, "Is Not Worth", money }; break;
268 case 2: action = { "We Can't Get Rid Of This", money, nounTok }; break;
269 case 3: action = { "Millenials Will No Longer Spend", money, "For",
270 verbly::token::definiteArticle(nounTok) }; break;
271 case 4: action = { "Why Does This", money, nounTok, "Exist?" }; break;
272 case 5: action = { "Someone Spent", money, "Making This", nounTok,
273 "That No One Will Buy" }; break;
274 case 6: action = { "What A Waste: This", nounTok, "Will Rot Because",
275 "No One Can Afford Its", money, "Price Tag" }; break;
276 case 7: action = { "This", money, nounTok, "Was A Mistake" }; break;
277 }
278
279 return action.compile();
280}
281
282void capital::sendTweet(std::string text, Magick::Image image) const
283{
284 Magick::Blob outputBlob;
285 image.magick("jpg");
286 image.write(&outputBlob);
287
288 long media_id = client_->uploadMedia("image/jpeg",
289 (const char*) outputBlob.data(), outputBlob.length());
290
291 client_->updateStatus(std::move(text), {media_id});
292}
diff --git a/capital.h b/capital.h new file mode 100644 index 0000000..1f763db --- /dev/null +++ b/capital.h
@@ -0,0 +1,45 @@
1#ifndef CAPITAL_H_09912EAE
2#define CAPITAL_H_09912EAE
3
4#include <random>
5#include <string>
6#include <verbly.h>
7#include <stdexcept>
8#include <memory>
9#include <Magick++.h>
10#include <twitter.h>
11
12class capital {
13public:
14
15 capital(
16 std::string configFile,
17 std::mt19937& rng);
18
19 void run() const;
20
21private:
22
23 verbly::word getPicturedNoun() const;
24
25 Magick::Image getImageForNoun(verbly::word pictured) const;
26
27 std::string generateTweetText(verbly::word pictured) const;
28
29 void sendTweet(std::string text, Magick::Image image) const;
30
31 class could_not_get_image : public std::runtime_error {
32 public:
33
34 could_not_get_image() : std::runtime_error("Could not get image for noun")
35 {
36 }
37 };
38
39 std::mt19937& rng_;
40 std::unique_ptr<verbly::database> database_;
41 std::unique_ptr<twitter::client> client_;
42
43};
44
45#endif /* end of include guard: CAPITAL_H_09912EAE */
diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..ffcd4a2 --- /dev/null +++ b/main.cpp
@@ -0,0 +1,33 @@
1#include "capital.h"
2
3int main(int argc, char** argv)
4{
5 Magick::InitializeMagick(nullptr);
6
7 std::random_device randomDevice;
8 std::mt19937 rng(randomDevice());
9
10 if (argc != 2)
11 {
12 std::cout << "usage: capital [configfile]" << std::endl;
13 return -1;
14 }
15
16 std::string configfile(argv[1]);
17
18 try
19 {
20 capital bot(configfile, rng);
21
22 try
23 {
24 bot.run();
25 } catch (const std::exception& ex)
26 {
27 std::cout << "Error running bot: " << ex.what() << std::endl;
28 }
29 } catch (const std::exception& ex)
30 {
31 std::cout << "Error initializing bot: " << ex.what() << std::endl;
32 }
33} \ No newline at end of file
diff --git a/vendor/libtwittercpp b/vendor/libtwittercpp new file mode 160000
Subproject df906121dd862c0f704e44f28ee079158c431c4
diff --git a/vendor/verbly b/vendor/verbly new file mode 160000
Subproject bea545d62732fe048ca9cec7f82e4be1185cb87
diff --git a/vendor/yaml-cpp b/vendor/yaml-cpp new file mode 160000
Subproject beb44b872c07c74556314e730c6f20a00b32e8e