diff options
Diffstat (limited to 'father.cpp')
-rw-r--r-- | father.cpp | 190 |
1 files changed, 144 insertions, 46 deletions
diff --git a/father.cpp b/father.cpp index d95f506..1bef9e3 100644 --- a/father.cpp +++ b/father.cpp | |||
@@ -1,4 +1,4 @@ | |||
1 | #include <twitter.h> | 1 | #include <mastodonpp/mastodonpp.hpp> |
2 | #include <random> | 2 | #include <random> |
3 | #include <yaml-cpp/yaml.h> | 3 | #include <yaml-cpp/yaml.h> |
4 | #include <iostream> | 4 | #include <iostream> |
@@ -11,6 +11,8 @@ | |||
11 | #include <iterator> | 11 | #include <iterator> |
12 | #include <verbly.h> | 12 | #include <verbly.h> |
13 | #include <hkutil/string.h> | 13 | #include <hkutil/string.h> |
14 | #include <json.hpp> | ||
15 | #include "timeline.h" | ||
14 | 16 | ||
15 | // Sync followers every 4 hours. | 17 | // Sync followers every 4 hours. |
16 | const int CHECK_FOLLOWERS_EVERY = 4 * 60 / 5; | 18 | const int CHECK_FOLLOWERS_EVERY = 4 * 60 / 5; |
@@ -32,6 +34,46 @@ verbly::word findWordOfType( | |||
32 | } | 34 | } |
33 | } | 35 | } |
34 | 36 | ||
37 | std::set<std::string> getPaginatedList( | ||
38 | mastodonpp::Connection& connection, | ||
39 | mastodonpp::API::endpoint_type endpoint, | ||
40 | std::string account_id) | ||
41 | { | ||
42 | std::set<std::string> result; | ||
43 | |||
44 | mastodonpp::parametermap parameters; | ||
45 | for (;;) | ||
46 | { | ||
47 | parameters["id"] = account_id; | ||
48 | |||
49 | auto answer = connection.get(endpoint, parameters); | ||
50 | if (!answer) | ||
51 | { | ||
52 | if (answer.curl_error_code == 0) | ||
53 | { | ||
54 | std::cout << "HTTP status: " << answer.http_status << std::endl; | ||
55 | } | ||
56 | else | ||
57 | { | ||
58 | std::cout << "libcurl error " << std::to_string(answer.curl_error_code) | ||
59 | << ": " << answer.error_message << std::endl; | ||
60 | } | ||
61 | return {}; | ||
62 | } | ||
63 | |||
64 | parameters = answer.next(); | ||
65 | if (parameters.empty()) break; | ||
66 | |||
67 | nlohmann::json body = nlohmann::json::parse(answer.body); | ||
68 | for (const auto& item : body) | ||
69 | { | ||
70 | result.insert(item["id"].get<std::string>()); | ||
71 | } | ||
72 | } | ||
73 | |||
74 | return result; | ||
75 | } | ||
76 | |||
35 | int main(int argc, char** argv) | 77 | int main(int argc, char** argv) |
36 | { | 78 | { |
37 | std::random_device randomDevice; | 79 | std::random_device randomDevice; |
@@ -48,17 +90,38 @@ int main(int argc, char** argv) | |||
48 | 90 | ||
49 | verbly::database database(config["verbly_datafile"].as<std::string>()); | 91 | verbly::database database(config["verbly_datafile"].as<std::string>()); |
50 | 92 | ||
51 | twitter::auth auth( | 93 | mastodonpp::Instance instance{ |
52 | config["consumer_key"].as<std::string>(), | 94 | config["mastodon_instance"].as<std::string>(), |
53 | config["consumer_secret"].as<std::string>(), | 95 | config["mastodon_token"].as<std::string>()}; |
54 | config["access_key"].as<std::string>(), | 96 | mastodonpp::Connection connection{instance}; |
55 | config["access_secret"].as<std::string>()); | ||
56 | 97 | ||
57 | auto startedTime = std::chrono::system_clock::now(); | 98 | nlohmann::json account_details; |
99 | { | ||
100 | const mastodonpp::parametermap parameters {}; | ||
101 | auto answer = connection.get(mastodonpp::API::v1::accounts_verify_credentials, parameters); | ||
102 | if (!answer) | ||
103 | { | ||
104 | if (answer.curl_error_code == 0) | ||
105 | { | ||
106 | std::cout << "HTTP status: " << answer.http_status << std::endl; | ||
107 | } | ||
108 | else | ||
109 | { | ||
110 | std::cout << "libcurl error " << std::to_string(answer.curl_error_code) | ||
111 | << ": " << answer.error_message << std::endl; | ||
112 | } | ||
113 | return 1; | ||
114 | } | ||
115 | std::cout << answer.body << std::endl; | ||
116 | account_details = nlohmann::json::parse(answer.body); | ||
117 | } | ||
118 | |||
119 | timeline home_timeline(mastodonpp::API::v1::timelines_home); | ||
120 | home_timeline.poll(connection); // just ignore the results | ||
58 | 121 | ||
59 | twitter::client client(auth); | 122 | auto startedTime = std::chrono::system_clock::now(); |
60 | 123 | ||
61 | std::set<twitter::user_id> friends; | 124 | std::set<std::string> friends; |
62 | int followerTimeout = 0; | 125 | int followerTimeout = 0; |
63 | 126 | ||
64 | for (;;) | 127 | for (;;) |
@@ -68,11 +131,17 @@ int main(int argc, char** argv) | |||
68 | // Sync friends with followers. | 131 | // Sync friends with followers. |
69 | try | 132 | try |
70 | { | 133 | { |
71 | friends = client.getFriends(); | 134 | friends = getPaginatedList( |
135 | connection, | ||
136 | mastodonpp::API::v1::accounts_id_following, | ||
137 | account_details["id"].get<std::string>()); | ||
72 | 138 | ||
73 | std::set<twitter::user_id> followers = client.getFollowers(); | 139 | std::set<std::string> followers = getPaginatedList( |
140 | connection, | ||
141 | mastodonpp::API::v1::accounts_id_followers, | ||
142 | account_details["id"].get<std::string>()); | ||
74 | 143 | ||
75 | std::list<twitter::user_id> oldFriends; | 144 | std::list<std::string> oldFriends; |
76 | std::set_difference( | 145 | std::set_difference( |
77 | std::begin(friends), | 146 | std::begin(friends), |
78 | std::end(friends), | 147 | std::end(friends), |
@@ -80,7 +149,7 @@ int main(int argc, char** argv) | |||
80 | std::end(followers), | 149 | std::end(followers), |
81 | std::back_inserter(oldFriends)); | 150 | std::back_inserter(oldFriends)); |
82 | 151 | ||
83 | std::set<twitter::user_id> newFollowers; | 152 | std::set<std::string> newFollowers; |
84 | std::set_difference( | 153 | std::set_difference( |
85 | std::begin(followers), | 154 | std::begin(followers), |
86 | std::end(followers), | 155 | std::end(followers), |
@@ -88,24 +157,44 @@ int main(int argc, char** argv) | |||
88 | std::end(friends), | 157 | std::end(friends), |
89 | std::inserter(newFollowers, std::begin(newFollowers))); | 158 | std::inserter(newFollowers, std::begin(newFollowers))); |
90 | 159 | ||
91 | for (twitter::user_id f : oldFriends) | 160 | for (const std::string& f : oldFriends) |
92 | { | 161 | { |
93 | client.unfollow(f); | 162 | const mastodonpp::parametermap parameters {{"id", f}}; |
163 | auto answer = connection.post(mastodonpp::API::v1::accounts_id_unfollow, parameters); | ||
164 | if (!answer) | ||
165 | { | ||
166 | if (answer.curl_error_code == 0) | ||
167 | { | ||
168 | std::cout << "HTTP status: " << answer.http_status << std::endl; | ||
169 | } | ||
170 | else | ||
171 | { | ||
172 | std::cout << "libcurl error " << std::to_string(answer.curl_error_code) | ||
173 | << ": " << answer.error_message << std::endl; | ||
174 | } | ||
175 | } | ||
94 | } | 176 | } |
95 | 177 | ||
96 | std::list<twitter::user> newFollowerObjs = | 178 | for (const std::string& f : newFollowers) |
97 | client.hydrateUsers(std::move(newFollowers)); | ||
98 | |||
99 | for (twitter::user f : newFollowerObjs) | ||
100 | { | 179 | { |
101 | if (!f.isProtected()) | 180 | const mastodonpp::parametermap parameters {{"id", f}, {"reblogs", "false"}}; |
181 | auto answer = connection.post(mastodonpp::API::v1::accounts_id_follow, parameters); | ||
182 | if (!answer) | ||
102 | { | 183 | { |
103 | client.follow(f); | 184 | if (answer.curl_error_code == 0) |
185 | { | ||
186 | std::cout << "HTTP status: " << answer.http_status << std::endl; | ||
187 | } | ||
188 | else | ||
189 | { | ||
190 | std::cout << "libcurl error " << std::to_string(answer.curl_error_code) | ||
191 | << ": " << answer.error_message << std::endl; | ||
192 | } | ||
104 | } | 193 | } |
105 | } | 194 | } |
106 | } catch (const twitter::twitter_error& error) | 195 | } catch (const std::exception& error) |
107 | { | 196 | { |
108 | std::cout << "Twitter error while syncing followers: " << error.what() | 197 | std::cout << "Error while syncing followers: " << error.what() |
109 | << std::endl; | 198 | << std::endl; |
110 | } | 199 | } |
111 | 200 | ||
@@ -117,25 +206,32 @@ int main(int argc, char** argv) | |||
117 | try | 206 | try |
118 | { | 207 | { |
119 | // Poll the timeline. | 208 | // Poll the timeline. |
120 | std::list<twitter::tweet> tweets = client.getHomeTimeline().poll(); | 209 | std::list<nlohmann::json> posts = home_timeline.poll(connection); |
121 | 210 | ||
122 | for (twitter::tweet& tweet : tweets) | 211 | for (const nlohmann::json& post : posts) |
123 | { | 212 | { |
124 | auto createdTime = | ||
125 | std::chrono::system_clock::from_time_t(tweet.getCreatedAt()); | ||
126 | |||
127 | if ( | 213 | if ( |
128 | // Only monitor people you are following | 214 | // Only monitor people you are following |
129 | friends.count(tweet.getAuthor().getID()) | 215 | friends.count(post["account"]["id"].get<std::string>()) |
130 | // Ignore tweets from before the bot started up | ||
131 | && createdTime > startedTime | ||
132 | // Ignore retweets | 216 | // Ignore retweets |
133 | && !tweet.isRetweet() | 217 | && post["reblog"].is_null()) |
134 | // Ignore messages | ||
135 | && tweet.getText().front() != '@') | ||
136 | { | 218 | { |
219 | std::string post_content = post["content"].get<std::string>(); | ||
220 | std::string::size_type pos; | ||
221 | while ((pos = post_content.find("<")) != std::string::npos) | ||
222 | { | ||
223 | std::string prefix = post_content.substr(0, pos); | ||
224 | std::string rest = post_content.substr(pos); | ||
225 | std::string::size_type right_pos = rest.find(">"); | ||
226 | if (right_pos == std::string::npos) { | ||
227 | post_content = prefix; | ||
228 | } else { | ||
229 | post_content = prefix + rest.substr(right_pos); | ||
230 | } | ||
231 | } | ||
232 | |||
137 | std::vector<std::string> tokens = | 233 | std::vector<std::string> tokens = |
138 | hatkirby::split<std::vector<std::string>>(tweet.getText(), " "); | 234 | hatkirby::split<std::vector<std::string>>(post_content, " "); |
139 | 235 | ||
140 | std::vector<std::string> canonical; | 236 | std::vector<std::string> canonical; |
141 | for (std::string token : tokens) | 237 | for (std::string token : tokens) |
@@ -212,21 +308,23 @@ int main(int argc, char** argv) | |||
212 | name)), | 308 | name)), |
213 | "I'm Dad."}; | 309 | "I'm Dad."}; |
214 | 310 | ||
215 | std::string result = | 311 | std::string result = "@" + post["account"]["acct"].get<std::string>() + " " + action.compile(); |
216 | tweet.generateReplyPrefill(client.getUser()) | ||
217 | + action.compile(); | ||
218 | 312 | ||
219 | std::cout << result << std::endl; | 313 | mastodonpp::parametermap parameters{ |
314 | {"status", result}, | ||
315 | {"in_reply_to_id", post["id"].get<std::string>()}}; | ||
220 | 316 | ||
221 | if (result.length() <= 140) | 317 | auto answer{connection.post(mastodonpp::API::v1::statuses, parameters)}; |
318 | if (!answer) | ||
222 | { | 319 | { |
223 | try | 320 | if (answer.curl_error_code == 0) |
224 | { | 321 | { |
225 | client.replyToTweet(result, tweet); | 322 | std::cout << "HTTP status: " << answer.http_status << std::endl; |
226 | } catch (const twitter::twitter_error& e) | 323 | } |
324 | else | ||
227 | { | 325 | { |
228 | std::cout << "Twitter error while tweeting: " << e.what() | 326 | std::cout << "libcurl error " << std::to_string(answer.curl_error_code) |
229 | << std::endl; | 327 | << ": " << answer.error_message << std::endl; |
230 | } | 328 | } |
231 | } | 329 | } |
232 | } | 330 | } |
@@ -234,7 +332,7 @@ int main(int argc, char** argv) | |||
234 | } | 332 | } |
235 | } | 333 | } |
236 | } | 334 | } |
237 | } catch (const twitter::rate_limit_exceeded&) | 335 | } catch (const std::exception&) |
238 | { | 336 | { |
239 | // Wait out the rate limit (10 minutes here and 5 below = 15). | 337 | // Wait out the rate limit (10 minutes here and 5 below = 15). |
240 | std::this_thread::sleep_for(std::chrono::minutes(10)); | 338 | std::this_thread::sleep_for(std::chrono::minutes(10)); |