mirror of https://notabug.org/acetone/ircabot.git
2097 lines
82 KiB
C++
2097 lines
82 KiB
C++
#include "httpserver.h"
|
|
#include "ircclient.h"
|
|
#include "connectiondata.h"
|
|
#include "global.h"
|
|
#include "version.h"
|
|
|
|
#include <QRegularExpression>
|
|
#include <QDirIterator>
|
|
#include <QDateTime>
|
|
#include <QHostAddress>
|
|
#include <QTcpSocket>
|
|
#include <QDebug>
|
|
#include <QJsonDocument>
|
|
#include <QJsonArray>
|
|
#include <QJsonObject>
|
|
#include <QFile>
|
|
#include <QDir>
|
|
|
|
const char CRITICAL_ERROR[] {"<title>Critical error</title><center><h1>NOT FOUND</H1><p>Maybe it's compile time error</p></center>"};
|
|
|
|
HttpServer::HttpServer(const QString &address, quint16 port, const QString& dataFolder,
|
|
const QString& serviceName, const QString& serviceEmoji, bool ajaxIsDisabled, QObject *parent) :
|
|
QObject(parent),
|
|
m_TcpServer(new QTcpServer),
|
|
m_serviceName(serviceName),
|
|
m_dataFolder(dataFolder),
|
|
m_ajaxIsDisabled(ajaxIsDisabled)
|
|
{
|
|
if (not m_TcpServer->listen(QHostAddress(address), port)) {
|
|
throw std::runtime_error("HttpServer not binded at " +
|
|
address.toStdString() + " : " + QString::number(port).toStdString());
|
|
}
|
|
consoleLog(address + " : " + QString::number(port));
|
|
if (m_ajaxIsDisabled) {
|
|
consoleLog("JavaScript on webpages removed and AJAX disabled!");
|
|
}
|
|
|
|
if (not QFile::exists(m_dataFolder+"main_page.txt")) {
|
|
QFile mp(m_dataFolder+"main_page.txt");
|
|
if (mp.open(QIODevice::WriteOnly)) {
|
|
mp.write("# Main page file.\n"
|
|
"# HTML is supported. For line breaks, use <br> or <p></p>.\n\n"
|
|
"<center>\n"
|
|
"# Your images from \"" + m_dataFolder.toUtf8() +
|
|
"custom_images\" must have URL \"/~images/\"\n"
|
|
" <img src=\"/~images/example.png\">\n"
|
|
"</center>\n");
|
|
mp.close();
|
|
}
|
|
else {
|
|
throw std::runtime_error("main_page.txt not exist and creating failed");
|
|
}
|
|
}
|
|
|
|
QDir dir {m_dataFolder};
|
|
if (not dir.cd("custom_images")) {
|
|
if (dir.mkdir("custom_images")) {
|
|
dir.cd("custom_images");
|
|
QFile examplePng(dir.path()+global::slash+"example.png");
|
|
if (examplePng.open(QIODevice::WriteOnly)) {
|
|
QFile nativePng("://html/custom_img_example.png");
|
|
if (nativePng.open(QIODevice::ReadOnly)) {
|
|
examplePng.write(nativePng.readAll());
|
|
nativePng.close();
|
|
examplePng.close();
|
|
}
|
|
}
|
|
} else {
|
|
consoleLog("Creating folder \"custom_images\" failed");
|
|
}
|
|
}
|
|
|
|
m_serviceButton = HTML_LEFT_MENU_MAIN_POINT;
|
|
replaceTag(m_serviceButton, "EMOJI", serviceEmoji);
|
|
replaceTag(m_serviceButton, "SERVICE_NAME", serviceName);
|
|
|
|
connect (m_TcpServer, &QTcpServer::newConnection, this, &HttpServer::acceptor);
|
|
}
|
|
|
|
HttpServer::~HttpServer()
|
|
{
|
|
m_TcpServer->close();
|
|
m_TcpServer->deleteLater();
|
|
}
|
|
|
|
QString HttpServer::convertToClickableLink(const QString &httpLine)
|
|
{
|
|
QString result;
|
|
if (not httpLine.contains(QRegularExpression("http.?://"))) return result;
|
|
|
|
QString displayedName {httpLine};
|
|
displayedName.remove(QRegularExpression("http.?://(www\\.)?"));
|
|
displayedName.remove(QRegularExpression("/$"));
|
|
result = "<a href=\"" + httpLine + "\"> " + displayedName + " </a>";
|
|
return result;
|
|
}
|
|
|
|
std::pair<QString, QString> HttpServer::splitUserNameAndMessage(const QString &rawLine)
|
|
{
|
|
std::pair<QString, QString> result;
|
|
|
|
QString nick {rawLine};
|
|
nick.remove(QRegularExpression("\\]\\s.*$"));
|
|
nick.remove(QRegularExpression("^\\["));
|
|
if (nick.isEmpty()) {
|
|
return result;
|
|
}
|
|
nick = nick.toHtmlEscaped();
|
|
|
|
// long nicks
|
|
if (nick.size() > MAX_NICKNAME_LENGTH_WITHOUT_WBR) {
|
|
int lastWbr = 0;
|
|
for (int i = 0; i < nick.size(); i++) {
|
|
if (i-lastWbr > MAX_NICKNAME_LENGTH_WITHOUT_WBR) {
|
|
nick.insert(i, "<wbr>");
|
|
lastWbr = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
QString text {rawLine};
|
|
text.remove(QRegularExpression("^\\[[^\\s]*\\]\\s"));
|
|
if (text.isEmpty()) {
|
|
return result;
|
|
}
|
|
text = text.toHtmlEscaped();
|
|
|
|
// http links
|
|
bool linksFound {false};
|
|
while (QRegularExpression("(^|\\s)http.?://").match(text).hasMatch()) {
|
|
if (not linksFound) linksFound = true;
|
|
int pos = text.indexOf(QRegularExpression("(^|\\s)http.?://"));
|
|
if (pos == -1) {
|
|
consoleLog("Bug! HttpServer.cpp while (QRegularExpression(\"(^|\\s)http.?://\").match(text).hasMatch())");
|
|
break;
|
|
}
|
|
QString rawLink {text};
|
|
rawLink.remove(0, pos);
|
|
if (rawLink.startsWith(' ')) {
|
|
rawLink.remove(0,1);
|
|
}
|
|
int space = rawLink.indexOf(' ');
|
|
if (space > 0) {
|
|
rawLink.remove(space, rawLink.size()-space);
|
|
}
|
|
text.replace(rawLink, convertToClickableLink(rawLink));
|
|
}
|
|
// long lines
|
|
int space = 0;
|
|
bool nbTag = false; // For safe HTML tags like a ⁢ via <wbr>!
|
|
bool isHref = false;
|
|
for (int i = 0; i < text.size(); i++) {
|
|
if (text[i] == ' ') {
|
|
space = i;
|
|
if (isHref) {
|
|
isHref = false;
|
|
}
|
|
else {
|
|
if (text.indexOf("href=\"http", i+1) == i+1) {
|
|
isHref = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nbTag and text[i-1] == ';') {
|
|
nbTag = false;
|
|
}
|
|
if (text.indexOf(QRegularExpression("(\\&|\\<|\\>|\\").*"), i) == i) {
|
|
nbTag = true;
|
|
}
|
|
|
|
if (not isHref and i-space > MAX_MESSAGE_LENGTH_WITHOUT_WBR and not nbTag) {
|
|
text.insert(i, "<wbr>");
|
|
space = i;
|
|
}
|
|
}
|
|
if (linksFound) text.replace(" </a>", "</a>"); // delete whitespace in links end
|
|
|
|
result.first = nick;
|
|
result.second = text;
|
|
return result;
|
|
}
|
|
|
|
void HttpServer::consoleLog(const QString &message)
|
|
{
|
|
qInfo().noquote() << "[WEB_UI]" << message;
|
|
}
|
|
|
|
void HttpServer::debugLog(const QString &req)
|
|
{
|
|
QFile log(m_dataFolder + "debug.log");
|
|
if (log.open(QIODevice::WriteOnly | QIODevice::Append)) {
|
|
log.write(QDateTime::currentDateTime().toString().toUtf8() + ":\n" + req.toUtf8() + "\n\n");
|
|
log.close();
|
|
}
|
|
}
|
|
|
|
void HttpServer::acceptor()
|
|
{
|
|
QTcpSocket* socket = m_TcpServer->nextPendingConnection();
|
|
connect(socket, &QTcpSocket::readyRead, this, &HttpServer::reader);
|
|
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
|
|
}
|
|
|
|
void HttpServer::reader()
|
|
{
|
|
QTcpSocket* socket = static_cast<QTcpSocket*>(sender());
|
|
QString request = socket->read(BUFFER_SIZE);
|
|
if (not request.startsWith("GET") and not request.startsWith("HEAD")) {
|
|
if (socket->isOpen()) {
|
|
socket->write("Your request has been rejected!\n");
|
|
socket->disconnectFromHost();
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool isHeadRequest = false;
|
|
if (request.startsWith("HEAD ")) {
|
|
isHeadRequest = true;
|
|
}
|
|
|
|
QString urlPath = getRequestPath(request);
|
|
if (urlPath == "/favicon.ico") {
|
|
QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
|
|
if (eTag == HTTP_ACTUAL_ETAG) {
|
|
if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
|
|
}
|
|
else {
|
|
QFile icon("://html/favicon.ico");
|
|
if (icon.open(QIODevice::ReadOnly)) {
|
|
QByteArray file = icon.readAll();
|
|
icon.close();
|
|
QString header = HEADER_ICO;
|
|
replaceTag(header, "SIZE", QString::number(file.size()));
|
|
if (socket->isOpen()) {
|
|
socket->write(header.toUtf8());
|
|
if (not isHeadRequest) socket->write(file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (urlPath == "/") {
|
|
writeMainPage(socket, isHeadRequest);
|
|
}
|
|
else if (urlPath == "/newmessage.mp3" and not m_ajaxIsDisabled) {
|
|
QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
|
|
if (eTag == HTTP_ACTUAL_ETAG) {
|
|
if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
|
|
}
|
|
else {
|
|
QFile mp3("://html/newmessage.mp3");
|
|
if (mp3.open(QIODevice::ReadOnly)) {
|
|
QByteArray file = mp3.readAll();
|
|
mp3.close();
|
|
QString header = HEADER_MP3;
|
|
replaceTag(header, "SIZE", QString::number(file.size()));
|
|
if (socket->isOpen()) {
|
|
socket->write(header.toUtf8());
|
|
if (not isHeadRequest) socket->write(file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (urlPath == "/style.css") {
|
|
QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
|
|
if (eTag == HTTP_ACTUAL_ETAG) {
|
|
if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
|
|
}
|
|
else {
|
|
QFile css("://html/style.css");
|
|
if (css.open(QIODevice::ReadOnly)) {
|
|
QByteArray file = css.readAll();
|
|
css.close();
|
|
QString header = HEADER_CSS;
|
|
replaceTag(header, "SIZE", QString::number(file.size()));
|
|
if (socket->isOpen()) {
|
|
socket->write(header.toUtf8());
|
|
if (not isHeadRequest) socket->write(file);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (urlPath.endsWith(".svg")) {
|
|
QString eTag = global::getValue(request, "If-None-Match", global::eHttpHeader);
|
|
if (eTag == HTTP_ACTUAL_ETAG) {
|
|
if (socket->isOpen()) socket->write(HEADER_304.toUtf8());
|
|
}
|
|
else {
|
|
QFile svg("://html"+urlPath);
|
|
if (svg.open(QIODevice::ReadOnly)) {
|
|
QByteArray file = svg.readAll();
|
|
svg.close();
|
|
QString header = HEADER_SVG;
|
|
replaceTag(header, "SIZE", QString::number(file.size()));
|
|
if (socket->isOpen()) {
|
|
socket->write(header.toUtf8());
|
|
if (not isHeadRequest) socket->write(file);
|
|
}
|
|
}
|
|
else {
|
|
if (socket->isOpen()) {
|
|
socket->write(HEADER_404.toUtf8());
|
|
if (not isHeadRequest) writeErrorPage(socket);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (urlPath == "/realtimechat.js" and not m_ajaxIsDisabled) {
|
|
QFile js("://html/realtimechat.js");
|
|
if (js.open(QIODevice::ReadOnly)) {
|
|
QByteArray file = js.readAll();
|
|
js.close();
|
|
QString header = HEADER_JS;
|
|
replaceTag(header, "SIZE", QString::number(file.size()));
|
|
if (socket->isOpen()) {
|
|
socket->write(header.toUtf8());
|
|
if (not isHeadRequest) socket->write(file);
|
|
}
|
|
}
|
|
}
|
|
else if (urlPath.startsWith("/ajax/")) {
|
|
if (m_ajaxIsDisabled) {
|
|
writeErrorJson(socket, "AJAX disabled");
|
|
} else {
|
|
writeAjaxAnswer(socket, urlPath, isHeadRequest);
|
|
}
|
|
}
|
|
else if (urlPath.startsWith("/realtimereadingchat/")) {
|
|
if (m_ajaxIsDisabled) {
|
|
writeErrorPage(socket, "DISABLED"
|
|
"<p>No JS, no AJAX, no real time reading!</p>");
|
|
} else {
|
|
writeRealTimeChatPage(socket, urlPath, isHeadRequest);
|
|
}
|
|
}
|
|
else if (urlPath.startsWith("/~images/")) {
|
|
writeCustomPicture(socket, urlPath, isHeadRequest);
|
|
}
|
|
else {
|
|
writeRegularPage(socket, urlPath, isHeadRequest);
|
|
}
|
|
socket->disconnectFromHost();
|
|
}
|
|
|
|
void HttpServer::ircBotFirstInfo(QString server, QStringList channels)
|
|
{
|
|
for (const auto &c: channels) {
|
|
if (c.isEmpty()) continue;
|
|
m_servers[server][c] = QStringList();
|
|
}
|
|
}
|
|
|
|
void HttpServer::ircUsersOnline(QString server, QString channel, QStringList users)
|
|
{
|
|
if (server.isEmpty()) return;
|
|
if (channel.isEmpty()) return;
|
|
QStringList sortedNicknames;
|
|
QStringList ownersNicks;
|
|
QStringList operNicks;
|
|
QStringList halfopNicks;
|
|
QStringList adminNicks;
|
|
QStringList voicedNicks;
|
|
QStringList plainNicks;
|
|
for (auto rawOneNick: users) {
|
|
rawOneNick = rawOneNick.toHtmlEscaped();
|
|
if (rawOneNick.startsWith('~')) {
|
|
ownersNicks.push_back(rawOneNick);
|
|
}
|
|
else if (rawOneNick.startsWith('@')) {
|
|
operNicks.push_back(rawOneNick);
|
|
}
|
|
else if (rawOneNick.startsWith('%')) {
|
|
halfopNicks.push_back(rawOneNick);
|
|
}
|
|
else if (rawOneNick.startsWith('&')) {
|
|
adminNicks.push_back(rawOneNick);
|
|
}
|
|
else if (rawOneNick.startsWith('+')) {
|
|
voicedNicks.push_back(rawOneNick);
|
|
}
|
|
else {
|
|
plainNicks.push_back(rawOneNick);
|
|
}
|
|
}
|
|
if (not ownersNicks.isEmpty()) {
|
|
std::sort(ownersNicks.begin(), ownersNicks.end());
|
|
sortedNicknames += ownersNicks;
|
|
}
|
|
if (not operNicks.isEmpty()) {
|
|
std::sort(operNicks.begin(), operNicks.end());
|
|
sortedNicknames += operNicks;
|
|
}
|
|
if (not halfopNicks.isEmpty()) {
|
|
std::sort(halfopNicks.begin(), halfopNicks.end());
|
|
sortedNicknames += halfopNicks;
|
|
}
|
|
if (not adminNicks.isEmpty()) {
|
|
std::sort(adminNicks.begin(), adminNicks.end());
|
|
sortedNicknames += adminNicks;
|
|
}
|
|
if (not voicedNicks.isEmpty()) {
|
|
std::sort(voicedNicks.begin(), voicedNicks.end());
|
|
sortedNicknames += voicedNicks;
|
|
}
|
|
if (not plainNicks.isEmpty()) {
|
|
std::sort(plainNicks.begin(), plainNicks.end());
|
|
sortedNicknames += plainNicks;
|
|
}
|
|
sortedNicknames.removeAll("");
|
|
m_servers[server][channel] = sortedNicknames;
|
|
}
|
|
|
|
void HttpServer::ircChannelTopic(QString server, QString channel, QString topic)
|
|
{
|
|
m_channelsTopic[server][channel] = topic;
|
|
}
|
|
|
|
void HttpServer::ircServerOnline(QString server, quint8 status)
|
|
{
|
|
if (server.isEmpty()) return;
|
|
bool online = status;
|
|
m_serversOnline[server] = online;
|
|
}
|
|
|
|
void HttpServer::ircBotNick(QString server, QString nickname)
|
|
{
|
|
m_botNick[server] = nickname;
|
|
}
|
|
|
|
void HttpServer::ircMessageCache(QString server, QString channel, QString nick, QString text)
|
|
{
|
|
QString channelId {server+channel};
|
|
if (not m_messageCache.contains(channelId)) return;
|
|
// remove timed out session
|
|
if (QDateTime::currentMSecsSinceEpoch() - MSECS_TO_AUTOREMOVE_MESSAGES_FROM_BUFFER >
|
|
m_messageCache[channelId]->getLastPing())
|
|
{
|
|
consoleLog("Message caching disabled for "+server+"/#"+channel+". No active reader.");
|
|
m_messageCache.remove(channelId);
|
|
return;
|
|
}
|
|
else {
|
|
auto processedMsg {splitUserNameAndMessage("["+nick+"] " + text)};
|
|
m_messageCache[channelId]->saveNewMessage(processedMsg.first, processedMsg.second);
|
|
}
|
|
}
|
|
|
|
QString HttpServer::getRequestPath(const QString &req)
|
|
{
|
|
if (req.isEmpty()) return QString();
|
|
|
|
QString result(req);
|
|
|
|
int begin = result.indexOf(' ');
|
|
if (begin == -1) return QString();
|
|
|
|
result.remove(0, begin+1);
|
|
|
|
int space = result.indexOf(' ');
|
|
int size = result.size();
|
|
result.remove(space, size-space);
|
|
result = QByteArray::fromPercentEncoding(result.toUtf8());
|
|
|
|
return result;
|
|
}
|
|
|
|
QString HttpServer::getWordFromPath(const QString &path)
|
|
{
|
|
QString result {path};
|
|
result.remove(QRegularExpression("\\?.*$")); // any actions like a ?toSearch=
|
|
if (result.startsWith('/')) {
|
|
result.remove(QRegularExpression("^/"));
|
|
}
|
|
result.remove(QRegularExpression("/.*$"));
|
|
return result;
|
|
}
|
|
|
|
void HttpServer::writeMainPage(QTcpSocket *socket, bool isHeadRequest)
|
|
{
|
|
auto renderStart = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
QFile main("://html/main.html");
|
|
QString page;
|
|
if (main.open(QIODevice::ReadOnly)) {
|
|
page = main.readAll();
|
|
main.close();
|
|
}
|
|
else {
|
|
if (socket->isOpen()) {
|
|
socket->write(HEADER_404.toUtf8());
|
|
if (not isHeadRequest) socket->write(CRITICAL_ERROR);
|
|
}
|
|
}
|
|
|
|
replaceTag(page, "PAGE_TITLE", m_serviceName + " | IRC logger");
|
|
|
|
//// Left menu compilation
|
|
QString htmlServersSectionS;
|
|
for (const auto &s: m_servers) {
|
|
if (s.first.isEmpty()) continue; // empty server name?
|
|
|
|
QString htmlServersSection = HTML_SERVER_SECTION;
|
|
replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first);
|
|
replaceTag(htmlServersSection, "SERVER_NAME", s.first);
|
|
|
|
QString htmlChannelLineS;
|
|
for (const auto &c: s.second) {
|
|
QString htmlChannelLine;
|
|
htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
|
|
replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
|
|
|
|
QString channelNameForUrl {c.first};
|
|
channelNameForUrl.remove('#');
|
|
QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
|
|
replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
|
|
|
|
htmlChannelLineS += htmlChannelLine;
|
|
}
|
|
replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
|
|
|
|
bool online {false};
|
|
for (const auto &srv: m_serversOnline) {
|
|
if (srv.first == s.first) {
|
|
online = srv.second;
|
|
break;
|
|
}
|
|
}
|
|
if (online) {
|
|
replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
|
|
} else {
|
|
replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
|
|
}
|
|
htmlServersSectionS += htmlServersSection;
|
|
}
|
|
QString serviceButtonSelected {m_serviceButton};
|
|
serviceButtonSelected.replace("class=\"left_menu__mainitem\"",
|
|
"class=\"left_menu__mainitem\" style=\"opacity: 1\"");
|
|
htmlServersSectionS.push_front(serviceButtonSelected);
|
|
replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
|
|
page.remove(QRegularExpression("<div class=\"main_header\">.*<!-- main_middle -->", QRegularExpression::DotMatchesEverythingOption));
|
|
|
|
QString payloadBlock = HTML_PAYLOAD_ABOUT;
|
|
payloadBlock.remove("<span style=\"color: green; display: block; font-size: 24px; text-align: center;\">{{ABOUT_TITLE}}</span><br>\n");
|
|
|
|
QString aboutBlock;
|
|
QFile mp(m_dataFolder+global::slash+"main_page.txt");
|
|
if (mp.open(QIODevice::ReadOnly)) {
|
|
QString rbuffer = mp.readLine();
|
|
while (not rbuffer.isEmpty()) {
|
|
if (rbuffer.startsWith('#')) {
|
|
rbuffer = mp.readLine();
|
|
continue;
|
|
}
|
|
removeBrakelineSymbols(rbuffer);
|
|
if (not rbuffer.isEmpty()) {
|
|
aboutBlock += rbuffer;
|
|
}
|
|
rbuffer = mp.readLine();
|
|
}
|
|
}
|
|
else {
|
|
aboutBlock = "No information provided";
|
|
}
|
|
replaceTag(payloadBlock, "ABOUT_TEXT", aboutBlock);
|
|
replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
|
|
|
|
//// Footer
|
|
replaceTag(page, "VERSION", IRCABOT_VERSION);
|
|
replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR);
|
|
|
|
//// Finish
|
|
replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart));
|
|
QString mainHeader = HEADER_HTML;
|
|
replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
|
|
|
|
if (socket->isOpen()) {
|
|
socket->write(mainHeader.toUtf8());
|
|
if (not isHeadRequest) socket->write(page.toUtf8());
|
|
}
|
|
}
|
|
|
|
void HttpServer::writeCustomPicture(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
|
|
{
|
|
QString fileName {urlPath};
|
|
fileName.remove(QRegularExpression("^/~images/"));
|
|
|
|
QFile f {m_dataFolder+global::slash+"custom_images"+global::slash+fileName};
|
|
if (not f.exists()) {
|
|
writeErrorPage(socket, fileName + "<p>IMAGE NOT FOUND</p>");
|
|
return;
|
|
}
|
|
QByteArray file;
|
|
if (f.open(QIODevice::ReadOnly)) {
|
|
file = f.readAll();
|
|
f.close();
|
|
}
|
|
else {
|
|
writeErrorPage(socket, "FORBIDDEN");
|
|
return;
|
|
}
|
|
|
|
QString picHeader = HEADER_IMG;
|
|
QString fileType {fileName};
|
|
fileType.remove(QRegularExpression(".*\\."));
|
|
replaceTag(picHeader, "TYPE", fileType);
|
|
replaceTag(picHeader, "SIZE", QString::number(file.size()));
|
|
if (socket->isOpen()) {
|
|
socket->write(picHeader.toUtf8());
|
|
if (not isHeadRequest) socket->write(file);
|
|
}
|
|
}
|
|
|
|
void HttpServer::writeErrorPage(QTcpSocket *socket, const QString& text)
|
|
{
|
|
if (socket->isOpen()) {
|
|
socket->write(HEADER_404.toUtf8());
|
|
socket->write("<title>404</title><center><h1>"+text.toUtf8()+"</H1></center>");
|
|
}
|
|
}
|
|
|
|
void HttpServer::writeErrorJson(QTcpSocket * socket, const QString &text)
|
|
{
|
|
QString header = HEADER_JSON;
|
|
QByteArray body {
|
|
"{\"status\": false, \"message\": \"" + text.toUtf8() + "\"}"
|
|
};
|
|
replaceTag(header, "SIZE", QString::number(body.size()));
|
|
socket->write(header.toUtf8());
|
|
socket->write(body);
|
|
}
|
|
|
|
void HttpServer::removeBrakelineSymbols(QString &line)
|
|
{
|
|
line.remove('\r');
|
|
line.remove('\n');
|
|
line.remove('\t');
|
|
}
|
|
|
|
inline void HttpServer::replaceTag(QString &page, const QString &tag, const QString &payload)
|
|
{
|
|
page.replace("{{"+tag+"}}", payload);
|
|
}
|
|
|
|
void HttpServer::writeRegularPage(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
|
|
{
|
|
auto renderStart = QDateTime::currentMSecsSinceEpoch();
|
|
QString searchRequest;
|
|
bool isRegexp {false};
|
|
int specSymbol = urlPath.indexOf('?'); // any actions like a ?toSearch=
|
|
if (specSymbol != -1) {
|
|
searchRequest = global::getValue(urlPath, "toSearch", global::eForWeb);
|
|
isRegexp = global::getValue(urlPath, "isRegexp", global::eForWeb) == "on";
|
|
urlPath.remove(specSymbol, urlPath.size()-specSymbol);
|
|
}
|
|
|
|
QString server = getWordFromPath(urlPath);
|
|
QDir fsPath(m_dataFolder+server);
|
|
if (not fsPath.exists()) {
|
|
if (isHeadRequest) {
|
|
if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
|
|
} else {
|
|
writeErrorPage(socket, "REQUESTED SERVER LOG NOT EXIST");
|
|
}
|
|
return;
|
|
}
|
|
|
|
urlPath.remove(QRegularExpression("^.*/"+server));
|
|
QString channel = getWordFromPath(urlPath);
|
|
channel.remove(QRegularExpression("\\?.*$"));
|
|
if (channel.isEmpty()) {
|
|
writeAboutServerPage(socket, server, isHeadRequest);
|
|
return;
|
|
}
|
|
|
|
if (not fsPath.cd(channel)) {
|
|
if (isHeadRequest) {
|
|
if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
|
|
} else {
|
|
writeErrorPage(socket, "REQUESTED CHANNEL LOG NOT EXIST");
|
|
}
|
|
return;
|
|
}
|
|
|
|
QString originalServerName;
|
|
for (const auto &s: m_servers) {
|
|
if (global::toLowerAndNoSpaces(s.first) == server) {
|
|
originalServerName = s.first;
|
|
}
|
|
}
|
|
|
|
QString originalChannelName;
|
|
for (const auto &server: m_servers) {
|
|
for (const auto &channel_users: server.second) {
|
|
if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) {
|
|
originalChannelName = global::toLowerAndNoSpaces(channel_users.first);
|
|
}
|
|
}
|
|
}
|
|
|
|
urlPath.remove(QRegularExpression("^.*/"+channel));
|
|
QString year = getWordFromPath(urlPath);
|
|
year.remove(QRegularExpression("\\?.*$"));
|
|
QString month;
|
|
QString day;
|
|
if (not year.isEmpty() and fsPath.cd(year)) {
|
|
urlPath.remove(QRegularExpression("^.*/"+year));
|
|
month = getWordFromPath(urlPath);
|
|
month.remove(QRegularExpression("\\?.*$"));
|
|
if (not month.isEmpty() and fsPath.cd(month)) {
|
|
if (urlPath.startsWith("/"+month+"/")) {
|
|
urlPath.remove(0,1);
|
|
int pos = urlPath.indexOf('/');
|
|
if (pos != -1) {
|
|
urlPath.remove(0,pos);
|
|
day = getWordFromPath(urlPath);
|
|
|
|
if (urlPath.endsWith(".txt")) {
|
|
QFile plain(fsPath.path()+global::slash+day);
|
|
if (plain.open(QIODevice::ReadOnly)) {
|
|
QString header = HEADER_TEXT;
|
|
QByteArray file = plain.readAll();
|
|
plain.close();
|
|
replaceTag(header, "SIZE", QString::number(file.size()));
|
|
if (socket->isOpen()) {
|
|
socket->write(header.toUtf8());
|
|
if (not isHeadRequest) socket->write(file);
|
|
}
|
|
}
|
|
else {
|
|
if (isHeadRequest) {
|
|
if (socket->isOpen()) socket->write(HEADER_404.toUtf8());
|
|
} else {
|
|
writeErrorPage(socket, "FILE OPENING FAILED");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
if (not QFile::exists(fsPath.path()+global::slash+day+".txt")) {
|
|
day.clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else { month.clear(); }
|
|
}
|
|
else { year.clear(); }
|
|
|
|
QFile main("://html/main.html");
|
|
QString page;
|
|
if (main.open(QIODevice::ReadOnly)) {
|
|
page = main.readAll();
|
|
main.close();
|
|
}
|
|
else {
|
|
if (socket->isOpen()) {
|
|
socket->write(HEADER_404.toUtf8());
|
|
if (not isHeadRequest) socket->write(CRITICAL_ERROR);
|
|
}
|
|
}
|
|
|
|
if (isRegexp) {
|
|
page.replace("<input id=\"main_header__search_checkbox__button\" type=\"checkbox\" name=\"isRegexp\">",
|
|
"<input id=\"main_header__search_checkbox__button\" type=\"checkbox\" name=\"isRegexp\" checked>");
|
|
}
|
|
|
|
//// Left menu compilation
|
|
QString htmlServersSectionS;
|
|
for (const auto &s: m_servers) {
|
|
if (s.first.isEmpty()) continue; // empty server name?
|
|
|
|
QString htmlServersSection = HTML_SERVER_SECTION;
|
|
replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first);
|
|
replaceTag(htmlServersSection, "SERVER_NAME", s.first);
|
|
|
|
QString htmlChannelLineS;
|
|
for (const auto &c: s.second) {
|
|
QString htmlChannelLine;
|
|
if (originalServerName == s.first and originalChannelName == c.first) {
|
|
htmlChannelLine = HTML_SERVER_SECTION_CHANNEL_SELECTED;
|
|
} else {
|
|
htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
|
|
}
|
|
replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
|
|
|
|
QString channelNameForUrl {c.first};
|
|
channelNameForUrl.remove('#');
|
|
QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
|
|
if (not year.isEmpty()) {
|
|
channelLink += "/" + year;
|
|
if (not month.isEmpty()) {
|
|
channelLink += "/" + month;
|
|
if (not day.isEmpty()) {
|
|
channelLink += "/" + day;
|
|
}
|
|
}
|
|
}
|
|
|
|
replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
|
|
|
|
htmlChannelLineS += htmlChannelLine;
|
|
}
|
|
replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
|
|
|
|
bool online {false};
|
|
for (const auto &srv: m_serversOnline) {
|
|
if (srv.first == s.first) {
|
|
online = srv.second;
|
|
break;
|
|
}
|
|
}
|
|
if (online) {
|
|
replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
|
|
} else {
|
|
replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
|
|
}
|
|
if (s.first == originalServerName) {
|
|
htmlServersSectionS.push_front(htmlServersSection);
|
|
} else {
|
|
htmlServersSectionS += htmlServersSection;
|
|
}
|
|
}
|
|
htmlServersSectionS.push_front(m_serviceButton);
|
|
replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
|
|
|
|
//// Main section header compilation
|
|
QString& topic = m_channelsTopic[originalServerName][originalChannelName];
|
|
topic = topic.replace('\"', """);
|
|
|
|
QString titlePostfix = " | " + m_serviceName;
|
|
if (not topic.isEmpty()) {
|
|
titlePostfix.push_front(" | " + topic);
|
|
}
|
|
if (m_servers.size() > 1) {
|
|
replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+")" + titlePostfix);
|
|
} else {
|
|
replaceTag(page, "PAGE_TITLE", originalChannelName + titlePostfix);
|
|
}
|
|
|
|
replaceTag(page, "CHANNEL_TOPIC", topic);
|
|
replaceTag(page, "MAIN_HEADER", originalChannelName);
|
|
|
|
if (m_ajaxIsDisabled) {
|
|
page.remove("<a href=\"{{REALTIME_LINK}}\" title=\"{{AIRPLAIN_TITLE}}\" class=\"main_header__title_airplaine\"></a>");
|
|
}
|
|
else {
|
|
replaceTag(page, "REALTIME_LINK", "/realtimereadingchat/"+server+"/"+channel);
|
|
replaceTag(page, "AIRPLAIN_TITLE", "Read in real time");
|
|
}
|
|
|
|
|
|
QString middlePath = "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"\">" + "/" + "</a>";
|
|
if (not year.isEmpty()) {
|
|
middlePath += "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"/"+year+"\">" + year + "</a>";
|
|
if (not month.isEmpty()) {
|
|
middlePath += "/<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"/"+year+"/"+month+"\">" + month + "</a>";
|
|
}
|
|
}
|
|
|
|
QString arrows = HTML_PAYLOAD_ADDITIONAL_ARROWS;
|
|
QString currentYear {QDateTime::currentDateTime().toString("yyyy")};
|
|
QString currentMonth {QDateTime::currentDateTime().toString("MM")};
|
|
QString currentDay {QDateTime::currentDateTime().toString("dd")};
|
|
replaceTag(arrows, "CURRENT_DATA_LOG", "/"+server+"/"+channel+"/"+
|
|
currentYear+"/"+currentMonth+"/"+currentDay);
|
|
replaceTag(page, "ADDITIONAL_BUTTON", arrows);
|
|
|
|
int currentOnline = 0;
|
|
QString onlineUserS;
|
|
for (const auto &user: m_servers[originalServerName][originalChannelName]) {
|
|
if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) {
|
|
continue;
|
|
}
|
|
QString onlineUser = HTML_ONLINE_POINT;
|
|
replaceTag(onlineUser, "NICKNAME", user);
|
|
onlineUserS += onlineUser;
|
|
currentOnline++;
|
|
}
|
|
replaceTag(page, "ONLINE", QString::number(currentOnline));
|
|
replaceTag(page, "ONLINE_LIST", onlineUserS);
|
|
|
|
if (not searchRequest.isEmpty()) {
|
|
page.replace("<input class=\"main_header__search_input\" type=\"search\" name=\"toSearch\" placeholder=\"{{SEARCH_PLACEHOLDER}}\">",
|
|
"<input class=\"main_header__search_input\" type=\"search\" name=\"toSearch\" value=\"" + searchRequest.toHtmlEscaped() + "\">");
|
|
} else if (middlePath == "<a style=\"text-decoration: none\" href=\"/"+server+"/"+channel+"\">" + "/" + "</a>") {
|
|
replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName);
|
|
} else if (month.isEmpty()) {
|
|
replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName + "/" + year);
|
|
} else {
|
|
replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName + "/" + year + "/" + month);
|
|
}
|
|
|
|
//// Main section body compilation
|
|
QString payloadBlock;
|
|
|
|
// Search request
|
|
if (not searchRequest.isEmpty()) {
|
|
uint counter = 0;
|
|
QRegularExpression userRgx(searchRequest, QRegularExpression::CaseInsensitiveOption);
|
|
bool rgxIsValid = false;
|
|
if (isRegexp and userRgx.isValid()) {
|
|
rgxIsValid = true;
|
|
}
|
|
consoleLog("Search request (" + server + "): " + searchRequest);
|
|
|
|
QStringList paths;
|
|
QDirIterator it(fsPath.path());
|
|
while (it.hasNext()) {
|
|
QString currentPath = it.next();
|
|
if (currentPath.endsWith(".") or currentPath.endsWith("..")) continue;
|
|
QString logFolder = m_dataFolder;
|
|
#ifdef WIN32
|
|
logFolder.replace('\\', '/');
|
|
#endif
|
|
|
|
QString server {currentPath}; // Folder wich is not server folder is ignored
|
|
server.remove(QRegularExpression("^"+logFolder));
|
|
server.remove(QRegularExpression("/.*$"));
|
|
|
|
bool serverIsOk = false;
|
|
for (const auto &srv: m_servers) {
|
|
if (global::toLowerAndNoSpaces(srv.first) == server) {
|
|
serverIsOk = true;
|
|
break;
|
|
}
|
|
}
|
|
if (not serverIsOk) continue;
|
|
|
|
QString currentChannel {currentPath}; // Folder wich is not channel folder is ignored
|
|
currentChannel.remove(QRegularExpression("^"+logFolder+"[^/]*/"));
|
|
currentChannel.remove(QRegularExpression("/.*$"));
|
|
|
|
bool channelIsOk = false; // Канал явно указан в конфиге
|
|
for (const auto &ch: m_servers[originalServerName]) {
|
|
QString searchChan {ch.first};
|
|
searchChan.remove('#');
|
|
if (searchChan == currentChannel) {
|
|
channelIsOk = true;
|
|
break;
|
|
}
|
|
}
|
|
if (not channelIsOk) continue;
|
|
|
|
paths.push_back(currentPath);
|
|
}
|
|
|
|
if (paths.isEmpty()) {
|
|
payloadBlock = HTML_PAYLOAD_ERROR;
|
|
replaceTag(payloadBlock, "ERROR_TITLE", "Not found");
|
|
replaceTag(payloadBlock, "ERROR_TEXT", "");
|
|
}
|
|
else {
|
|
std::map<QString, QStringList> matchedPathsAndMessages;
|
|
|
|
if (not month.isEmpty()) {
|
|
for (const auto& path: paths) {
|
|
if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(path).hasMatch()) continue;
|
|
|
|
QFile file(path);
|
|
if (not file.open(QIODevice::ReadOnly)) {
|
|
consoleLog("Error! I can't open log file " + fsPath.path());
|
|
continue;
|
|
}
|
|
QString buffer {file.readLine()};
|
|
while (not buffer.isEmpty()) {
|
|
removeBrakelineSymbols(buffer);
|
|
|
|
bool finded = false;
|
|
if (rgxIsValid) {
|
|
if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
|
|
finded = true;
|
|
}
|
|
} else {
|
|
if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
|
|
finded = true;
|
|
}
|
|
}
|
|
if (finded) {
|
|
std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
|
|
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
|
|
buffer = file.readLine();
|
|
continue;
|
|
}
|
|
counter++;
|
|
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
|
|
if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
|
|
message.replace("class=\"main_payload__chat\"",
|
|
"class=\"main_payload__chat\" style=\"opacity: .5\"");
|
|
}
|
|
for (const auto &user: m_servers[originalServerName][originalChannelName]) {
|
|
if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) {
|
|
message.replace("class=\"main_payload__chat_username\"",
|
|
"class=\"main_payload__chat_username\" style=\"color: green\"");
|
|
break;
|
|
}
|
|
}
|
|
replaceTag(message, "USERNAME", rawMessage.first);
|
|
replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
|
|
matchedPathsAndMessages[path].push_back(message);
|
|
}
|
|
buffer = file.readLine();
|
|
}
|
|
file.close();
|
|
}
|
|
}
|
|
else if (month.isEmpty() and not year.isEmpty()){
|
|
for (const auto &p: paths) {
|
|
QStringList slavePaths;
|
|
|
|
QDirIterator it(p);
|
|
while (it.hasNext()) {
|
|
QString fileName = it.next();
|
|
if (fileName.endsWith(".") or fileName.endsWith("..")) continue;
|
|
if (not QRegularExpression("\\.txt$").match(fileName).hasMatch()) continue;
|
|
slavePaths.push_back(fileName);
|
|
}
|
|
for (const auto &path: slavePaths) {
|
|
if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(path).hasMatch()) continue;
|
|
|
|
QFile file(path);
|
|
if (not file.open(QIODevice::ReadOnly)) {
|
|
consoleLog("Error! I can't open log file " + fsPath.path());
|
|
continue;
|
|
}
|
|
QString buffer {file.readLine()};
|
|
while (not buffer.isEmpty()) {
|
|
removeBrakelineSymbols(buffer);
|
|
|
|
bool finded = false;
|
|
if (rgxIsValid) {
|
|
if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
|
|
finded = true;
|
|
}
|
|
} else {
|
|
if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
|
|
finded = true;
|
|
}
|
|
}
|
|
if (finded) {
|
|
std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
|
|
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
|
|
buffer = file.readLine();
|
|
continue;
|
|
}
|
|
counter++;
|
|
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
|
|
if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
|
|
message.replace("class=\"main_payload__chat\"",
|
|
"class=\"main_payload__chat\" style=\"opacity: .5\"");
|
|
}
|
|
for (const auto &user: m_servers[originalServerName][originalChannelName]) {
|
|
if (QRegularExpression("^^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) {
|
|
message.replace("class=\"main_payload__chat_username\"",
|
|
"class=\"main_payload__chat_username\" style=\"color: green\"");
|
|
break;
|
|
}
|
|
}
|
|
replaceTag(message, "USERNAME", rawMessage.first);
|
|
replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
|
|
matchedPathsAndMessages[path].push_back(message);
|
|
}
|
|
buffer = file.readLine();
|
|
}
|
|
file.close();
|
|
}
|
|
}
|
|
}
|
|
else { // root directory
|
|
QStringList yearPaths;
|
|
for (const auto& p: paths) {
|
|
if (not QRegularExpression("/2[0-9]{3}$").match(p).hasMatch()) continue;
|
|
yearPaths.push_back(p);
|
|
}
|
|
QStringList fileNameS;
|
|
for (const auto& p: yearPaths) {
|
|
QStringList slavePaths;
|
|
|
|
QDirIterator it(p);
|
|
while (it.hasNext()) {
|
|
QString folderName = it.next();
|
|
if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
|
|
if (not QRegularExpression("/[0-9]{2}$").match(folderName).hasMatch()) continue;
|
|
slavePaths.push_back(folderName);
|
|
}
|
|
|
|
for (const auto &path: slavePaths) {
|
|
QDirIterator itMonth(path);
|
|
while (itMonth.hasNext()) {
|
|
QString fileName = itMonth.next();
|
|
if (fileName.endsWith(".") or fileName.endsWith("..")) continue;
|
|
if (not QRegularExpression("^.*[0-9]{2}\\.txt$").match(fileName).hasMatch()) continue;
|
|
fileNameS.push_back(fileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& path: fileNameS) {
|
|
QFile file(path);
|
|
if (not file.open(QIODevice::ReadOnly)) {
|
|
consoleLog("Error! I can't open log file " + fsPath.path());
|
|
continue;
|
|
}
|
|
QString buffer {file.readLine()};
|
|
while (not buffer.isEmpty()) {
|
|
removeBrakelineSymbols(buffer);
|
|
|
|
bool finded = false;
|
|
if (rgxIsValid) {
|
|
if (QRegularExpression(searchRequest, QRegularExpression::CaseInsensitiveOption).match(buffer).hasMatch()) {
|
|
finded = true;
|
|
}
|
|
} else {
|
|
if (buffer.contains(searchRequest, Qt::CaseInsensitive)) {
|
|
finded = true;
|
|
}
|
|
}
|
|
if (finded) {
|
|
std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
|
|
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
|
|
buffer = file.readLine();
|
|
continue;
|
|
}
|
|
counter++;
|
|
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
|
|
if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
|
|
message.replace("class=\"main_payload__chat\"",
|
|
"class=\"main_payload__chat\" style=\"opacity: .5\"");
|
|
}
|
|
for (const auto &user: m_servers[originalServerName][originalChannelName]) {
|
|
if (QRegularExpression("^^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) {
|
|
message.replace("class=\"main_payload__chat_username\"",
|
|
"class=\"main_payload__chat_username\" style=\"color: green\"");
|
|
break;
|
|
}
|
|
}
|
|
replaceTag(message, "USERNAME", rawMessage.first);
|
|
replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
|
|
matchedPathsAndMessages[path].push_back(message);
|
|
}
|
|
buffer = file.readLine();
|
|
}
|
|
file.close();
|
|
}
|
|
}
|
|
|
|
if (matchedPathsAndMessages.empty()) {
|
|
payloadBlock = HTML_PAYLOAD_ERROR;
|
|
replaceTag(payloadBlock, "ERROR_TITLE", "Not found");
|
|
replaceTag(payloadBlock, "ERROR_TEXT", "");
|
|
}
|
|
else {
|
|
QStringList findedPaths;
|
|
for (const auto& fp: matchedPathsAndMessages) {
|
|
findedPaths << fp.first;
|
|
}
|
|
std::sort(findedPaths.begin(), findedPaths.end());
|
|
|
|
for (auto& link: findedPaths) {
|
|
QString logFolder {m_dataFolder};
|
|
logFolder.remove(QRegularExpression(".$"));
|
|
QStringList& messages = matchedPathsAndMessages[link];
|
|
link.remove(logFolder);
|
|
link.remove(QRegularExpression("\\.txt$"));
|
|
|
|
QString finded = HTML_PAYLOAD_LIST_POINT_MESSAGE;
|
|
finded.replace("class=\"main_payload__block\"", "class=\"main_payload__block\" style=\"background: #b6c7d6\"");
|
|
replaceTag(finded, "POINT_LINK", link);
|
|
link.remove(QRegularExpression("^.*"+channel));
|
|
replaceTag(finded, "POINT_CONTENT", link);
|
|
payloadBlock += finded;
|
|
for(const auto& m: messages) {
|
|
payloadBlock += m;
|
|
}
|
|
payloadBlock += " \n";
|
|
}
|
|
}
|
|
}
|
|
int lastWbr = 0;
|
|
for (int i = 0; i < searchRequest.size(); i++) {
|
|
if (i-lastWbr > MAX_MESSAGE_LENGTH_WITHOUT_WBR) {
|
|
searchRequest.insert(i, "<wbr>");
|
|
lastWbr = i;
|
|
}
|
|
}
|
|
middlePath += " <span style=\"color: #4f9c65\">" + searchRequest.toHtmlEscaped() + "</span> ";
|
|
if (rgxIsValid) middlePath += "rgx";
|
|
middlePath += "(" + QString::number(counter) + ")";
|
|
}
|
|
|
|
// Plain log explorer
|
|
else {
|
|
|
|
if (year.isEmpty()) { // /
|
|
QStringList folderNameS;
|
|
QDirIterator it(fsPath.path());
|
|
while (it.hasNext()) {
|
|
QString folderName = it.next();
|
|
if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
|
|
while(folderName.contains('/')) {
|
|
folderName.remove(QRegularExpression("^.*/"));
|
|
}
|
|
folderName.remove(QRegularExpression("\\.txt$"));
|
|
folderNameS << folderName;
|
|
}
|
|
if (not folderNameS.isEmpty()) {
|
|
std::sort(folderNameS.begin(), folderNameS.end());
|
|
for (const auto &f: folderNameS) {
|
|
QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER;
|
|
replaceTag(onePoint, "POINT_CONTENT", f);
|
|
replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+f);
|
|
payloadBlock += onePoint;
|
|
}
|
|
}
|
|
}
|
|
else if (not year.isEmpty() and month.isEmpty()) { // /YYYY
|
|
QStringList folderNameS;
|
|
QDirIterator it(fsPath.path());
|
|
while (it.hasNext()) {
|
|
QString folderName = it.next();
|
|
if (folderName.endsWith(".") or folderName.endsWith("..")) continue;
|
|
while(folderName.contains('/')) {
|
|
folderName.remove(QRegularExpression("^.*/"));
|
|
}
|
|
folderNameS << folderName;
|
|
}
|
|
if (not folderNameS.isEmpty()) {
|
|
std::sort(folderNameS.begin(), folderNameS.end());
|
|
for (const auto &f: folderNameS) {
|
|
QString onePoint = HTML_PAYLOAD_LIST_POINT_FOLDER;
|
|
bool yearIsOk {false};
|
|
bool monthIsOk {false};
|
|
QDate dateOfMonth (year.toInt(&yearIsOk), f.toInt(&monthIsOk), 1);
|
|
QString nameOfMonth;
|
|
if (yearIsOk and monthIsOk) {
|
|
nameOfMonth = QLocale().standaloneMonthName(dateOfMonth.month(), QLocale::ShortFormat);
|
|
}
|
|
QDirIterator dayLogs(fsPath.path() + global::slash + f);
|
|
int8_t dayLogsCounter {0};
|
|
while (dayLogs.hasNext()) {
|
|
QString dayLogFile = dayLogs.next();
|
|
if (dayLogFile.endsWith(".") or dayLogFile.endsWith("..")) continue;
|
|
dayLogsCounter++;
|
|
}
|
|
QString filesLabel;
|
|
dayLogsCounter == 1 ? filesLabel = "day" : filesLabel = "days";
|
|
replaceTag(onePoint, "POINT_CONTENT", "<span style=\"font-weight: bold\">" + f + "</span> (" + nameOfMonth + ") " + QString::number(dayLogsCounter) + " " + filesLabel);
|
|
replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+year+"/"+f);
|
|
payloadBlock += onePoint;
|
|
}
|
|
}
|
|
}
|
|
else if (not month.isEmpty() and day.isEmpty()) { // /YYYY/MM
|
|
QStringList fileNameS;
|
|
QDirIterator it(fsPath.path());
|
|
while (it.hasNext()) {
|
|
QString fileName = it.next();
|
|
if (fileName.endsWith(".") or fileName.endsWith("..")) continue; // QDir::NoDotAndNoDotDot not works!
|
|
while(fileName.contains('/')) {
|
|
fileName.remove(QRegularExpression("^.*/"));
|
|
}
|
|
fileName.remove(QRegularExpression("\\.txt$"));
|
|
fileNameS << fileName;
|
|
}
|
|
if (not fileNameS.isEmpty()) {
|
|
std::sort(fileNameS.begin(), fileNameS.end());
|
|
for (const auto &a: fileNameS) {
|
|
QString onePoint = HTML_PAYLOAD_LIST_POINT_MESSAGE;
|
|
bool yearIsOk {false};
|
|
bool monthIsOk {false};
|
|
bool dayIsOk {false};
|
|
QDate dateOfDay (year.toInt(&yearIsOk), month.toInt(&monthIsOk), a.toInt(&dayIsOk));
|
|
QString nameOfDay;
|
|
if (yearIsOk and monthIsOk and dayIsOk) {
|
|
nameOfDay = QLocale().standaloneDayName(dateOfDay.dayOfWeek(), QLocale::ShortFormat);
|
|
}
|
|
auto logFileSizeBytes = QFile(fsPath.path() + global::slash + a +".txt").size();
|
|
auto logFileSizeinKb = logFileSizeBytes/1000;
|
|
QString logFileSizeString;
|
|
if (logFileSizeinKb != 0) {
|
|
logFileSizeString = QString::number(logFileSizeinKb)+"."+
|
|
QString::number((logFileSizeBytes - logFileSizeinKb*1000)/10)+
|
|
" KB";
|
|
} else {
|
|
logFileSizeString = QString::number(logFileSizeBytes)+" B";
|
|
}
|
|
replaceTag(onePoint, "POINT_CONTENT", "<span style=\"font-weight: bold\">" + a + "</span> (" + nameOfDay + ") " + logFileSizeString);
|
|
replaceTag(onePoint, "POINT_LINK", "/"+server+"/"+channel+"/"+year+"/"+month+"/"+a);
|
|
payloadBlock += onePoint;
|
|
}
|
|
}
|
|
}
|
|
else if (not day.isEmpty()) { // /YYYY/MM/dd
|
|
QFile file(fsPath.path()+global::slash+day+".txt");
|
|
if (not file.open(QIODevice::ReadOnly)) {
|
|
consoleLog("Error! I can't open log file " + fsPath.path());
|
|
payloadBlock = HTML_PAYLOAD_ERROR;
|
|
replaceTag(payloadBlock, "ERROR_TITLE", "Internal error");
|
|
replaceTag(payloadBlock, "ERROR_TEXT", "Requested log file openning failed");
|
|
}
|
|
else {
|
|
middlePath += "/" + day;
|
|
QString buffer = file.readLine();
|
|
while (not buffer.isEmpty()) {
|
|
removeBrakelineSymbols(buffer);
|
|
|
|
std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
|
|
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
|
|
buffer = file.readLine();
|
|
continue;
|
|
}
|
|
|
|
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
|
|
if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
|
|
message.replace("class=\"main_payload__chat\"",
|
|
"class=\"main_payload__chat\" style=\"opacity: .5\"");
|
|
}
|
|
for (const auto &user: m_servers[originalServerName][originalChannelName]) {
|
|
if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+rawMessage.first+"$").match(user).hasMatch()) {
|
|
message.replace("class=\"main_payload__chat_username\"",
|
|
"class=\"main_payload__chat_username\" style=\"color: green\"");
|
|
break;
|
|
}
|
|
}
|
|
replaceTag(message, "USERNAME", rawMessage.first);
|
|
replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
|
|
payloadBlock += message;
|
|
buffer = file.readLine();
|
|
}
|
|
file.close();
|
|
|
|
// arrows to prev & next days
|
|
bool prevDayFound {false};
|
|
uint prevDayUInt {day.toUShort()};
|
|
uint prevMonthUInt {month.toUShort()};
|
|
uint prevYearUInt {year.toUShort()};
|
|
QDir fsPathForPrev {fsPath};
|
|
for(uint i = prevDayUInt-1; i >= 1; i--) { // current month
|
|
QString d {fsPathForPrev.path()+global::slash};
|
|
if (i < 10) d += "0";
|
|
d += QString::number(i)+".txt";
|
|
if (QFile::exists(d)) {
|
|
prevDayUInt = i;
|
|
prevDayFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (not prevDayFound) { // other month
|
|
fsPathForPrev.cdUp();
|
|
bool mf {false};
|
|
for (uint i = prevMonthUInt-1; i >= 1; i--) {
|
|
QString m;
|
|
if (i < 10) m += "0";
|
|
m += QString::number(i);
|
|
if (fsPathForPrev.cd(m)) {
|
|
prevMonthUInt = i;
|
|
mf = true;
|
|
break;
|
|
}
|
|
}
|
|
if (mf) {
|
|
for(uint i = 31; i >= 1; i--) {
|
|
QString d {fsPathForPrev.path()+global::slash};
|
|
if (i < 10) d += "0";
|
|
d += QString::number(i)+".txt";
|
|
if (QFile::exists(d)) {
|
|
prevDayUInt = i;
|
|
prevDayFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (not prevDayFound) { // other year
|
|
fsPathForPrev.cdUp();
|
|
bool yf {false};
|
|
for (uint i = prevYearUInt-1; i > prevYearUInt-21; i--) {
|
|
if (fsPathForPrev.cd(QString::number(i))) {
|
|
prevYearUInt = i;
|
|
yf = true;
|
|
break;
|
|
}
|
|
}
|
|
if (yf) {
|
|
bool mf {false};
|
|
for (uint i = 12; i >= 1; i--) {
|
|
QString m;
|
|
if (i < 10) m += "0";
|
|
m += QString::number(i);
|
|
if (fsPathForPrev.cd(m)) {
|
|
prevMonthUInt = i;
|
|
mf = true;
|
|
break;
|
|
}
|
|
}
|
|
if (mf) {
|
|
for(uint i = 31; i >= 1; i--) {
|
|
QString d {fsPathForPrev.path()+global::slash};
|
|
if (i < 10) d += "0";
|
|
d += QString::number(i)+".txt";
|
|
if (QFile::exists(d)) {
|
|
prevDayUInt = i;
|
|
prevDayFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
QString leftArrow;
|
|
if (prevDayFound) {
|
|
leftArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_PREV;
|
|
QString link = "/"+server+"/"+channel+"/"+QString::number(prevYearUInt)+"/";
|
|
QString m;
|
|
if (prevMonthUInt < 10) m += "0";
|
|
m += QString::number(prevMonthUInt);
|
|
link += m+"/";
|
|
QString d;
|
|
if (prevDayUInt < 10) d += "0";
|
|
d += QString::number(prevDayUInt);
|
|
link += d;
|
|
replaceTag(leftArrow, "LINK", link);
|
|
} else {
|
|
leftArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_PREV_FALSE;
|
|
}
|
|
middlePath += leftArrow;
|
|
|
|
bool nextDayFound {false};
|
|
uint nextDayUInt {day.toUShort()};
|
|
uint nextMonthUInt {month.toUShort()};
|
|
uint nextYearUInt {year.toUShort()};
|
|
QDir fsPathForNext {fsPath};
|
|
for(uint i = nextDayUInt+1; i <= 31; i++) { // current month
|
|
QString d {fsPathForNext.path()+global::slash};
|
|
if (i < 10) d += "0";
|
|
d += QString::number(i)+".txt";
|
|
if (QFile::exists(d)) {
|
|
nextDayUInt = i;
|
|
nextDayFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (not nextDayFound) { // other month
|
|
fsPathForNext.cdUp();
|
|
bool mf {false};
|
|
for (uint i = nextMonthUInt+1; i <= 12; i++) {
|
|
QString m;
|
|
if (i < 10) m += "0";
|
|
m += QString::number(i);
|
|
if (fsPathForNext.cd(m)) {
|
|
nextMonthUInt = i;
|
|
mf = true;
|
|
break;
|
|
}
|
|
}
|
|
if (mf) {
|
|
for(uint i = 1; i <= 31; i++) {
|
|
QString d {fsPathForNext.path()+global::slash};
|
|
if (i < 10) d += "0";
|
|
d += QString::number(i)+".txt";
|
|
if (QFile::exists(d)) {
|
|
nextDayUInt = i;
|
|
nextDayFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (not nextDayFound) { // other year
|
|
fsPathForNext.cdUp();
|
|
bool yf {false};
|
|
for (uint i = nextYearUInt+1; i < nextYearUInt+20; i++) {
|
|
if (fsPathForNext.cd(QString::number(i))) {
|
|
nextYearUInt = i;
|
|
yf = true;
|
|
break;
|
|
}
|
|
}
|
|
if (yf) {
|
|
bool mf {false};
|
|
for (uint i = 1; i <= 12; i++) {
|
|
QString m;
|
|
if (i < 10) m += "0";
|
|
m += QString::number(i);
|
|
if (fsPathForNext.cd(m)) {
|
|
nextMonthUInt = i;
|
|
mf = true;
|
|
break;
|
|
}
|
|
}
|
|
if (mf) {
|
|
for(uint i = 1; i <= 31; i++) {
|
|
QString d {fsPathForNext.path()+global::slash};
|
|
if (i < 10) d += "0";
|
|
d += QString::number(i)+".txt";
|
|
if (QFile::exists(d)) {
|
|
nextDayUInt = i;
|
|
nextDayFound = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
QString rightArrow;
|
|
if (nextDayFound) {
|
|
rightArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_NEXT;
|
|
QString link = "/"+server+"/"+channel+"/"+QString::number(nextYearUInt)+"/";
|
|
QString m;
|
|
if (nextMonthUInt < 10) m += "0";
|
|
m += QString::number(nextMonthUInt);
|
|
link += m+"/";
|
|
QString d;
|
|
if (nextDayUInt < 10) d += "0";
|
|
d += QString::number(nextDayUInt);
|
|
link += d;
|
|
replaceTag(rightArrow, "LINK", link);
|
|
} else {
|
|
rightArrow = HTML_PAYLOAD_MIDDLEPATH_ARROW_NEXT_FALSE;
|
|
}
|
|
middlePath += rightArrow;
|
|
}
|
|
}
|
|
if (payloadBlock.isEmpty()) {
|
|
payloadBlock = HTML_PAYLOAD_ERROR;
|
|
replaceTag(payloadBlock, "ERROR_TITLE", "Empty");
|
|
replaceTag(payloadBlock, "ERROR_TEXT", "No logs found for this channel");
|
|
}
|
|
}
|
|
|
|
replaceTag(page, "MIDDLE_PATH", middlePath);
|
|
replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
|
|
|
|
//// Footer
|
|
replaceTag(page, "VERSION", IRCABOT_VERSION);
|
|
replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR);
|
|
|
|
replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart));
|
|
|
|
QString mainHeader = HEADER_HTML;
|
|
replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
|
|
|
|
if (socket->isOpen()) {
|
|
socket->write(mainHeader.toUtf8());
|
|
if (not isHeadRequest) socket->write(page.toUtf8());
|
|
}
|
|
}
|
|
|
|
void HttpServer::writeRealTimeChatPage(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
|
|
{
|
|
if (isHeadRequest) {
|
|
QString header = HEADER_HTML;
|
|
replaceTag(header, "SIZE", "0");
|
|
socket->write(header.toUtf8());
|
|
return;
|
|
}
|
|
|
|
auto renderStart = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
urlPath.remove(QRegularExpression("^/realtimereadingchat/"));
|
|
QString server = getWordFromPath(urlPath);
|
|
if (server.isEmpty()) {
|
|
writeErrorPage(socket, "SERVER VALUE IS MISSING");
|
|
return;
|
|
}
|
|
|
|
urlPath.remove(QRegularExpression("^"+server));
|
|
QString channel = getWordFromPath(urlPath);
|
|
if (channel.isEmpty()) {
|
|
writeErrorPage(socket, "CHANNEL VALUE IS MISSING");
|
|
return;
|
|
}
|
|
|
|
QString originalServerName;
|
|
for (const auto &s: m_servers) {
|
|
if (global::toLowerAndNoSpaces(s.first) == server) {
|
|
originalServerName = s.first;
|
|
}
|
|
}
|
|
if (originalServerName.isEmpty()) {
|
|
writeErrorPage(socket, "REQUESTED SERVER NOT EXIST");
|
|
return;
|
|
}
|
|
|
|
QString originalChannelName;
|
|
for (const auto &server: m_servers) {
|
|
for (const auto &channel_users: server.second) {
|
|
if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) {
|
|
originalChannelName = global::toLowerAndNoSpaces(channel_users.first);
|
|
}
|
|
}
|
|
}
|
|
if (originalChannelName.isEmpty()) {
|
|
writeErrorPage(socket, "REQUESTED CHANNEL NOT EXIST");
|
|
return;
|
|
}
|
|
|
|
QFile main("://html/main.html");
|
|
QString page;
|
|
if (main.open(QIODevice::ReadOnly)) {
|
|
page = main.readAll();
|
|
main.close();
|
|
}
|
|
else {
|
|
if (socket->isOpen()) {
|
|
socket->write(HEADER_404.toUtf8());
|
|
if (not isHeadRequest) socket->write(CRITICAL_ERROR);
|
|
}
|
|
}
|
|
|
|
QString year {QDateTime::currentDateTime().toString("yyyy")};
|
|
QString month {QDateTime::currentDateTime().toString("MM")};
|
|
QString day {QDateTime::currentDateTime().toString("dd")};
|
|
|
|
//// Left menu compilation
|
|
QString htmlServersSectionS;
|
|
for (const auto &s: m_servers) {
|
|
if (s.first.isEmpty()) continue; // empty server name?
|
|
|
|
QString htmlServersSection = HTML_SERVER_SECTION;
|
|
replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+server);
|
|
replaceTag(htmlServersSection, "SERVER_NAME", s.first);
|
|
if (s.first == originalServerName) {
|
|
htmlServersSection.replace("<span style=\"font-size: 17px;\">{{ONLINE_STATUS}}",
|
|
"<span style=\"font-size: 17px;\" id=\"serverStatus\">{{ONLINE_STATUS}}");
|
|
}
|
|
|
|
QString htmlChannelLineS;
|
|
for (const auto &c: s.second) {
|
|
QString htmlChannelLine;
|
|
if (originalServerName == s.first and originalChannelName == c.first) {
|
|
htmlChannelLine = HTML_SERVER_SECTION_CHANNEL_SELECTED;
|
|
} else {
|
|
htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
|
|
}
|
|
replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
|
|
|
|
QString channelNameForUrl {c.first};
|
|
channelNameForUrl.remove('#');
|
|
QString channelLink = "/realtimereadingchat/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
|
|
replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
|
|
|
|
htmlChannelLineS += htmlChannelLine;
|
|
}
|
|
replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
|
|
|
|
bool online {false};
|
|
for (const auto &srv: m_serversOnline) {
|
|
if (srv.first == s.first) {
|
|
online = srv.second;
|
|
break;
|
|
}
|
|
}
|
|
if (online) {
|
|
replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
|
|
} else {
|
|
replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
|
|
}
|
|
if (s.first == originalServerName) {
|
|
htmlServersSectionS.push_front(htmlServersSection);
|
|
} else {
|
|
htmlServersSectionS += htmlServersSection;
|
|
}
|
|
}
|
|
htmlServersSectionS.push_front(m_serviceButton);
|
|
replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
|
|
|
|
//// Main section header compilation
|
|
QString& topic = m_channelsTopic[originalServerName][originalChannelName];
|
|
topic = topic.replace('\"', """);
|
|
|
|
QString titlePostfix = " | " + m_serviceName;
|
|
if (not topic.isEmpty()) {
|
|
titlePostfix.push_front(" | " + topic);
|
|
}
|
|
if (m_servers.size() > 1) {
|
|
replaceTag(page, "PAGE_TITLE", originalChannelName + " ("+originalServerName+") [real time]" + titlePostfix);
|
|
} else {
|
|
replaceTag(page, "PAGE_TITLE", originalChannelName + " [real time]" + titlePostfix);
|
|
}
|
|
|
|
replaceTag(page, "CHANNEL_TOPIC", topic);
|
|
replaceTag(page, "MAIN_HEADER", originalChannelName);
|
|
replaceTag(page, "ADDITIONAL_BUTTON", HTML_PAYLOAD_ADDITIONAL_NOTIFY);
|
|
replaceTag(page, "REALTIME_LINK", "/"+server+"/"+channel+"/"+year+"/"+month+"/"+day);
|
|
replaceTag(page, "AIRPLAIN_TITLE", "Back to plain text log");
|
|
page.replace("class=\"main_header__title_airplaine\"", "class=\"main_header__title_airplaine\" style=\"transform: scale(-1,1)\"");
|
|
|
|
int currentOnline = 0;
|
|
QString onlineUserS;
|
|
for (const auto &user: m_servers[originalServerName][originalChannelName]) {
|
|
if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) {
|
|
continue;
|
|
}
|
|
QString onlineUser = HTML_ONLINE_POINT;
|
|
replaceTag(onlineUser, "NICKNAME", user);
|
|
onlineUserS += onlineUser;
|
|
currentOnline++;
|
|
}
|
|
page.replace("{{ONLINE}}", "<span id=\"online\">{{ONLINE}}</span>");
|
|
replaceTag(page, "ONLINE", QString::number(currentOnline));
|
|
page.replace("<div class=\"main_middle__online_list\">", "<div id=\"onlineList\" class=\"main_middle__online_list\">");
|
|
replaceTag(page, "ONLINE_LIST", onlineUserS);
|
|
|
|
page.replace("class=\"main_header__search_form\"", "action=\"/"+server+"/"+channel+"\" class=\"main_header__search_form\"");
|
|
replaceTag(page, "SEARCH_PLACEHOLDER", originalChannelName);
|
|
|
|
page.replace("<div class=\"main_middle__path\">", "<div class=\"main_middle__path\" id=\"path\">");
|
|
replaceTag(page, "MIDDLE_PATH", "");
|
|
|
|
//// Payload
|
|
page.replace("<div class=\"main_payload\">", "<div class=\"main_payload\" id=\"payload\">");
|
|
bool logsExisted {false};
|
|
QFile file;
|
|
QDir fsPath(m_dataFolder+server+global::slash+channel);
|
|
if (fsPath.cd(year)) {
|
|
if (fsPath.cd(month)) {
|
|
file.setFileName(fsPath.path()+global::slash+day+".txt");
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
logsExisted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
QString payloadBlock;
|
|
if (logsExisted) {
|
|
QString buffer = file.readLine();
|
|
while (not buffer.isEmpty()) {
|
|
removeBrakelineSymbols(buffer);
|
|
|
|
std::pair<QString, QString> rawMessage = splitUserNameAndMessage(buffer);
|
|
if (rawMessage.first.isEmpty() or rawMessage.second.isEmpty()) {
|
|
buffer = file.readLine();
|
|
continue;
|
|
}
|
|
|
|
QString message = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
|
|
if (rawMessage.second == global::BLINDED_MESSAGE_MERKER) {
|
|
message.replace("class=\"main_payload__chat\"",
|
|
"class=\"main_payload__chat\" style=\"opacity: .5\"");
|
|
}
|
|
message.replace("class=\"main_payload__chat_username\"",
|
|
"class=\"main_payload__chat_username\" style=\"color: #e34f10\"");
|
|
|
|
replaceTag(message, "USERNAME", rawMessage.first);
|
|
replaceTag(message, "MESSAGE_TEXT", rawMessage.second);
|
|
payloadBlock += message;
|
|
buffer = file.readLine();
|
|
}
|
|
file.close();
|
|
}
|
|
QString hr = HTML_PAYLOAD_LIST_CHAT_MESSAGE;
|
|
hr.replace("class=\"main_payload__chat_mail\"", "id=\"hr\" class=\"main_payload__chat_mail\"");
|
|
hr.replace("class=\"main_payload__chat_username\"",
|
|
"class=\"main_payload__chat_username\" style=\"color: white; text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;\"");
|
|
replaceTag(hr, "USERNAME", "IRCaBot");
|
|
replaceTag(hr, "MESSAGE_TEXT", "<b>New messages won't show without JavaScript.</b><br>"
|
|
"My JS code is small and simple. Check it at "
|
|
"<a href=\"/realtimechat.js\">/realtimechat.js</a> and come back "
|
|
"with enabled!");
|
|
|
|
payloadBlock += hr;
|
|
replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
|
|
|
|
//// Footer
|
|
replaceTag(page, "VERSION", IRCABOT_VERSION);
|
|
replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR);
|
|
|
|
//// Finish
|
|
page.replace("</body>", " <div id=\"LMId\" style=\"display: none\">" + QString::number(QDateTime::currentMSecsSinceEpoch()) + "</div>\n"
|
|
" <div id=\"ajaxPath\" style=\"display: none\">" + server + "/" + channel + "</div>\n"
|
|
" <script src=\"/realtimechat.js\"></script>\n</body>");
|
|
replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart));
|
|
QString mainHeader = HEADER_HTML;
|
|
replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
|
|
|
|
if (socket->isOpen()) {
|
|
socket->write(mainHeader.toUtf8());
|
|
if (not isHeadRequest) socket->write(page.toUtf8());
|
|
}
|
|
}
|
|
|
|
void HttpServer::writeAboutServerPage(QTcpSocket *socket, QString &server, bool isHeadRequest)
|
|
{
|
|
auto renderStart = QDateTime::currentMSecsSinceEpoch();
|
|
|
|
QString originalServerName;
|
|
for (const auto &s: m_servers) {
|
|
if (global::toLowerAndNoSpaces(s.first) == server) {
|
|
originalServerName = s.first;
|
|
}
|
|
}
|
|
if (originalServerName.isEmpty()) {
|
|
writeErrorPage(socket, "SERVER NOT EXIST");
|
|
return;
|
|
}
|
|
|
|
QFile main("://html/main.html");
|
|
QString page;
|
|
if (main.open(QIODevice::ReadOnly)) {
|
|
page = main.readAll();
|
|
main.close();
|
|
}
|
|
else {
|
|
if (socket->isOpen()) {
|
|
socket->write(HEADER_404.toUtf8());
|
|
if (not isHeadRequest) socket->write(CRITICAL_ERROR);
|
|
}
|
|
}
|
|
|
|
replaceTag(page, "PAGE_TITLE", "About " + originalServerName + " | " + m_serviceName);
|
|
|
|
//// Left menu compilation
|
|
QString htmlServersSectionS;
|
|
for (const auto &s: m_servers) {
|
|
if (s.first.isEmpty()) continue; // empty server name?
|
|
|
|
QString htmlServersSection = HTML_SERVER_SECTION;
|
|
if (s.first == originalServerName) {
|
|
htmlServersSection.replace("<div class=\"left_menu__item\">",
|
|
"<div class=\"left_menu__item\" style=\"background: #f0f5fa\">");
|
|
}
|
|
replaceTag(htmlServersSection, "ABOUT_SERVER", "/"+s.first);
|
|
replaceTag(htmlServersSection, "SERVER_NAME", s.first);
|
|
|
|
QString htmlChannelLineS;
|
|
for (const auto &c: s.second) {
|
|
QString htmlChannelLine;
|
|
htmlChannelLine = HTML_SERVER_SECTION_CHANNEL;
|
|
replaceTag(htmlChannelLine, "CHANNEL_NAME", c.first);
|
|
|
|
QString channelNameForUrl {c.first};
|
|
channelNameForUrl.remove('#');
|
|
QString channelLink = "/" + global::toLowerAndNoSpaces(s.first) + "/" + channelNameForUrl;
|
|
replaceTag(htmlChannelLine, "CHANNEL_LINK", channelLink);
|
|
|
|
htmlChannelLineS += htmlChannelLine;
|
|
}
|
|
replaceTag(htmlServersSection, "CHANNELS", htmlChannelLineS);
|
|
|
|
bool online {false};
|
|
for (const auto &srv: m_serversOnline) {
|
|
if (srv.first == s.first) {
|
|
online = srv.second;
|
|
break;
|
|
}
|
|
}
|
|
if (online) {
|
|
replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_ONLINE_MARKER);
|
|
} else {
|
|
replaceTag(htmlServersSection, "ONLINE_STATUS", HTML_SERVER_OFFLINE_MARKER);
|
|
}
|
|
if (s.first == originalServerName) {
|
|
htmlServersSectionS.push_front(htmlServersSection);
|
|
} else {
|
|
htmlServersSectionS += htmlServersSection;
|
|
}
|
|
}
|
|
htmlServersSectionS.push_front(m_serviceButton);
|
|
replaceTag(page, "SERVERS_SECTION", htmlServersSectionS);
|
|
page.remove(QRegularExpression("<div class=\"main_header\">.*<!-- main_middle -->", QRegularExpression::DotMatchesEverythingOption));
|
|
|
|
QString payloadBlock = HTML_PAYLOAD_ABOUT;
|
|
replaceTag(payloadBlock, "ABOUT_TITLE", originalServerName);
|
|
|
|
QString aboutBlock;
|
|
QFile about(m_dataFolder+server+global::slash+"about_server.txt");
|
|
if (about.open(QIODevice::ReadOnly)) {
|
|
QString rbuffer = about.readLine();
|
|
while (not rbuffer.isEmpty()) {
|
|
if (rbuffer.startsWith('#')) {
|
|
rbuffer = about.readLine();
|
|
continue;
|
|
}
|
|
removeBrakelineSymbols(rbuffer);
|
|
if (not rbuffer.isEmpty()) {
|
|
aboutBlock += rbuffer;
|
|
}
|
|
rbuffer = about.readLine();
|
|
}
|
|
}
|
|
else {
|
|
aboutBlock = "No information provided";
|
|
}
|
|
replaceTag(payloadBlock, "ABOUT_TEXT", aboutBlock);
|
|
|
|
replaceTag(page, "PAYLOAD_BLOCK", payloadBlock);
|
|
|
|
//// Footer
|
|
replaceTag(page, "VERSION", IRCABOT_VERSION);
|
|
replaceTag(page, "COPYRIGHT_YEAR", COPYRIGHT_YEAR);
|
|
|
|
//// Finish
|
|
replaceTag(page, "RENDERING_TIMER", QString::number(QDateTime::currentMSecsSinceEpoch() - renderStart));
|
|
QString mainHeader = HEADER_HTML;
|
|
replaceTag(mainHeader, "SIZE", QString::number(QByteArray(page.toUtf8()).size()));
|
|
|
|
if (socket->isOpen()) {
|
|
socket->write(mainHeader.toUtf8());
|
|
if (not isHeadRequest) socket->write(page.toUtf8());
|
|
}
|
|
}
|
|
|
|
void HttpServer::writeAjaxAnswer(QTcpSocket *socket, QString &urlPath, bool isHeadRequest)
|
|
{
|
|
if (isHeadRequest) {
|
|
QString header = HEADER_JSON;
|
|
replaceTag(header, "SIZE", "0");
|
|
socket->write(header.toUtf8());
|
|
return;
|
|
}
|
|
|
|
//// Validate
|
|
urlPath.remove(QRegularExpression("^/ajax/"));
|
|
QString server = getWordFromPath(urlPath);
|
|
if (server.isEmpty()) {
|
|
writeErrorJson(socket, "Invalid request. Server value is missing!");
|
|
return;
|
|
}
|
|
|
|
urlPath.remove(QRegularExpression("^"+server));
|
|
QString channel = getWordFromPath(urlPath);
|
|
if (channel.isEmpty()) {
|
|
writeErrorJson(socket, "Invalid request. Channel value is missing!");
|
|
return;
|
|
}
|
|
|
|
QString originalServerName;
|
|
for (const auto &s: m_servers) {
|
|
if (global::toLowerAndNoSpaces(s.first) == server) {
|
|
originalServerName = s.first;
|
|
}
|
|
}
|
|
if (originalServerName.isEmpty()) {
|
|
writeErrorJson(socket, "Invalid request. Server not exist!");
|
|
return;
|
|
}
|
|
|
|
QString originalChannelName;
|
|
for (const auto &server: m_servers) {
|
|
for (const auto &channel_users: server.second) {
|
|
if (global::toLowerAndNoSpaces(channel_users.first) == "#"+channel) {
|
|
originalChannelName = global::toLowerAndNoSpaces(channel_users.first);
|
|
}
|
|
}
|
|
}
|
|
if (originalChannelName.isEmpty()) {
|
|
writeErrorJson(socket, "Invalid request. Channel not exist!");
|
|
return;
|
|
}
|
|
|
|
//// Parse
|
|
bool userOnlineCounterIsOk {false};
|
|
auto userOnlineCounter = global::getValue(urlPath, "onlineCounter", global::eForWeb).toInt(&userOnlineCounterIsOk);
|
|
if (not userOnlineCounterIsOk) {
|
|
writeErrorJson(socket, "Invalid request: 'onlineCounter' (int) value is missing!");
|
|
return;
|
|
}
|
|
bool userMessageLastIdIsOk {false};
|
|
qint64 userMessageLastId = global::getValue(urlPath, "messageId", global::eForWeb).toLongLong(&userMessageLastIdIsOk);
|
|
if (not userMessageLastIdIsOk) {
|
|
writeErrorJson(socket, "Invalid request: 'messageId' value is missing!");
|
|
return;
|
|
}
|
|
bool userServerStatus = global::getValue(urlPath, "serverStatus", global::eForWeb) == "true";
|
|
|
|
//// Building
|
|
QJsonObject jResponse;
|
|
jResponse.insert("status", QJsonValue(true));
|
|
|
|
// online server
|
|
if (userServerStatus != m_serversOnline[originalServerName]) {
|
|
jResponse.insert("serverStatusChanged", QJsonValue(true));
|
|
jResponse.insert("serverStatus", QJsonValue(m_serversOnline[originalServerName]));
|
|
} else {
|
|
jResponse.insert("serverStatusChanged", QJsonValue(false));
|
|
}
|
|
// online users
|
|
if (m_servers[originalServerName][originalChannelName].size()-1/*self*/ != userOnlineCounter) {
|
|
jResponse.insert("onlineUsersChanged", QJsonValue(true));
|
|
QJsonObject jOnline;
|
|
int currentOnline = m_servers[originalServerName][originalChannelName].size();
|
|
if (currentOnline > 0) currentOnline -= 1;
|
|
jOnline.insert("count", QJsonValue(currentOnline));
|
|
QJsonArray jOnlineList;
|
|
for (const auto& user: m_servers[originalServerName][originalChannelName]) {
|
|
if (QRegularExpression("^(.*;|~|@|\\&|\\+)?"+m_botNick[originalServerName]+"$").match(user).hasMatch()) {
|
|
continue;
|
|
}
|
|
jOnlineList.push_back(QJsonValue(user));
|
|
}
|
|
jOnline.insert("list", jOnlineList);
|
|
jResponse.insert("online", jOnline);
|
|
} else {
|
|
jResponse.insert("onlineUsersChanged", QJsonValue(false));
|
|
}
|
|
// new messages
|
|
bool newMessagesIsExisted {false};
|
|
QString channelId {server+channel};
|
|
QJsonArray jNewMessages;
|
|
qint64 newLastMessageId {userMessageLastId};
|
|
|
|
if (not m_messageCache.contains(channelId)) {
|
|
m_messageCache.insert(channelId, new MessagePool);
|
|
consoleLog("Message caching enabled for "+server+"/#"+channel+". Real time reading started.");
|
|
}
|
|
else {
|
|
auto messages = *(m_messageCache[channelId]->getMessages());
|
|
for (const auto& msg: messages) {
|
|
if (userMessageLastId < msg.first) {
|
|
if (not newMessagesIsExisted) newMessagesIsExisted = true;
|
|
QJsonObject jOneNewMessage;
|
|
jOneNewMessage.insert("user", msg.second->getSender());
|
|
jOneNewMessage.insert("text", msg.second->getText());
|
|
jNewMessages.push_back(jOneNewMessage);
|
|
newLastMessageId = msg.first;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newMessagesIsExisted) {
|
|
jResponse.insert("LMIdChanged" /* last message id == LMId */, QJsonValue(true));
|
|
jResponse.insert("newMessages", jNewMessages);
|
|
jResponse.insert("LMId", QJsonValue(QString::number(newLastMessageId)));
|
|
} else {
|
|
jResponse.insert("LMIdChanged", QJsonValue(false));
|
|
}
|
|
|
|
//// Finish
|
|
QByteArray response {QJsonDocument(jResponse).toJson()};
|
|
QString header = HEADER_JSON;
|
|
replaceTag(header, "SIZE", QString::number(response.size()));
|
|
socket->write(header.toUtf8());
|
|
socket->write(response);
|
|
}
|
|
|
|
/*\
|
|
|*| //////////'''''''''''''\\\\\\\\\\
|
|
|*| HAPPY NEW YEAR IN RUSSIAN PRISON!
|
|
|*| |||||||||| 2022 ||||||||||
|
|
|*| \\\\\\\\\\_____________//////////
|
|
\*/
|
|
|
|
Message::Message(const QString& s, const QString& t, qint64 timestamp, QObject *parent) :
|
|
QObject(parent),
|
|
m_sender(s),
|
|
m_text(t),
|
|
m_timestamp(timestamp)
|
|
{
|
|
connect (&m_selfKiller, &QTimer::timeout, [&](){emit outDated(m_timestamp);});
|
|
m_selfKiller.setSingleShot(true);
|
|
m_selfKiller.start(MSECS_TO_AUTOREMOVE_MESSAGES_FROM_BUFFER);
|
|
}
|
|
|
|
const QString Message::getSender()
|
|
{
|
|
return m_sender;
|
|
}
|
|
|
|
const QString Message::getText()
|
|
{
|
|
return m_text;
|
|
}
|
|
|
|
MessagePool::MessagePool(QObject *parent) :
|
|
QObject(parent),
|
|
m_lastPing(QDateTime::currentMSecsSinceEpoch())
|
|
{}
|
|
|
|
void MessagePool::saveNewMessage(const QString &nick, const QString &text)
|
|
{
|
|
qint64 timestamp = QDateTime::currentMSecsSinceEpoch();
|
|
Message* newMessage = new Message (nick, text, timestamp);
|
|
connect (newMessage, SIGNAL(outDated(qint64)), this, SLOT (messageToDelete(qint64)));
|
|
m_messages.insert({timestamp, newMessage});
|
|
}
|
|
|
|
const std::multimap<qint64, Message *>* MessagePool::getMessages()
|
|
{
|
|
m_lastPing = QDateTime::currentMSecsSinceEpoch();
|
|
return &m_messages;
|
|
}
|
|
|
|
qint64 MessagePool::getLastPing()
|
|
{
|
|
return m_lastPing;
|
|
}
|
|
|
|
void MessagePool::messageToDelete(qint64 timestamp)
|
|
{
|
|
while (m_messages.find(timestamp) != m_messages.end()) {
|
|
auto it = m_messages.find(timestamp);
|
|
it->second->deleteLater();
|
|
m_messages.erase(it);
|
|
}
|
|
}
|