123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- #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::string cADMIN; // никнейм админа
- std::string cERROR; // сообщение об ошибке
- std::string cSUCCESS; // сообщение об успехе
- std::string cLOGPATH; // директория с логами
- std::string cFIND; // команда поиска
- std::string cNOTFOUND; // поиск не увенчался успехом
- std::string cFINDZERO; // команда поиска без параметров
- std::string cLINKS; // ссылки на лог (в конце выдачи в ЛС)
- std::string cTRYLATER; // "перегрузка, попробуйте позже"
- std::string cHELP; // подсказка
- uint8_t cMINLEN; // минимальная длина искомого слова
- std::map<std::string, std::string> conf;
- std::map<std::string, std::string> custom;
- 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, cLINKS);
- 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 = cLOGPATH + 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(cLOGPATH)) return values;
- boost::filesystem::recursive_directory_iterator dir(cLOGPATH), 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];
- }
- 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"))
- {
- conf[child.first] = child.second.get_value<std::string>();
- }
- cADMIN = conf["admin"];
- cERROR = conf["error"];
- cSUCCESS = conf["success"];
- cLOGPATH = conf["logpath"];
- cFIND = conf["find"];
- cNOTFOUND = conf["notfound"];
- cFINDZERO = conf["findzero"];
- cLINKS = conf["links"];
- cTRYLATER = conf["trylater"];
- cHELP = conf["help"];
- cMINLEN = std::stoi(conf["minlen"]);
- for (auto child: pt.get_child("custom"))
- {
- custom[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(cLOGPATH))
- {
- if (! boost::filesystem::exists(cLOGPATH + slash + year + slash + month))
- {
- boost::filesystem::create_directories(cLOGPATH + slash + year + slash + month);
- }
- out.open (cLOGPATH + 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() == cADMIN && (msg.find("reload") == 0)) //// Reload
- {
- if (read_config()) tsc->write_to_channel(cSUCCESS);
- else tsc->write_to_channel(cERROR); // FIXME - падение в случае неудачи
- }
- else if (msg.find(cFIND) == 0) //// Поиск
- {
- std::regex date_check(cFIND + " [0-9]{4}.[0-9]{2}.[0-9]{2}.*", std::regex_constants::egrep);
- if (msg.length() < cFIND.length() + cMINLEN + 1)
- { // Защита от коротких запросов
- tsc->write_to_channel(tsc->get_msg_nick() + ", " + cERROR
- + " (" + std::to_string(cMINLEN) + ")");
- }
- else if (msg.find(' ') == std::string::npos)
- { // Пустой запрос (без пробела после ключевого слова поиска)
- tsc->write_to_channel(tsc->get_msg_nick() + ", " + cFINDZERO);
- }
- else if (std::regex_match(msg, date_check)) { //// Запрос по дате
- std::string pattern;
- std::string date = msg.substr(cFIND.size()+1, 10); // 10 == date size
- if (msg.substr(cFIND.size()+11).find(' ') != std::string::npos)
- { // Поиск по слову
- pattern = msg.substr(cFIND.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() + ", " + cSUCCESS);
- 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() + ", " + cTRYLATER);
- }
- }
- else tsc->write_to_channel(tsc->get_msg_nick() + ", " + cERROR);
- }
- else { //// Поиск с минимальной выдачей
- std::string target = msg.substr(cFIND.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() + ", " + cNOTFOUND);
- }
- }
- else // Обработчик кастомных ответов
- {
- handled = false;
- for (auto value: custom)
- {
- 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() + ", " + cHELP);
- }
- }
- 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(cADMIN, 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;
- }
|