ircabot/main.cpp

408 lines
14 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include <iostream>
#include <fstream>
#include <iomanip>
#include <regex>
#include <algorithm>
#include <vector>
#include <map>
#include <thread>
#include <boost/filesystem.hpp>
#include "tcpsyncclient.h"
boost::asio::io_service service;
std::string config_file = "ircbot.json";
TcpSyncClient * volatile tsc = nullptr;
bool tsc_created = false;
#ifdef WIN32
std::string slash = "\\";
#else
std::string slash = "/";
#endif
void log(std::string text)
{
std::cout << "[DBG] " << text << std::endl;
}
std::map<std::string, std::string> conf =
{
{ "admin" , "" }, // никнейм админа
{ "error" , "" }, // сообщение об ошибке
{ "success" , "" }, // сообщение об успехе
{ "logpath" , "" }, // директория с логами
{ "find" , "" }, // команда поиска
{ "notfound", "" }, // поиск не увенчался успехом
{ "findzero", "" }, // команда поиска без параметров
{ "links" , "" }, // ссылки на лог (в конце выдачи в ЛС)
{ "trylater", "" } // "перегрузка, попробуйте позже"
};
std::mutex mtx;
std::vector<std::string> vectorStringsTransit;
std::string stringNickTransit;
constexpr unsigned sendVectorToUser_MAXIMUM = 3;
unsigned sendVectorToUser_COUNTER = 0;
void sendVectorToUser()
{
log ("sendVectorToUser+ " + std::to_string(++sendVectorToUser_COUNTER));
mtx.lock();
std::vector<std::string> messages = vectorStringsTransit;
vectorStringsTransit.clear();
std::string nick = stringNickTransit;
stringNickTransit.clear();
mtx.unlock();
int messageCounter = 0;
bool stopped = false;
for (auto str: messages)
{
if (tsc->have_pm_from_user(nick)) { // Ник появился в стопе
stopped = true;
break;
}
if (messageCounter++ < 20) {
tsc->write_to_user(nick, str);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
else {
messageCounter = 0;
std::this_thread::sleep_for(std::chrono::seconds(2));
tsc->write_to_user(nick, str);
}
}
tsc->write_to_user(nick, stopped ? "*** STOP ***" : "*** END ***");
tsc->write_to_user(nick, conf["links"]);
log ("sendVectorToUser+ " + std::to_string(--sendVectorToUser_COUNTER));
}
std::vector<std::string> search_detail(std::string date, std::string text)
{
std::string year = date.substr(0, 4); // YYYY
std::string month = date.substr(year.size()+1, 2); // MM
std::string day = date.substr(year.size() + month.size() + 2, 2); // DD
std::vector<std::string> result;
log ("search_detail() " + year + "-" + month + "-" + day + " '" + text + "'");
std::regex regex;
if (text != "") // Нужен не весь лог, а конкретные сообщения
{
std::regex r(".*" + text + ".*", std::regex_constants::basic | std::regex_constants::icase);
regex = r;
}
std::string path = conf["logpath"] + slash + year + slash + month + slash + day + ".txt";
if (! boost::filesystem::exists(path)) return result;
std::ifstream log(path);
std::string buffer;
while(getline(log, buffer))
{
if (text == "") result.push_back(buffer);
else if (std::regex_match(buffer, regex)) result.push_back(buffer);
}
log.close();
return result;
}
std::string search(std::string text)
{
std::string values; // Строка для возврата
uint64_t success = 0; // Счетчик уникальных вхождений
uint64_t total = 0; // Общий счетчик вхождений
std::map<std::string, uint64_t> stats; // Линковка даты и ее счетчика
std::vector<std::string> matches; // Значения, компонуемые в итоговую строку
std::regex regex(".*" + text + ".*", std::regex_constants::basic | std::regex_constants::icase);
if (! boost::filesystem::exists(conf["logpath"])) return values;
boost::filesystem::recursive_directory_iterator dir(conf["logpath"]), end;
for (; dir != end; ++dir)
{
if (boost::filesystem::is_directory(dir->path())) continue;
std::string buffer;
std::ifstream log(dir->path().c_str());
while(getline(log, buffer))
{
if (std::regex_match(buffer, regex))
{
++total;
bool first = true;
std::string date = buffer.substr(0, buffer.find(' '));
stats[date] += 1; // Счетчик конкретной даты
for (auto entry: matches)
{
if (entry.find(date) != std::string::npos)
{
first = false;
}
}
if (first)
{
matches.push_back(date);
++success;
}
}
}
log.close();
}
if (matches.size() > 0)
{
for (auto it = matches.begin(), end = matches.end(); it != end; ++it)
{
*it += " (" + std::to_string(stats[*it]) + ")";
}
std::sort(matches.begin(), matches.end());
values += "[" + text + ": " + std::to_string(total) + "] ";
for (int i = matches.size()-1, count = 0; i >= 0; --i, ++count)
{ // Компоновка выходной строки
if (values.find('-') != std::string::npos) values += ", ";
values += matches[i];
}
for (auto it = values.begin(), end = values.end(); it != end; ++it)
{ // Замена тире на точку
if (*it == '-') *it = '.';
}
values += ".";
}
return values;
}
bool read_config()
{
if (!boost::filesystem::exists(config_file)) {
log ("Config not found");
return false;
}
boost::property_tree::ptree pt;
boost::property_tree::read_json(config_file, pt);
for (auto child: pt.get_child("handler"))
{
for (size_t i = 0; i < conf.size(); ++i)
{
conf[child.first] = child.second.get_value<std::string>();
}
}
return true;
}
int write_log(std::string msg)
{
time_t now = time(0); // Парсинг год/месяц/день
tm *gmtm = gmtime(&now);
std::string year = std::to_string (1900 + gmtm->tm_year);
std::string month = std::to_string (1 + gmtm->tm_mon);
month.shrink_to_fit();
if (month.size() < 2) month = "0" + month;
std::string day = std::to_string(gmtm->tm_mday);
day.shrink_to_fit();
if (day.size() < 2) day = "0" + day;
std::ofstream out;
if (boost::filesystem::exists(conf["logpath"]))
{
if (! boost::filesystem::exists(conf["logpath"] + slash + year + slash + month))
{
boost::filesystem::create_directories(conf["logpath"] + slash + year + slash + month);
}
out.open (conf["logpath"] + slash + year + slash + month + slash + day + ".txt", std::ios::app);
if (! out.is_open()) return 1;
}
else return 2;
out << year << "-" << month << "-" << day << " " << msg << std::endl;
out.close();
return 0;
}
void usage(std::string path)
{
std::cout << "Usage: " << path << " <path/to/config.json>" << std::endl;
}
void make_tsc()
{
tsc = new TcpSyncClient(service, config_file);
for(int i=0; i<2; ++i)
{
if (tsc != nullptr)
{
if (tsc->to_start)
{
tsc->start();
tsc_created = true;
return;
}
}
else {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
std::cerr << "make_tsc() time" << std::endl;
if (tsc != nullptr) delete tsc;
}
void handler()
{
if (!tsc_created) std::this_thread::sleep_for(std::chrono::milliseconds(600));
bool handled = false;
while (true)
{
if (tsc->to_read) { // Есть сообщения, адресованные боту
std::string msg = tsc->get_msg();
if (tsc->get_msg_nick() == conf["admin"] && (msg.find("reload") == 0)) //// Reload
{
if (read_config()) tsc->write_to_channel(conf["success"]);
else tsc->write_to_channel("Ошибка.");
}
else if (msg.find(conf["find"]) == 0) //// Поиск
{
std::regex date_check(conf["find"] + " [0-9]{4}.[0-9]{2}.[0-9]{2}.*", std::regex_constants::egrep);
if (msg.find('*') != std::string::npos || msg.find('.') != std::string::npos) {
// Защита от хитрой регулярки
tsc->write_to_channel(tsc->get_msg_nick() + ", " + conf["error"]);
}
else if (msg.find(' ') == std::string::npos) {
tsc->write_to_channel(tsc->get_msg_nick() + ", " + conf["findzero"]);
}
else if (std::regex_match(msg, date_check)) { //// Запрос по дате
std::string pattern;
std::string date = msg.substr(conf["find"].size()+1, 10); // 10 == date size
if (msg.substr(conf["find"].size()+11).find(' ') != std::string::npos)
{ // Поиск по слову
pattern = msg.substr(conf["find"].size() + date.size() + 2);
}
std::vector<std::string> result = search_detail(date, pattern);
if (! result.empty())
{
std::string nick = tsc->get_msg_nick();
std::string header = date;
if (pattern != "") header += " # " + pattern;
if (sendVectorToUser_COUNTER < sendVectorToUser_MAXIMUM)
{
tsc->write_to_channel(tsc->get_msg_nick() + ", " + conf["success"]);
tsc->write_to_user(nick, "[" + header + "]");
mtx.lock();
vectorStringsTransit = result;
stringNickTransit = nick;
mtx.unlock();
std::thread (sendVectorToUser).detach();
}
else {
tsc->write_to_channel(tsc->get_msg_nick() + ", " + conf["trylater"]);
}
}
else tsc->write_to_channel(tsc->get_msg_nick() + ", " + conf["error"]);
}
else { //// Поиск с минимальной выдачей
std::string target = msg.substr(conf["find"].size()+1);
std::string result = search(target);
if (result != "")
{
int shift = 0; // Для корректного переноса по сообщениям
constexpr size_t partsize = 350;
for (size_t i = 0; i <= result.size() / partsize; ++i)
{
int start = i * partsize;
if (shift)
{
start -= shift;
shift = 0;
}
if (result.size() > (i+1)*partsize)
{
std::string push = result.substr(start, partsize);
while (push.back() != ',') {
push.pop_back();
++shift;
}
tsc->write_to_channel (push);
}
else {
tsc->write_to_channel (result.substr(start));
}
}
}
else tsc->write_to_channel(tsc->get_msg_nick() + ", " + conf["notfound"]);
}
}
else // Общий обработчик
{
handled = false;
for (auto value: conf)
{
if (msg.find(value.first) != std::string::npos)
{
tsc->write_to_channel(tsc->get_msg_nick() + ", " + value.second);
handled = true;
break;
}
}
if (!handled) tsc->write_to_channel(tsc->get_msg_nick() + ", " + conf["help"]);
}
}
if (tsc->to_raw) { // Все сообщения на канале
std::string raw = tsc->get_raw();
int res = write_log ("[" + tsc->get_raw_nick() + "] " + raw);
if (res) { // Сообщение администратору об ошибке логирования
std::string report = "Can't write to log. ";
if (res == 1) report += "Can't open the file.";
if (res == 2) report += "Logpath not found.";
tsc->write_to_user(conf["admin"], report);
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
int main(int argc, char * argv[])
{
if (argc > 1) config_file = static_cast<std::string>(argv[1]);
else {
usage(static_cast<std::string>(argv[0]));
return 1;
}
if (!read_config()) return 2;
std::thread connection(make_tsc);
handler();
connection.join();
return 3;
}