summary refs log tree commit diff stats
path: root/father.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'father.cpp')
-rw-r--r--father.cpp190
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.
16const int CHECK_FOLLOWERS_EVERY = 4 * 60 / 5; 18const int CHECK_FOLLOWERS_EVERY = 4 * 60 / 5;
@@ -32,6 +34,46 @@ verbly::word findWordOfType(
32 } 34 }
33} 35}
34 36
37std::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
35int main(int argc, char** argv) 77int 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));