diff options
author | Kelly Rauchenberger <fefferburbia@gmail.com> | 2017-02-19 15:36:40 -0500 |
---|---|---|
committer | Kelly Rauchenberger <fefferburbia@gmail.com> | 2017-02-19 15:36:40 -0500 |
commit | 95b7ae535c0b005466e34096f5ce427046a403d2 (patch) | |
tree | 806d031c7894322c56d2457d64a51f893b3c38ba /father.cpp | |
download | father-95b7ae535c0b005466e34096f5ce427046a403d2.tar.gz father-95b7ae535c0b005466e34096f5ce427046a403d2.tar.bz2 father-95b7ae535c0b005466e34096f5ce427046a403d2.zip |
Created bot
Diffstat (limited to 'father.cpp')
-rw-r--r-- | father.cpp | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/father.cpp b/father.cpp new file mode 100644 index 0000000..7ea5080 --- /dev/null +++ b/father.cpp | |||
@@ -0,0 +1,260 @@ | |||
1 | #include <twitter.h> | ||
2 | #include <random> | ||
3 | #include <yaml-cpp/yaml.h> | ||
4 | #include <iostream> | ||
5 | #include <thread> | ||
6 | #include <chrono> | ||
7 | #include <string> | ||
8 | #include <algorithm> | ||
9 | #include <set> | ||
10 | #include <list> | ||
11 | #include <iterator> | ||
12 | #include <verbly.h> | ||
13 | |||
14 | template <class OutputIterator> | ||
15 | void split(std::string input, std::string delimiter, OutputIterator out) | ||
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 | |||
35 | template <class Container> | ||
36 | Container 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 | |||
45 | verbly::word findWordOfType( | ||
46 | verbly::database& database, | ||
47 | std::string form, | ||
48 | verbly::part_of_speech partOfSpeech) | ||
49 | { | ||
50 | std::vector<verbly::word> isThing = database.words( | ||
51 | (verbly::notion::partOfSpeech == partOfSpeech) | ||
52 | && (verbly::form::text == form)).all(); | ||
53 | |||
54 | if (isThing.empty()) | ||
55 | { | ||
56 | return {}; | ||
57 | } else { | ||
58 | return isThing.front(); | ||
59 | } | ||
60 | } | ||
61 | |||
62 | int main(int argc, char** argv) | ||
63 | { | ||
64 | std::random_device randomDevice; | ||
65 | std::mt19937 rng{randomDevice()}; | ||
66 | |||
67 | if (argc != 2) | ||
68 | { | ||
69 | std::cout << "usage: father [configfile]" << std::endl; | ||
70 | return -1; | ||
71 | } | ||
72 | |||
73 | std::string configfile(argv[1]); | ||
74 | YAML::Node config = YAML::LoadFile(configfile); | ||
75 | |||
76 | twitter::auth auth; | ||
77 | auth.setConsumerKey(config["consumer_key"].as<std::string>()); | ||
78 | auth.setConsumerSecret(config["consumer_secret"].as<std::string>()); | ||
79 | auth.setAccessKey(config["access_key"].as<std::string>()); | ||
80 | auth.setAccessSecret(config["access_secret"].as<std::string>()); | ||
81 | |||
82 | std::set<twitter::user_id> streamedFriends; | ||
83 | |||
84 | verbly::database database(config["verbly_datafile"].as<std::string>()); | ||
85 | |||
86 | twitter::client client(auth); | ||
87 | |||
88 | std::cout << "Starting streaming..." << std::endl; | ||
89 | twitter::stream userStream(client, [&] (const twitter::notification& n) { | ||
90 | if (n.getType() == twitter::notification::type::friends) | ||
91 | { | ||
92 | streamedFriends = n.getFriends(); | ||
93 | } else if (n.getType() == twitter::notification::type::follow) | ||
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 | { | ||
101 | if ( | ||
102 | // Only monitor people you are following | ||
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 | { | ||
110 | std::vector<std::string> tokens = | ||
111 | split<std::vector<std::string>>(n.getTweet().getText(), " "); | ||
112 | |||
113 | std::vector<std::string> canonical; | ||
114 | for (std::string token : tokens) | ||
115 | { | ||
116 | std::string canonStr; | ||
117 | for (char ch : token) | ||
118 | { | ||
119 | if (std::isalpha(ch)) | ||
120 | { | ||
121 | canonStr += std::tolower(ch); | ||
122 | } | ||
123 | } | ||
124 | |||
125 | canonical.push_back(canonStr); | ||
126 | } | ||
127 | |||
128 | std::vector<std::string>::iterator imIt = | ||
129 | std::find(std::begin(canonical), std::end(canonical), "im"); | ||
130 | |||
131 | if (imIt != std::end(canonical)) | ||
132 | { | ||
133 | imIt++; | ||
134 | |||
135 | if (imIt != std::end(canonical)) | ||
136 | { | ||
137 | verbly::token name; | ||
138 | |||
139 | verbly::word firstAdverb = findWordOfType( | ||
140 | database, | ||
141 | *imIt, | ||
142 | verbly::part_of_speech::adverb); | ||
143 | |||
144 | if (firstAdverb.isValid()) | ||
145 | { | ||
146 | std::vector<std::string>::iterator adjIt = imIt; | ||
147 | adjIt++; | ||
148 | |||
149 | if (adjIt != std::end(canonical)) | ||
150 | { | ||
151 | verbly::word secondAdjective = findWordOfType( | ||
152 | database, | ||
153 | *adjIt, | ||
154 | verbly::part_of_speech::adjective); | ||
155 | |||
156 | if (secondAdjective.isValid()) | ||
157 | { | ||
158 | name << verbly::token::capitalize(firstAdverb); | ||
159 | name << verbly::token::capitalize(secondAdjective); | ||
160 | } | ||
161 | } | ||
162 | } | ||
163 | |||
164 | if (name.isEmpty()) | ||
165 | { | ||
166 | verbly::word firstAdjective = findWordOfType( | ||
167 | database, | ||
168 | *imIt, | ||
169 | verbly::part_of_speech::adjective); | ||
170 | |||
171 | if (firstAdjective.isValid()) | ||
172 | { | ||
173 | name = verbly::token::capitalize(firstAdjective); | ||
174 | } | ||
175 | } | ||
176 | |||
177 | if ((!name.isEmpty()) | ||
178 | && (std::bernoulli_distribution(1.0/10.0)(rng))) | ||
179 | { | ||
180 | verbly::token action = { | ||
181 | "Hi", | ||
182 | verbly::token::punctuation(",", name), | ||
183 | "I'm Dad."}; | ||
184 | |||
185 | std::string result = | ||
186 | n.getTweet().generateReplyPrefill(client.getUser()) | ||
187 | + action.compile(); | ||
188 | |||
189 | if (result.length() <= 140) | ||
190 | { | ||
191 | try | ||
192 | { | ||
193 | client.replyToTweet(result, n.getTweet()); | ||
194 | } catch (const twitter::twitter_error& e) | ||
195 | { | ||
196 | std::cout << "Twitter error: " << e.what() << std::endl; | ||
197 | } | ||
198 | } | ||
199 | } | ||
200 | } | ||
201 | } | ||
202 | } | ||
203 | } else if (n.getType() == twitter::notification::type::followed) | ||
204 | { | ||
205 | try | ||
206 | { | ||
207 | client.follow(n.getUser()); | ||
208 | } catch (const twitter::twitter_error& error) | ||
209 | { | ||
210 | std::cout << "Twitter error while following @" | ||
211 | << n.getUser().getScreenName() << ": " << error.what() << std::endl; | ||
212 | } | ||
213 | } | ||
214 | }); | ||
215 | |||
216 | std::this_thread::sleep_for(std::chrono::minutes(1)); | ||
217 | |||
218 | // Every once in a while, check if we've lost any followers, and if we have, | ||
219 | // unfollow the people who have unfollowed us. | ||
220 | for (;;) | ||
221 | { | ||
222 | try | ||
223 | { | ||
224 | std::set<twitter::user_id> friends = client.getFriends(); | ||
225 | std::set<twitter::user_id> followers = client.getFollowers(); | ||
226 | |||
227 | std::list<twitter::user_id> oldFriends; | ||
228 | std::set_difference( | ||
229 | std::begin(friends), | ||
230 | std::end(friends), | ||
231 | std::begin(followers), | ||
232 | std::end(followers), | ||
233 | std::back_inserter(oldFriends)); | ||
234 | |||
235 | for (auto f : oldFriends) | ||
236 | { | ||
237 | client.unfollow(f); | ||
238 | } | ||
239 | |||
240 | std::list<twitter::user_id> newFollowers; | ||
241 | std::set_difference( | ||
242 | std::begin(followers), | ||
243 | std::end(followers), | ||
244 | std::begin(friends), | ||
245 | std::end(friends), | ||
246 | std::back_inserter(newFollowers)); | ||
247 | |||
248 | for (auto f : newFollowers) | ||
249 | { | ||
250 | client.follow(f); | ||
251 | } | ||
252 | } catch (const twitter::twitter_error& e) | ||
253 | { | ||
254 | std::cout << "Twitter error: " << e.what() << std::endl; | ||
255 | } | ||
256 | |||
257 | std::this_thread::sleep_for(std::chrono::hours(4)); | ||
258 | } | ||
259 | } | ||
260 | |||