From 95b7ae535c0b005466e34096f5ce427046a403d2 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sun, 19 Feb 2017 15:36:40 -0500 Subject: Created bot --- .gitmodules | 9 ++ CMakeLists.txt | 13 +++ father.cpp | 260 +++++++++++++++++++++++++++++++++++++++++++++++++++ vendor/libtwittercpp | 1 + vendor/verbly | 1 + vendor/yaml-cpp | 1 + 6 files changed, 285 insertions(+) create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 father.cpp create mode 160000 vendor/libtwittercpp create mode 160000 vendor/verbly create mode 160000 vendor/yaml-cpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ec4d45e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "vendor/libtwittercpp"] + path = vendor/libtwittercpp + url = git@github.com:hatkirby/libtwittercpp +[submodule "vendor/yaml-cpp"] + path = vendor/yaml-cpp + url = git@github.com:jbeder/yaml-cpp +[submodule "vendor/verbly"] + path = vendor/verbly + url = git@github.com:hatkirby/verbly diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8266a02 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required (VERSION 3.1) +project (father) + +add_subdirectory(vendor/libtwittercpp) +add_subdirectory(vendor/verbly) +add_subdirectory(vendor/yaml-cpp EXCLUDE_FROM_ALL) + +include_directories(vendor/libtwittercpp/src vendor/yaml-cpp/include vendor/verbly/lib) +add_executable(father father.cpp) +set_property(TARGET father PROPERTY CXX_STANDARD 11) +set_property(TARGET father PROPERTY CXX_STANDARD_REQUIRED ON) +target_link_libraries(father verbly yaml-cpp twitter++) + diff --git a/father.cpp b/father.cpp new file mode 100644 index 0000000..7ea5080 --- /dev/null +++ b/father.cpp @@ -0,0 +1,260 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +void split(std::string input, std::string delimiter, OutputIterator out) +{ + while (!input.empty()) + { + int divider = input.find(delimiter); + if (divider == std::string::npos) + { + *out = input; + out++; + + input = ""; + } else { + *out = input.substr(0, divider); + out++; + + input = input.substr(divider+delimiter.length()); + } + } +} + +template +Container split(std::string input, std::string delimiter) +{ + Container result; + + split(input, delimiter, std::back_inserter(result)); + + return result; +} + +verbly::word findWordOfType( + verbly::database& database, + std::string form, + verbly::part_of_speech partOfSpeech) +{ + std::vector isThing = database.words( + (verbly::notion::partOfSpeech == partOfSpeech) + && (verbly::form::text == form)).all(); + + if (isThing.empty()) + { + return {}; + } else { + return isThing.front(); + } +} + +int main(int argc, char** argv) +{ + std::random_device randomDevice; + std::mt19937 rng{randomDevice()}; + + if (argc != 2) + { + std::cout << "usage: father [configfile]" << std::endl; + return -1; + } + + std::string configfile(argv[1]); + YAML::Node config = YAML::LoadFile(configfile); + + twitter::auth auth; + auth.setConsumerKey(config["consumer_key"].as()); + auth.setConsumerSecret(config["consumer_secret"].as()); + auth.setAccessKey(config["access_key"].as()); + auth.setAccessSecret(config["access_secret"].as()); + + std::set streamedFriends; + + verbly::database database(config["verbly_datafile"].as()); + + twitter::client client(auth); + + std::cout << "Starting streaming..." << std::endl; + twitter::stream userStream(client, [&] (const twitter::notification& n) { + if (n.getType() == twitter::notification::type::friends) + { + streamedFriends = n.getFriends(); + } else if (n.getType() == twitter::notification::type::follow) + { + streamedFriends.insert(n.getUser().getID()); + } else if (n.getType() == twitter::notification::type::unfollow) + { + streamedFriends.erase(n.getUser().getID()); + } else if (n.getType() == twitter::notification::type::tweet) + { + if ( + // Only monitor people you are following + (streamedFriends.count(n.getTweet().getAuthor().getID()) == 1) + // Ignore retweets + && (!n.getTweet().isRetweet()) + // Ignore messages + && (n.getTweet().getText().front() != '@') + ) + { + std::vector tokens = + split>(n.getTweet().getText(), " "); + + std::vector canonical; + for (std::string token : tokens) + { + std::string canonStr; + for (char ch : token) + { + if (std::isalpha(ch)) + { + canonStr += std::tolower(ch); + } + } + + canonical.push_back(canonStr); + } + + std::vector::iterator imIt = + std::find(std::begin(canonical), std::end(canonical), "im"); + + if (imIt != std::end(canonical)) + { + imIt++; + + if (imIt != std::end(canonical)) + { + verbly::token name; + + verbly::word firstAdverb = findWordOfType( + database, + *imIt, + verbly::part_of_speech::adverb); + + if (firstAdverb.isValid()) + { + std::vector::iterator adjIt = imIt; + adjIt++; + + if (adjIt != std::end(canonical)) + { + verbly::word secondAdjective = findWordOfType( + database, + *adjIt, + verbly::part_of_speech::adjective); + + if (secondAdjective.isValid()) + { + name << verbly::token::capitalize(firstAdverb); + name << verbly::token::capitalize(secondAdjective); + } + } + } + + if (name.isEmpty()) + { + verbly::word firstAdjective = findWordOfType( + database, + *imIt, + verbly::part_of_speech::adjective); + + if (firstAdjective.isValid()) + { + name = verbly::token::capitalize(firstAdjective); + } + } + + if ((!name.isEmpty()) + && (std::bernoulli_distribution(1.0/10.0)(rng))) + { + verbly::token action = { + "Hi", + verbly::token::punctuation(",", name), + "I'm Dad."}; + + std::string result = + n.getTweet().generateReplyPrefill(client.getUser()) + + action.compile(); + + if (result.length() <= 140) + { + try + { + client.replyToTweet(result, n.getTweet()); + } catch (const twitter::twitter_error& e) + { + std::cout << "Twitter error: " << e.what() << std::endl; + } + } + } + } + } + } + } else if (n.getType() == twitter::notification::type::followed) + { + try + { + client.follow(n.getUser()); + } catch (const twitter::twitter_error& error) + { + std::cout << "Twitter error while following @" + << n.getUser().getScreenName() << ": " << error.what() << std::endl; + } + } + }); + + std::this_thread::sleep_for(std::chrono::minutes(1)); + + // Every once in a while, check if we've lost any followers, and if we have, + // unfollow the people who have unfollowed us. + for (;;) + { + try + { + std::set friends = client.getFriends(); + std::set followers = client.getFollowers(); + + std::list oldFriends; + std::set_difference( + std::begin(friends), + std::end(friends), + std::begin(followers), + std::end(followers), + std::back_inserter(oldFriends)); + + for (auto f : oldFriends) + { + client.unfollow(f); + } + + std::list newFollowers; + std::set_difference( + std::begin(followers), + std::end(followers), + std::begin(friends), + std::end(friends), + std::back_inserter(newFollowers)); + + for (auto f : newFollowers) + { + client.follow(f); + } + } catch (const twitter::twitter_error& e) + { + std::cout << "Twitter error: " << e.what() << std::endl; + } + + std::this_thread::sleep_for(std::chrono::hours(4)); + } +} + diff --git a/vendor/libtwittercpp b/vendor/libtwittercpp new file mode 160000 index 0000000..df90612 --- /dev/null +++ b/vendor/libtwittercpp @@ -0,0 +1 @@ +Subproject commit df906121dd862c0f704e44f28ee079158c431c41 diff --git a/vendor/verbly b/vendor/verbly new file mode 160000 index 0000000..8916c69 --- /dev/null +++ b/vendor/verbly @@ -0,0 +1 @@ +Subproject commit 8916c6911c23ca7ff7e78a90b30a295c2b9b9c22 diff --git a/vendor/yaml-cpp b/vendor/yaml-cpp new file mode 160000 index 0000000..bedb28f --- /dev/null +++ b/vendor/yaml-cpp @@ -0,0 +1 @@ +Subproject commit bedb28fdb4fd52d97e02f6cb946cae631037089e -- cgit 1.4.1