httpserver.cpp 91 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274
  1. #include "httpserver.h"
  2. #include "ircclient.h"
  3. #include "connectiondata.h"
  4. #include "global.h"
  5. #include <QRegularExpression>
  6. #include <QDirIterator>
  7. #include <QDateTime>
  8. #include <QHostAddress>
  9. #include <QTcpSocket>
  10. #include <QDebug>
  11. #include <QJsonDocument>
  12. #include <QJsonArray>
  13. #include <QJsonObject>
  14. #include <QMutex>
  15. #include <QFile>
  16. #include <QDir>
  17. const char CRITICAL_ERROR[] {"<title>Critical error</title><center><h1>NOT FOUND</H1><p>Maybe it's compile time error</p></center>"};
  18. HttpServer::HttpServer(const QString &address, quint16 port, const QString& dataFolder,
  19. const QString& serviceName, const QString& serviceEmoji, bool ajaxIsDisabled, QObject *parent) :
  20. QObject(parent),
  21. m_TcpServer(new QTcpServer),
  22. m_serviceName(serviceName),
  23. m_dataFolder(dataFolder),
  24. m_ajaxIsDisabled(ajaxIsDisabled)
  25. {
  26. if (not m_TcpServer->listen(QHostAddress(address), port)) {
  27. throw std::runtime_error("HttpServer not binded at " +
  28. address.toStdString() + " : " + QString::number(port).toStdString());
  29. }
  30. consoleLog("http://" +address + ":" + QString::number(port) + "/");
  31. if (m_ajaxIsDisabled) {
  32. consoleLog("JavaScript on webpages removed and AJAX disabled!");
  33. }
  34. if (not QFile::exists(m_dataFolder+"main_page.txt")) {
  35. QFile mp(m_dataFolder+"main_page.txt");
  36. if (mp.open(QIODevice::WriteOnly)) {
  37. mp.write("# Main page file.\n"
  38. "# HTML is supported. For line breaks, use <br> or <p></p>.\n\n"
  39. "<center>\n"
  40. "# Your images from \"" + m_dataFolder.toUtf8() + "custom_images\" must have URL \"/~images/\"\n"
  41. "<img src=\"/~images/example.png\">\n"
  42. "<p><span style=\"color: gray\">local time:</span> " +LOCAL_TIME_MARKER_FOR_MAIN_PAGE.toUtf8()+ "<br>\n"
  43. "<span style=\"color: gray\">daily requests:</span> " +DAILY_REQUESTS_COUNTER_VALUE_FOR_MAIN_PAGE.toUtf8()+ "</p>\n"
  44. "</center>\n");
  45. mp.close();
  46. }
  47. else {
  48. throw std::runtime_error("main_page.txt not exist and creating failed");
  49. }
  50. }
  51. QDir dir {m_dataFolder};
  52. if (not dir.cd("custom_images")) {
  53. if (dir.mkdir("custom_images")) {
  54. dir.cd("custom_images");
  55. QFile examplePng(dir.path()+global::slash+"example.png");
  56. if (examplePng.open(QIODevice::WriteOnly)) {
  57. QFile nativePng("://html/custom_img_example.png");
  58. if (nativePng.open(QIODevice::ReadOnly)) {
  59. examplePng.write(nativePng.readAll());
  60. nativePng.close();
  61. examplePng.close();
  62. }
  63. }
  64. } else {
  65. consoleLog("Creating folder \"custom_images\" failed");
  66. }
  67. }
  68. m_serviceButton = HTML_LEFT_MENU_MAIN_POINT;
  69. replaceTag(m_serviceButton, "EMOJI", serviceEmoji);
  70. replaceTag(m_serviceButton, "SERVICE_NAME", serviceName);
  71. connect (m_TcpServer, &QTcpServer::newConnection, this, &HttpServer::acceptor);
  72. }
  73. HttpServer::~HttpServer()
  74. {
  75. m_TcpServer->close();
  76. m_TcpServer->deleteLater();
  77. }
  78. QString HttpServer::convertToClickableLink(const QString &httpLine)
  79. {
  80. QString result;
  81. if (not httpLine.contains(QRegularExpression("http.?://"))) return result;
  82. QString displayedName {httpLine};
  83. displayedName.remove(QRegularExpression("http.?://(www\\.)?"));
  84. displayedName.remove(QRegularExpression("/$"));
  85. result = "<a href=\"" + httpLine + "\"> " + displayedName + " </a>";
  86. return result;
  87. }
  88. std::pair<QString, QString> HttpServer::splitUserNameAndMessage(const QString &rawLine)
  89. {
  90. std::pair<QString, QString> result;
  91. QString nick {rawLine};
  92. nick.remove(QRegularExpression("\\]\\s.*$"));
  93. nick.remove(QRegularExpression("^\\["));
  94. if (nick.isEmpty()) {
  95. return result;
  96. }
  97. nick = nick.toHtmlEscaped();
  98. // long nicks
  99. if (nick.size() > MAX_NICKNAME_LENGTH_WITHOUT_WBR) {
  100. int lastWbr = 0;
  101. for (int i = 0; i < nick.size(); i++) {
  102. if (i-lastWbr > MAX_NICKNAME_LENGTH_WITHOUT_WBR) {
  103. nick.insert(i, "<wbr>");
  104. lastWbr = i;
  105. }
  106. }
  107. }
  108. QString text {rawLine};
  109. text.remove(QRegularExpression("^\\[[^\\s]*\\]\\s"));
  110. if (text.isEmpty()) {
  111. return result;
  112. }
  113. text = text.toHtmlEscaped();
  114. // http links
  115. bool linksFound {false};
  116. while (QRegularExpression("(^|\\s)http.?://").match(text).hasMatch()) {
  117. if (not linksFound) linksFound = true;
  118. int pos = text.indexOf(QRegularExpression("(^|\\s)http.?://"));
  119. if (pos == -1) {
  120. consoleLog("Bug! HttpServer.cpp while (QRegularExpression(\"(^|\\s)http.?://\").match(text).hasMatch())");
  121. break;
  122. }
  123. QString rawLink {text};
  124. rawLink.remove(0, pos);
  125. if (rawLink.startsWith(' ')) {
  126. rawLink.remove(0,1);
  127. }
  128. int space = rawLink.indexOf(' ');
  129. if (space > 0) {
  130. rawLink.remove(space, rawLink.size()-space);
  131. }
  132. text.replace(rawLink, convertToClickableLink(rawLink));
  133. }
  134. // long lines
  135. int space = 0;
  136. bool nbTag = false; // For safe HTML tags like a &it; via <wbr>!
  137. bool isHref = false;
  138. for (int i = 0; i < text.size(); i++) {
  139. if (text[i] == ' ') {
  140. space = i;
  141. if (isHref) {
  142. isHref = false;
  143. }
  144. else {
  145. if (text.indexOf("href=\"http", i+1) == i+1) {
  146. isHref = true;
  147. }
  148. }
  149. }
  150. if (nbTag and text[i-1] == ';') {
  151. nbTag = false;
  152. }
  153. if (text.indexOf(QRegularExpression("(\\&amp;|\\&lt;|\\&gt;|\\&quot;).*"), i) == i) {
  154. nbTag = true;
  155. }
  156. if (not isHref and i-space > MAX_MESSAGE_LENGTH_WITHOUT_WBR and not nbTag) {
  157. text.insert(i, "<wbr>");
  158. space = i;
  159. }
  160. }
  161. if (linksFound) text.replace(" </a>", "</a>"); // delete whitespace in links end
  162. result.first = nick;
  163. result.second = text;
  164. return result;
  165. }
  166. void HttpServer::consoleLog(const QString &message)
  167. {
  168. qInfo().noquote() << "[WEB_UI]" << message;
  169. }
  170. void HttpServer::debugLog(const QString &req)
  171. {
  172. QFile log(m_dataFolder + "debug.log");
  173. if (log.open(QIODevice::WriteOnly | QIODevice::Append)) {
  174. log.write(QDateTime::currentDateTime().toString().toUtf8() + ":\n" + req.toUtf8() + "\n\n");
  175. log.close();
  176. }
  177. }
  178. void HttpServer::acceptor()
  179. {
  180. QTcpSocket* socket = m_TcpServer->nextPendingConnection();
  181. static uint sockcount = 0;
  182. if (++sockcount < 20)
  183. {
  184. connect(socket, &QTcpSocket::readyRead, this, &HttpServer::reader);
  185. connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
  186. connect(socket, &QTcpSocket::disconnected, [&]() {--sockcount;} );
  187. }
  188. else
  189. {
  190. socket->close();
  191. socket->deleteLater();
  192. }
  193. }
  194. void HttpServer::reader()
  195. {
  196. QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
  197. QString request = socket->read(BUFFER_SIZE);
  198. if (not request.startsWith("GET") and not request.startsWith("HEAD")) {
  199. if (socket->isOpen()) {
  200. socket->write("Your request has been rejected!\n");
  201. socket->disconnectFromHost();
  202. }
  203. return;
  204. }
  205. bool isHeadRequest = false;
  206. if (request.startsWith("HEAD ")) {
  207. isHeadRequest = true;
  208. }
  209. QString urlPath = getRequestPath(request);
  210. if (urlPath == "/favicon.ico") {
  211. QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
  212. if (eTag == HTTP_ACTUAL_ETAG) {
  213. if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
  214. }
  215. else {
  216. QFile icon("://html/favicon.ico");
  217. if (icon.open(QIODevice::ReadOnly)) {
  218. QByteArray file = icon.readAll();
  219. icon.close();
  220. QString header = HEADER_ICO;
  221. replaceTag(header, "SIZE", QString::number(file.size()));
  222. if (socket->isOpen()) {
  223. socket->write(header.toUtf8());
  224. if (not isHeadRequest) socket->write(file);
  225. }
  226. }
  227. }
  228. }
  229. else if (urlPath == "/") {
  230. ++m_requestCounterPlainInfo;
  231. writeMainPage(socket, isHeadRequest);
  232. }
  233. else if (urlPath == "/newmessage.mp3" and not m_ajaxIsDisabled) {
  234. QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
  235. if (eTag == HTTP_ACTUAL_ETAG) {
  236. if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
  237. }
  238. else {
  239. QFile mp3("://html/newmessage.mp3");
  240. if (mp3.open(QIODevice::ReadOnly)) {
  241. QByteArray file = mp3.readAll();
  242. mp3.close();
  243. QString header = HEADER_MP3;
  244. replaceTag(header, "SIZE", QString::number(file.size()));
  245. if (socket->isOpen()) {
  246. socket->write(header.toUtf8());
  247. if (not isHeadRequest) socket->write(file);
  248. }
  249. }
  250. }
  251. }
  252. else if (urlPath == "/style.css") {
  253. QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
  254. if (eTag == HTTP_ACTUAL_ETAG) {
  255. if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
  256. }
  257. else {
  258. QFile css("://html/style.css");
  259. if (css.open(QIODevice::ReadOnly)) {
  260. QByteArray file = css.readAll();
  261. css.close();
  262. QString header = HEADER_CSS;
  263. replaceTag(header, "SIZE", QString::number(file.size()));
  264. if (socket->isOpen()) {
  265. socket->write(header.toUtf8());
  266. if (not isHeadRequest) socket->write(file);
  267. }
  268. }
  269. }
  270. }
  271. else if (urlPath.endsWith(".svg")) {
  272. QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
  273. if (eTag == HTTP_ACTUAL_ETAG) {
  274. if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
  275. }
  276. else {
  277. QFile svg("://html"+urlPath);
  278. if (svg.open(QIODevice::ReadOnly)) {
  279. QByteArray file = svg.readAll();
  280. svg.close();
  281. QString header = HEADER_SVG;
  282. replaceTag(header, "SIZE", QString::number(file.size()));
  283. if (socket->isOpen()) {
  284. socket->write(header.toUtf8());
  285. if (not isHeadRequest) socket->write(file);
  286. }
  287. }
  288. else {
  289. if (socket->isOpen()) {
  290. socket->write(HEADER_404.toUtf8());
  291. if (not isHeadRequest) writeErrorPage(socket);
  292. }
  293. }
  294. }
  295. }
  296. else if (urlPath == "/realtimechat.js" and not m_ajaxIsDisabled) {
  297. QFile js("://html/realtimechat_js");
  298. if (js.open(QIODevice::ReadOnly)) {
  299. QByteArray file = js.readAll();
  300. js.close();
  301. QString header = HEADER_JS;
  302. replaceTag(header, "SIZE", QString::number(file.size()));
  303. if (socket->isOpen()) {
  304. socket->write(header.toUtf8());
  305. if (not isHeadRequest) socket->write(file);
  306. }
  307. }
  308. }
  309. else if (urlPath.startsWith("/ajax/")) {
  310. if (m_ajaxIsDisabled) {
  311. writeErrorJson(socket, "AJAX disabled");
  312. } else {
  313. ++m_requestCounterAjax;
  314. writeAjaxAnswer(socket, urlPath, isHeadRequest);
  315. }
  316. }
  317. else if (urlPath.startsWith("/~realtime/")) {
  318. if (m_ajaxIsDisabled) {
  319. writeErrorPage(socket, "DISABLED"
  320. "<p>No JS, no AJAX, no real time reading!</p>");
  321. } else {
  322. ++m_requestCounterPlainInfo;
  323. writeRealTimeChatPage(socket, urlPath, isHeadRequest);
  324. }
  325. }
  326. else if (urlPath.startsWith("/~images/")) {
  327. writeCustomPicture(socket, urlPath, isHeadRequest);
  328. }
  329. else {
  330. ++m_requestCounterPlainInfo;
  331. writeRegularPage(socket, urlPath, isHeadRequest);
  332. }
  333. socket->disconnectFromHost();
  334. }
  335. void HttpServer::ircBotFirstInfo(QString server, QStringList channels)
  336. {
  337. for (const auto &c: channels) {
  338. if (c.isEmpty()) continue;
  339. m_servers[server][c] = QStringList();
  340. }
  341. }
  342. void HttpServer::ircUsersOnline(QString server, QString channel, QStringList users)
  343. {
  344. if (server.isEmpty()) return;
  345. if (channel.isEmpty()) return;
  346. QStringList sortedNicknames;
  347. QStringList ownersNicks;
  348. QStringList operNicks;
  349. QStringList halfopNicks;
  350. QStringList adminNicks;
  351. QStringList voicedNicks;
  352. QStringList plainNicks;
  353. for (auto rawOneNick: users) {
  354. rawOneNick = rawOneNick.toHtmlEscaped();
  355. if (rawOneNick.startsWith('~')) {
  356. ownersNicks.push_back(rawOneNick);
  357. }
  358. else if (rawOneNick.startsWith('@')) {
  359. operNicks.push_back(rawOneNick);
  360. }
  361. else if (rawOneNick.startsWith('%')) {
  362. halfopNicks.push_back(rawOneNick);
  363. }
  364. else if (rawOneNick.startsWith('&')) {
  365. adminNicks.push_back(rawOneNick);
  366. }
  367. else if (rawOneNick.startsWith('+')) {
  368. voicedNicks.push_back(rawOneNick);
  369. }
  370. else {
  371. plainNicks.push_back(rawOneNick);
  372. }
  373. }
  374. if (not ownersNicks.isEmpty()) {
  375. std::sort(ownersNicks.begin(), ownersNicks.end());
  376. sortedNicknames += ownersNicks;
  377. }
  378. if (not operNicks.isEmpty()) {
  379. std::sort(operNicks.begin(), operNicks.end());
  380. sortedNicknames += operNicks;
  381. }
  382. if (not halfopNicks.isEmpty()) {
  383. std::sort(halfopNicks.begin(), halfopNicks.end());
  384. sortedNicknames += halfopNicks;
  385. }
  386. if (not adminNicks.isEmpty()) {
  387. std::sort(adminNicks.begin(), adminNicks.end());
  388. sortedNicknames += adminNicks;
  389. }
  390. if (not voicedNicks.isEmpty()) {
  391. std::sort(voicedNicks.begin(), voicedNicks.end());
  392. sortedNicknames += voicedNicks;
  393. }
  394. if (not plainNicks.isEmpty()) {
  395. std::sort(plainNicks.begin(), plainNicks.end());
  396. sortedNicknames += plainNicks;
  397. }
  398. sortedNicknames.removeAll("");
  399. m_servers[server][channel] = sortedNicknames;
  400. }
  401. void HttpServer::ircChannelTopic(QString server, QString channel, QString topic)
  402. {
  403. m_channelsTopic[server][channel] = topic;
  404. }
  405. void HttpServer::ircServerOnline(QString server, quint8 status)
  406. {
  407. if (server.isEmpty()) return;
  408. bool online = status;
  409. m_serversOnline[server] = online;
  410. }
  411. void HttpServer::ircBotNick(QString server, QString nickname)
  412. {
  413. m_botNick[server] = nickname;
  414. }
  415. void HttpServer::ircMessageCache(QString server, QString channel, QString nick, QString text)
  416. {
  417. QString channelId {server+channel};
  418. if (not m_messageCache.contains(channelId)) return;
  419. // remove timed out session
  420. if (QDateTime::currentMSecsSinceEpoch() - MSECS_TO_AUTOREMOVE_MESSAGES_FROM_BUFFER >
  421. m_messageCache[channelId]->getLastPing())
  422. {
  423. consoleLog("Message caching disabled for "+server+"/#"+channel+". No active reader.");
  424. m_messageCache.remove(channelId);
  425. return;
  426. }
  427. else {
  428. auto processedMsg {splitUserNameAndMessage("["+nick+"] " + text)};
  429. m_messageCache[channelId]->saveNewMessage(processedMsg.first, processedMsg.second);
  430. }
  431. }
  432. QString HttpServer::getRequestPath(const QString &req)
  433. {
  434. if (req.isEmpty()) return QString();
  435. QString result(req);
  436. int begin = result.indexOf(' ');
  437. if (begin == -1) return QString();
  438. result.remove(0, begin+1);
  439. int space = result.indexOf(' ');
  440. int size = result.size();
  441. result.remove(space, size-space);
  442. result = QByteArray::fromPercentEncoding(result.toUtf8());
  443. return result;
  444. }
  445. QString HttpServer::getWordFromPath(const QString &path)
  446. {
  447. QString result {path};
  448. result.remove(QRegularExpression("\\?.*$")); // any actions like a ?toSearch=
  449. if (result.startsWith('/')) {
  450. result.remove(QRegularExpression("^/"));
  451. }
  452. result.remove(QRegularExpression("/.*$"));
  453. return result;
  454. }
  455. void HttpServer::writeMainPage(QTcpSocket *socket, bool isHeadRequest)
  456. {
  457. auto renderStart = QDateTime::currentMSecsSinceEpoch();
  458. QFile main("://html/main.html");
  459. QString page;
  460. if (main.open(QIODevice::ReadOnly)) {
  461. page = main.readAll();
  462. main.close();
  463. }
  464. else {
  465. if (socket->isOpen()) {
  466. socket->write(HEADER_404.toUtf8());
  467. if (not isHeadRequest) socket->write(CRITICAL_ERROR);
  468. }
  469. }
  470. replaceTag(page, "PAGE_TITLE", m_serviceName + " | IRC logger");
  471. //// Left menu compilation
  472. QString htmlServersSectionS;
  473. for (const auto &s: m_servers) {
  474. if (s.first.isEmpty()) continue; // empty server name?
  475. QString htmlServersSection = HTML_SERVER_SECTION;
  476. replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first);
  477. replaceTag(htmlServersSection, "SERVER_NAME", s.first);
  478. QString htmlChannelLineS;
  479. for (const auto &c: s.second) {
  480. QString htmlChannelLine;
  481. htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
  482. replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
  483. QString channelNameForUrl {c.first};
  484. channelNameForUrl.remove('#');
  485. QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
  486. replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
  487. htmlChannelLineS += htmlChannelLine;
  488. }
  489. replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
  490. bool online {false};
  491. for (const auto &srv: m_serversOnline) {
  492. if (srv.first == s.first) {
  493. online = srv.second;
  494. break;
  495. }
  496. }
  497. if (online) {
  498. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
  499. } else {
  500. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
  501. }
  502. htmlServersSectionS += htmlServersSection;
  503. }
  504. QString serviceButtonSelected {m_serviceButton};
  505. serviceButtonSelected.replace("class=\"left_menu__mainitem\"",
  506. "class=\"left_menu__mainitem\" style=\"opacity: 1\"");
  507. htmlServersSectionS.push_front(serviceButtonSelected);
  508. replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
  509. page.remove(QRegularExpression("<div class=\"main_header\">.*<!-- main_middle -->", QRegularExpression::DotMatchesEverythingOption));
  510. QString payloadBlock = HTML_PAYLOAD_ABOUT;
  511. payloadBlock.remove("<span style=\"color: green; display: block; font-size: 24px; text-align: center;\">{{ABOUT_TITLE}}</span><br>\n");
  512. QString aboutBlock;
  513. QFile mp(m_dataFolder+global::slash+"main_page.txt");
  514. if (mp.open(QIODevice::ReadOnly)) {
  515. QString rbuffer = mp.readLine();
  516. while (not rbuffer.isEmpty()) {
  517. if (rbuffer.startsWith('#')) {
  518. rbuffer = mp.readLine();
  519. continue;
  520. }
  521. removeBrakelineSymbols(rbuffer);
  522. if (not rbuffer.isEmpty()) {
  523. aboutBlock += rbuffer;
  524. }
  525. rbuffer = mp.readLine();
  526. }
  527. aboutBlock.replace(LOCAL_TIME_MARKER_FOR_MAIN_PAGE, QTime::currentTime().toString(Qt::DateFormat::ISODate));
  528. QString reqCounterStr = QString::number(*m_requestCounterPlainInfo.value());
  529. if (not m_ajaxIsDisabled)
  530. {
  531. reqCounterStr += " (html) / " + QString::number(*m_requestCounterAjax.value()) + " (ajax)";
  532. }
  533. aboutBlock.replace(DAILY_REQUESTS_COUNTER_VALUE_FOR_MAIN_PAGE, reqCounterStr);
  534. }
  535. else {
  536. aboutBlock = "No information provided";
  537. }
  538. replaceTag(payloadBlock, "ABOUT_TEXT", aboutBlock);
  539. replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
  540. //// Footer
  541. replaceTag(page, "VERSION", global::IRCABOT_VERSION);
  542. replaceTag(page, "COPYRIGHT_YEAR", global::COPYRIGHT_YEAR);
  543. //// Finish
  544. replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart));
  545. QString mainHeader = HEADER_HTML;
  546. replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
  547. if (socket->isOpen()) {
  548. socket->write(mainHeader.toUtf8());
  549. if (not isHeadRequest) socket->write(page.toUtf8());
  550. }
  551. }
  552. void HttpServer::writeCustomPicture(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
  553. {
  554. QString fileName {urlPath};
  555. fileName.remove(QRegularExpression("^/~images/"));
  556. QFile f {m_dataFolder+global::slash+"custom_images"+global::slash+fileName};
  557. if (not f.exists()) {
  558. writeErrorPage(socket, fileName + "<p>IMAGE NOT FOUND</p>");
  559. return;
  560. }
  561. QByteArray file;
  562. if (f.open(QIODevice::ReadOnly)) {
  563. file = f.readAll();
  564. f.close();
  565. }
  566. else {
  567. writeErrorPage(socket, "FORBIDDEN");
  568. return;
  569. }
  570. QString picHeader = HEADER_IMG;
  571. QString fileType {fileName};
  572. fileType.remove(QRegularExpression(".*\\."));
  573. replaceTag(picHeader, "TYPE", fileType);
  574. replaceTag(picHeader, "SIZE", QString::number(file.size()));
  575. if (socket->isOpen()) {
  576. socket->write(picHeader.toUtf8());
  577. if (not isHeadRequest) socket->write(file);
  578. }
  579. }
  580. void HttpServer::writeErrorPage(QTcpSocket *socket, const QString& text)
  581. {
  582. if (socket->isOpen()) {
  583. socket->write(HEADER_404.toUtf8());
  584. socket->write("<title>404</title>"
  585. "<center>"
  586. "<p><h1>"+text.toUtf8()+"</H1></p>"
  587. "<p><h3>[<a href=\"/\" style=\"text-decoration: none\">main page</a>]</h3></p>"
  588. "</center>");
  589. }
  590. }
  591. void HttpServer::writeErrorJson(QTcpSocket * socket, const QString &text)
  592. {
  593. QString header = HEADER_JSON;
  594. QByteArray body {
  595. "{\"status\": false, \"message\": \"" + text.toUtf8() + "\"}"
  596. };
  597. replaceTag(header, "SIZE", QString::number(body.size()));
  598. socket->write(header.toUtf8());
  599. socket->write(body);
  600. }
  601. void HttpServer::removeBrakelineSymbols(QString &line)
  602. {
  603. line.remove('\r');
  604. line.remove('\n');
  605. line.remove('\t');
  606. }
  607. inline void HttpServer::replaceTag(QString &page, const QString &tag, const QString &payload)
  608. {
  609. page.replace("{{"+tag+"}}", payload);
  610. }
  611. void HttpServer::writeRegularPage(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
  612. {
  613. auto renderStart = QDateTime::currentMSecsSinceEpoch();
  614. QString searchRequest;
  615. bool isRegexp {false};
  616. int specSymbol = urlPath.indexOf('?'); // any actions like a ?toSearch=
  617. if (specSymbol != -1) {
  618. searchRequest = global::getValue(urlPath, "toSearch", global::eForWeb);
  619. isRegexp = global::getValue(urlPath, "isRegexp", global::eForWeb) == "on";
  620. urlPath.remove(specSymbol, urlPath.size()-specSymbol);
  621. }
  622. QString server = getWordFromPath(urlPath);
  623. QDir fsPath(m_dataFolder+server);
  624. if (not fsPath.exists()) {
  625. if (isHeadRequest) {
  626. if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
  627. } else {
  628. writeErrorPage(socket, "REQUESTED SERVER LOG NOT EXIST");
  629. }
  630. return;
  631. }
  632. urlPath.remove(QRegularExpression("^.*/"+server));
  633. QString channel = getWordFromPath(urlPath);
  634. channel.remove(QRegularExpression("\\?.*$"));
  635. if (channel.isEmpty()) {
  636. writeAboutServerPage(socket, server, isHeadRequest);
  637. return;
  638. }
  639. if (not fsPath.cd(channel)) {
  640. if (isHeadRequest) {
  641. if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
  642. } else {
  643. writeErrorPage(socket, "REQUESTED CHANNEL LOG NOT EXIST");
  644. }
  645. return;
  646. }
  647. QString originalServerName;
  648. for (const auto &s: m_servers) {
  649. if (global::toLowerAndNoSpaces(s.first) == server) {
  650. originalServerName = s.first;
  651. }
  652. }
  653. QString originalChannelName;
  654. for (const auto &server: m_servers) {
  655. for (const auto &channel_users: server.second) {
  656. if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) {
  657. originalChannelName = global::toLowerAndNoSpaces(channel_users.first);
  658. }
  659. }
  660. }
  661. urlPath.remove(QRegularExpression("^.*/"+channel));
  662. QString year = getWordFromPath(urlPath);
  663. year.remove(QRegularExpression("\\?.*$"));
  664. QString month;
  665. QString day;
  666. if (not year.isEmpty() and fsPath.cd(year)) {
  667. urlPath.remove(QRegularExpression("^.*/"+year));
  668. month = getWordFromPath(urlPath);
  669. month.remove(QRegularExpression("\\?.*$"));
  670. if (not month.isEmpty() and fsPath.cd(month)) {
  671. if (urlPath.startsWith("/"+month+"/")) {
  672. urlPath.remove(0,1);
  673. int pos = urlPath.indexOf('/');
  674. if (pos != -1) {
  675. urlPath.remove(0,pos);
  676. day = getWordFromPath(urlPath);
  677. if (urlPath.endsWith(".txt")) {
  678. QFile plain(fsPath.path()+global::slash+day);
  679. if (plain.open(QIODevice::ReadOnly)) {
  680. QString header = HEADER_TEXT;
  681. QByteArray file = plain.readAll();
  682. plain.close();
  683. replaceTag(header, "SIZE", QString::number(file.size()));
  684. if (socket->isOpen()) {
  685. socket->write(header.toUtf8());
  686. if (not isHeadRequest) socket->write(file);
  687. }
  688. }
  689. else {
  690. if (isHeadRequest) {
  691. if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
  692. } else {
  693. writeErrorPage(socket, "FILE OPENING FAILED");
  694. }
  695. }
  696. return;
  697. }
  698. else {
  699. if (not QFile::exists(fsPath.path()+global::slash+day+".txt")) {
  700. day.clear();
  701. }
  702. }
  703. }
  704. }
  705. }
  706. else { month.clear(); }
  707. }
  708. else { year.clear(); }
  709. QFile main("://html/main.html");
  710. QString page;
  711. if (main.open(QIODevice::ReadOnly)) {
  712. page = main.readAll();
  713. main.close();
  714. }
  715. else {
  716. if (socket->isOpen()) {
  717. socket->write(HEADER_404.toUtf8());
  718. if (not isHeadRequest) socket->write(CRITICAL_ERROR);
  719. }
  720. }
  721. if (isRegexp) {
  722. page.replace("<input id=\"main_header__search_checkbox__button\" type=\"checkbox\" name=\"isRegexp\">",
  723. "<input id=\"main_header__search_checkbox__button\" type=\"checkbox\" name=\"isRegexp\" checked>");
  724. }
  725. //// Left menu compilation
  726. QString htmlServersSectionS;
  727. for (const auto &s: m_servers) {
  728. if (s.first.isEmpty()) continue; // empty server name?
  729. QString htmlServersSection = HTML_SERVER_SECTION;
  730. replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first);
  731. replaceTag(htmlServersSection, "SERVER_NAME", s.first);
  732. QString htmlChannelLineS;
  733. for (const auto &c: s.second) {
  734. QString htmlChannelLine;
  735. if (originalServerName == s.first and originalChannelName == c.first) {
  736. htmlChannelLine = HTML_SERVER_SECTION_CHANNEL_SELECTED;
  737. } else {
  738. htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
  739. }
  740. replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
  741. QString channelNameForUrl {c.first};
  742. channelNameForUrl.remove('#');
  743. QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
  744. if (not year.isEmpty()) {
  745. channelLink += "/" + year;
  746. if (not month.isEmpty()) {
  747. channelLink += "/" + month;
  748. if (not day.isEmpty()) {
  749. channelLink += "/" + day;
  750. }
  751. }
  752. }
  753. replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
  754. htmlChannelLineS += htmlChannelLine;
  755. }
  756. replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
  757. bool online {false};
  758. for (const auto &srv: m_serversOnline) {
  759. if (srv.first == s.first) {
  760. online = srv.second;
  761. break;
  762. }
  763. }
  764. if (online) {
  765. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
  766. } else {
  767. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
  768. }
  769. if (s.first == originalServerName) {
  770. htmlServersSectionS.push_front(htmlServersSection);
  771. } else {
  772. htmlServersSectionS += htmlServersSection;
  773. }
  774. }
  775. htmlServersSectionS.push_front(m_serviceButton);
  776. replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
  777. //// Main section header compilation
  778. QString& topic = m_channelsTopic[originalServerName][originalChannelName];
  779. topic = topic.replace('\"', "&quot;");
  780. QString titlePostfix = " | " + m_serviceName;
  781. if (not topic.isEmpty()) {
  782. titlePostfix.push_front(" | " + topic);
  783. }
  784. if (m_servers.size() > 1) {
  785. replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+")" + titlePostfix);
  786. } else {
  787. replaceTag(page, "PAGE_TITLE", originalChannelName + titlePostfix);
  788. }
  789. replaceTag(page, "CHANNEL_TOPIC", topic);
  790. replaceTag(page, "MAIN_HEADER", originalChannelName);
  791. if (m_ajaxIsDisabled) {
  792. page.remove("<a href=\"{{REALTIME_LINK}}\" title=\"{{AIRPLAIN_TITLE}}\" class=\"main_header__title_airplaine\"></a>");
  793. }
  794. else {
  795. replaceTag(page, "REALTIME_LINK", "/~realtime/"+server+"/"+channel);
  796. replaceTag(page, "AIRPLAIN_TITLE", "Read in real time");
  797. }
  798. QString middlePath = "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"\">" + "/" + "</a>";
  799. if (not year.isEmpty()) {
  800. middlePath += "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"/"+year+"\">" + year + "</a>";
  801. if (not month.isEmpty()) {
  802. middlePath += "/<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"/"+year+"/"+month+"\">" + month + "</a>";
  803. }
  804. }
  805. QString arrows = HTML_PAYLOAD_ADDITIONAL_ARROWS;
  806. QString currentYear {QDateTime::currentDateTime().toString("yyyy")};
  807. QString currentMonth {QDateTime::currentDateTime().toString("MM")};
  808. QString currentDay {QDateTime::currentDateTime().toString("dd")};
  809. replaceTag(arrows, "CURRENT_DATA_LOG", "/"+server+"/"+channel+"/"+
  810. currentYear+"/"+currentMonth+"/"+currentDay);
  811. replaceTag(page, "ADDITIONAL_BUTTON", arrows);
  812. int currentOnline = 0;
  813. QString onlineUserS;
  814. for (const auto &user: m_servers[originalServerName][originalChannelName]) {
  815. if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) {
  816. continue;
  817. }
  818. QString onlineUser = HTML_ONLINE_POINT;
  819. replaceTag(onlineUser, "NICKNAME", user);
  820. onlineUserS += onlineUser;
  821. currentOnline++;
  822. }
  823. replaceTag(page, "ONLINE", QString::number(currentOnline));
  824. replaceTag(page, "ONLINE_LIST", onlineUserS);
  825. if (not searchRequest.isEmpty()) {
  826. page.replace("<input class=\"main_header__search_input\" type=\"search\" name=\"toSearch\" placeholder=\"{{SEARCH_PLACEHOLDER}}\">",
  827. "<input class=\"main_header__search_input\" type=\"search\" name=\"toSearch\" value=\"" + searchRequest.toHtmlEscaped() + "\">");
  828. } else if (middlePath == "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"\">" + "/" + "</a>") {
  829. replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName);
  830. } else if (month.isEmpty()) {
  831. replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName + "/" + year);
  832. } else {
  833. replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName + "/" + year + "/" + month);
  834. }
  835. //// Main section body compilation
  836. QString payloadBlock;
  837. std::pair<QString,QString> lastGreenNickname; // nick, color
  838. std::pair<QString,QString> lastRedNickname; // nick, color
  839. quint64 messageAnchorCounter = 0;
  840. NickColorist nickColorist;
  841. bool colorEdited = false;
  842. // Search request
  843. if (not searchRequest.isEmpty()) {
  844. static QMutex searchMtx;
  845. uint counter = 0;
  846. QRegularExpression userRgx(searchRequest, QRegularExpression::CaseInsensitiveOption);
  847. bool rgxIsValid = false;
  848. if (isRegexp and userRgx.isValid()) {
  849. rgxIsValid = true;
  850. }
  851. if (searchMtx.tryLock())
  852. {
  853. consoleLog("Search request (" + server + "): " + searchRequest);
  854. QStringList paths;
  855. QDirIterator it(fsPath.path());
  856. while (it.hasNext()) {
  857. QString currentPath = it.next();
  858. if (currentPath.endsWith(".") or currentPath.endsWith("..")) continue;
  859. QString logFolder = m_dataFolder;
  860. #ifdef WIN32
  861. logFolder.replace('\\', '/');
  862. #endif
  863. QString server {currentPath}; // Folder wich is not server folder is ignored
  864. server.remove(QRegularExpression("^"+logFolder));
  865. server.remove(QRegularExpression("/.*$"));
  866. bool serverIsOk = false;
  867. for (const auto &srv: m_servers) {
  868. if (global::toLowerAndNoSpaces(srv.first) == server) {
  869. serverIsOk = true;
  870. break;
  871. }
  872. }
  873. if (not serverIsOk) continue;
  874. QString currentChannel {currentPath}; // Folder wich is not channel folder is ignored
  875. currentChannel.remove(QRegularExpression("^"+logFolder+"[^/]*/"));
  876. currentChannel.remove(QRegularExpression("/.*$"));
  877. bool channelIsOk = false; // Канал явно указан в конфиге
  878. for (const auto &ch: m_servers[originalServerName]) {
  879. QString searchChan {ch.first};
  880. searchChan.remove('#');
  881. if (searchChan == currentChannel) {
  882. channelIsOk = true;
  883. break;
  884. }
  885. }
  886. if (not channelIsOk) continue;
  887. paths.push_back(currentPath);
  888. }
  889. if (paths.isEmpty()) {
  890. payloadBlock = HTML_PAYLOAD_ERROR;
  891. replaceTag(payloadBlock, "ERROR_TITLE", "Not found");
  892. replaceTag(payloadBlock, "ERROR_TEXT", "");
  893. }
  894. else {
  895. std::map<QString, QStringList> matchedPathsAndMessages;
  896. if (not month.isEmpty()) {
  897. for (const auto& path: paths) {
  898. if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(path).hasMatch()) continue;
  899. QFile file(path);
  900. if (not file.open(QIODevice::ReadOnly)) {
  901. consoleLog("Error! I can't open log file " + fsPath.path());
  902. continue;
  903. }
  904. QString buffer {file.readLine()};
  905. while (not buffer.isEmpty()) {
  906. removeBrakelineSymbols(buffer);
  907. messageAnchorCounter++;
  908. bool finded = false;
  909. if (rgxIsValid) {
  910. if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
  911. finded = true;
  912. }
  913. } else {
  914. if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
  915. finded = true;
  916. }
  917. }
  918. if (finded) {
  919. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  920. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  921. buffer = file.readLine();
  922. continue;
  923. }
  924. counter++;
  925. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  926. if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
  927. message.replace("class=\"main_payload__chat\"",
  928. "class=\"main_payload__chat\" style=\"opacity: .5\"");
  929. }
  930. message.remove(" name=\"{{ANCHOR}}\"");
  931. for (const auto &user: m_servers[originalServerName][originalChannelName]) {
  932. if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) {
  933. if (lastGreenNickname.first == rawMessage.first) {
  934. replaceTag(message, "COLOR", lastGreenNickname.second);
  935. }else{
  936. replaceTag(message, "COLOR", nickColorist.getGreenColor());
  937. lastGreenNickname.first = rawMessage.first;
  938. lastGreenNickname.second = nickColorist.getGreenColor(false);
  939. }
  940. break;
  941. }
  942. }
  943. if (not colorEdited) {
  944. if (lastRedNickname.first == rawMessage.first) {
  945. replaceTag(message, "COLOR", lastRedNickname.second);
  946. }else{
  947. replaceTag(message, "COLOR", nickColorist.getRedColor());
  948. lastRedNickname.first = rawMessage.first;
  949. lastRedNickname.second = nickColorist.getRedColor(false);
  950. }
  951. }
  952. QString logFolder {m_dataFolder};
  953. logFolder.remove(QRegularExpression(".$"));
  954. QString link {path};
  955. link.remove(logFolder);
  956. link.remove(QRegularExpression("\\.txt$"));
  957. replaceTag(message, "ANCHOR", link+"#"+ANCHOR_SUFFIX+QString::number(messageAnchorCounter-1));
  958. replaceTag(message, "USERNAME", rawMessage.first);
  959. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  960. matchedPathsAndMessages[path].push_back(message);
  961. }
  962. buffer = file.readLine();
  963. }
  964. file.close();
  965. messageAnchorCounter = 0;
  966. }
  967. }
  968. else if (month.isEmpty() and not year.isEmpty()){
  969. for (const auto &p: paths) {
  970. QStringList slavePaths;
  971. QDirIterator it(p);
  972. while (it.hasNext()) {
  973. QString fileName = it.next();
  974. if (fileName.endsWith(".") or fileName.endsWith("..")) continue;
  975. if (not QRegularExpression("\\.txt$").match(fileName).hasMatch()) continue;
  976. slavePaths.push_back(fileName);
  977. }
  978. for (const auto &path: slavePaths) {
  979. if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(path).hasMatch()) continue;
  980. QFile file(path);
  981. if (not file.open(QIODevice::ReadOnly)) {
  982. consoleLog("Error! I can't open log file " + fsPath.path());
  983. continue;
  984. }
  985. QString buffer {file.readLine()};
  986. while (not buffer.isEmpty()) {
  987. messageAnchorCounter++;
  988. removeBrakelineSymbols(buffer);
  989. bool finded = false;
  990. if (rgxIsValid) {
  991. if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
  992. finded = true;
  993. }
  994. } else {
  995. if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
  996. finded = true;
  997. }
  998. }
  999. if (finded) {
  1000. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  1001. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  1002. buffer = file.readLine();
  1003. continue;
  1004. }
  1005. counter++;
  1006. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  1007. if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
  1008. message.replace("class=\"main_payload__chat\"",
  1009. "class=\"main_payload__chat\" style=\"opacity: .5\"");
  1010. }
  1011. message.remove(" name=\"{{ANCHOR}}\"");
  1012. for (const auto &user: m_servers[originalServerName][originalChannelName]) {
  1013. if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) {
  1014. if (lastGreenNickname.first == rawMessage.first) {
  1015. replaceTag(message, "COLOR", lastGreenNickname.second);
  1016. }else{
  1017. replaceTag(message, "COLOR", nickColorist.getGreenColor());
  1018. lastGreenNickname.first = rawMessage.first;
  1019. lastGreenNickname.second = nickColorist.getGreenColor(false);
  1020. }
  1021. break;
  1022. }
  1023. }
  1024. if (not colorEdited) {
  1025. if (lastRedNickname.first == rawMessage.first) {
  1026. replaceTag(message, "COLOR", lastRedNickname.second);
  1027. }else{
  1028. replaceTag(message, "COLOR", nickColorist.getRedColor());
  1029. lastRedNickname.first = rawMessage.first;
  1030. lastRedNickname.second = nickColorist.getRedColor(false);
  1031. }
  1032. }
  1033. QString logFolder {m_dataFolder};
  1034. logFolder.remove(QRegularExpression(".$"));
  1035. QString link {path};
  1036. link.remove(logFolder);
  1037. link.remove(QRegularExpression("\\.txt$"));
  1038. replaceTag(message, "ANCHOR", link+"#"+ANCHOR_SUFFIX+QString::number(messageAnchorCounter-1));
  1039. replaceTag(message, "USERNAME", rawMessage.first);
  1040. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  1041. matchedPathsAndMessages[path].push_back(message);
  1042. }
  1043. buffer = file.readLine();
  1044. }
  1045. file.close();
  1046. messageAnchorCounter = 0;
  1047. }
  1048. }
  1049. }
  1050. else { // root directory
  1051. QStringList yearPaths;
  1052. for (const auto& p: paths) {
  1053. if (not QRegularExpression("/2[0-9]{3}$").match(p).hasMatch()) continue;
  1054. yearPaths.push_back(p);
  1055. }
  1056. QStringList fileNameS;
  1057. for (const auto& p: yearPaths) {
  1058. QStringList slavePaths;
  1059. QDirIterator it(p);
  1060. while (it.hasNext()) {
  1061. QString folderName = it.next();
  1062. if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
  1063. if (not QRegularExpression("/[0-9]{2}$").match(folderName).hasMatch()) continue;
  1064. slavePaths.push_back(folderName);
  1065. }
  1066. for (const auto &path: slavePaths) {
  1067. QDirIterator itMonth(path);
  1068. while (itMonth.hasNext()) {
  1069. QString fileName = itMonth.next();
  1070. if (fileName.endsWith(".") or fileName.endsWith("..")) continue;
  1071. if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(fileName).hasMatch()) continue;
  1072. fileNameS.push_back(fileName);
  1073. }
  1074. }
  1075. }
  1076. for (const auto& path: fileNameS) {
  1077. QFile file(path);
  1078. if (not file.open(QIODevice::ReadOnly)) {
  1079. consoleLog("Error! I can't open log file " + fsPath.path());
  1080. continue;
  1081. }
  1082. QString buffer {file.readLine()};
  1083. while (not buffer.isEmpty()) {
  1084. removeBrakelineSymbols(buffer);
  1085. messageAnchorCounter++;
  1086. bool finded = false;
  1087. if (rgxIsValid) {
  1088. if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
  1089. finded = true;
  1090. }
  1091. } else {
  1092. if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
  1093. finded = true;
  1094. }
  1095. }
  1096. if (finded) {
  1097. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  1098. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  1099. buffer = file.readLine();
  1100. continue;
  1101. }
  1102. counter++;
  1103. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  1104. if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
  1105. message.replace("class=\"main_payload__chat\"",
  1106. "class=\"main_payload__chat\" style=\"opacity: .5\"");
  1107. }
  1108. message.remove(" name=\"{{ANCHOR}}\"");
  1109. for (const auto &user: m_servers[originalServerName][originalChannelName]) {
  1110. if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) {
  1111. if (lastGreenNickname.first == rawMessage.first) {
  1112. replaceTag(message, "COLOR", lastGreenNickname.second);
  1113. }else{
  1114. replaceTag(message, "COLOR", nickColorist.getGreenColor());
  1115. lastGreenNickname.first = rawMessage.first;
  1116. lastGreenNickname.second = nickColorist.getGreenColor(false);
  1117. }
  1118. break;
  1119. }
  1120. }
  1121. if (not colorEdited) {
  1122. if (lastRedNickname.first == rawMessage.first) {
  1123. replaceTag(message, "COLOR", lastRedNickname.second);
  1124. }else{
  1125. replaceTag(message, "COLOR", nickColorist.getRedColor());
  1126. lastRedNickname.first = rawMessage.first;
  1127. lastRedNickname.second = nickColorist.getRedColor(false);
  1128. }
  1129. }
  1130. QString logFolder {m_dataFolder};
  1131. logFolder.remove(QRegularExpression(".$"));
  1132. QString link {path};
  1133. link.remove(logFolder);
  1134. link.remove(QRegularExpression("\\.txt$"));
  1135. replaceTag(message, "ANCHOR", link+"#"+ANCHOR_SUFFIX+QString::number(messageAnchorCounter-1));
  1136. replaceTag(message, "USERNAME", rawMessage.first);
  1137. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  1138. matchedPathsAndMessages[path].push_back(message);
  1139. }
  1140. buffer = file.readLine();
  1141. }
  1142. file.close();
  1143. messageAnchorCounter = 0;
  1144. }
  1145. }
  1146. if (matchedPathsAndMessages.empty()) {
  1147. payloadBlock = HTML_PAYLOAD_ERROR;
  1148. replaceTag(payloadBlock, "ERROR_TITLE", "Not found");
  1149. replaceTag(payloadBlock, "ERROR_TEXT", "");
  1150. }
  1151. else {
  1152. QStringList findedPaths;
  1153. for (const auto& fp: matchedPathsAndMessages) {
  1154. findedPaths << fp.first;
  1155. }
  1156. std::sort(findedPaths.begin(), findedPaths.end());
  1157. for (auto& link: findedPaths) {
  1158. QString logFolder {m_dataFolder};
  1159. logFolder.remove(QRegularExpression(".$"));
  1160. QStringList& messages = matchedPathsAndMessages[link];
  1161. link.remove(logFolder);
  1162. link.remove(QRegularExpression("\\.txt$"));
  1163. QString finded = HTML_PAYLOAD_LIST_POINT_MESSAGE;
  1164. finded.replace("class=\"main_payload__block\"", "class=\"main_payload__block\" style=\"background: #b6c7d6\"");
  1165. replaceTag(finded, "POINT_LINK", link);
  1166. link.remove(QRegularExpression("^.*"+channel));
  1167. replaceTag(finded, "POINT_CONTENT", link);
  1168. payloadBlock += finded;
  1169. for(const auto& m: messages) {
  1170. payloadBlock += m;
  1171. }
  1172. payloadBlock += "&nbsp;\n";
  1173. }
  1174. }
  1175. }
  1176. int lastWbr = 0;
  1177. for (int i = 0; i < searchRequest.size(); i++) {
  1178. if (i-lastWbr > MAX_MESSAGE_LENGTH_WITHOUT_WBR) {
  1179. searchRequest.insert(i, "<wbr>");
  1180. lastWbr = i;
  1181. }
  1182. }
  1183. searchMtx.unlock();
  1184. } // searchMtx.tryLock()
  1185. else
  1186. {
  1187. consoleLog("Search request (" + server + "): " + searchRequest + " rejected by mutex");
  1188. payloadBlock = HTML_PAYLOAD_ERROR;
  1189. replaceTag(payloadBlock, "ERROR_TITLE", "Try later");
  1190. replaceTag(payloadBlock, "ERROR_TEXT", "Please try again later. The search service is busy.");
  1191. }
  1192. middlePath += "&nbsp;<span style=\"color: #4f9c65\">" + searchRequest.toHtmlEscaped() + "</span>&nbsp;";
  1193. if (rgxIsValid) middlePath += "rgx";
  1194. middlePath += "(" + QString::number(counter) + ")";
  1195. }
  1196. // Plain log explorer
  1197. else {
  1198. if (year.isEmpty()) { // /
  1199. QStringList folderNameS;
  1200. QDirIterator it(fsPath.path());
  1201. while (it.hasNext()) {
  1202. QString folderName = it.next();
  1203. if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
  1204. while(folderName.contains('/')) {
  1205. folderName.remove(QRegularExpression("^.*/"));
  1206. }
  1207. folderName.remove(QRegularExpression("\\.txt$"));
  1208. folderNameS << folderName;
  1209. }
  1210. if (not folderNameS.isEmpty()) {
  1211. std::sort(folderNameS.begin(), folderNameS.end());
  1212. for (const auto &f: folderNameS) {
  1213. QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER;
  1214. replaceTag(onePoint, "POINT_CONTENT", f);
  1215. replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+f);
  1216. payloadBlock += onePoint;
  1217. }
  1218. }
  1219. }
  1220. else if (not year.isEmpty() and month.isEmpty()) { // /YYYY
  1221. QStringList folderNameS;
  1222. QDirIterator it(fsPath.path());
  1223. while (it.hasNext()) {
  1224. QString folderName = it.next();
  1225. if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
  1226. while(folderName.contains('/')) {
  1227. folderName.remove(QRegularExpression("^.*/"));
  1228. }
  1229. folderNameS << folderName;
  1230. }
  1231. if (not folderNameS.isEmpty()) {
  1232. std::sort(folderNameS.begin(), folderNameS.end());
  1233. for (const auto &f: folderNameS) {
  1234. QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER;
  1235. bool yearIsOk {false};
  1236. bool monthIsOk {false};
  1237. QDate dateOfMonth (year.toInt(&yearIsOk), f.toInt(&monthIsOk), 1);
  1238. QString nameOfMonth;
  1239. if (yearIsOk and monthIsOk) {
  1240. nameOfMonth = QLocale().standaloneMonthName(dateOfMonth.month(), QLocale::ShortFormat);
  1241. }
  1242. QDirIterator dayLogs(fsPath.path() + global::slash + f);
  1243. int8_t dayLogsCounter {0};
  1244. while (dayLogs.hasNext()) {
  1245. QString dayLogFile = dayLogs.next();
  1246. if (dayLogFile.endsWith(".") or dayLogFile.endsWith("..")) continue;
  1247. dayLogsCounter++;
  1248. }
  1249. QString filesLabel;
  1250. dayLogsCounter == 1 ? filesLabel = "day" : filesLabel = "days";
  1251. replaceTag(onePoint, "POINT_CONTENT", "<span style=\"font-weight: bold\">" + f + "</span>&nbsp;(" + nameOfMonth + ") " + QString::number(dayLogsCounter) + " " + filesLabel);
  1252. replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+year+"/"+f);
  1253. payloadBlock += onePoint;
  1254. }
  1255. }
  1256. }
  1257. else if (not month.isEmpty() and day.isEmpty()) { // /YYYY/MM
  1258. QStringList fileNameS;
  1259. QDirIterator it(fsPath.path());
  1260. while (it.hasNext()) {
  1261. QString fileName = it.next();
  1262. if (fileName.endsWith(".") or fileName.endsWith("..")) continue; // QDir::NoDotAndNoDotDot not works!
  1263. while(fileName.contains('/')) {
  1264. fileName.remove(QRegularExpression("^.*/"));
  1265. }
  1266. fileName.remove(QRegularExpression("\\.txt$"));
  1267. fileNameS << fileName;
  1268. }
  1269. if (not fileNameS.isEmpty()) {
  1270. std::sort(fileNameS.begin(), fileNameS.end());
  1271. for (const auto &a: fileNameS) {
  1272. QString onePoint = HTML_PAYLOAD_LIST_POINT_MESSAGE;
  1273. bool yearIsOk {false};
  1274. bool monthIsOk {false};
  1275. bool dayIsOk {false};
  1276. QDate dateOfDay (year.toInt(&yearIsOk), month.toInt(&monthIsOk), a.toInt(&dayIsOk));
  1277. QString nameOfDay;
  1278. if (yearIsOk and monthIsOk and dayIsOk) {
  1279. nameOfDay = QLocale().standaloneDayName(dateOfDay.dayOfWeek(), QLocale::ShortFormat);
  1280. }
  1281. auto logFileSizeBytes = QFile(fsPath.path() + global::slash + a +".txt").size();
  1282. auto logFileSizeinKb = logFileSizeBytes/1000;
  1283. QString logFileSizeString;
  1284. if (logFileSizeinKb != 0) {
  1285. logFileSizeString = QString::number(logFileSizeinKb)+"."+
  1286. QString::number((logFileSizeBytes - logFileSizeinKb*1000)/10)+
  1287. " KB";
  1288. } else {
  1289. logFileSizeString = QString::number(logFileSizeBytes)+" B";
  1290. }
  1291. replaceTag(onePoint, "POINT_CONTENT", "<span style=\"font-weight: bold\">" + a + "</span>&nbsp;(" + nameOfDay + ") " + logFileSizeString);
  1292. replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+year+"/"+month+"/"+a);
  1293. payloadBlock += onePoint;
  1294. }
  1295. }
  1296. }
  1297. else if (not day.isEmpty()) { // /YYYY/MM/dd
  1298. QFile file(fsPath.path()+global::slash+day+".txt");
  1299. if (not file.open(QIODevice::ReadOnly)) {
  1300. consoleLog("Error! I can't open log file " + fsPath.path());
  1301. payloadBlock = HTML_PAYLOAD_ERROR;
  1302. replaceTag(payloadBlock, "ERROR_TITLE", "Internal error");
  1303. replaceTag(payloadBlock, "ERROR_TEXT", "Requested log file openning failed");
  1304. }
  1305. else {
  1306. middlePath += "/" + day;
  1307. QString buffer = file.readLine();
  1308. while (not buffer.isEmpty()) {
  1309. removeBrakelineSymbols(buffer);
  1310. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  1311. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  1312. buffer = file.readLine();
  1313. continue;
  1314. }
  1315. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  1316. if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
  1317. message.replace("class=\"main_payload__chat\"",
  1318. "class=\"main_payload__chat\" style=\"opacity: .5\"");
  1319. }
  1320. for (const auto &user: m_servers[originalServerName][originalChannelName]) {
  1321. if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) {
  1322. if (lastGreenNickname.first == rawMessage.first) {
  1323. replaceTag(message, "COLOR", lastGreenNickname.second);
  1324. }else{
  1325. replaceTag(message, "COLOR", nickColorist.getGreenColor());
  1326. lastGreenNickname.first = rawMessage.first;
  1327. lastGreenNickname.second = nickColorist.getGreenColor(false);
  1328. }
  1329. break;
  1330. }
  1331. }
  1332. if (not colorEdited) {
  1333. if (lastRedNickname.first == rawMessage.first) {
  1334. replaceTag(message, "COLOR", lastRedNickname.second);
  1335. }else{
  1336. replaceTag(message, "COLOR", nickColorist.getRedColor());
  1337. lastRedNickname.first = rawMessage.first;
  1338. lastRedNickname.second = nickColorist.getRedColor(false);
  1339. }
  1340. }
  1341. message.replace("href=\"", "href=\"#");
  1342. replaceTag(message, "ANCHOR", ANCHOR_SUFFIX+QString::number(messageAnchorCounter++));
  1343. replaceTag(message, "USERNAME", rawMessage.first);
  1344. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  1345. payloadBlock += message;
  1346. buffer = file.readLine();
  1347. }
  1348. file.close();
  1349. // arrows to prev & next days
  1350. bool prevDayFound {false};
  1351. uint prevDayUInt {day.toUShort()};
  1352. uint prevMonthUInt {month.toUShort()};
  1353. uint prevYearUInt {year.toUShort()};
  1354. QDir fsPathForPrev {fsPath};
  1355. for(uint i = prevDayUInt-1; i >= 1; i--) { // current month
  1356. QString d {fsPathForPrev.path()+global::slash};
  1357. if (i < 10) d += "0";
  1358. d += QString::number(i)+".txt";
  1359. if (QFile::exists(d)) {
  1360. prevDayUInt = i;
  1361. prevDayFound = true;
  1362. break;
  1363. }
  1364. }
  1365. if (not prevDayFound) { // other month
  1366. fsPathForPrev.cdUp();
  1367. bool mf {false};
  1368. for (uint i = prevMonthUInt-1; i >= 1; i--) {
  1369. QString m;
  1370. if (i < 10) m += "0";
  1371. m += QString::number(i);
  1372. if (fsPathForPrev.cd(m)) {
  1373. prevMonthUInt = i;
  1374. mf = true;
  1375. break;
  1376. }
  1377. }
  1378. if (mf) {
  1379. for(uint i = 31; i >= 1; i--) {
  1380. QString d {fsPathForPrev.path()+global::slash};
  1381. if (i < 10) d += "0";
  1382. d += QString::number(i)+".txt";
  1383. if (QFile::exists(d)) {
  1384. prevDayUInt = i;
  1385. prevDayFound = true;
  1386. break;
  1387. }
  1388. }
  1389. }
  1390. }
  1391. if (not prevDayFound) { // other year
  1392. fsPathForPrev.cdUp();
  1393. bool yf {false};
  1394. for (uint i = prevYearUInt-1; i > prevYearUInt-21; i--) {
  1395. if (fsPathForPrev.cd(QString::number(i))) {
  1396. prevYearUInt = i;
  1397. yf = true;
  1398. break;
  1399. }
  1400. }
  1401. if (yf) {
  1402. bool mf {false};
  1403. for (uint i = 12; i >= 1; i--) {
  1404. QString m;
  1405. if (i < 10) m += "0";
  1406. m += QString::number(i);
  1407. if (fsPathForPrev.cd(m)) {
  1408. prevMonthUInt = i;
  1409. mf = true;
  1410. break;
  1411. }
  1412. }
  1413. if (mf) {
  1414. for(uint i = 31; i >= 1; i--) {
  1415. QString d {fsPathForPrev.path()+global::slash};
  1416. if (i < 10) d += "0";
  1417. d += QString::number(i)+".txt";
  1418. if (QFile::exists(d)) {
  1419. prevDayUInt = i;
  1420. prevDayFound = true;
  1421. break;
  1422. }
  1423. }
  1424. }
  1425. }
  1426. }
  1427. QString leftArrow;
  1428. if (prevDayFound) {
  1429. leftArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_PREV;
  1430. QString link = "/"+server+"/"+channel+"/"+QString::number(prevYearUInt)+"/";
  1431. QString m;
  1432. if (prevMonthUInt < 10) m += "0";
  1433. m += QString::number(prevMonthUInt);
  1434. link += m+"/";
  1435. QString d;
  1436. if (prevDayUInt < 10) d += "0";
  1437. d += QString::number(prevDayUInt);
  1438. link += d;
  1439. replaceTag(leftArrow, "LINK", link);
  1440. } else {
  1441. leftArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_PREV_FALSE;
  1442. }
  1443. middlePath += leftArrow;
  1444. bool nextDayFound {false};
  1445. uint nextDayUInt {day.toUShort()};
  1446. uint nextMonthUInt {month.toUShort()};
  1447. uint nextYearUInt {year.toUShort()};
  1448. QDir fsPathForNext {fsPath};
  1449. for(uint i = nextDayUInt+1; i <= 31; i++) { // current month
  1450. QString d {fsPathForNext.path()+global::slash};
  1451. if (i < 10) d += "0";
  1452. d += QString::number(i)+".txt";
  1453. if (QFile::exists(d)) {
  1454. nextDayUInt = i;
  1455. nextDayFound = true;
  1456. break;
  1457. }
  1458. }
  1459. if (not nextDayFound) { // other month
  1460. fsPathForNext.cdUp();
  1461. bool mf {false};
  1462. for (uint i = nextMonthUInt+1; i <= 12; i++) {
  1463. QString m;
  1464. if (i < 10) m += "0";
  1465. m += QString::number(i);
  1466. if (fsPathForNext.cd(m)) {
  1467. nextMonthUInt = i;
  1468. mf = true;
  1469. break;
  1470. }
  1471. }
  1472. if (mf) {
  1473. for(uint i = 1; i <= 31; i++) {
  1474. QString d {fsPathForNext.path()+global::slash};
  1475. if (i < 10) d += "0";
  1476. d += QString::number(i)+".txt";
  1477. if (QFile::exists(d)) {
  1478. nextDayUInt = i;
  1479. nextDayFound = true;
  1480. break;
  1481. }
  1482. }
  1483. }
  1484. }
  1485. if (not nextDayFound) { // other year
  1486. fsPathForNext.cdUp();
  1487. bool yf {false};
  1488. for (uint i = nextYearUInt+1; i < nextYearUInt+20; i++) {
  1489. if (fsPathForNext.cd(QString::number(i))) {
  1490. nextYearUInt = i;
  1491. yf = true;
  1492. break;
  1493. }
  1494. }
  1495. if (yf) {
  1496. bool mf {false};
  1497. for (uint i = 1; i <= 12; i++) {
  1498. QString m;
  1499. if (i < 10) m += "0";
  1500. m += QString::number(i);
  1501. if (fsPathForNext.cd(m)) {
  1502. nextMonthUInt = i;
  1503. mf = true;
  1504. break;
  1505. }
  1506. }
  1507. if (mf) {
  1508. for(uint i = 1; i <= 31; i++) {
  1509. QString d {fsPathForNext.path()+global::slash};
  1510. if (i < 10) d += "0";
  1511. d += QString::number(i)+".txt";
  1512. if (QFile::exists(d)) {
  1513. nextDayUInt = i;
  1514. nextDayFound = true;
  1515. break;
  1516. }
  1517. }
  1518. }
  1519. }
  1520. }
  1521. QString rightArrow;
  1522. if (nextDayFound) {
  1523. rightArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_NEXT;
  1524. QString link = "/"+server+"/"+channel+"/"+QString::number(nextYearUInt)+"/";
  1525. QString m;
  1526. if (nextMonthUInt < 10) m += "0";
  1527. m += QString::number(nextMonthUInt);
  1528. link += m+"/";
  1529. QString d;
  1530. if (nextDayUInt < 10) d += "0";
  1531. d += QString::number(nextDayUInt);
  1532. link += d;
  1533. replaceTag(rightArrow, "LINK", link);
  1534. } else {
  1535. rightArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_NEXT_FALSE;
  1536. }
  1537. middlePath += rightArrow;
  1538. }
  1539. }
  1540. if (payloadBlock.isEmpty()) {
  1541. payloadBlock = HTML_PAYLOAD_ERROR;
  1542. replaceTag(payloadBlock, "ERROR_TITLE", "Empty");
  1543. replaceTag(payloadBlock, "ERROR_TEXT", "No logs found for this channel");
  1544. }
  1545. }
  1546. replaceTag(page, "MIDDLE_PATH", middlePath);
  1547. replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
  1548. //// Footer
  1549. replaceTag(page, "VERSION", global::IRCABOT_VERSION);
  1550. replaceTag(page, "COPYRIGHT_YEAR", global::COPYRIGHT_YEAR);
  1551. replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart));
  1552. QString mainHeader = HEADER_HTML;
  1553. replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
  1554. if (socket->isOpen()) {
  1555. socket->write(mainHeader.toUtf8());
  1556. if (not isHeadRequest) socket->write(page.toUtf8());
  1557. }
  1558. }
  1559. void HttpServer::writeRealTimeChatPage(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
  1560. {
  1561. if (isHeadRequest) {
  1562. QString header = HEADER_HTML;
  1563. replaceTag(header, "SIZE", "0");
  1564. socket->write(header.toUtf8());
  1565. return;
  1566. }
  1567. auto renderStart = QDateTime::currentMSecsSinceEpoch();
  1568. urlPath.remove(QRegularExpression("^/~realtime/"));
  1569. QString server = getWordFromPath(urlPath);
  1570. if (server.isEmpty()) {
  1571. writeErrorPage(socket, "SERVER VALUE IS MISSING");
  1572. return;
  1573. }
  1574. urlPath.remove(QRegularExpression("^"+server));
  1575. QString channel = getWordFromPath(urlPath);
  1576. if (channel.isEmpty()) {
  1577. writeErrorPage(socket, "CHANNEL VALUE IS MISSING");
  1578. return;
  1579. }
  1580. QString originalServerName;
  1581. for (const auto &s: m_servers) {
  1582. if (global::toLowerAndNoSpaces(s.first) == server) {
  1583. originalServerName = s.first;
  1584. }
  1585. }
  1586. if (originalServerName.isEmpty()) {
  1587. writeErrorPage(socket, "REQUESTED SERVER NOT EXIST");
  1588. return;
  1589. }
  1590. QString originalChannelName;
  1591. for (const auto &server: m_servers) {
  1592. for (const auto &channel_users: server.second) {
  1593. if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) {
  1594. originalChannelName = global::toLowerAndNoSpaces(channel_users.first);
  1595. }
  1596. }
  1597. }
  1598. if (originalChannelName.isEmpty()) {
  1599. writeErrorPage(socket, "REQUESTED CHANNEL NOT EXIST");
  1600. return;
  1601. }
  1602. QFile main("://html/main.html");
  1603. QString page;
  1604. if (main.open(QIODevice::ReadOnly)) {
  1605. page = main.readAll();
  1606. main.close();
  1607. }
  1608. else {
  1609. if (socket->isOpen()) {
  1610. socket->write(HEADER_404.toUtf8());
  1611. if (not isHeadRequest) socket->write(CRITICAL_ERROR);
  1612. }
  1613. }
  1614. QString year {QDateTime::currentDateTime().toString("yyyy")};
  1615. QString month {QDateTime::currentDateTime().toString("MM")};
  1616. QString day {QDateTime::currentDateTime().toString("dd")};
  1617. //// Left menu compilation
  1618. QString htmlServersSectionS;
  1619. for (const auto &s: m_servers) {
  1620. if (s.first.isEmpty()) continue; // empty server name?
  1621. QString htmlServersSection = HTML_SERVER_SECTION;
  1622. replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+server);
  1623. replaceTag(htmlServersSection, "SERVER_NAME", s.first);
  1624. if (s.first == originalServerName) {
  1625. htmlServersSection.replace("<span style=\"font-size: 17px;\">{{ONLINE_STATUS}}",
  1626. "<span style=\"font-size: 17px;\" id=\"serverStatus\">{{ONLINE_STATUS}}");
  1627. }
  1628. QString htmlChannelLineS;
  1629. for (const auto &c: s.second) {
  1630. QString htmlChannelLine;
  1631. if (originalServerName == s.first and originalChannelName == c.first) {
  1632. htmlChannelLine = HTML_SERVER_SECTION_CHANNEL_SELECTED;
  1633. } else {
  1634. htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
  1635. }
  1636. replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
  1637. QString channelNameForUrl {c.first};
  1638. channelNameForUrl.remove('#');
  1639. QString channelLink = "/~realtime/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
  1640. replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
  1641. htmlChannelLineS += htmlChannelLine;
  1642. }
  1643. replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
  1644. bool online {false};
  1645. for (const auto &srv: m_serversOnline) {
  1646. if (srv.first == s.first) {
  1647. online = srv.second;
  1648. break;
  1649. }
  1650. }
  1651. if (online) {
  1652. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
  1653. } else {
  1654. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
  1655. }
  1656. if (s.first == originalServerName) {
  1657. htmlServersSectionS.push_front(htmlServersSection);
  1658. } else {
  1659. htmlServersSectionS += htmlServersSection;
  1660. }
  1661. }
  1662. htmlServersSectionS.push_front(m_serviceButton);
  1663. replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
  1664. //// Main section header compilation
  1665. QString& topic = m_channelsTopic[originalServerName][originalChannelName];
  1666. topic = topic.replace('\"', "&quot;");
  1667. QString titlePostfix = " | " + m_serviceName;
  1668. if (not topic.isEmpty()) {
  1669. titlePostfix.push_front(" | " + topic);
  1670. }
  1671. if (m_servers.size() > 1) {
  1672. replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+") [real time]" + titlePostfix);
  1673. } else {
  1674. replaceTag(page, "PAGE_TITLE", originalChannelName + " [real time]" + titlePostfix);
  1675. }
  1676. replaceTag(page, "CHANNEL_TOPIC", topic);
  1677. replaceTag(page, "MAIN_HEADER", originalChannelName);
  1678. replaceTag(page, "ADDITIONAL_BUTTON", HTML_PAYLOAD_ADDITIONAL_NOTIFY);
  1679. replaceTag(page, "REALTIME_LINK", "/"+server+"/"+channel+"/"+year+"/"+month+"/"+day);
  1680. replaceTag(page, "AIRPLAIN_TITLE", "Back to plain text log");
  1681. page.replace("class=\"main_header__title_airplaine\"", "class=\"main_header__title_airplaine\" style=\"transform: scale(-1,1)\"");
  1682. int currentOnline = 0;
  1683. QString onlineUserS;
  1684. for (const auto &user: m_servers[originalServerName][originalChannelName]) {
  1685. if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) {
  1686. continue;
  1687. }
  1688. QString onlineUser = HTML_ONLINE_POINT;
  1689. replaceTag(onlineUser, "NICKNAME", user);
  1690. onlineUserS += onlineUser;
  1691. currentOnline++;
  1692. }
  1693. page.replace("{{ONLINE}}", "<span id=\"online\">{{ONLINE}}</span>");
  1694. replaceTag(page, "ONLINE", QString::number(currentOnline));
  1695. page.replace("<div class=\"main_middle__online_list\">", "<div id=\"onlineList\" class=\"main_middle__online_list\">");
  1696. replaceTag(page, "ONLINE_LIST", onlineUserS);
  1697. page.replace("class=\"main_header__search_form\"", "action=\"/"+server+"/"+channel+"\" class=\"main_header__search_form\"");
  1698. replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName);
  1699. page.replace("<div class=\"main_middle__path\">", "<div class=\"main_middle__path\" id=\"path\">");
  1700. replaceTag(page, "MIDDLE_PATH", "");
  1701. //// Payload
  1702. page.replace("<div class=\"main_payload\">", "<div class=\"main_payload\" id=\"payload\">");
  1703. bool logsExisted {false};
  1704. QFile file;
  1705. QDir fsPath(m_dataFolder+server+global::slash+channel);
  1706. if (fsPath.cd(year)) {
  1707. if (fsPath.cd(month)) {
  1708. file.setFileName(fsPath.path()+global::slash+day+".txt");
  1709. if (file.open(QIODevice::ReadOnly)) {
  1710. logsExisted = true;
  1711. }
  1712. }
  1713. }
  1714. QString payloadBlock;
  1715. if (logsExisted) {
  1716. QString buffer = file.readLine();
  1717. while (not buffer.isEmpty()) {
  1718. removeBrakelineSymbols(buffer);
  1719. std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
  1720. if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
  1721. buffer = file.readLine();
  1722. continue;
  1723. }
  1724. QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  1725. message.remove("name=\"{{ANCHOR}}\" href=\"{{ANCHOR}}\" ");
  1726. if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
  1727. message.replace("class=\"main_payload__chat\"",
  1728. "class=\"main_payload__chat\" style=\"opacity: .5\"");
  1729. }
  1730. message.replace("class=\"main_payload__chat_username\"",
  1731. "class=\"main_payload__chat_username\" style=\"color: #e34f10\"");
  1732. replaceTag(message, "USERNAME", rawMessage.first);
  1733. replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
  1734. payloadBlock += message;
  1735. buffer = file.readLine();
  1736. }
  1737. file.close();
  1738. }
  1739. QString hr = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
  1740. hr.replace("class=\"main_payload__chat_mail\"", "id=\"hr\" class=\"main_payload__chat_mail\"");
  1741. hr.replace("class=\"main_payload__chat_username\"",
  1742. "class=\"main_payload__chat_username\" style=\"color: white; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;\"");
  1743. replaceTag(hr, "USERNAME", "IRCaBot");
  1744. replaceTag(hr, "MESSAGE_TEXT", "<b>New messages won't show without JavaScript.</b><br>"
  1745. "My JS code is small and simple. Check it at "
  1746. "<a href=\"/realtimechat.js\">/realtimechat.js</a> and come back "
  1747. "with enabled!");
  1748. payloadBlock += hr;
  1749. replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
  1750. //// Footer
  1751. replaceTag(page, "VERSION", global::IRCABOT_VERSION);
  1752. replaceTag(page, "COPYRIGHT_YEAR", global::COPYRIGHT_YEAR);
  1753. //// Finish
  1754. page.replace("</body>", " <div id=\"LMId\" style=\"display: none\">" + QString::number(QDateTime::currentMSecsSinceEpoch()) + "</div>\n"
  1755. " <div id=\"ajaxPath\" style=\"display: none\">" + server + "/" + channel + "</div>\n"
  1756. " <script src=\"/realtimechat.js\"></script>\n</body>");
  1757. replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart));
  1758. QString mainHeader = HEADER_HTML;
  1759. replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
  1760. if (socket->isOpen()) {
  1761. socket->write(mainHeader.toUtf8());
  1762. if (not isHeadRequest) socket->write(page.toUtf8());
  1763. }
  1764. }
  1765. void HttpServer::writeAboutServerPage(QTcpSocket *socket, QString &server, bool isHeadRequest)
  1766. {
  1767. auto renderStart = QDateTime::currentMSecsSinceEpoch();
  1768. QString originalServerName;
  1769. for (const auto &s: m_servers) {
  1770. if (global::toLowerAndNoSpaces(s.first) == server) {
  1771. originalServerName = s.first;
  1772. }
  1773. }
  1774. if (originalServerName.isEmpty()) {
  1775. writeErrorPage(socket, "SERVER NOT EXIST");
  1776. return;
  1777. }
  1778. QFile main("://html/main.html");
  1779. QString page;
  1780. if (main.open(QIODevice::ReadOnly)) {
  1781. page = main.readAll();
  1782. main.close();
  1783. }
  1784. else {
  1785. if (socket->isOpen()) {
  1786. socket->write(HEADER_404.toUtf8());
  1787. if (not isHeadRequest) socket->write(CRITICAL_ERROR);
  1788. }
  1789. }
  1790. replaceTag(page, "PAGE_TITLE", "About " + originalServerName + " | " + m_serviceName);
  1791. //// Left menu compilation
  1792. QString htmlServersSectionS;
  1793. for (const auto &s: m_servers) {
  1794. if (s.first.isEmpty()) continue; // empty server name?
  1795. QString htmlServersSection = HTML_SERVER_SECTION;
  1796. if (s.first == originalServerName) {
  1797. htmlServersSection.replace("<div class=\"left_menu__item\">",
  1798. "<div class=\"left_menu__item\" style=\"background: #f0f5fa\">");
  1799. }
  1800. replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first);
  1801. replaceTag(htmlServersSection, "SERVER_NAME", s.first);
  1802. QString htmlChannelLineS;
  1803. for (const auto &c: s.second) {
  1804. QString htmlChannelLine;
  1805. htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
  1806. replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
  1807. QString channelNameForUrl {c.first};
  1808. channelNameForUrl.remove('#');
  1809. QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
  1810. replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
  1811. htmlChannelLineS += htmlChannelLine;
  1812. }
  1813. replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
  1814. bool online {false};
  1815. for (const auto &srv: m_serversOnline) {
  1816. if (srv.first == s.first) {
  1817. online = srv.second;
  1818. break;
  1819. }
  1820. }
  1821. if (online) {
  1822. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
  1823. } else {
  1824. replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
  1825. }
  1826. if (s.first == originalServerName) {
  1827. htmlServersSectionS.push_front(htmlServersSection);
  1828. } else {
  1829. htmlServersSectionS += htmlServersSection;
  1830. }
  1831. }
  1832. htmlServersSectionS.push_front(m_serviceButton);
  1833. replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
  1834. page.remove(QRegularExpression("<div class=\"main_header\">.*<!-- main_middle -->", QRegularExpression::DotMatchesEverythingOption));
  1835. QString payloadBlock = HTML_PAYLOAD_ABOUT;
  1836. replaceTag(payloadBlock, "ABOUT_TITLE", originalServerName);
  1837. QString aboutBlock;
  1838. QFile about(m_dataFolder+server+global::slash+"about_server.txt");
  1839. if (about.open(QIODevice::ReadOnly))
  1840. {
  1841. QString rbuffer = about.readLine();
  1842. while (not rbuffer.isEmpty()) {
  1843. if (rbuffer.startsWith('#')) {
  1844. rbuffer = about.readLine();
  1845. continue;
  1846. }
  1847. removeBrakelineSymbols(rbuffer);
  1848. if (not rbuffer.isEmpty()) {
  1849. aboutBlock += rbuffer;
  1850. }
  1851. rbuffer = about.readLine();
  1852. }
  1853. }
  1854. else {
  1855. aboutBlock = "No information provided";
  1856. }
  1857. replaceTag(payloadBlock, "ABOUT_TEXT", aboutBlock);
  1858. replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
  1859. //// Footer
  1860. replaceTag(page, "VERSION", global::IRCABOT_VERSION);
  1861. replaceTag(page, "COPYRIGHT_YEAR", global::COPYRIGHT_YEAR);
  1862. //// Finish
  1863. replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart));
  1864. QString mainHeader = HEADER_HTML;
  1865. replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
  1866. if (socket->isOpen()) {
  1867. socket->write(mainHeader.toUtf8());
  1868. if (not isHeadRequest) socket->write(page.toUtf8());
  1869. }
  1870. }
  1871. void HttpServer::writeAjaxAnswer(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
  1872. {
  1873. if (isHeadRequest) {
  1874. QString header = HEADER_JSON;
  1875. replaceTag(header, "SIZE", "0");
  1876. socket->write(header.toUtf8());
  1877. return;
  1878. }
  1879. //// Validate
  1880. urlPath.remove(QRegularExpression("^/ajax/"));
  1881. QString server = getWordFromPath(urlPath);
  1882. if (server.isEmpty()) {
  1883. writeErrorJson(socket, "Invalid request. Server value is missing!");
  1884. return;
  1885. }
  1886. urlPath.remove(QRegularExpression("^"+server));
  1887. QString channel = getWordFromPath(urlPath);
  1888. if (channel.isEmpty()) {
  1889. writeErrorJson(socket, "Invalid request. Channel value is missing!");
  1890. return;
  1891. }
  1892. QString originalServerName;
  1893. for (const auto &s: m_servers) {
  1894. if (global::toLowerAndNoSpaces(s.first) == server) {
  1895. originalServerName = s.first;
  1896. }
  1897. }
  1898. if (originalServerName.isEmpty()) {
  1899. writeErrorJson(socket, "Invalid request. Server not exist!");
  1900. return;
  1901. }
  1902. QString originalChannelName;
  1903. for (const auto &server: m_servers) {
  1904. for (const auto &channel_users: server.second) {
  1905. if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) {
  1906. originalChannelName = global::toLowerAndNoSpaces(channel_users.first);
  1907. }
  1908. }
  1909. }
  1910. if (originalChannelName.isEmpty()) {
  1911. writeErrorJson(socket, "Invalid request. Channel not exist!");
  1912. return;
  1913. }
  1914. //// Parse
  1915. bool userOnlineCounterIsOk {false};
  1916. auto userOnlineCounter = global::getValue(urlPath, "onlineCounter", global::eForWeb).toInt(&userOnlineCounterIsOk);
  1917. if (not userOnlineCounterIsOk) {
  1918. writeErrorJson(socket, "Invalid request: 'onlineCounter' (int) value is missing!");
  1919. return;
  1920. }
  1921. bool userMessageLastIdIsOk {false};
  1922. qint64 userMessageLastId = global::getValue(urlPath, "messageId", global::eForWeb).toLongLong(&userMessageLastIdIsOk);
  1923. if (not userMessageLastIdIsOk) {
  1924. writeErrorJson(socket, "Invalid request: 'messageId' value is missing!");
  1925. return;
  1926. }
  1927. bool userServerStatus = global::getValue(urlPath, "serverStatus", global::eForWeb) == "true";
  1928. //// Building
  1929. QJsonObject jResponse;
  1930. jResponse.insert("status", QJsonValue(true));
  1931. // online server
  1932. if (userServerStatus != m_serversOnline[originalServerName]) {
  1933. jResponse.insert("serverStatusChanged", QJsonValue(true));
  1934. jResponse.insert("serverStatus", QJsonValue(m_serversOnline[originalServerName]));
  1935. } else {
  1936. jResponse.insert("serverStatusChanged", QJsonValue(false));
  1937. }
  1938. // online users
  1939. if (m_servers[originalServerName][originalChannelName].size()-1/*self*/ != userOnlineCounter) {
  1940. jResponse.insert("onlineUsersChanged", QJsonValue(true));
  1941. QJsonObject jOnline;
  1942. int currentOnline = m_servers[originalServerName][originalChannelName].size();
  1943. if (currentOnline > 0) currentOnline -= 1;
  1944. jOnline.insert("count", QJsonValue(currentOnline));
  1945. QJsonArray jOnlineList;
  1946. for (const auto& user: m_servers[originalServerName][originalChannelName]) {
  1947. if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) {
  1948. continue;
  1949. }
  1950. jOnlineList.push_back(QJsonValue(user));
  1951. }
  1952. jOnline.insert("list", jOnlineList);
  1953. jResponse.insert("online", jOnline);
  1954. } else {
  1955. jResponse.insert("onlineUsersChanged", QJsonValue(false));
  1956. }
  1957. // new messages
  1958. bool newMessagesIsExisted {false};
  1959. QString channelId {server+channel};
  1960. QJsonArray jNewMessages;
  1961. qint64 newLastMessageId {userMessageLastId};
  1962. if (not m_messageCache.contains(channelId)) {
  1963. m_messageCache.insert(channelId, new MessagePool);
  1964. consoleLog("Message caching enabled for "+server+"/#"+channel+". Real time reading started.");
  1965. }
  1966. else {
  1967. auto messages = *(m_messageCache[channelId]->getMessages());
  1968. for (const auto& msg: messages) {
  1969. if (userMessageLastId < msg.first) {
  1970. if (not newMessagesIsExisted) newMessagesIsExisted = true;
  1971. QJsonObject jOneNewMessage;
  1972. jOneNewMessage.insert("user", msg.second->getSender());
  1973. jOneNewMessage.insert("text", msg.second->getText());
  1974. jNewMessages.push_back(jOneNewMessage);
  1975. newLastMessageId = msg.first;
  1976. }
  1977. }
  1978. }
  1979. if (newMessagesIsExisted) {
  1980. jResponse.insert("LMIdChanged" /* last message id == LMId */, QJsonValue(true));
  1981. jResponse.insert("newMessages", jNewMessages);
  1982. jResponse.insert("LMId", QJsonValue(QString::number(newLastMessageId)));
  1983. } else {
  1984. jResponse.insert("LMIdChanged", QJsonValue(false));
  1985. }
  1986. //// Finish
  1987. QByteArray response {QJsonDocument(jResponse).toJson()};
  1988. QString header = HEADER_JSON;
  1989. replaceTag(header, "SIZE", QString::number(response.size()));
  1990. socket->write(header.toUtf8());
  1991. socket->write(response);
  1992. }
  1993. /*\
  1994. |*| //////////'''''''''''''\\\\\\\\\\
  1995. |*| HAPPY NEW YEAR IN RUSSIAN PRISON!
  1996. |*| |||||||||| 2022 ||||||||||
  1997. |*| \\\\\\\\\\_____________//////////
  1998. \*/
  1999. Message::Message(const QString& s, const QString& t, qint64 timestamp, QObject *parent) :
  2000. QObject(parent),
  2001. m_sender(s),
  2002. m_text(t),
  2003. m_timestamp(timestamp)
  2004. {
  2005. connect (&m_selfKiller, &QTimer::timeout, [&](){emit outDated(m_timestamp);});
  2006. m_selfKiller.setSingleShot(true);
  2007. m_selfKiller.start(MSECS_TO_AUTOREMOVE_MESSAGES_FROM_BUFFER);
  2008. }
  2009. const QString Message::getSender()
  2010. {
  2011. return m_sender;
  2012. }
  2013. const QString Message::getText()
  2014. {
  2015. return m_text;
  2016. }
  2017. MessagePool::MessagePool(QObject *parent) :
  2018. QObject(parent),
  2019. m_lastPing(QDateTime::currentMSecsSinceEpoch())
  2020. {}
  2021. void MessagePool::saveNewMessage(const QString &nick, const QString &text)
  2022. {
  2023. qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
  2024. Message* newMessage = new Message (nick, text, timestamp);
  2025. connect (newMessage, SIGNAL(outDated(qint64)), this, SLOT (messageToDelete(qint64)));
  2026. m_messages.insert({timestamp, newMessage});
  2027. }
  2028. const std::multimap<qint64, Message *>* MessagePool::getMessages()
  2029. {
  2030. m_lastPing = QDateTime::currentMSecsSinceEpoch();
  2031. return &m_messages;
  2032. }
  2033. qint64 MessagePool::getLastPing()
  2034. {
  2035. return m_lastPing;
  2036. }
  2037. void MessagePool::messageToDelete(qint64 timestamp)
  2038. {
  2039. while (m_messages.find(timestamp) != m_messages.end()) {
  2040. auto it = m_messages.find(timestamp);
  2041. it->second->deleteLater();
  2042. m_messages.erase(it);
  2043. }
  2044. }
  2045. const QString &NickColorist::getGreenColor(bool next)
  2046. {
  2047. if (not next) {
  2048. return *m_currentGreen;
  2049. }
  2050. if (m_currentGreen == &GREEN_1) {
  2051. m_currentGreen = &GREEN_2;
  2052. }else{
  2053. m_currentGreen = &GREEN_1;
  2054. }
  2055. return *m_currentGreen;
  2056. }
  2057. const QString &NickColorist::getRedColor(bool next)
  2058. {
  2059. if (not next) {
  2060. return *m_currentRed;
  2061. }
  2062. if (m_currentRed == &RED_1) {
  2063. m_currentRed = &RED_2;
  2064. }else{
  2065. m_currentRed = &RED_1;
  2066. }
  2067. return *m_currentRed;
  2068. }
  2069. void RequestCounter::operator++()
  2070. {
  2071. if (m_lastUpdateDate != QDate::currentDate()) {
  2072. m_requestsCounter = 1;
  2073. m_lastUpdateDate = QDate::currentDate();
  2074. }else{
  2075. m_requestsCounter++;
  2076. }
  2077. }
  2078. quint64 *RequestCounter::value()
  2079. {
  2080. return &m_requestsCounter;
  2081. }