about summary refs log tree commit diff stats
path: root/snitch.cpp
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2018-08-05 15:59:57 -0400
committerKelly Rauchenberger <fefferburbia@gmail.com>2018-08-05 15:59:57 -0400
commit4f57eb0e3a2163a32bf84475e3c575c851599760 (patch)
tree7e0baa9719e116ab5abd2e9e1fef85e68aae2d3a /snitch.cpp
parent3561dc270c5f7212150b02ba09e856917639ba2b (diff)
downloadsnitch-4f57eb0e3a2163a32bf84475e3c575c851599760.tar.gz
snitch-4f57eb0e3a2163a32bf84475e3c575c851599760.tar.bz2
snitch-4f57eb0e3a2163a32bf84475e3c575c851599760.zip
Switched from streaming API to timeline polling
Diffstat (limited to 'snitch.cpp')
-rw-r--r--snitch.cpp194
1 files changed, 115 insertions, 79 deletions
diff --git a/snitch.cpp b/snitch.cpp index bed6e3c..1d21449 100644 --- a/snitch.cpp +++ b/snitch.cpp
@@ -6,6 +6,9 @@
6#include <iostream> 6#include <iostream>
7#include <algorithm> 7#include <algorithm>
8 8
9// Sync followers every 4 hours.
10const int CHECK_FOLLOWERS_EVERY = 4 * 60 / 5;
11
9int main(int argc, char** argv) 12int main(int argc, char** argv)
10{ 13{
11 if (argc != 2) 14 if (argc != 2)
@@ -16,13 +19,13 @@ int main(int argc, char** argv)
16 19
17 std::string configfile(argv[1]); 20 std::string configfile(argv[1]);
18 YAML::Node config = YAML::LoadFile(configfile); 21 YAML::Node config = YAML::LoadFile(configfile);
19 22
20 twitter::auth auth; 23 twitter::auth auth(
21 auth.setConsumerKey(config["consumer_key"].as<std::string>()); 24 config["consumer_key"].as<std::string>(),
22 auth.setConsumerSecret(config["consumer_secret"].as<std::string>()); 25 config["consumer_secret"].as<std::string>(),
23 auth.setAccessKey(config["access_key"].as<std::string>()); 26 config["access_key"].as<std::string>(),
24 auth.setAccessSecret(config["access_secret"].as<std::string>()); 27 config["access_secret"].as<std::string>());
25 28
26 std::ifstream img_file(config["image"].as<std::string>()); 29 std::ifstream img_file(config["image"].as<std::string>());
27 img_file.seekg(0, std::ios::end); 30 img_file.seekg(0, std::ios::end);
28 size_t img_len = img_file.tellg(); 31 size_t img_len = img_file.tellg();
@@ -30,100 +33,133 @@ int main(int argc, char** argv)
30 img_file.seekg(0, std::ios::beg); 33 img_file.seekg(0, std::ios::beg);
31 img_file.read(img_buf, img_len); 34 img_file.read(img_buf, img_len);
32 img_file.close(); 35 img_file.close();
33 36
34 std::vector<std::string> triggers { 37 std::vector<std::string> triggers {
35 "calling the cops", 38 "calling the cops",
36 "calling the police", 39 "calling the police",
37 "call the cops", 40 "call the cops",
38 "call the police" 41 "call the police"
39 }; 42 };
40 43
44 auto startedTime = std::chrono::system_clock::now();
45
41 // Initialize the client 46 // Initialize the client
42 twitter::client client(auth); 47 twitter::client client(auth);
43 std::this_thread::sleep_for(std::chrono::minutes(1)); 48
44 49 std::set<twitter::user_id> friends;
45 // Start streaming 50 int followerTimeout = 0;
46 std::cout << "Starting streaming" << std::endl; 51
47 std::set<twitter::user_id> streamed_friends; 52 for (;;)
48 twitter::stream userStream(client, [&] (const twitter::notification& n) { 53 {
49 if (n.getType() == twitter::notification::type::friends) 54 if (followerTimeout == 0)
50 {
51 streamed_friends = n.getFriends();
52 } else if (n.getType() == twitter::notification::type::follow)
53 {
54 streamed_friends.insert(n.getUser().getID());
55 } else if (n.getType() == twitter::notification::type::unfollow)
56 {
57 streamed_friends.erase(n.getUser().getID());
58 } else if (n.getType() == twitter::notification::type::tweet)
59 { 55 {
60 // Only monitor people you are following 56 // Sync friends with followers.
61 if (streamed_friends.count(n.getTweet().getAuthor().getID()) == 1) 57 try
62 { 58 {
63 std::string orig = n.getTweet().getText(); 59 friends = client.getFriends();
64 std::string canonical; 60
65 std::transform(std::begin(orig), std::end(orig), std::back_inserter(canonical), [] (char ch) { 61 std::set<twitter::user_id> followers = client.getFollowers();
66 return std::tolower(ch); 62
67 }); 63 std::list<twitter::user_id> oldFriends;
68 64 std::set_difference(
69 for (auto trigger : triggers) 65 std::begin(friends),
66 std::end(friends),
67 std::begin(followers),
68 std::end(followers),
69 std::back_inserter(oldFriends));
70
71 std::list<twitter::user_id> newFollowers;
72 std::set_difference(
73 std::begin(followers),
74 std::end(followers),
75 std::begin(friends),
76 std::end(friends),
77 std::back_inserter(newFollowers));
78
79 for (twitter::user_id f : oldFriends)
70 { 80 {
71 if (canonical.find(trigger) != std::string::npos) 81 client.unfollow(f);
72 {
73 std::cout << "Calling the cops on @" << n.getTweet().getAuthor().getScreenName() << std::endl;
74
75 try
76 {
77 long media_id = client.uploadMedia("image/jpeg", (const char*) img_buf, img_len);
78 client.replyToTweet(n.getTweet().generateReplyPrefill(client.getUser()), n.getTweet().getID(), {media_id});
79 } catch (const twitter::twitter_error& e)
80 {
81 std::cout << "Twitter error: " << e.what() << std::endl;
82 }
83
84 break;
85 }
86 } 82 }
87 } 83
88 } else if (n.getType() == twitter::notification::type::followed) 84 for (twitter::user_id f : newFollowers)
89 { 85 {
90 try 86 client.follow(f);
91 { 87 }
92 client.follow(n.getUser()); 88 } catch (const twitter::twitter_error& error)
93 } catch (const twitter::twitter_error& e)
94 { 89 {
95 std::cout << "Twitter error while following @" << n.getUser().getScreenName() << ": " << e.what() << std::endl; 90 std::cout << "Twitter error while syncing followers: " << error.what()
91 << std::endl;
96 } 92 }
93
94 followerTimeout = CHECK_FOLLOWERS_EVERY;
97 } 95 }
98 }, true, true);
99 96
100 for (;;) 97 followerTimeout--;
101 { 98
102 std::this_thread::sleep_for(std::chrono::minutes(1));
103
104 try 99 try
105 { 100 {
106 std::set<twitter::user_id> friends = client.getFriends(); 101 // Poll the timeline.
107 std::set<twitter::user_id> followers = client.getFollowers(); 102 std::list<twitter::tweet> tweets = client.getHomeTimeline().poll();
108
109 std::list<twitter::user_id> old_friends, new_followers;
110 std::set_difference(std::begin(friends), std::end(friends), std::begin(followers), std::end(followers), std::back_inserter(old_friends));
111 std::set_difference(std::begin(followers), std::end(followers), std::begin(friends), std::end(friends), std::back_inserter(new_followers));
112 103
113 for (auto f : old_friends) 104 for (twitter::tweet& tweet : tweets)
114 { 105 {
115 client.unfollow(f); 106 auto createdTime =
116 } 107 std::chrono::system_clock::from_time_t(tweet.getCreatedAt());
117 108
118 for (auto f : new_followers) 109 if (
119 { 110 // Only monitor people you are following
120 client.follow(f); 111 friends.count(tweet.getAuthor().getID()) &&
112 // Ignore tweets from before the bot started up
113 createdTime > startedTime)
114 {
115 std::string orig = tweet.getText();
116 std::string canonical;
117
118 std::transform(
119 std::begin(orig),
120 std::end(orig),
121 std::back_inserter(canonical),
122 [] (char ch) {
123 return std::tolower(ch);
124 });
125
126 for (const std::string& trigger : triggers)
127 {
128 if (canonical.find(trigger) != std::string::npos)
129 {
130 std::cout << "Calling the cops on @"
131 << tweet.getAuthor().getScreenName() << std::endl;
132
133 try
134 {
135 long media_id =
136 client.uploadMedia(
137 "image/jpeg",
138 static_cast<const char*>(img_buf),
139 img_len);
140
141 client.replyToTweet(
142 tweet.generateReplyPrefill(client.getUser()),
143 tweet.getID(),
144 {media_id});
145 } catch (const twitter::twitter_error& e)
146 {
147 std::cout << "Twitter error while tweeting: " << e.what()
148 << std::endl;
149 }
150
151 break;
152 }
153 }
154 }
121 } 155 }
122 } catch (const twitter::twitter_error& e) 156 } catch (const twitter::rate_limit_exceeded&)
123 { 157 {
124 std::cout << "Twitter error: " << e.what() << std::endl; 158 // Wait out the rate limit (10 minutes here and 5 below = 15).
159 std::this_thread::sleep_for(std::chrono::minutes(10));
125 } 160 }
126 161
127 std::this_thread::sleep_for(std::chrono::hours(4)); 162 // We can poll the timeline at most once every five minutes.
163 std::this_thread::sleep_for(std::chrono::minutes(5));
128 } 164 }
129} 165}