#include "webserverchat.h" #include "qcolor.h" #include #include #include #include #include #include HttpServerChat::HttpServerChat(const QStringList &fontList, int port, const QString &backgroundColor, QObject *parent) : QObject(parent) , m_server(nullptr) , m_fontList(fontList) , m_backgroundColor(backgroundColor) , m_deleteByTime(true) // По умолчанию удаление по времени , m_maxMsgCount(100) // Значение по умолчанию { m_server = new QTcpServer(this); m_server->listen(QHostAddress::Any, port); connect(m_server, &QTcpServer::newConnection, this, &HttpServerChat::onNewConnection); m_currentStyle.freez = m_isFreez; } HttpServerChat::~HttpServerChat() { stop(); } bool HttpServerChat::start() { if (m_server->isListening()) { emit serverStarted(true); return true; } if (!m_server->listen(QHostAddress::Any, m_server->serverPort())) { emit serverStarted(false); return false; } emit serverStarted(true); return true; } void HttpServerChat::stop() { if (m_server) { m_server->close(); } for (auto client : m_clients) { if (client->state() == QAbstractSocket::ConnectedState) { client->close(); } client->deleteLater(); } m_clients.clear(); } quint16 HttpServerChat::port() const { return m_server ? m_server->serverPort() : 0; } void HttpServerChat::addMessage(const StyleChat &style) { m_currentStyle = style; ChatMessage msg; msg.nickname = style.nick; msg.content = style.context; msg.timestamp = QDateTime::currentSecsSinceEpoch(); msg.timeMsg = style.timeMsg; msg.freez = style.freez; msg.transparency = style.transparency; // Копируем стилевые параметры msg.blockColor = style.blockColor; msg.borderColor = style.borderColor; msg.fontColor = style.fontColor; msg.fontFamily = style.fontFamily; msg.fontSize = style.fontSize; msg.borderSize = style.borderSize; msg.padding = style.padding; if (!m_deleteByTime) { // Режим удаления по количеству // Проверяем, не превышен ли лимит if (m_messages.size() >= m_maxMsgCount) { // Ищем незамороженное сообщение для удаления bool removed = false; for (int i = 0; i < m_messages.size(); ++i) { if (!m_messages[i].freez) { m_messages.removeAt(i); removed = true; break; } } // Если все сообщения заморожены и лимит превышен, // не добавляем новое сообщение if (!removed) { return; } } } m_messages.append(msg); // Обновляем всех подключенных клиентов for (auto client : m_clients) { if (client->state() == QAbstractSocket::ConnectedState) { // Отправляем JavaScript для обновления страницы QString js = ""; sendResponse(client, "text/html", js); } } } void HttpServerChat::activeServer(bool enabled) { if (enabled) { if (!m_server->isListening()) { m_server->listen(QHostAddress::Any, m_server->serverPort()); } } else { m_server->close(); } } void HttpServerChat::changeBackground(const QString &color) { m_backgroundColor = color; } void HttpServerChat::onNewConnection() { while (m_server->hasPendingConnections()) { QTcpSocket *socket = m_server->nextPendingConnection(); m_clients.append(socket); connect(socket, &QTcpSocket::readyRead, this, &HttpServerChat::readClient); connect(socket, &QTcpSocket::disconnected, this, &HttpServerChat::discardClient); } } void HttpServerChat::readClient() { QTcpSocket *socket = qobject_cast(sender()); if (!socket) return; QByteArray requestData = socket->readAll(); QString request = QString::fromUtf8(requestData); if (!request.isEmpty()) { processRequest(socket, request); } } void HttpServerChat::processRequest(QTcpSocket *socket, const QString &request) { QStringList lines = request.split("\r\n"); if (lines.isEmpty()) return; QString firstLine = lines[0]; QStringList parts = firstLine.split(" "); if (parts.size() < 2) return; QString method = parts[0]; QString path = parts[1]; cleanupOldMessages(); if (method == "GET") { if (path == "/" || path == "/index.html") { sendHtmlPage(socket); } else if (path == "/messages") { sendJSONMessages(socket); } else if (path.startsWith("/fonts/")) { QString filePath = path.mid(1); // Убираем первый слэш serveStaticFile(socket, filePath); } else { // Игнорируем favicon и другие запросы if (!path.contains("favicon.ico") && !path.contains(".well-known")) { } sendResponse(socket, "text/html", "", 404); } } } void HttpServerChat::sendHtmlPage(QTcpSocket *socket) { QString html = generateHTML(); sendResponse(socket, "text/html", html); } void HttpServerChat::sendJSONMessages(QTcpSocket *socket) { QString json = generateJSON(); sendResponse(socket, "application/json; charset=utf-8", json); } void HttpServerChat::serveStaticFile(QTcpSocket *socket, const QString &filePath) { QString fullPath = QCoreApplication::applicationDirPath() + "/" + filePath; QFile file(fullPath); if (file.exists() && file.open(QIODevice::ReadOnly)) { QString mimeType = getMimeType(filePath); QByteArray content = file.readAll(); file.close(); QString response = QString( "HTTP/1.1 200 OK\r\n" "Content-Type: %1\r\n" "Content-Length: %2\r\n" "Cache-Control: no-cache\r\n" "Connection: close\r\n" "\r\n") .arg(mimeType) .arg(content.size()); socket->write(response.toUtf8()); socket->write(content); socket->flush(); } else { sendResponse(socket, "text/html", "

