From 25bc817bc4a68b81a0ff0c2485c2c903a8e3851f Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sat, 25 Aug 2018 08:51:41 -0400 Subject: Created bot --- .gitignore | 6 ++ .gitmodules | 3 + CMakeLists.txt | 22 +++++++ designer.cpp | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++ designer.h | 22 +++++++ fanmail.cpp | 106 +++++++++++++++++++++++++++++++ fanmail.h | 33 ++++++++++ main.cpp | 38 +++++++++++ vendor/rawr-ebooks | 1 + 9 files changed, 412 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 designer.cpp create mode 100644 designer.h create mode 100644 fanmail.cpp create mode 100644 fanmail.h create mode 100644 main.cpp create mode 160000 vendor/rawr-ebooks diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..062f3b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +res +.DS_Store +CMakeCache.txt +CMakeFiles +cmake_install.cmake +Makefile diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..57f0e20 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/rawr-ebooks"] + path = vendor/rawr-ebooks + 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 @@ +cmake_minimum_required (VERSION 3.1) +project (fanmail) + +#add_subdirectory(vendor/libtwittercpp) +add_subdirectory(vendor/rawr-ebooks EXCLUDE_FROM_ALL) + +find_package(PkgConfig) +pkg_check_modules(yaml-cpp yaml-cpp REQUIRED) +pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED) + +include_directories( + vendor/rawr-ebooks/vendor/libtwittercpp/src + vendor/rawr-ebooks + ${yaml-cpp_INCLUDE_DIRS} + ${GraphicsMagick_INCLUDE_DIRS}) + +link_directories(${GraphicsMagick_LIBRARY_DIRS}) + +add_executable(fanmail fanmail.cpp designer.cpp main.cpp) +set_property(TARGET fanmail PROPERTY CXX_STANDARD 14) +set_property(TARGET fanmail PROPERTY CXX_STANDARD_REQUIRED ON) +target_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 @@ +#include "designer.h" +#include +#include +#include + +designer::designer( + std::string imagesDir) : + colDist_ {0, 1, 100, 30, 5, 1, 1} +{ + DIR* imgdir; + struct dirent* ent; + if ((imgdir = opendir(imagesDir.c_str())) == nullptr) + { + throw std::invalid_argument("Couldn't find images"); + } + + int maxStripLen = 0; + std::map stripLength; + + while ((ent = readdir(imgdir)) != nullptr) + { + std::string dname(ent->d_name); + std::stringstream nameParse; + int stripNum; + int panelNum; + char sep; + std::string ext; + + nameParse << dname; + + if (nameParse >> stripNum && + nameParse >> sep && + nameParse >> panelNum && + nameParse >> ext && + ext == ".png") + { + if (!stripLength.count(stripNum) || stripLength[stripNum] < panelNum) + { + stripLength[stripNum] = panelNum; + } + + if (panelNum > maxStripLen) + { + maxStripLen = panelNum; + } + + comics_[stripNum][panelNum] = imagesDir + "/" + dname; + } + } + + closedir(imgdir); + + std::vector lenHist(maxStripLen, 0); + for (const auto& mapping : stripLength) + { + lenHist[mapping.second]++; + } + + lenDist_ = + std::discrete_distribution( + std::begin(lenHist), + std::end(lenHist)); +} + +Magick::Image designer::generate(std::mt19937& rng) const +{ + int numPanels = lenDist_(rng); + std::vector panels; + + int numCols = std::min(colDist_(rng), numPanels); + + int curCol = 0; + int numRows = 0; + int curRowWidth = 0; + int maxRowWidth = 0; + + std::map rowHeight; + int curRowHeight = 0; + + for (int i = 0; i < numPanels; i++) + { + if (curCol == 0) + { + numRows++; + } + + std::uniform_int_distribution stripPick(1, comics_.size()); + int stripNum = stripPick(rng); + const auto& strip = comics_.at(stripNum); + + size_t low = i / static_cast(numPanels) * strip.size(); + size_t high = (i+1) / static_cast(numPanels) * strip.size(); + std::uniform_int_distribution panelPick( + low, + std::min(high, strip.size() - 1)); + + int panelNum = panelPick(rng) + 1; + const std::string& panel = strip.at(panelNum); + + std::cout << panel << std::endl; + + Magick::Image curfile(panel); + curfile.borderColor("black"); + curfile.backgroundColor("black"); + + if (std::bernoulli_distribution(1.0 / 50.0)(rng)) + { + std::normal_distribution rotDist(0.0, 20.0); + double rotAmt = rotDist(rng); + + curfile.rotate(rotAmt); + } + + if (std::bernoulli_distribution(1.0 / 2.0)(rng)) + { + std::geometric_distribution borderDist(1.0 / 8.0); + curfile.border( + Magick::Geometry{ + borderDist(rng), + borderDist(rng)}); + } + + curRowWidth += curfile.columns(); + + if (curfile.rows() > curRowHeight) + { + curRowHeight = curfile.rows(); + } + + panels.emplace_back(std::move(curfile)); + + curCol++; + + if (curCol >= numCols || (i == numPanels - 1)) + { + if (curRowWidth > maxRowWidth) + { + maxRowWidth = curRowWidth; + } + + rowHeight[numRows - 1] = curRowHeight; + + curCol = 0; + curRowWidth = 0; + curRowHeight = 0; + } + } + + int fileHeight = 0; + for (const auto& mapping : rowHeight) + { + fileHeight += mapping.second; + } + + Magick::Image result{ + Magick::Geometry(maxRowWidth, fileHeight), + "black"}; + + int curx = 0; + int cury = 0; + int thisCol = 0; + int thisRow = 0; + for (const Magick::Image& panel : panels) + { + if (thisCol == numCols) + { + thisCol = 0; + cury += rowHeight.at(thisRow); + thisRow++; + curx = 0; + } + + result.composite(panel, curx, cury); + + curx += panel.columns(); + + thisCol++; + } + + return result; +} diff --git a/designer.h b/designer.h new file mode 100644 index 0000000..007d535 --- /dev/null +++ b/designer.h @@ -0,0 +1,22 @@ +#ifndef DESIGNER_H_863F77D3 +#define DESIGNER_H_863F77D3 + +#include +#include +#include + +class designer { +public: + + designer(std::string imagesPath); + + Magick::Image generate(std::mt19937& rng) const; + +private: + + mutable std::discrete_distribution colDist_; + mutable std::discrete_distribution lenDist_; + std::map> comics_; +}; + +#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 @@ +#include "fanmail.h" +#include +#include +#include +#include +#include + +fanmail::fanmail( + std::string configFile, + std::mt19937& rng) : + rng_(rng) +{ + // Load the config file. + YAML::Node config = YAML::LoadFile(configFile); + + // Set up the Twitter client. + auth_ = + std::unique_ptr( + new twitter::auth( + config["consumer_key"].as(), + config["consumer_secret"].as(), + config["access_key"].as(), + config["access_secret"].as())); + + client_ = + std::unique_ptr( + new twitter::client(*auth_)); + + // Set up the layout designer. + layout_ = std::unique_ptr( + new designer(config["images"].as())); + + // Set up librawr. + std::ifstream infile(config["corpus"].as()); + std::string corpus; + std::string line; + while (getline(infile, line)) + { + if (line.back() == '\r') + { + line.pop_back(); + } + + corpus += line + "\n"; + } + + kgramstats_.addCorpus(corpus); + kgramstats_.compile(3); +} + +void fanmail::run() const +{ + for (;;) + { + std::cout << "Generating tweet..." << std::endl; + + try + { + // Design the comic strip. + Magick::Image image = layout_->generate(rng_); + + // Write the tweet. + std::string doc = kgramstats_.randomSentence(15); + doc.resize(40); + + std::cout << doc << std::endl; + + // Send the tweet. + std::cout << "Sending tweet..." << std::endl; + + sendTweet(std::move(image), std::move(doc)); + + std::cout << "Tweeted!" << std::endl; + + // Wait. + std::binomial_distribution waitDist(35, 0.5); + int waitTime = waitDist(rng_) + 1; + + std::cout << "Waiting for " << waitTime << " hours..." << std::endl; + + std::this_thread::sleep_for(std::chrono::hours(waitTime)); + } catch (const Magick::ErrorImage& ex) + { + std::cout << "Image error: " << ex.what() << std::endl; + } catch (const twitter::twitter_error& ex) + { + std::cout << "Twitter error: " << ex.what() << std::endl; + + std::this_thread::sleep_for(std::chrono::hours(1)); + } + + std::cout << std::endl; + } +} + +void fanmail::sendTweet(Magick::Image image, std::string doc) const +{ + Magick::Blob outputimg; + image.magick("png"); + image.write(&outputimg); + + long media_id = client_->uploadMedia("image/png", + static_cast(outputimg.data()), outputimg.length()); + + client_->updateStatus(std::move(doc), {media_id}); +} diff --git a/fanmail.h b/fanmail.h new file mode 100644 index 0000000..a9ef046 --- /dev/null +++ b/fanmail.h @@ -0,0 +1,33 @@ +#ifndef FANMAIL_H_11D8D668 +#define FANMAIL_H_11D8D668 + +#include +#include +#include +#include +#include +#include +#include "designer.h" + +class fanmail { +public: + + fanmail( + std::string configFile, + std::mt19937& rng); + + void run() const; + +private: + + void sendTweet(Magick::Image image, std::string doc) const; + + std::mt19937& rng_; + std::unique_ptr auth_; + std::unique_ptr client_; + std::unique_ptr layout_; + rawr kgramstats_; + +}; + +#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 @@ +#include "fanmail.h" +#include + +int main(int argc, char** argv) +{ + Magick::InitializeMagick(nullptr); + + std::random_device randomDevice; + std::mt19937 rng(randomDevice()); + + // We also have to seed the C-style RNG because librawr uses it. + srand(time(NULL)); + rand(); rand(); rand(); rand(); + + if (argc != 2) + { + std::cout << "usage: fanmail [configfile]" << std::endl; + return -1; + } + + std::string configfile(argv[1]); + + try + { + fanmail bot(configfile, rng); + + try + { + bot.run(); + } catch (const std::exception& ex) + { + std::cout << "Error running bot: " << ex.what() << std::endl; + } + } catch (const std::exception& ex) + { + std::cout << "Error initializing bot: " << ex.what() << std::endl; + } +} diff --git a/vendor/rawr-ebooks b/vendor/rawr-ebooks new file mode 160000 index 0000000..26d75f7 --- /dev/null +++ b/vendor/rawr-ebooks @@ -0,0 +1 @@ +Subproject commit 26d75f744913a8856e46f5fccbfda8f8336924a0 -- cgit 1.4.1