about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--.gitmodules3
-rw-r--r--CMakeLists.txt22
-rw-r--r--designer.cpp181
-rw-r--r--designer.h22
-rw-r--r--fanmail.cpp106
-rw-r--r--fanmail.h33
-rw-r--r--main.cpp38
m---------vendor/rawr-ebooks0
9 files changed, 411 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..062f3b8 --- /dev/null +++ b/.gitignore
@@ -0,0 +1,6 @@
1res
2.DS_Store
3CMakeCache.txt
4CMakeFiles
5cmake_install.cmake
6Makefile
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..57f0e20 --- /dev/null +++ b/.gitmodules
@@ -0,0 +1,3 @@
1[submodule "vendor/rawr-ebooks"]
2 path = vendor/rawr-ebooks
3 url = git@github.com:hatkirby/rawr-ebooks
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c9f662b --- /dev/null +++ b/CMakeLists.txt
@@ -0,0 +1,22 @@
1cmake_minimum_required (VERSION 3.1)
2project (fanmail)
3
4#add_subdirectory(vendor/libtwittercpp)
5add_subdirectory(vendor/rawr-ebooks EXCLUDE_FROM_ALL)
6
7find_package(PkgConfig)
8pkg_check_modules(yaml-cpp yaml-cpp REQUIRED)
9pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED)
10
11include_directories(
12 vendor/rawr-ebooks/vendor/libtwittercpp/src
13 vendor/rawr-ebooks
14 ${yaml-cpp_INCLUDE_DIRS}
15 ${GraphicsMagick_INCLUDE_DIRS})
16
17link_directories(${GraphicsMagick_LIBRARY_DIRS})
18
19add_executable(fanmail fanmail.cpp designer.cpp main.cpp)
20set_property(TARGET fanmail PROPERTY CXX_STANDARD 14)
21set_property(TARGET fanmail PROPERTY CXX_STANDARD_REQUIRED ON)
22target_link_libraries(fanmail ${yaml-cpp_LIBRARIES} ${GraphicsMagick_LIBRARIES} twitter++ rawr) \ No newline at end of file
diff --git a/designer.cpp b/designer.cpp new file mode 100644 index 0000000..8d803bf --- /dev/null +++ b/designer.cpp
@@ -0,0 +1,181 @@
1#include "designer.h"
2#include <dirent.h>
3#include <sstream>
4#include <iostream>
5
6designer::designer(
7 std::string imagesDir) :
8 colDist_ {0, 1, 100, 30, 5, 1, 1}
9{
10 DIR* imgdir;
11 struct dirent* ent;
12 if ((imgdir = opendir(imagesDir.c_str())) == nullptr)
13 {
14 throw std::invalid_argument("Couldn't find images");
15 }
16
17 int maxStripLen = 0;
18 std::map<int, int> stripLength;
19
20 while ((ent = readdir(imgdir)) != nullptr)
21 {
22 std::string dname(ent->d_name);
23 std::stringstream nameParse;
24 int stripNum;
25 int panelNum;
26 char sep;
27 std::string ext;
28
29 nameParse << dname;
30
31 if (nameParse >> stripNum &&
32 nameParse >> sep &&
33 nameParse >> panelNum &&
34 nameParse >> ext &&
35 ext == ".png")
36 {
37 if (!stripLength.count(stripNum) || stripLength[stripNum] < panelNum)
38 {
39 stripLength[stripNum] = panelNum;
40 }
41
42 if (panelNum > maxStripLen)
43 {
44 maxStripLen = panelNum;
45 }
46
47 comics_[stripNum][panelNum] = imagesDir + "/" + dname;
48 }
49 }
50
51 closedir(imgdir);
52
53 std::vector<int> lenHist(maxStripLen, 0);
54 for (const auto& mapping : stripLength)
55 {
56 lenHist[mapping.second]++;
57 }
58
59 lenDist_ =
60 std::discrete_distribution<int>(
61 std::begin(lenHist),
62 std::end(lenHist));
63}
64
65Magick::Image designer::generate(std::mt19937& rng) const
66{
67 int numPanels = lenDist_(rng);
68 std::vector<Magick::Image> panels;
69
70 int numCols = std::min(colDist_(rng), numPanels);
71
72 int curCol = 0;
73 int numRows = 0;
74 int curRowWidth = 0;
75 int maxRowWidth = 0;
76
77 std::map<int, int> rowHeight;
78 int curRowHeight = 0;
79
80 for (int i = 0; i < numPanels; i++)
81 {
82 if (curCol == 0)
83 {
84 numRows++;
85 }
86
87 std::uniform_int_distribution<int> stripPick(1, comics_.size());
88 int stripNum = stripPick(rng);
89 const auto& strip = comics_.at(stripNum);
90
91 size_t low = i / static_cast<double>(numPanels) * strip.size();
92 size_t high = (i+1) / static_cast<double>(numPanels) * strip.size();
93 std::uniform_int_distribution<int> panelPick(
94 low,
95 std::min(high, strip.size() - 1));
96
97 int panelNum = panelPick(rng) + 1;
98 const std::string& panel = strip.at(panelNum);
99
100 std::cout << panel << std::endl;
101
102 Magick::Image curfile(panel);
103 curfile.borderColor("black");
104 curfile.backgroundColor("black");
105
106 if (std::bernoulli_distribution(1.0 / 50.0)(rng))
107 {
108 std::normal_distribution<double> rotDist(0.0, 20.0);
109 double rotAmt = rotDist(rng);
110
111 curfile.rotate(rotAmt);
112 }
113
114 if (std::bernoulli_distribution(1.0 / 2.0)(rng))
115 {
116 std::geometric_distribution<unsigned int> borderDist(1.0 / 8.0);
117 curfile.border(
118 Magick::Geometry{
119 borderDist(rng),
120 borderDist(rng)});
121 }
122
123 curRowWidth += curfile.columns();
124
125 if (curfile.rows() > curRowHeight)
126 {
127 curRowHeight = curfile.rows();
128 }
129
130 panels.emplace_back(std::move(curfile));
131
132 curCol++;
133
134 if (curCol >= numCols || (i == numPanels - 1))
135 {
136 if (curRowWidth > maxRowWidth)
137 {
138 maxRowWidth = curRowWidth;
139 }
140
141 rowHeight[numRows - 1] = curRowHeight;
142
143 curCol = 0;
144 curRowWidth = 0;
145 curRowHeight = 0;
146 }
147 }
148
149 int fileHeight = 0;
150 for (const auto& mapping : rowHeight)
151 {
152 fileHeight += mapping.second;
153 }
154
155 Magick::Image result{
156 Magick::Geometry(maxRowWidth, fileHeight),
157 "black"};
158
159 int curx = 0;
160 int cury = 0;
161 int thisCol = 0;
162 int thisRow = 0;
163 for (const Magick::Image& panel : panels)
164 {
165 if (thisCol == numCols)
166 {
167 thisCol = 0;
168 cury += rowHeight.at(thisRow);
169 thisRow++;
170 curx = 0;
171 }
172
173 result.composite(panel, curx, cury);
174
175 curx += panel.columns();
176
177 thisCol++;
178 }
179
180 return result;
181}
diff --git a/designer.h b/designer.h new file mode 100644 index 0000000..007d535 --- /dev/null +++ b/designer.h
@@ -0,0 +1,22 @@
1#ifndef DESIGNER_H_863F77D3
2#define DESIGNER_H_863F77D3
3
4#include <random>
5#include <string>
6#include <Magick++.h>
7
8class designer {
9public:
10
11 designer(std::string imagesPath);
12
13 Magick::Image generate(std::mt19937& rng) const;
14
15private:
16
17 mutable std::discrete_distribution<int> colDist_;
18 mutable std::discrete_distribution<int> lenDist_;
19 std::map<int, std::map<int, std::string>> comics_;
20};
21
22#endif /* end of include guard: DESIGNER_H_863F77D3 */
diff --git a/fanmail.cpp b/fanmail.cpp new file mode 100644 index 0000000..785a574 --- /dev/null +++ b/fanmail.cpp
@@ -0,0 +1,106 @@
1#include "fanmail.h"
2#include <yaml-cpp/yaml.h>
3#include <thread>
4#include <chrono>
5#include <fstream>
6#include <iostream>
7
8fanmail::fanmail(
9 std::string configFile,
10 std::mt19937& rng) :
11 rng_(rng)
12{
13 // Load the config file.
14 YAML::Node config = YAML::LoadFile(configFile);
15
16 // Set up the Twitter client.
17 auth_ =
18 std::unique_ptr<twitter::auth>(
19 new twitter::auth(
20 config["consumer_key"].as<std::string>(),
21 config["consumer_secret"].as<std::string>(),
22 config["access_key"].as<std::string>(),
23 config["access_secret"].as<std::string>()));
24
25 client_ =
26 std::unique_ptr<twitter::client>(
27 new twitter::client(*auth_));
28
29 // Set up the layout designer.
30 layout_ = std::unique_ptr<designer>(
31 new designer(config["images"].as<std::string>()));
32
33 // Set up librawr.
34 std::ifstream infile(config["corpus"].as<std::string>());
35 std::string corpus;
36 std::string line;
37 while (getline(infile, line))
38 {
39 if (line.back() == '\r')
40 {
41 line.pop_back();
42 }
43
44 corpus += line + "\n";
45 }
46
47 kgramstats_.addCorpus(corpus);
48 kgramstats_.compile(3);
49}
50
51void fanmail::run() const
52{
53 for (;;)
54 {
55 std::cout << "Generating tweet..." << std::endl;
56
57 try
58 {
59 // Design the comic strip.
60 Magick::Image image = layout_->generate(rng_);
61
62 // Write the tweet.
63 std::string doc = kgramstats_.randomSentence(15);
64 doc.resize(40);
65
66 std::cout << doc << std::endl;
67
68 // Send the tweet.
69 std::cout << "Sending tweet..." << std::endl;
70
71 sendTweet(std::move(image), std::move(doc));
72
73 std::cout << "Tweeted!" << std::endl;
74
75 // Wait.
76 std::binomial_distribution<int> waitDist(35, 0.5);
77 int waitTime = waitDist(rng_) + 1;
78
79 std::cout << "Waiting for " << waitTime << " hours..." << std::endl;
80
81 std::this_thread::sleep_for(std::chrono::hours(waitTime));
82 } catch (const Magick::ErrorImage& ex)
83 {
84 std::cout << "Image error: " << ex.what() << std::endl;
85 } catch (const twitter::twitter_error& ex)
86 {
87 std::cout << "Twitter error: " << ex.what() << std::endl;
88
89 std::this_thread::sleep_for(std::chrono::hours(1));
90 }
91
92 std::cout << std::endl;
93 }
94}
95
96void fanmail::sendTweet(Magick::Image image, std::string doc) const
97{
98 Magick::Blob outputimg;
99 image.magick("png");
100 image.write(&outputimg);
101
102 long media_id = client_->uploadMedia("image/png",
103 static_cast<const char*>(outputimg.data()), outputimg.length());
104
105 client_->updateStatus(std::move(doc), {media_id});
106}
diff --git a/fanmail.h b/fanmail.h new file mode 100644 index 0000000..a9ef046 --- /dev/null +++ b/fanmail.h
@@ -0,0 +1,33 @@
1#ifndef FANMAIL_H_11D8D668
2#define FANMAIL_H_11D8D668
3
4#include <random>
5#include <string>
6#include <memory>
7#include <Magick++.h>
8#include <twitter.h>
9#include <rawr.h>
10#include "designer.h"
11
12class fanmail {
13public:
14
15 fanmail(
16 std::string configFile,
17 std::mt19937& rng);
18
19 void run() const;
20
21private:
22
23 void sendTweet(Magick::Image image, std::string doc) const;
24
25 std::mt19937& rng_;
26 std::unique_ptr<twitter::auth> auth_;
27 std::unique_ptr<twitter::client> client_;
28 std::unique_ptr<designer> layout_;
29 rawr kgramstats_;
30
31};
32
33#endif /* end of include guard: SAP_H_11D8D668 */
diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e1cdb8b --- /dev/null +++ b/main.cpp
@@ -0,0 +1,38 @@
1#include "fanmail.h"
2#include <iostream>
3
4int main(int argc, char** argv)
5{
6 Magick::InitializeMagick(nullptr);
7
8 std::random_device randomDevice;
9 std::mt19937 rng(randomDevice());
10
11 // We also have to seed the C-style RNG because librawr uses it.
12 srand(time(NULL));
13 rand(); rand(); rand(); rand();
14
15 if (argc != 2)
16 {
17 std::cout << "usage: fanmail [configfile]" << std::endl;
18 return -1;
19 }
20
21 std::string configfile(argv[1]);
22
23 try
24 {
25 fanmail bot(configfile, rng);
26
27 try
28 {
29 bot.run();
30 } catch (const std::exception& ex)
31 {
32 std::cout << "Error running bot: " << ex.what() << std::endl;
33 }
34 } catch (const std::exception& ex)
35 {
36 std::cout << "Error initializing bot: " << ex.what() << std::endl;
37 }
38}
diff --git a/vendor/rawr-ebooks b/vendor/rawr-ebooks new file mode 160000
Subproject 26d75f744913a8856e46f5fccbfda8f8336924a