404 Not Found

" + filePath + "

", 404); } } QString HttpServerChat::generateHTML() { // Генерация CSS для шрифтов QString fontFaces; for (const QString &fontFile : m_fontList) { QString fontName = fontFile; fontName.replace(".ttf", "").replace(".otf", "").replace(".ttc", ""); fontFaces += QString("@font-face { font-family: '%1'; src: url(fonts/%2); }\n") .arg(fontName).arg(fontFile); } QString deleteByTimeJS = m_deleteByTime ? "true" : "false"; QString html = "\n" "\n" "\n" "\n" "\n" "\n" "\n" "Chat Messages\n" "\n" "\n" "\n" "\n" "
\n" "\n" ""; return html.arg(m_backgroundColor).arg(fontFaces).arg(deleteByTimeJS); } QString HttpServerChat::generateJSON() { QJsonArray jsonArray; for (const ChatMessage &msg : m_messages) { QJsonObject obj; obj["nickname"] = msg.nickname; obj["content"] = msg.content; obj["timestamp"] = msg.timestamp; obj["timeMsg"] = msg.timeMsg; obj["freez"] = msg.freez; obj["transparency"] = msg.transparency; // Конвертируем цветовые названия в HEX формат QString blockColor = msg.blockColor.isEmpty() ? m_currentStyle.blockColor : msg.blockColor; QString borderColor = msg.borderColor.isEmpty() ? m_currentStyle.borderColor : msg.borderColor; QString fontColor = msg.fontColor.isEmpty() ? m_currentStyle.fontColor : msg.fontColor; // Конвертируем названия цветов в HEX формат obj["color"] = colorNameToHex(blockColor); obj["borderColor"] = colorNameToHex(borderColor); obj["fontColor"] = colorNameToHex(fontColor); obj["family"] = msg.fontFamily.isEmpty() ? m_currentStyle.fontFamily : msg.fontFamily; obj["fontSize"] = msg.fontSize > 0 ? msg.fontSize : m_currentStyle.fontSize; obj["borderSize"] = msg.borderSize > 0 ? msg.borderSize : m_currentStyle.borderSize; obj["padding"] = msg.padding > 0 ? msg.padding : m_currentStyle.padding; jsonArray.append(obj); } QJsonDocument doc(jsonArray); return QString::fromUtf8(doc.toJson()); } QString HttpServerChat::colorNameToHex(const QString &colorName) const { if (colorName.isEmpty()) { return "#000000"; } // Если строка уже начинается с #, это HEX формат if (colorName.startsWith('#')) { return colorName; } // Проверяем, является ли это названием цвета QColor color; if (color.isValidColor(colorName)) { color.setNamedColor(colorName); return color.name(); } // Пробуем создать QColor из строки color = QColor(colorName); if (color.isValid()) { return color.name(); } // Если ничего не получилось, возвращаем черный по умолчанию return "#000000"; } void HttpServerChat::sendResponse(QTcpSocket *socket, const QString &contentType, const QString &content, int statusCode) { QString statusText; switch(statusCode) { case 200: statusText = "OK"; break; case 404: statusText = "Not Found"; break; default: statusText = "OK"; break; } QByteArray responseData = QString( "HTTP/1.1 %1 %2\r\n" "Content-Type: %3\r\n" "Content-Length: %4\r\n" "Cache-Control: no-cache, no-store, must-revalidate\r\n" "Access-Control-Allow-Origin: *\r\n" "Connection: keep-alive\r\n" "\r\n" "%5") .arg(statusCode) .arg(statusText) .arg(contentType) .arg(content.toUtf8().size()) .arg(content) .toUtf8(); socket->write(responseData); socket->flush(); } QString HttpServerChat::getMimeType(const QString &filePath) { if (filePath.endsWith(".html")) return "text/html"; if (filePath.endsWith(".css")) return "text/css"; if (filePath.endsWith(".js")) return "application/javascript"; if (filePath.endsWith(".png")) return "image/png"; if (filePath.endsWith(".jpg") || filePath.endsWith(".jpeg")) return "image/jpeg"; if (filePath.endsWith(".gif")) return "image/gif"; if (filePath.endsWith(".svg")) return "image/svg+xml"; if (filePath.endsWith(".ico")) return "image/x-icon"; if (filePath.endsWith(".ttf")) return "font/ttf"; if (filePath.endsWith(".otf")) return "font/otf"; if (filePath.endsWith(".woff")) return "font/woff"; if (filePath.endsWith(".woff2")) return "font/woff2"; return "text/plain"; } void HttpServerChat::discardClient() { QTcpSocket *socket = qobject_cast(sender()); if (socket) { m_clients.removeOne(socket); socket->deleteLater(); } } // Реализация методов получения настроек QString HttpServerChat::getFontFamily() const { return m_currentStyle.fontFamily; } int HttpServerChat::getFontSize() const { return m_currentStyle.fontSize; } QString HttpServerChat::getFontColor() const { return m_currentStyle.fontColor; } QString HttpServerChat::getBlockColor() const { return m_currentStyle.blockColor; } QString HttpServerChat::getBorderColor() const { return m_currentStyle.borderColor; } int HttpServerChat::getBorderSize() const { return m_currentStyle.borderSize; } int HttpServerChat::getPadding() const { return m_currentStyle.padding; } int HttpServerChat::getMessageTimeout() const { return m_currentStyle.timeMsg; } QString HttpServerChat::getBackgroundColor() const { return m_backgroundColor; } int HttpServerChat::getTransparency() const { return m_currentStyle.transparency; } void HttpServerChat::setFontFamily(const QString &fontFamily) { m_currentStyle.fontFamily = fontFamily; } void HttpServerChat::setFontSize(int fontSize) { m_currentStyle.fontSize = fontSize; } void HttpServerChat::setFontColor(const QString &fontColor) { m_currentStyle.fontColor = fontColor; } void HttpServerChat::setBlockColor(const QString &blockColor) { m_currentStyle.blockColor = blockColor; } void HttpServerChat::setBorderColor(const QString &borderColor) { m_currentStyle.borderColor = borderColor; } void HttpServerChat::setBorderSize(int borderSize) { m_currentStyle.borderSize = borderSize; } void HttpServerChat::setPadding(int padding) { m_currentStyle.padding = padding; } void HttpServerChat::setMessageTimeout(int timeout) { m_currentStyle.timeMsg = timeout; } void HttpServerChat::setTransparency(int transparency) { m_currentStyle.transparency = qBound(0, transparency, 255); } void HttpServerChat::setDeleteMode(bool deleteByTime, int maxMsgCount) { m_deleteByTime = deleteByTime; m_maxMsgCount = maxMsgCount; if (!deleteByTime) { // Режим удаления по количеству // Удаляем самые старые сообщения, НО сохраняем замороженные while (m_messages.size() > m_maxMsgCount) { // Ищем незамороженное сообщение для удаления bool removed = false; for (int i = 0; i < m_messages.size(); ++i) { if (!m_messages[i].freez) { m_messages.removeAt(i); removed = true; break; } } // Если все сообщения заморожены, выходим из цикла if (!removed) { break; } } } else { // Режим удаления по времени // Замороженные сообщения сохраняются в любом случае cleanupOldMessages(); } } void HttpServerChat::cleanupOldMessages() { if (!m_deleteByTime) { return; // Если удаление по времени отключено, выходим } qint64 currentTime = QDateTime::currentSecsSinceEpoch(); // Проходим сообщения в обратном порядке для безопасного удаления for (int i = m_messages.size() - 1; i >= 0; --i) { const ChatMessage &msg = m_messages[i]; // Пропускаем замороженные сообщения if (msg.freez) { continue; } qint64 messageAge = currentTime - msg.timestamp; if (messageAge >= msg.timeMsg) { m_messages.removeAt(i); } } }