From f605bf346904c18b3ba69c8cf2a624c5905f8cf5 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Mon, 6 Aug 2018 16:01:06 -0400 Subject: Switched from streaming API to timeline polling Also included hkutil for the string split function. --- father.cpp | 327 ++++++++++++++++++++++++++++--------------------------------- 1 file changed, 152 insertions(+), 175 deletions(-) (limited to 'father.cpp') diff --git a/father.cpp b/father.cpp index 198b38a..4ff4a3c 100644 --- a/father.cpp +++ b/father.cpp @@ -10,37 +10,10 @@ #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; -} +// Sync followers every 4 hours. +const int CHECK_FOLLOWERS_EVERY = 4 * 60 / 5; verbly::word findWordOfType( verbly::database& database, @@ -75,189 +48,193 @@ int main(int argc, char** argv) verbly::database database(config["verbly_datafile"].as()); - 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()); + twitter::auth auth( + config["consumer_key"].as(), + config["consumer_secret"].as(), + config["access_key"].as(), + config["access_secret"].as()); - std::set streamedFriends; + auto startedTime = std::chrono::system_clock::now(); 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) + std::set friends; + int followerTimeout = 0; + + for (;;) + { + if (followerTimeout == 0) { - 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() != '@') - ) + // Sync friends with followers. + try { - std::vector tokens = - split>(n.getTweet().getText(), " "); + 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)); + + std::list newFollowers; + std::set_difference( + std::begin(followers), + std::end(followers), + std::begin(friends), + std::end(friends), + std::back_inserter(newFollowers)); + + for (twitter::user_id f : oldFriends) + { + client.unfollow(f); + } - std::vector canonical; - for (std::string token : tokens) + for (twitter::user_id f : newFollowers) { - std::string canonStr; - for (char ch : token) + client.follow(f); + } + } catch (const twitter::twitter_error& error) + { + std::cout << "Twitter error while syncing followers: " << error.what() + << std::endl; + } + + followerTimeout = CHECK_FOLLOWERS_EVERY; + } + + followerTimeout--; + + try + { + // Poll the timeline. + std::list tweets = client.getHomeTimeline().poll(); + + for (twitter::tweet& tweet : tweets) + { + auto createdTime = + std::chrono::system_clock::from_time_t(tweet.getCreatedAt()); + + if ( + // Only monitor people you are following + friends.count(tweet.getAuthor().getID()) + // Ignore tweets from before the bot started up + && createdTime > startedTime + // Ignore retweets + && !tweet.isRetweet() + // Ignore messages + && tweet.getText().front() != '@') + { + std::vector tokens = + hatkirby::split>(tweet.getText(), " "); + + std::vector canonical; + for (std::string token : tokens) { - if (std::isalpha(ch)) + std::string canonStr; + for (char ch : token) { - canonStr += std::tolower(ch); + 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"); + canonical.push_back(canonStr); + } - if (imIt != std::end(canonical)) - { - imIt++; + std::vector::iterator imIt = + std::find(std::begin(canonical), std::end(canonical), "im"); if (imIt != std::end(canonical)) { - verbly::token name; + imIt++; - verbly::word firstAdverb = findWordOfType( - database, - *imIt, - verbly::part_of_speech::adverb); - - if (firstAdverb.isValid()) + if (imIt != std::end(canonical)) { - std::vector::iterator adjIt = imIt; - adjIt++; + verbly::token name; + + verbly::word firstAdverb = findWordOfType( + database, + *imIt, + verbly::part_of_speech::adverb); - if (adjIt != std::end(canonical)) + if (firstAdverb.isValid()) { - verbly::word secondAdjective = findWordOfType( - database, - *adjIt, - verbly::part_of_speech::adjective); + std::vector::iterator adjIt = imIt; + adjIt++; - if (secondAdjective.isValid()) + if (adjIt != std::end(canonical)) { - name << firstAdverb; - name << secondAdjective; + verbly::word secondAdjective = findWordOfType( + database, + *adjIt, + verbly::part_of_speech::adjective); + + if (secondAdjective.isValid()) + { + name << firstAdverb; + name << secondAdjective; + } } } - } - - if (name.isEmpty()) - { - verbly::word firstAdjective = findWordOfType( - database, - *imIt, - verbly::part_of_speech::adjective); - if (firstAdjective.isValid()) + if (name.isEmpty()) { - name = firstAdjective; + verbly::word firstAdjective = findWordOfType( + database, + *imIt, + verbly::part_of_speech::adjective); + + if (firstAdjective.isValid()) + { + name = firstAdjective; + } } - } - if ((!name.isEmpty()) - && (std::bernoulli_distribution(1.0/10.0)(rng))) - { - verbly::token action = { - "Hi", - verbly::token::punctuation(",", - verbly::token::capitalize( - verbly::token::casing::title_case, - name)), - "I'm Dad."}; - - std::string result = - n.getTweet().generateReplyPrefill(client.getUser()) - + action.compile(); - - if (result.length() <= 140) + if ((!name.isEmpty()) + && (std::bernoulli_distribution(1.0/10.0)(rng))) { - try - { - client.replyToTweet(result, n.getTweet()); - } catch (const twitter::twitter_error& e) + verbly::token action = { + "Hi", + verbly::token::punctuation(",", + verbly::token::capitalize( + verbly::token::casing::title_case, + name)), + "I'm Dad."}; + + std::string result = + tweet.generateReplyPrefill(client.getUser()) + + action.compile(); + + std::cout << result << std::endl; + + if (result.length() <= 140) { - std::cout << "Twitter error: " << e.what() << std::endl; + try + { + client.replyToTweet(result, tweet); + } catch (const twitter::twitter_error& e) + { + std::cout << "Twitter error while tweeting: " << e.what() + << std::endl; + } } } } } } } - } else if (n.getType() == twitter::notification::type::followed) + } catch (const twitter::rate_limit_exceeded&) { - try - { - client.follow(n.getUser()); - } catch (const twitter::twitter_error& error) - { - std::cout << "Twitter error while following @" - << n.getUser().getScreenName() << ": " << error.what() << std::endl; - } + // Wait out the rate limit (10 minutes here and 5 below = 15). + std::this_thread::sleep_for(std::chrono::minutes(10)); } - }); - 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)); + // We can poll the timeline at most once every five minutes. + std::this_thread::sleep_for(std::chrono::minutes(5)); } } - -- cgit 1.4.1