summary refs log tree commit diff stats
path: root/father.cpp
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2018-08-06 16:01:06 -0400
committerKelly Rauchenberger <fefferburbia@gmail.com>2018-08-06 16:01:06 -0400
commitf605bf346904c18b3ba69c8cf2a624c5905f8cf5 (patch)
tree3b0f26cf2c05c78cdb98ae668a2273cae420e943 /father.cpp
parent2b85c7941c95b46203dbd65e012844774495104a (diff)
downloadfather-f605bf346904c18b3ba69c8cf2a624c5905f8cf5.tar.gz
father-f605bf346904c18b3ba69c8cf2a624c5905f8cf5.tar.bz2
father-f605bf346904c18b3ba69c8cf2a624c5905f8cf5.zip
Switched from streaming API to timeline polling
Also included hkutil for the string split function.
Diffstat (limited to 'father.cpp')
-rw-r--r--father.cpp327
1 files changed, 152 insertions, 175 deletions
diff --git a/father.cpp b/father.cpp index 198b38a..4ff4a3c 100644 --- a/father.cpp +++ b/father.cpp
@@ -10,37 +10,10 @@
10#include <list> 10#include <list>
11#include <iterator> 11#include <iterator>
12#include <verbly.h> 12#include <verbly.h>
13#include <hkutil/string.h>
13 14
14template <class OutputIterator> 15// Sync followers every 4 hours.
15void split(std::string input, std::string delimiter, OutputIterator out) 16const int CHECK_FOLLOWERS_EVERY = 4 * 60 / 5;
16{
17 while (!input.empty())
18 {
19 int divider = input.find(delimiter);
20 if (divider == std::string::npos)
21 {
22 *out = input;
23 out++;
24
25 input = "";
26 } else {
27 *out = input.substr(0, divider);
28 out++;
29
30 input = input.substr(divider+delimiter.length());
31 }
32 }
33}
34
35template <class Container>
36Container split(std::string input, std::string delimiter)
37{
38 Container result;
39
40 split(input, delimiter, std::back_inserter(result));
41
42 return result;
43}
44 17
45verbly::word findWordOfType( 18verbly::word findWordOfType(
46 verbly::database& database, 19 verbly::database& database,
@@ -75,189 +48,193 @@ int main(int argc, char** argv)
75 48
76 verbly::database database(config["verbly_datafile"].as<std::string>()); 49 verbly::database database(config["verbly_datafile"].as<std::string>());
77 50
78 twitter::auth auth; 51 twitter::auth auth(
79 auth.setConsumerKey(config["consumer_key"].as<std::string>()); 52 config["consumer_key"].as<std::string>(),
80 auth.setConsumerSecret(config["consumer_secret"].as<std::string>()); 53 config["consumer_secret"].as<std::string>(),
81 auth.setAccessKey(config["access_key"].as<std::string>()); 54 config["access_key"].as<std::string>(),
82 auth.setAccessSecret(config["access_secret"].as<std::string>()); 55 config["access_secret"].as<std::string>());
83 56
84 std::set<twitter::user_id> streamedFriends; 57 auto startedTime = std::chrono::system_clock::now();
85 58
86 twitter::client client(auth); 59 twitter::client client(auth);
87 60
88 std::cout << "Starting streaming..." << std::endl; 61 std::set<twitter::user_id> friends;
89 twitter::stream userStream(client, [&] (const twitter::notification& n) { 62 int followerTimeout = 0;
90 if (n.getType() == twitter::notification::type::friends) 63
91 { 64 for (;;)
92 streamedFriends = n.getFriends(); 65 {
93 } else if (n.getType() == twitter::notification::type::follow) 66 if (followerTimeout == 0)
94 {
95 streamedFriends.insert(n.getUser().getID());
96 } else if (n.getType() == twitter::notification::type::unfollow)
97 {
98 streamedFriends.erase(n.getUser().getID());
99 } else if (n.getType() == twitter::notification::type::tweet)
100 { 67 {
101 if ( 68 // Sync friends with followers.
102 // Only monitor people you are following 69 try
103 (streamedFriends.count(n.getTweet().getAuthor().getID()) == 1)
104 // Ignore retweets
105 && (!n.getTweet().isRetweet())
106 // Ignore messages
107 && (n.getTweet().getText().front() != '@')
108 )
109 { 70 {
110 std::vector<std::string> tokens = 71 friends = client.getFriends();
111 split<std::vector<std::string>>(n.getTweet().getText(), " "); 72
73 std::set<twitter::user_id> followers = client.getFollowers();
74
75 std::list<twitter::user_id> oldFriends;
76 std::set_difference(
77 std::begin(friends),
78 std::end(friends),
79 std::begin(followers),
80 std::end(followers),
81 std::back_inserter(oldFriends));
82
83 std::list<twitter::user_id> newFollowers;
84 std::set_difference(
85 std::begin(followers),
86 std::end(followers),
87 std::begin(friends),
88 std::end(friends),
89 std::back_inserter(newFollowers));
90
91 for (twitter::user_id f : oldFriends)
92 {
93 client.unfollow(f);
94 }
112 95
113 std::vector<std::string> canonical; 96 for (twitter::user_id f : newFollowers)
114 for (std::string token : tokens)
115 { 97 {
116 std::string canonStr; 98 client.follow(f);
117 for (char ch : token) 99 }
100 } catch (const twitter::twitter_error& error)
101 {
102 std::cout << "Twitter error while syncing followers: " << error.what()
103 << std::endl;
104 }
105
106 followerTimeout = CHECK_FOLLOWERS_EVERY;
107 }
108
109 followerTimeout--;
110
111 try
112 {
113 // Poll the timeline.
114 std::list<twitter::tweet> tweets = client.getHomeTimeline().poll();
115
116 for (twitter::tweet& tweet : tweets)
117 {
118 auto createdTime =
119 std::chrono::system_clock::from_time_t(tweet.getCreatedAt());
120
121 if (
122 // Only monitor people you are following
123 friends.count(tweet.getAuthor().getID())
124 // Ignore tweets from before the bot started up
125 && createdTime > startedTime
126 // Ignore retweets
127 && !tweet.isRetweet()
128 // Ignore messages
129 && tweet.getText().front() != '@')
130 {
131 std::vector<std::string> tokens =
132 hatkirby::split<std::vector<std::string>>(tweet.getText(), " ");
133
134 std::vector<std::string> canonical;
135 for (std::string token : tokens)
118 { 136 {
119 if (std::isalpha(ch)) 137 std::string canonStr;
138 for (char ch : token)
120 { 139 {
121 canonStr += std::tolower(ch); 140 if (std::isalpha(ch))
141 {
142 canonStr += std::tolower(ch);
143 }
122 } 144 }
123 }
124
125 canonical.push_back(canonStr);
126 }
127 145
128 std::vector<std::string>::iterator imIt = 146 canonical.push_back(canonStr);
129 std::find(std::begin(canonical), std::end(canonical), "im"); 147 }
130 148
131 if (imIt != std::end(canonical)) 149 std::vector<std::string>::iterator imIt =
132 { 150 std::find(std::begin(canonical), std::end(canonical), "im");
133 imIt++;
134 151
135 if (imIt != std::end(canonical)) 152 if (imIt != std::end(canonical))
136 { 153 {
137 verbly::token name; 154 imIt++;
138 155
139 verbly::word firstAdverb = findWordOfType( 156 if (imIt != std::end(canonical))
140 database,
141 *imIt,
142 verbly::part_of_speech::adverb);
143
144 if (firstAdverb.isValid())
145 { 157 {
146 std::vector<std::string>::iterator adjIt = imIt; 158 verbly::token name;
147 adjIt++; 159
160 verbly::word firstAdverb = findWordOfType(
161 database,
162 *imIt,
163 verbly::part_of_speech::adverb);
148 164
149 if (adjIt != std::end(canonical)) 165 if (firstAdverb.isValid())
150 { 166 {
151 verbly::word secondAdjective = findWordOfType( 167 std::vector<std::string>::iterator adjIt = imIt;
152 database, 168 adjIt++;
153 *adjIt,
154 verbly::part_of_speech::adjective);
155 169
156 if (secondAdjective.isValid()) 170 if (adjIt != std::end(canonical))
157 { 171 {
158 name << firstAdverb; 172 verbly::word secondAdjective = findWordOfType(
159 name << secondAdjective; 173 database,
174 *adjIt,
175 verbly::part_of_speech::adjective);
176
177 if (secondAdjective.isValid())
178 {
179 name << firstAdverb;
180 name << secondAdjective;
181 }
160 } 182 }
161 } 183 }
162 }
163
164 if (name.isEmpty())
165 {
166 verbly::word firstAdjective = findWordOfType(
167 database,
168 *imIt,
169 verbly::part_of_speech::adjective);
170 184
171 if (firstAdjective.isValid()) 185 if (name.isEmpty())
172 { 186 {
173 name = firstAdjective; 187 verbly::word firstAdjective = findWordOfType(
188 database,
189 *imIt,
190 verbly::part_of_speech::adjective);
191
192 if (firstAdjective.isValid())
193 {
194 name = firstAdjective;
195 }
174 } 196 }
175 }
176 197
177 if ((!name.isEmpty()) 198 if ((!name.isEmpty())
178 && (std::bernoulli_distribution(1.0/10.0)(rng))) 199 && (std::bernoulli_distribution(1.0/10.0)(rng)))
179 {
180 verbly::token action = {
181 "Hi",
182 verbly::token::punctuation(",",
183 verbly::token::capitalize(
184 verbly::token::casing::title_case,
185 name)),
186 "I'm Dad."};
187
188 std::string result =
189 n.getTweet().generateReplyPrefill(client.getUser())
190 + action.compile();
191
192 if (result.length() <= 140)
193 { 200 {
194 try 201 verbly::token action = {
195 { 202 "Hi",
196 client.replyToTweet(result, n.getTweet()); 203 verbly::token::punctuation(",",
197 } catch (const twitter::twitter_error& e) 204 verbly::token::capitalize(
205 verbly::token::casing::title_case,
206 name)),
207 "I'm Dad."};
208
209 std::string result =
210 tweet.generateReplyPrefill(client.getUser())
211 + action.compile();
212
213 std::cout << result << std::endl;
214
215 if (result.length() <= 140)
198 { 216 {
199 std::cout << "Twitter error: " << e.what() << std::endl; 217 try
218 {
219 client.replyToTweet(result, tweet);
220 } catch (const twitter::twitter_error& e)
221 {
222 std::cout << "Twitter error while tweeting: " << e.what()
223 << std::endl;
224 }
200 } 225 }
201 } 226 }
202 } 227 }
203 } 228 }
204 } 229 }
205 } 230 }
206 } else if (n.getType() == twitter::notification::type::followed) 231 } catch (const twitter::rate_limit_exceeded&)
207 { 232 {
208 try 233 // Wait out the rate limit (10 minutes here and 5 below = 15).
209 { 234 std::this_thread::sleep_for(std::chrono::minutes(10));
210 client.follow(n.getUser());
211 } catch (const twitter::twitter_error& error)
212 {
213 std::cout << "Twitter error while following @"
214 << n.getUser().getScreenName() << ": " << error.what() << std::endl;
215 }
216 } 235 }
217 });
218 236
219 std::this_thread::sleep_for(std::chrono::minutes(1)); 237 // We can poll the timeline at most once every five minutes.
220 238 std::this_thread::sleep_for(std::chrono::minutes(5));
221 // Every once in a while, check if we've lost any followers, and if we have,
222 // unfollow the people who have unfollowed us.
223 for (;;)
224 {
225 try
226 {
227 std::set<twitter::user_id> friends = client.getFriends();
228 std::set<twitter::user_id> followers = client.getFollowers();
229
230 std::list<twitter::user_id> oldFriends;
231 std::set_difference(
232 std::begin(friends),
233 std::end(friends),
234 std::begin(followers),
235 std::end(followers),
236 std::back_inserter(oldFriends));
237
238 for (auto f : oldFriends)
239 {
240 client.unfollow(f);
241 }
242
243 std::list<twitter::user_id> newFollowers;
244 std::set_difference(
245 std::begin(followers),
246 std::end(followers),
247 std::begin(friends),
248 std::end(friends),
249 std::back_inserter(newFollowers));
250
251 for (auto f : newFollowers)
252 {
253 client.follow(f);
254 }
255 } catch (const twitter::twitter_error& e)
256 {
257 std::cout << "Twitter error: " << e.what() << std::endl;
258 }
259
260 std::this_thread::sleep_for(std::chrono::hours(4));
261 } 239 }
262} 240}
263