From 4f57eb0e3a2163a32bf84475e3c575c851599760 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sun, 5 Aug 2018 15:59:57 -0400 Subject: Switched from streaming API to timeline polling --- snitch.cpp | 194 ++++++++++++++++++++++++++++++--------------------- vendor/libtwittercpp | 2 +- 2 files changed, 116 insertions(+), 80 deletions(-) diff --git a/snitch.cpp b/snitch.cpp index bed6e3c..1d21449 100644 --- a/snitch.cpp +++ b/snitch.cpp @@ -6,6 +6,9 @@ #include #include +// Sync followers every 4 hours. +const int CHECK_FOLLOWERS_EVERY = 4 * 60 / 5; + int main(int argc, char** argv) { if (argc != 2) @@ -16,13 +19,13 @@ int main(int argc, char** argv) 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()); - + + twitter::auth auth( + config["consumer_key"].as(), + config["consumer_secret"].as(), + config["access_key"].as(), + config["access_secret"].as()); + std::ifstream img_file(config["image"].as()); img_file.seekg(0, std::ios::end); size_t img_len = img_file.tellg(); @@ -30,100 +33,133 @@ int main(int argc, char** argv) img_file.seekg(0, std::ios::beg); img_file.read(img_buf, img_len); img_file.close(); - + std::vector triggers { "calling the cops", "calling the police", "call the cops", "call the police" }; - + + auto startedTime = std::chrono::system_clock::now(); + // Initialize the client twitter::client client(auth); - std::this_thread::sleep_for(std::chrono::minutes(1)); - - // Start streaming - std::cout << "Starting streaming" << std::endl; - std::set streamed_friends; - twitter::stream userStream(client, [&] (const twitter::notification& n) { - if (n.getType() == twitter::notification::type::friends) - { - streamed_friends = n.getFriends(); - } else if (n.getType() == twitter::notification::type::follow) - { - streamed_friends.insert(n.getUser().getID()); - } else if (n.getType() == twitter::notification::type::unfollow) - { - streamed_friends.erase(n.getUser().getID()); - } else if (n.getType() == twitter::notification::type::tweet) + + std::set friends; + int followerTimeout = 0; + + for (;;) + { + if (followerTimeout == 0) { - // Only monitor people you are following - if (streamed_friends.count(n.getTweet().getAuthor().getID()) == 1) + // Sync friends with followers. + try { - std::string orig = n.getTweet().getText(); - std::string canonical; - std::transform(std::begin(orig), std::end(orig), std::back_inserter(canonical), [] (char ch) { - return std::tolower(ch); - }); - - for (auto trigger : triggers) + 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) { - if (canonical.find(trigger) != std::string::npos) - { - std::cout << "Calling the cops on @" << n.getTweet().getAuthor().getScreenName() << std::endl; - - try - { - long media_id = client.uploadMedia("image/jpeg", (const char*) img_buf, img_len); - client.replyToTweet(n.getTweet().generateReplyPrefill(client.getUser()), n.getTweet().getID(), {media_id}); - } catch (const twitter::twitter_error& e) - { - std::cout << "Twitter error: " << e.what() << std::endl; - } - - break; - } + client.unfollow(f); } - } - } else if (n.getType() == twitter::notification::type::followed) - { - try - { - client.follow(n.getUser()); - } catch (const twitter::twitter_error& e) + + for (twitter::user_id f : newFollowers) + { + client.follow(f); + } + } catch (const twitter::twitter_error& error) { - std::cout << "Twitter error while following @" << n.getUser().getScreenName() << ": " << e.what() << std::endl; + std::cout << "Twitter error while syncing followers: " << error.what() + << std::endl; } + + followerTimeout = CHECK_FOLLOWERS_EVERY; } - }, true, true); - for (;;) - { - std::this_thread::sleep_for(std::chrono::minutes(1)); - + followerTimeout--; + try { - std::set friends = client.getFriends(); - std::set followers = client.getFollowers(); - - std::list old_friends, new_followers; - std::set_difference(std::begin(friends), std::end(friends), std::begin(followers), std::end(followers), std::back_inserter(old_friends)); - std::set_difference(std::begin(followers), std::end(followers), std::begin(friends), std::end(friends), std::back_inserter(new_followers)); + // Poll the timeline. + std::list tweets = client.getHomeTimeline().poll(); - for (auto f : old_friends) + for (twitter::tweet& tweet : tweets) { - client.unfollow(f); - } - - for (auto f : new_followers) - { - client.follow(f); + 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) + { + std::string orig = tweet.getText(); + std::string canonical; + + std::transform( + std::begin(orig), + std::end(orig), + std::back_inserter(canonical), + [] (char ch) { + return std::tolower(ch); + }); + + for (const std::string& trigger : triggers) + { + if (canonical.find(trigger) != std::string::npos) + { + std::cout << "Calling the cops on @" + << tweet.getAuthor().getScreenName() << std::endl; + + try + { + long media_id = + client.uploadMedia( + "image/jpeg", + static_cast(img_buf), + img_len); + + client.replyToTweet( + tweet.generateReplyPrefill(client.getUser()), + tweet.getID(), + {media_id}); + } catch (const twitter::twitter_error& e) + { + std::cout << "Twitter error while tweeting: " << e.what() + << std::endl; + } + + break; + } + } + } } - } catch (const twitter::twitter_error& e) + } catch (const twitter::rate_limit_exceeded&) { - std::cout << "Twitter error: " << e.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::hours(4)); + + // We can poll the timeline at most once every five minutes. + std::this_thread::sleep_for(std::chrono::minutes(5)); } } diff --git a/vendor/libtwittercpp b/vendor/libtwittercpp index d783c17..b7bb942 160000 --- a/vendor/libtwittercpp +++ b/vendor/libtwittercpp @@ -1 +1 @@ -Subproject commit d783c17151a98466e304b1e5f33bfca0be885fd8 +Subproject commit b7bb942cadfe3d657895af1557b78acc2559947e -- cgit 1.4.1