main.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. #include <iostream>
  2. #include <fstream>
  3. #include <iomanip>
  4. #include <regex>
  5. #include <algorithm>
  6. #include <vector>
  7. #include <map>
  8. #include <thread>
  9. #include <boost/filesystem.hpp>
  10. #include "tcpsyncclient.h"
  11. boost::asio::io_service service;
  12. std::string config_file = "ircbot.json";
  13. TcpSyncClient * volatile tsc = nullptr;
  14. bool tsc_created = false;
  15. #ifdef WIN32
  16. std::string slash = "\\";
  17. #else
  18. std::string slash = "/";
  19. #endif
  20. void log(std::string text)
  21. {
  22. std::cout << "[DBG] " << text << std::endl;
  23. }
  24. std::string cADMIN; // никнейм админа
  25. std::string cERROR; // сообщение об ошибке
  26. std::string cSUCCESS; // сообщение об успехе
  27. std::string cLOGPATH; // директория с логами
  28. std::string cFIND; // команда поиска
  29. std::string cNOTFOUND; // поиск не увенчался успехом
  30. std::string cFINDZERO; // команда поиска без параметров
  31. std::string cLINKS; // ссылки на лог (в конце выдачи в ЛС)
  32. std::string cTRYLATER; // "перегрузка, попробуйте позже"
  33. std::string cHELP; // подсказка
  34. uint8_t cMINLEN; // минимальная длина искомого слова
  35. std::map<std::string, std::string> conf;
  36. std::map<std::string, std::string> custom;
  37. std::mutex mtx;
  38. std::vector<std::string> vectorStringsTransit;
  39. std::string stringNickTransit;
  40. constexpr unsigned sendVectorToUser_MAXIMUM = 3;
  41. unsigned sendVectorToUser_COUNTER = 0;
  42. void sendVectorToUser()
  43. {
  44. log ("sendVectorToUser+ " + std::to_string(++sendVectorToUser_COUNTER));
  45. mtx.lock();
  46. std::vector<std::string> messages = vectorStringsTransit;
  47. vectorStringsTransit.clear();
  48. std::string nick = stringNickTransit;
  49. stringNickTransit.clear();
  50. mtx.unlock();
  51. int messageCounter = 0;
  52. bool stopped = false;
  53. for (auto str: messages)
  54. {
  55. if (tsc->have_pm_from_user(nick)) { // Ник появился в стопе
  56. stopped = true;
  57. break;
  58. }
  59. if (messageCounter++ < 20) {
  60. tsc->write_to_user(nick, str);
  61. std::this_thread::sleep_for(std::chrono::milliseconds(100));
  62. }
  63. else {
  64. messageCounter = 0;
  65. std::this_thread::sleep_for(std::chrono::seconds(2));
  66. tsc->write_to_user(nick, str);
  67. }
  68. }
  69. tsc->write_to_user(nick, stopped ? "*** STOP ***" : "*** END ***");
  70. tsc->write_to_user(nick, cLINKS);
  71. log ("sendVectorToUser- " + std::to_string(--sendVectorToUser_COUNTER));
  72. }
  73. std::vector<std::string> search_detail(std::string date, std::string text)
  74. {
  75. std::string year = date.substr(0, 4); // YYYY
  76. std::string month = date.substr(year.size()+1, 2); // MM
  77. std::string day = date.substr(year.size() + month.size() + 2, 2); // DD
  78. std::vector<std::string> result;
  79. log ("search_detail() " + year + "-" + month + "-" + day + " '" + text + "'");
  80. std::regex regex;
  81. if (text != "") // Нужен не весь лог, а конкретные сообщения
  82. {
  83. std::regex r(".*" + text + ".*", std::regex_constants::basic | std::regex_constants::icase);
  84. regex = r;
  85. }
  86. std::string path = cLOGPATH + slash + year + slash + month + slash + day + ".txt";
  87. if (! boost::filesystem::exists(path)) return result;
  88. std::ifstream log(path);
  89. std::string buffer;
  90. while(getline(log, buffer))
  91. {
  92. if (text == "") result.push_back(buffer);
  93. else if (std::regex_match(buffer, regex)) result.push_back(buffer);
  94. }
  95. log.close();
  96. return result;
  97. }
  98. std::string search(std::string text)
  99. {
  100. std::string values; // Строка для возврата
  101. uint64_t success = 0; // Счетчик уникальных вхождений
  102. uint64_t total = 0; // Общий счетчик вхождений
  103. std::map<std::string, uint64_t> stats; // Линковка даты и ее счетчика
  104. std::vector<std::string> matches; // Значения, компонуемые в итоговую строку
  105. std::regex regex(".*" + text + ".*", std::regex_constants::basic | std::regex_constants::icase);
  106. if (! boost::filesystem::exists(cLOGPATH)) return values;
  107. boost::filesystem::recursive_directory_iterator dir(cLOGPATH), end;
  108. for (; dir != end; ++dir)
  109. {
  110. if (boost::filesystem::is_directory(dir->path())) continue;
  111. std::string buffer;
  112. std::ifstream log(dir->path().c_str());
  113. while(getline(log, buffer))
  114. {
  115. if (std::regex_match(buffer, regex))
  116. {
  117. ++total;
  118. bool first = true;
  119. std::string date = buffer.substr(0, buffer.find(' '));
  120. stats[date] += 1; // Счетчик конкретной даты
  121. for (auto entry: matches)
  122. {
  123. if (entry.find(date) != std::string::npos)
  124. {
  125. first = false;
  126. }
  127. }
  128. if (first)
  129. {
  130. matches.push_back(date);
  131. ++success;
  132. }
  133. }
  134. }
  135. log.close();
  136. }
  137. if (matches.size() > 0)
  138. {
  139. for (auto it = matches.begin(), end = matches.end(); it != end; ++it)
  140. {
  141. *it += " (" + std::to_string(stats[*it]) + ")";
  142. }
  143. std::sort(matches.begin(), matches.end());
  144. values += "[" + text + ": " + std::to_string(total) + "] ";
  145. for (int i = matches.size()-1, count = 0; i >= 0; --i, ++count)
  146. { // Компоновка выходной строки
  147. if (values.find('-') != std::string::npos) values += ", ";
  148. values += matches[i];
  149. }
  150. values += ".";
  151. }
  152. return values;
  153. }
  154. bool read_config()
  155. {
  156. if (!boost::filesystem::exists(config_file)) {
  157. log ("Config not found");
  158. return false;
  159. }
  160. boost::property_tree::ptree pt;
  161. boost::property_tree::read_json(config_file, pt);
  162. for (auto child: pt.get_child("handler"))
  163. {
  164. conf[child.first] = child.second.get_value<std::string>();
  165. }
  166. cADMIN = conf["admin"];
  167. cERROR = conf["error"];
  168. cSUCCESS = conf["success"];
  169. cLOGPATH = conf["logpath"];
  170. cFIND = conf["find"];
  171. cNOTFOUND = conf["notfound"];
  172. cFINDZERO = conf["findzero"];
  173. cLINKS = conf["links"];
  174. cTRYLATER = conf["trylater"];
  175. cHELP = conf["help"];
  176. cMINLEN = std::stoi(conf["minlen"]);
  177. for (auto child: pt.get_child("custom"))
  178. {
  179. custom[child.first] = child.second.get_value<std::string>();
  180. }
  181. return true;
  182. }
  183. int write_log(std::string msg)
  184. {
  185. time_t now = time(0); // Парсинг год/месяц/день
  186. tm *gmtm = gmtime(&now);
  187. std::string year = std::to_string (1900 + gmtm->tm_year);
  188. std::string month = std::to_string (1 + gmtm->tm_mon);
  189. month.shrink_to_fit();
  190. if (month.size() < 2) month = "0" + month;
  191. std::string day = std::to_string(gmtm->tm_mday);
  192. day.shrink_to_fit();
  193. if (day.size() < 2) day = "0" + day;
  194. std::ofstream out;
  195. if (boost::filesystem::exists(cLOGPATH))
  196. {
  197. if (! boost::filesystem::exists(cLOGPATH + slash + year + slash + month))
  198. {
  199. boost::filesystem::create_directories(cLOGPATH + slash + year + slash + month);
  200. }
  201. out.open (cLOGPATH + slash + year + slash + month + slash + day + ".txt", std::ios::app);
  202. if (! out.is_open()) return 1;
  203. }
  204. else return 2;
  205. out << year << "-" << month << "-" << day << " " << msg << std::endl;
  206. out.close();
  207. return 0;
  208. }
  209. void usage(std::string path)
  210. {
  211. std::cout << "Usage: " << path << " <path/to/config.json>" << std::endl;
  212. }
  213. void make_tsc()
  214. {
  215. tsc = new TcpSyncClient(service, config_file);
  216. for(int i=0; i<2; ++i)
  217. {
  218. if (tsc != nullptr)
  219. {
  220. if (tsc->to_start)
  221. {
  222. tsc->start();
  223. tsc_created = true;
  224. return;
  225. }
  226. }
  227. else {
  228. std::this_thread::sleep_for(std::chrono::milliseconds(500));
  229. }
  230. }
  231. std::cerr << "make_tsc() time" << std::endl;
  232. if (tsc != nullptr) delete tsc;
  233. }
  234. void handler()
  235. {
  236. if (!tsc_created) std::this_thread::sleep_for(std::chrono::milliseconds(600));
  237. bool handled = false;
  238. while (true)
  239. {
  240. if (tsc->to_read) { // Есть сообщения, адресованные боту
  241. std::string msg = tsc->get_msg();
  242. if (tsc->get_msg_nick() == cADMIN && (msg.find("reload") == 0)) //// Reload
  243. {
  244. if (read_config()) tsc->write_to_channel(cSUCCESS);
  245. else tsc->write_to_channel(cERROR); // FIXME - падение в случае неудачи
  246. }
  247. else if (msg.find(cFIND) == 0) //// Поиск
  248. {
  249. std::regex date_check(cFIND + " [0-9]{4}.[0-9]{2}.[0-9]{2}.*", std::regex_constants::egrep);
  250. if (msg.length() < cFIND.length() + cMINLEN + 1)
  251. { // Защита от коротких запросов
  252. tsc->write_to_channel(tsc->get_msg_nick() + ", " + cERROR
  253. + " (" + std::to_string(cMINLEN) + ")");
  254. }
  255. else if (msg.find(' ') == std::string::npos)
  256. { // Пустой запрос (без пробела после ключевого слова поиска)
  257. tsc->write_to_channel(tsc->get_msg_nick() + ", " + cFINDZERO);
  258. }
  259. else if (std::regex_match(msg, date_check)) { //// Запрос по дате
  260. std::string pattern;
  261. std::string date = msg.substr(cFIND.size()+1, 10); // 10 == date size
  262. if (msg.substr(cFIND.size()+11).find(' ') != std::string::npos)
  263. { // Поиск по слову
  264. pattern = msg.substr(cFIND.size() + date.size() + 2);
  265. }
  266. std::vector<std::string> result = search_detail(date, pattern);
  267. if (! result.empty())
  268. {
  269. std::string nick = tsc->get_msg_nick();
  270. std::string header = date;
  271. if (pattern != "") header += " # " + pattern;
  272. if (sendVectorToUser_COUNTER < sendVectorToUser_MAXIMUM)
  273. {
  274. tsc->write_to_channel(tsc->get_msg_nick() + ", " + cSUCCESS);
  275. tsc->write_to_user(nick, "[" + header + "]");
  276. mtx.lock();
  277. vectorStringsTransit = result;
  278. stringNickTransit = nick;
  279. mtx.unlock();
  280. std::thread (sendVectorToUser).detach();
  281. }
  282. else {
  283. tsc->write_to_channel(tsc->get_msg_nick() + ", " + cTRYLATER);
  284. }
  285. }
  286. else tsc->write_to_channel(tsc->get_msg_nick() + ", " + cERROR);
  287. }
  288. else { //// Поиск с минимальной выдачей
  289. std::string target = msg.substr(cFIND.size()+1);
  290. std::string result = search(target);
  291. if (result != "")
  292. {
  293. int shift = 0; // Для корректного переноса по сообщениям
  294. constexpr size_t partsize = 350;
  295. for (size_t i = 0; i <= result.size() / partsize; ++i)
  296. {
  297. int start = i * partsize;
  298. if (shift)
  299. {
  300. start -= shift;
  301. shift = 0;
  302. }
  303. if (result.size() > (i+1)*partsize)
  304. {
  305. std::string push = result.substr(start, partsize);
  306. while (push.back() != ',') {
  307. push.pop_back();
  308. ++shift;
  309. }
  310. tsc->write_to_channel (push);
  311. }
  312. else {
  313. tsc->write_to_channel (result.substr(start));
  314. }
  315. }
  316. }
  317. else tsc->write_to_channel (tsc->get_msg_nick() + ", " + cNOTFOUND);
  318. }
  319. }
  320. else // Обработчик кастомных ответов
  321. {
  322. handled = false;
  323. for (auto value: custom)
  324. {
  325. if (msg.find(value.first) != std::string::npos)
  326. {
  327. tsc->write_to_channel(tsc->get_msg_nick() + ", " + value.second);
  328. handled = true;
  329. break;
  330. }
  331. }
  332. if (!handled) tsc->write_to_channel(tsc->get_msg_nick() + ", " + cHELP);
  333. }
  334. }
  335. if (tsc->to_raw) { // Все сообщения на канале
  336. std::string raw = tsc->get_raw();
  337. int res = write_log ("[" + tsc->get_raw_nick() + "] " + raw);
  338. if (res) { // Сообщение администратору об ошибке логирования
  339. std::string report = "Can't write to log. ";
  340. if (res == 1) report += "Can't open the file.";
  341. if (res == 2) report += "Logpath not found.";
  342. tsc->write_to_user(cADMIN, report);
  343. }
  344. }
  345. std::this_thread::sleep_for(std::chrono::milliseconds(50));
  346. }
  347. }
  348. int main(int argc, char * argv[])
  349. {
  350. if (argc > 1) config_file = static_cast<std::string>(argv[1]);
  351. else {
  352. usage(static_cast<std::string>(argv[0]));
  353. return 1;
  354. }
  355. if (!read_config()) return 2;
  356. std::thread connection(make_tsc);
  357. handler();
  358. connection.join();
  359. return 3;
  360. }