2018-02-27 23:38:05 +01:00
|
|
|
/* This file is part of mastobotmon.
|
|
|
|
* Copyright © 2018 tastytea <tastytea@tastytea.de>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, version 3.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
#include <string>
|
2018-03-02 08:48:02 +01:00
|
|
|
#include <cstring>
|
2018-02-27 23:38:05 +01:00
|
|
|
#include <cstdint>
|
2018-02-28 06:23:42 +01:00
|
|
|
#include <vector>
|
2018-03-02 06:11:18 +01:00
|
|
|
#include <chrono>
|
|
|
|
#include <ctime>
|
|
|
|
#include <sstream>
|
|
|
|
#include <iomanip> // get_time
|
2018-03-07 11:41:16 +01:00
|
|
|
#include <fstream>
|
|
|
|
#include <regex>
|
2018-03-07 09:21:13 +01:00
|
|
|
#include <jsoncpp/json/json.h>
|
2018-02-27 23:38:05 +01:00
|
|
|
#include "version.hpp"
|
|
|
|
#include "mastobotmon.hpp"
|
|
|
|
|
|
|
|
using std::cout;
|
|
|
|
using std::cerr;
|
|
|
|
using std::cin;
|
|
|
|
using std::string;
|
2018-03-02 06:11:18 +01:00
|
|
|
using std::uint16_t;
|
2018-02-27 23:38:05 +01:00
|
|
|
|
2018-03-07 18:33:13 +01:00
|
|
|
Json::Value config; // Declared in mastobotmon.hpp
|
|
|
|
|
|
|
|
const bool write_mentions(const string &straccount, Json::Value &mentions)
|
2018-03-07 11:41:16 +01:00
|
|
|
{
|
2018-03-07 18:33:13 +01:00
|
|
|
const string filepath = config["data_dir"].asString() + "/mentions_" + straccount + ".csv";
|
2018-03-07 11:41:16 +01:00
|
|
|
const std::regex restrip("<[^>]*>");
|
|
|
|
|
|
|
|
std::ofstream outfile(filepath, std::ios::app);
|
|
|
|
if (outfile.is_open())
|
|
|
|
{
|
|
|
|
string output;
|
|
|
|
for (auto &mention : mentions)
|
|
|
|
{
|
|
|
|
output = mention["status"]["account"]["acct"].asString() + ';';
|
|
|
|
output += mention["status"]["created_at"].asString() + ';';
|
|
|
|
output += mention["status"]["content"].asString() + '\n';
|
|
|
|
output = std::regex_replace(output, restrip, "");
|
|
|
|
outfile.write(output.c_str(), output.length());
|
|
|
|
}
|
|
|
|
outfile.close();
|
2018-03-07 18:33:13 +01:00
|
|
|
cout << "New mentions in: " << filepath << '\n';
|
2018-03-07 11:41:16 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-07 18:33:13 +01:00
|
|
|
cerr << "Error writing file: " << filepath << '\n';
|
2018-03-07 11:41:16 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-15 14:35:58 +01:00
|
|
|
const bool write_statistics(const string &straccount, Json::Value &account_json)
|
|
|
|
{
|
|
|
|
const string filepath = config["data_dir"].asString() + "/statistics_" + straccount + ".csv";
|
|
|
|
|
|
|
|
std::ofstream outfile(filepath, std::ios::app);
|
|
|
|
if (outfile.is_open())
|
|
|
|
{
|
|
|
|
string output;
|
|
|
|
std::chrono::time_point<std::chrono::system_clock> now = std::chrono::system_clock::now();
|
|
|
|
std::time_t now_t = std::chrono::system_clock::to_time_t(now);
|
|
|
|
std::tm now_tm = *std::localtime(&now_t);
|
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
ss << std::put_time(&now_tm, "%Y-%m-%dT%T");
|
|
|
|
output = ss.str() + ';';
|
|
|
|
output += account_json["statuses_count"].asString() + ';';
|
2018-03-15 19:02:27 +01:00
|
|
|
output += account_json["followers_count"].asString() + '\n';
|
2018-03-15 14:35:58 +01:00
|
|
|
outfile.write(output.c_str(), output.length());
|
|
|
|
outfile.close();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
cerr << "Error writing file: " << filepath << '\n';
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-02-27 23:38:05 +01:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2018-03-07 11:41:16 +01:00
|
|
|
uint16_t mainret = 0;
|
2018-03-07 18:33:13 +01:00
|
|
|
if (!read_config())
|
2018-02-27 23:38:05 +01:00
|
|
|
{
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-03-02 08:48:02 +01:00
|
|
|
if (argc > 1)
|
|
|
|
{
|
|
|
|
if ((std::strncmp(argv[1], "add", 3)) == 0)
|
|
|
|
{
|
2018-03-07 18:33:13 +01:00
|
|
|
add_account();
|
2018-03-02 08:48:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-28 06:23:42 +01:00
|
|
|
std::vector<Account> accounts;
|
|
|
|
|
2018-03-07 18:33:13 +01:00
|
|
|
for (auto it = config["accounts"].begin(); it != config["accounts"].end(); ++it)
|
2018-02-27 23:38:05 +01:00
|
|
|
{
|
2018-02-28 22:16:56 +01:00
|
|
|
// Construct an Account object for every account and store it in a vector
|
2018-03-07 09:21:13 +01:00
|
|
|
string instance = it.name();
|
2018-02-28 06:23:42 +01:00
|
|
|
instance = instance.substr(instance.find('@') + 1);
|
2018-02-28 22:16:56 +01:00
|
|
|
|
2018-03-07 09:21:13 +01:00
|
|
|
Account *acc = new Account(instance, (*it)["access_token"].asString());
|
2018-02-28 22:16:56 +01:00
|
|
|
acc->set_useragent("mastobotmon/" + string(global::version));
|
2018-03-07 09:21:13 +01:00
|
|
|
acc->set_minutes((*it)["minutes"].asUInt());
|
2018-03-07 11:41:16 +01:00
|
|
|
if (!(*it)["last_mention"].empty())
|
|
|
|
{
|
|
|
|
acc->set_last_mention_id((*it)["last_mention"].asUInt64());
|
|
|
|
}
|
2018-02-28 22:16:56 +01:00
|
|
|
accounts.push_back(*acc);
|
2018-02-28 06:23:42 +01:00
|
|
|
}
|
|
|
|
|
2018-03-07 18:33:13 +01:00
|
|
|
if (config["mode"] == "cron")
|
2018-02-28 06:23:42 +01:00
|
|
|
{
|
|
|
|
for (Account &acc : accounts)
|
|
|
|
{
|
|
|
|
std::string answer;
|
2018-02-28 22:16:56 +01:00
|
|
|
uint16_t ret = acc.get(Mastodon::API::v1::accounts_verify_credentials, answer);
|
2018-03-11 15:25:43 +01:00
|
|
|
if (std::stoi(acc.get_header("X-RateLimit-Remaining")) < 2)
|
|
|
|
{
|
|
|
|
cerr << "ERROR: Reached limit of API calls.\n";
|
|
|
|
cerr << "Counter will reset at " << acc.get_header("X-RateLimit-Reset") << '\n';
|
|
|
|
return 2;
|
|
|
|
}
|
2018-02-28 22:16:56 +01:00
|
|
|
if (ret == 0)
|
|
|
|
{
|
2018-03-07 09:21:13 +01:00
|
|
|
Json::Value json;
|
|
|
|
Json::Reader reader;
|
|
|
|
reader.parse(answer, json);
|
|
|
|
const string id = json["id"].asString();
|
2018-03-15 19:02:27 +01:00
|
|
|
const string straccount = json["acct"].asString() + "@" + acc.get_instance();
|
2018-03-15 14:35:58 +01:00
|
|
|
write_statistics(straccount, json);
|
2018-02-28 22:16:56 +01:00
|
|
|
|
|
|
|
Account::parametermap parameters(
|
|
|
|
{
|
|
|
|
{ "limit", { "1" } }
|
|
|
|
});
|
|
|
|
ret = acc.get(Mastodon::API::v1::accounts_id_statuses, id, parameters, answer);
|
|
|
|
if (ret == 0)
|
|
|
|
{
|
2018-03-07 09:21:13 +01:00
|
|
|
reader.parse(answer, json);
|
|
|
|
const string acct = json[0]["account"]["acct"].asString();
|
2018-03-02 06:11:18 +01:00
|
|
|
|
2018-03-07 09:21:13 +01:00
|
|
|
std::istringstream isslast(json[0]["created_at"].asString());
|
2018-03-02 06:11:18 +01:00
|
|
|
struct std::tm tm = {0};
|
|
|
|
isslast >> std::get_time(&tm, "%Y-%m-%dT%T");
|
2018-03-02 09:14:26 +01:00
|
|
|
std::time_t time = timegm(&tm);
|
2018-03-02 06:11:18 +01:00
|
|
|
|
|
|
|
const auto now = std::chrono::system_clock::now();
|
|
|
|
const auto last = std::chrono::system_clock::from_time_t(time);
|
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::minutes>(now - last);
|
|
|
|
|
|
|
|
if (elapsed.count() > acc.get_minutes())
|
|
|
|
{
|
2018-03-02 14:08:49 +01:00
|
|
|
uint16_t minutes = elapsed.count();
|
|
|
|
std::uint8_t hours = 0;
|
|
|
|
std:: uint8_t days = 0;
|
|
|
|
while (minutes >= 1440)
|
|
|
|
{
|
|
|
|
minutes -= 1440;
|
|
|
|
days += 1;
|
|
|
|
}
|
|
|
|
while (minutes >= 60)
|
|
|
|
{
|
|
|
|
minutes -= 60;
|
|
|
|
hours += 1;
|
|
|
|
}
|
|
|
|
cout << "ALERT: " << acct << " is inactive since ";
|
|
|
|
if (days > 0)
|
|
|
|
{
|
|
|
|
cout << std::to_string(days) << " days, ";
|
|
|
|
}
|
|
|
|
if (hours > 0)
|
|
|
|
{
|
|
|
|
cout << std::to_string(hours) << " hours and ";
|
|
|
|
}
|
|
|
|
cout << std::to_string(minutes) << " minutes.\n";
|
2018-03-02 06:11:18 +01:00
|
|
|
}
|
2018-03-07 11:41:16 +01:00
|
|
|
|
|
|
|
ret = acc.get_mentions(answer);
|
|
|
|
if (ret == 0)
|
|
|
|
{
|
|
|
|
reader.parse(answer, json);
|
|
|
|
if (!json.empty())
|
|
|
|
{
|
|
|
|
const std::uint64_t lastid = std::stoull(json[0]["id"].asString());
|
|
|
|
acc.set_last_mention_id(lastid);
|
2018-03-07 18:33:13 +01:00
|
|
|
config["accounts"][straccount]["last_mention"] = lastid;
|
|
|
|
write_mentions(straccount, json);
|
2018-03-07 11:41:16 +01:00
|
|
|
}
|
|
|
|
}
|
2018-02-28 22:16:56 +01:00
|
|
|
}
|
|
|
|
}
|
2018-02-28 06:23:42 +01:00
|
|
|
|
2018-02-28 22:16:56 +01:00
|
|
|
if (ret != 0)
|
|
|
|
{
|
|
|
|
cerr << "Error: " << ret << '\n';
|
2018-03-07 11:41:16 +01:00
|
|
|
mainret = ret;
|
2018-02-28 22:16:56 +01:00
|
|
|
}
|
2018-02-28 06:23:42 +01:00
|
|
|
}
|
2018-02-27 23:38:05 +01:00
|
|
|
}
|
|
|
|
|
2018-03-07 18:33:13 +01:00
|
|
|
if (!write_config())
|
2018-03-07 11:41:16 +01:00
|
|
|
{
|
|
|
|
cerr << "Couldn't write config file\n";
|
|
|
|
}
|
|
|
|
return mainret;
|
2018-02-27 23:38:05 +01:00
|
|
|
}
|