diff options
| author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2018-03-25 17:45:10 -0400 |
|---|---|---|
| committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2018-03-25 17:45:10 -0400 |
| commit | 2d8c8b0d8827159edcabd3a8665f45ef65cb3153 (patch) | |
| tree | 8afc85bab2a65685ae31dcbbce4db63e84b526f6 | |
| parent | 99fe6c21ad757c41e4c082fadaa47c79a93c10c5 (diff) | |
| download | toldya-2d8c8b0d8827159edcabd3a8665f45ef65cb3153.tar.gz toldya-2d8c8b0d8827159edcabd3a8665f45ef65cb3153.tar.bz2 toldya-2d8c8b0d8827159edcabd3a8665f45ef65cb3153.zip | |
Bot now uniformly picks a tweet author, then a tweet
This changes the distribution so that people who tweet more often aren't more likely to be picked. Also modernized the bot, added a gitignore, and made whitespace changes.
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | toldya.cpp | 182 |
2 files changed, 125 insertions, 63 deletions
| diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23fe4d0 --- /dev/null +++ b/.gitignore | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | .DS_Store | ||
| 2 | CMakeFiles | ||
| 3 | CMakeCache.txt | ||
| 4 | cmake_install.cmake | ||
| 5 | Makefile | ||
| 6 | *.swp | ||
| diff --git a/toldya.cpp b/toldya.cpp index d880c81..a5e7172 100644 --- a/toldya.cpp +++ b/toldya.cpp | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | #include <chrono> | 6 | #include <chrono> |
| 7 | #include <iostream> | 7 | #include <iostream> |
| 8 | #include <algorithm> | 8 | #include <algorithm> |
| 9 | #include <random> | ||
| 9 | 10 | ||
| 10 | int main(int argc, char** argv) | 11 | int main(int argc, char** argv) |
| 11 | { | 12 | { |
| @@ -15,53 +16,61 @@ int main(int argc, char** argv) | |||
| 15 | return -1; | 16 | return -1; |
| 16 | } | 17 | } |
| 17 | 18 | ||
| 19 | std::random_device randomDevice; | ||
| 20 | std::mt19937 rng(randomDevice()); | ||
| 21 | |||
| 18 | std::string configfile(argv[1]); | 22 | std::string configfile(argv[1]); |
| 19 | YAML::Node config = YAML::LoadFile(configfile); | 23 | YAML::Node config = YAML::LoadFile(configfile); |
| 20 | 24 | ||
| 21 | twitter::auth auth; | 25 | twitter::auth auth; |
| 22 | auth.setConsumerKey(config["consumer_key"].as<std::string>()); | 26 | auth.setConsumerKey(config["consumer_key"].as<std::string>()); |
| 23 | auth.setConsumerSecret(config["consumer_secret"].as<std::string>()); | 27 | auth.setConsumerSecret(config["consumer_secret"].as<std::string>()); |
| 24 | auth.setAccessKey(config["access_key"].as<std::string>()); | 28 | auth.setAccessKey(config["access_key"].as<std::string>()); |
| 25 | auth.setAccessSecret(config["access_secret"].as<std::string>()); | 29 | auth.setAccessSecret(config["access_secret"].as<std::string>()); |
| 26 | 30 | ||
| 27 | std::vector<std::string> captions { | 31 | std::vector<std::string> captions { |
| 28 | "It begins.", | 32 | "It begins.", |
| 29 | "Yikes.", | 33 | "Yikes.", |
| 30 | "Frightening.", | 34 | "Frightening.", |
| 31 | "This is how it starts..." | 35 | "This is how it starts..." |
| 32 | }; | 36 | }; |
| 33 | 37 | ||
| 34 | std::vector<twitter::tweet> potential; | 38 | std::map<twitter::user_id, std::vector<twitter::tweet>> potential; |
| 35 | std::set<twitter::tweet_id> deletions; | 39 | std::set<twitter::tweet_id> deletions; |
| 36 | std::mutex potential_mutex; | 40 | std::mutex potentialMutex; |
| 37 | 41 | ||
| 38 | twitter::client client(auth); | 42 | twitter::client client(auth); |
| 39 | std::set<twitter::user_id> streamed_friends; | 43 | std::set<twitter::user_id> streamedFriends; |
| 40 | 44 | ||
| 41 | std::cout << "Starting streaming" << std::endl; | 45 | std::cout << "Starting streaming" << std::endl; |
| 42 | 46 | ||
| 43 | twitter::stream user_stream(client, [&] (twitter::notification n) { | 47 | twitter::stream userStream(client, [&] (twitter::notification n) { |
| 44 | if (n.getType() == twitter::notification::type::friends) | 48 | if (n.getType() == twitter::notification::type::friends) |
| 45 | { | 49 | { |
| 46 | streamed_friends = n.getFriends(); | 50 | streamedFriends = n.getFriends(); |
| 47 | } else if (n.getType() == twitter::notification::type::follow) | 51 | } else if (n.getType() == twitter::notification::type::follow) |
| 48 | { | 52 | { |
| 49 | streamed_friends.insert(n.getUser().getID()); | 53 | streamedFriends.insert(n.getUser().getID()); |
| 50 | } else if (n.getType() == twitter::notification::type::unfollow) | 54 | } else if (n.getType() == twitter::notification::type::unfollow) |
| 51 | { | 55 | { |
| 52 | streamed_friends.erase(n.getUser().getID()); | 56 | streamedFriends.erase(n.getUser().getID()); |
| 53 | } else if (n.getType() == twitter::notification::type::tweet) | 57 | } else if (n.getType() == twitter::notification::type::tweet) |
| 54 | { | 58 | { |
| 59 | // Only monitor people you are following | ||
| 60 | // Ignore retweets | ||
| 61 | // Ignore messages | ||
| 55 | if ( | 62 | if ( |
| 56 | (streamed_friends.count(n.getTweet().getAuthor().getID()) == 1) // Only monitor people you are following | 63 | (streamedFriends.count(n.getTweet().getAuthor().getID()) == 1) |
| 57 | && (!n.getTweet().isRetweet()) // Ignore retweets | 64 | && (!n.getTweet().isRetweet()) |
| 58 | && (n.getTweet().getText().front() != '@') // Ignore messages | 65 | && (n.getTweet().getText().front() != '@') |
| 59 | ) | 66 | ) |
| 60 | { | 67 | { |
| 61 | std::lock_guard<std::mutex> potential_guard(potential_mutex); | 68 | std::lock_guard<std::mutex> potentialGuard(potentialMutex); |
| 62 | std::cout << n.getTweet().getID() << ": " << n.getTweet().getText() << std::endl; | 69 | std::cout << n.getTweet().getID() << ": " << n.getTweet().getText() |
| 63 | 70 | << std::endl; | |
| 64 | potential.push_back(std::move(n.getTweet())); | 71 | |
| 72 | potential[n.getTweet().getAuthor().getID()]. | ||
| 73 | push_back(std::move(n.getTweet())); | ||
| 65 | } | 74 | } |
| 66 | } else if (n.getType() == twitter::notification::type::followed) | 75 | } else if (n.getType() == twitter::notification::type::followed) |
| 67 | { | 76 | { |
| @@ -70,19 +79,20 @@ int main(int argc, char** argv) | |||
| 70 | client.follow(n.getUser()); | 79 | client.follow(n.getUser()); |
| 71 | } catch (const twitter::twitter_error& error) | 80 | } catch (const twitter::twitter_error& error) |
| 72 | { | 81 | { |
| 73 | std::cout << "Twitter error while following @" << n.getUser().getScreenName() << ": " << error.what() << std::endl; | 82 | std::cout << "Twitter error while following @" |
| 83 | << n.getUser().getScreenName() << ": " << error.what() << std::endl; | ||
| 74 | } | 84 | } |
| 75 | } else if (n.getType() == twitter::notification::type::deletion) | 85 | } else if (n.getType() == twitter::notification::type::deletion) |
| 76 | { | 86 | { |
| 77 | std::lock_guard<std::mutex> potential_guard(potential_mutex); | 87 | std::lock_guard<std::mutex> potentialGuard(potentialMutex); |
| 78 | std::cout << "Tweet " << n.getTweetID() << " was deleted." << std::endl; | 88 | std::cout << "Tweet " << n.getTweetID() << " was deleted." << std::endl; |
| 79 | 89 | ||
| 80 | deletions.insert(n.getTweetID()); | 90 | deletions.insert(n.getTweetID()); |
| 81 | } | 91 | } |
| 82 | }); | 92 | }); |
| 83 | 93 | ||
| 84 | std::this_thread::sleep_for(std::chrono::minutes(1)); | 94 | std::this_thread::sleep_for(std::chrono::minutes(1)); |
| 85 | 95 | ||
| 86 | for (;;) | 96 | for (;;) |
| 87 | { | 97 | { |
| 88 | // Wait until 9am | 98 | // Wait until 9am |
| @@ -92,7 +102,10 @@ int main(int argc, char** argv) | |||
| 92 | midtm->tm_min = 0; | 102 | midtm->tm_min = 0; |
| 93 | midtm->tm_sec = 0; | 103 | midtm->tm_sec = 0; |
| 94 | auto to_until = std::chrono::system_clock::from_time_t(std::mktime(midtm)); | 104 | auto to_until = std::chrono::system_clock::from_time_t(std::mktime(midtm)); |
| 95 | auto to_wait = std::chrono::duration_cast<std::chrono::seconds>((to_until + std::chrono::hours(24 + 9)) - std::chrono::system_clock::now()); | 105 | auto to_wait = std::chrono::duration_cast<std::chrono::seconds>( |
| 106 | (to_until + std::chrono::hours(24 + 9)) | ||
| 107 | - std::chrono::system_clock::now()); | ||
| 108 | |||
| 96 | int waitlen = to_wait.count(); | 109 | int waitlen = to_wait.count(); |
| 97 | if (waitlen == 0) | 110 | if (waitlen == 0) |
| 98 | { | 111 | { |
| @@ -108,95 +121,138 @@ int main(int argc, char** argv) | |||
| 108 | std::cout << "Sleeping for 1 minute..." << std::endl; | 121 | std::cout << "Sleeping for 1 minute..." << std::endl; |
| 109 | } else if (waitlen < 60*60) | 122 | } else if (waitlen < 60*60) |
| 110 | { | 123 | { |
| 111 | std::cout << "Sleeping for " << (waitlen/60) << " minutes..." << std::endl; | 124 | std::cout << "Sleeping for " << (waitlen/60) << " minutes..." |
| 125 | << std::endl; | ||
| 112 | } else if (waitlen == 60*60) | 126 | } else if (waitlen == 60*60) |
| 113 | { | 127 | { |
| 114 | std::cout << "Sleeping for 1 hour..." << std::endl; | 128 | std::cout << "Sleeping for 1 hour..." << std::endl; |
| 115 | } else if (waitlen < 60*60*24) | 129 | } else if (waitlen < 60*60*24) |
| 116 | { | 130 | { |
| 117 | std::cout << "Sleeping for " << (waitlen/60/60) << " hours..." << std::endl; | 131 | std::cout << "Sleeping for " << (waitlen/60/60) << " hours..." |
| 132 | << std::endl; | ||
| 118 | } else if (waitlen == 60*60*24) | 133 | } else if (waitlen == 60*60*24) |
| 119 | { | 134 | { |
| 120 | std::cout << "Sleeping for 1 day..." << std::endl; | 135 | std::cout << "Sleeping for 1 day..." << std::endl; |
| 121 | } else { | 136 | } else { |
| 122 | std::cout << "Sleeping for " << (waitlen/60/60/24) << " days..." << std::endl; | 137 | std::cout << "Sleeping for " << (waitlen/60/60/24) << " days..." |
| 138 | << std::endl; | ||
| 123 | } | 139 | } |
| 124 | 140 | ||
| 125 | std::this_thread::sleep_for(to_wait); | 141 | std::this_thread::sleep_for(to_wait); |
| 126 | 142 | ||
| 143 | // The rest of the loop deals with the potential tweets | ||
| 144 | std::lock_guard<std::mutex> potentialGuard(potentialMutex); | ||
| 145 | |||
| 127 | // Unfollow people who have unfollowed us | 146 | // Unfollow people who have unfollowed us |
| 128 | try | 147 | try |
| 129 | { | 148 | { |
| 130 | std::set<twitter::user_id> friends = client.getFriends(); | 149 | std::set<twitter::user_id> friends = client.getFriends(); |
| 131 | std::set<twitter::user_id> followers = client.getFollowers(); | 150 | std::set<twitter::user_id> followers = client.getFollowers(); |
| 132 | 151 | ||
| 133 | std::list<twitter::user_id> old_friends; | 152 | std::list<twitter::user_id> oldFriends; |
| 134 | std::list<twitter::user_id> new_followers; | 153 | std::set_difference( |
| 135 | std::set_difference(std::begin(friends), std::end(friends), std::begin(followers), std::end(followers), std::back_inserter(old_friends)); | 154 | std::begin(friends), |
| 136 | std::set_difference(std::begin(followers), std::end(followers), std::begin(friends), std::end(friends), std::back_inserter(new_followers)); | 155 | std::end(friends), |
| 137 | 156 | std::begin(followers), | |
| 138 | std::set<twitter::user_id> old_friends_set; | 157 | std::end(followers), |
| 139 | for (auto f : old_friends) | 158 | std::back_inserter(oldFriends)); |
| 159 | |||
| 160 | std::list<twitter::user_id> newFollowers; | ||
| 161 | std::set_difference( | ||
| 162 | std::begin(followers), | ||
| 163 | std::end(followers), | ||
| 164 | std::begin(friends), | ||
| 165 | std::end(friends), | ||
| 166 | std::back_inserter(newFollowers)); | ||
| 167 | |||
| 168 | std::set<twitter::user_id> oldFriendsSet; | ||
| 169 | for (twitter::user_id f : oldFriends) | ||
| 140 | { | 170 | { |
| 141 | old_friends_set.insert(f); | 171 | oldFriendsSet.insert(f); |
| 142 | 172 | ||
| 143 | try | 173 | try |
| 144 | { | 174 | { |
| 145 | client.unfollow(f); | 175 | client.unfollow(f); |
| 146 | } catch (const twitter::twitter_error& error) | 176 | } catch (const twitter::twitter_error& error) |
| 147 | { | 177 | { |
| 148 | std::cout << "Twitter error while unfollowing: " << error.what() << std::endl; | 178 | std::cout << "Twitter error while unfollowing: " << error.what() |
| 179 | << std::endl; | ||
| 149 | } | 180 | } |
| 150 | } | 181 | } |
| 151 | 182 | ||
| 152 | for (auto f : new_followers) | 183 | for (twitter::user_id f : newFollowers) |
| 153 | { | 184 | { |
| 154 | try | 185 | try |
| 155 | { | 186 | { |
| 156 | client.follow(f); | 187 | client.follow(f); |
| 157 | } catch (const twitter::twitter_error& error) | 188 | } catch (const twitter::twitter_error& error) |
| 158 | { | 189 | { |
| 159 | std::cout << "Twitter error while following: " << error.what() << std::endl; | 190 | std::cout << "Twitter error while following: " << error.what() |
| 191 | << std::endl; | ||
| 160 | } | 192 | } |
| 161 | } | 193 | } |
| 162 | 194 | ||
| 163 | std::lock_guard<std::mutex> potential_guard(potential_mutex); | 195 | // Filter the potential tweets for users that are still following us, and |
| 164 | std::vector<twitter::tweet> to_keep; | 196 | // and for tweets that haven't been deleted. |
| 165 | for (auto& pt : potential) | 197 | std::map<twitter::user_id, std::vector<twitter::tweet>> toKeep; |
| 198 | |||
| 199 | for (auto& p : potential) | ||
| 166 | { | 200 | { |
| 167 | if ( | 201 | // The author has not unfollowed |
| 168 | (old_friends_set.count(pt.getAuthor().getID()) == 0) && // The author has not unfollowed | 202 | if (!oldFriendsSet.count(p.first)) |
| 169 | (deletions.count(pt.getID()) == 0)) // The tweet was not deleted | ||
| 170 | { | 203 | { |
| 171 | to_keep.push_back(std::move(pt)); | 204 | std::vector<twitter::tweet> userTweets; |
| 205 | |||
| 206 | for (twitter::tweet& pt : p.second) | ||
| 207 | { | ||
| 208 | // The tweet was not deleted | ||
| 209 | if (!deletions.count(pt.getID())) | ||
| 210 | { | ||
| 211 | userTweets.push_back(std::move(pt)); | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | if (!userTweets.empty()) | ||
| 216 | { | ||
| 217 | toKeep[p.first] = std::move(userTweets); | ||
| 218 | } | ||
| 172 | } | 219 | } |
| 173 | } | 220 | } |
| 174 | 221 | ||
| 175 | potential = std::move(to_keep); | 222 | potential = std::move(toKeep); |
| 176 | deletions.clear(); | 223 | deletions.clear(); |
| 177 | } catch (const twitter::twitter_error& error) | 224 | } catch (const twitter::twitter_error& error) |
| 178 | { | 225 | { |
| 179 | std::cout << "Twitter error while getting friends/followers: " << error.what() << std::endl; | 226 | std::cout << "Twitter error while getting friends/followers: " |
| 227 | << error.what() << std::endl; | ||
| 180 | } | 228 | } |
| 181 | 229 | ||
| 182 | // Tweet! | 230 | // Tweet! |
| 183 | if (!potential.empty()) | 231 | if (!potential.empty()) |
| 184 | { | 232 | { |
| 185 | auto to_quote = std::move(potential[rand() % potential.size()]); | 233 | std::uniform_int_distribution<size_t> userDist(0, potential.size() - 1); |
| 186 | potential.clear(); | 234 | const std::vector<twitter::tweet>& toQuoteUser = |
| 187 | 235 | std::next(std::begin(potential), userDist(rng))->second; | |
| 188 | std::string caption = captions[rand() % captions.size()]; | 236 | |
| 189 | std::string doc = caption + " " + to_quote.getURL(); | 237 | std::uniform_int_distribution<size_t> postDist(0, toQuoteUser.size() - 1); |
| 190 | 238 | const twitter::tweet& toQuote = toQuoteUser.at(postDist(rng)); | |
| 239 | |||
| 240 | std::uniform_int_distribution<size_t> captionDist(0, captions.size() - 1); | ||
| 241 | const std::string& caption = captions.at(captionDist(rng)); | ||
| 242 | |||
| 243 | std::string doc = caption + " " + toQuote.getURL(); | ||
| 244 | |||
| 191 | try | 245 | try |
| 192 | { | 246 | { |
| 193 | client.updateStatus(doc); | 247 | client.updateStatus(doc); |
| 194 | 248 | ||
| 195 | std::cout << "Tweeted!" << std::endl; | 249 | std::cout << "Tweeted!" << std::endl; |
| 196 | } catch (const twitter::twitter_error& error) | 250 | } catch (const twitter::twitter_error& error) |
| 197 | { | 251 | { |
| 198 | std::cout << "Error tweeting: " << error.what() << std::endl; | 252 | std::cout << "Error tweeting: " << error.what() << std::endl; |
| 199 | } | 253 | } |
| 254 | |||
| 255 | potential.clear(); | ||
| 200 | } | 256 | } |
| 201 | } | 257 | } |
| 202 | } | 258 | } |
