diff --git a/TTW_Bot.pro b/TTW_Bot.pro index f34f0fc..ee65a50 100644 --- a/TTW_Bot.pro +++ b/TTW_Bot.pro @@ -21,15 +21,21 @@ RC_ICONS = ico\app_icon.ico INCLUDEPATH += $$PWD SOURCES += \ + commandprocessor.cpp \ emoteprovider.cpp \ fcolorsetting.cpp \ fcreatechat.cpp \ fcreatenotify.cpp \ ffontsetting.cpp \ + filemanager.cpp \ fsettingsws.cpp \ fsinglegrid.cpp \ + logmanager.cpp \ main.cpp \ + mediafilemanager.cpp \ neuralnetworkmanager.cpp \ + randommanager.cpp \ + randomresponses.cpp \ soundmanager.cpp \ tauth.cpp \ ttw_api.cpp \ @@ -46,15 +52,21 @@ SOURCES += \ HEADERS += \ badge.h \ + commandprocessor.h \ emoteprovider.h \ fcolorsetting.h \ fcreatechat.h \ fcreatenotify.h \ ffontsetting.h \ + filemanager.h \ fsettingsws.h \ fsinglegrid.h \ + logmanager.h \ + mediafilemanager.h \ miniaudio.h \ neuralnetworkmanager.h \ + randommanager.h \ + randomresponses.h \ soundmanager.h \ tauth.h \ timerinfo.h \ diff --git a/commandprocessor.cpp b/commandprocessor.cpp new file mode 100644 index 0000000..d106239 --- /dev/null +++ b/commandprocessor.cpp @@ -0,0 +1,476 @@ +#include "commandprocessor.h" +#include +#include +#include +#include +#include +#include + +CommandProcessor::CommandProcessor(QObject *parent) + : QObject(parent) +{ +} + +void CommandProcessor::setContext(const Context& context) +{ + m_context = context; +} + +QString CommandProcessor::generateResponse(QString userIndex, const QString &command, const QString &message) +{ + qDebug() << "generateResponse: userIndex =" << userIndex << "command =" << command; + + // Сначала пробуем найти пользователя по displayName + QString username = ""; + if (m_context.userManager) { + User* user = m_context.userManager->findUser(userIndex); + if (user) { + username = user->displayName; + qDebug() << "Найден пользователь:" << username; + } else { + qDebug() << "Пользователь не найден в UserManager по displayName:" << userIndex; + + // Попробуем найти по ID + user = m_context.userManager->findUserById(userIndex); + if (user) { + username = user->displayName; + qDebug() << "Найден пользователь по ID:" << username; + } + } + } + + if (username.isEmpty()) { + // Если не нашли в UserManager, используем переданное имя + username = userIndex; + qDebug() << "Используем переданное имя:" << username; + } + + Command cmd = findCommand(command); + if (cmd.command.isEmpty()) { + qDebug() << "Команда не найдена:" << command; + return QString("Команда '%1' не найдена").arg(command); + } + + qDebug() << "Найдена команда:" << cmd.command << "ответ:" << cmd.response; + + QString fullCommand = command + (message.isEmpty() ? "" : " " + message); + QString result = processCommand(username, fullCommand, cmd.response); + + qDebug() << "Итоговый результат:" << result; + return result; +} + +void CommandProcessor::addCommand(const QString &command, const QString &response) +{ + m_commands.append({command, response}); +} + +void CommandProcessor::addCommands(const QVector &commands) +{ + m_commands.append(commands); +} + +void CommandProcessor::clearCommands() +{ + m_commands.clear(); +} + +CommandProcessor::Command CommandProcessor::findCommand(const QString &commandName) const +{ + for (const Command &cmd : m_commands) { + if (cmd.command.compare(commandName, Qt::CaseInsensitive) == 0) { + return cmd; + } + } + return Command(); +} + +QString CommandProcessor::getUsernameByIndex(QString userIndex) const +{ + if (!m_context.userManager) { + return QString(); + } + + const User* user = m_context.userManager->getUserByIndex(userIndex); + return user ? user->displayName : QString(); +} + +QString CommandProcessor::processCommand(const QString &sender, const QString &fullCommand, const QString &rawResponse) +{ + QString response = rawResponse; + QString parameters = extractParameters(fullCommand); + + response = parseStatic(response, sender, parameters); + // Рекурсивная обработка случайных групп (до 5 уровней вложенности) + for (int i = 0; i < 5; i++) { + QString before = response; + response = parseRandomGroups(response); + + // Если после обработки строка не изменилась, значит групп больше нет + if (before == response) { + break; + } + } + response = parseRandomNumbers(response); // Затем обрабатываем случайные числа ВНУТРИ них + response = parseSounds(response); + response = parseTextFiles(response); + response = parseBan(response, sender); + response = parseAPI(response, sender); + + if (response.contains("[AI]", Qt::CaseInsensitive)) { + response = parseAI(response, parameters); + } + + return response; +} + +QString CommandProcessor::extractParameters(const QString &fullCommand) +{ + int spacePos = fullCommand.indexOf(' '); + if (spacePos != -1) { + return fullCommand.mid(spacePos + 1).trimmed(); + } + return QString(); +} + +QString CommandProcessor::parseStatic(const QString &response, const QString &sender, const QString ¶meters) +{ + QString res = response; + + res = res.replace("[USERNAME]", "@" + sender); + res = res.replace("[TO]", parameters); + + if (res.contains("[RANDOMUSER]")) { + QString randomUserName; + if (parameters.contains('@')) { + randomUserName = parameters; + } else if (m_context.userManager) { + const User* randomUser = m_context.userManager->getRandomUser(); + if (randomUser) { + randomUserName = "@" + randomUser->displayName; + } else { + randomUserName = "@viewer"; + } + } else { + randomUserName = "@viewer"; + } + res = res.replace("[RANDOMUSER]", randomUserName); + } + + return res; +} + +QString CommandProcessor::parseRandomNumbers(const QString &response) +{ + QString result = response; + QRegularExpression regex("\\[\\[([^\\]]+)\\]\\]"); + QRegularExpressionMatchIterator matches = regex.globalMatch(response); + + qDebug() << "parseRandomNumbers: исходная строка:" << response; + qDebug() << "Найдено совпадений:" << matches.hasNext(); + + while (matches.hasNext()) { + QRegularExpressionMatch match = matches.next(); + QString rangeName = match.captured(1); + qDebug() << "Найден диапазон:" << rangeName; + + if (m_context.randomManager) { + int randomNumber = m_context.randomManager->getRandomValue(rangeName); + qDebug() << "Получено случайное число:" << randomNumber << "для диапазона" << rangeName; + result.replace("[[" + rangeName + "]]", QString::number(randomNumber)); + } else { + qDebug() << "RandomManager не инициализирован!"; + int fallbackNumber = QRandomGenerator::global()->bounded(1, 101); + result.replace("[[" + rangeName + "]]", QString::number(fallbackNumber)); + } + } + + qDebug() << "Результат после замены:" << result; + return result; +} + +QString CommandProcessor::parseSounds(const QString &response) +{ + QString result = response; + QRegularExpression regex("\\|\\|([^\\|]+)\\|\\|"); + QRegularExpressionMatchIterator matches = regex.globalMatch(response); + + while (matches.hasNext()) { + QRegularExpressionMatch match = matches.next(); + QString soundCommand = match.captured(1); + QString fn = m_context.mediaFileManager->getFilePathByName(soundCommand); + + if (m_context.soundManager) { + m_context.soundManager->loadSound(SoundManager::SoundChannel::Channel1, fn); + m_context.soundManager->playSound(SoundManager::SoundChannel::Channel1); + } + + result.replace("||" + soundCommand + "||", ""); + } + + return result; +} + +QString CommandProcessor::parseTextFiles(const QString &response) +{ + QString result = response; + QRegularExpression regex("\\|\\(([^\\)]+)\\|\\("); + QRegularExpressionMatchIterator matches = regex.globalMatch(response); + + while (matches.hasNext()) { + QRegularExpressionMatch match = matches.next(); + QString fileName = match.captured(1); + + QString content = getTextFileContent(fileName); + if (!content.isEmpty()) { + result.replace("|(" + fileName + "|(", content); + } + } + + return result; +} + +QString CommandProcessor::parseRandomGroups(const QString &response) +{ + QString result = response; + QRegularExpression regex("\\{\\{([^\\}]+)\\}\\}"); + QRegularExpressionMatchIterator matches = regex.globalMatch(response); + + while (matches.hasNext()) { + QRegularExpressionMatch match = matches.next(); + QString groupName = match.captured(1); + + QString randomResponse = getRandomResponseFromGroup(groupName); + if (!randomResponse.isEmpty()) { + result.replace("{{" + groupName + "}}", randomResponse); + } + } + + return result; +} + +QString CommandProcessor::parseBan(const QString &response, const QString &sender) +{ + QString result = response; + QRegularExpression regex("\\[BAN(\\d*)\\]"); + QRegularExpressionMatchIterator matches = regex.globalMatch(response); + + while (matches.hasNext()) { + QRegularExpressionMatch match = matches.next(); + QString banTimeStr = match.captured(1); + int banSeconds = banTimeStr.isEmpty() ? 0 : banTimeStr.toInt(); + if (m_context.twitchAPI) { + if (m_context.userManager) { + User* user = m_context.userManager->findUser(sender); + qDebug() << user->displayName; + if (user && !user->id.isEmpty()) { + if (banSeconds > 0) { + m_context.twitchAPI->banUserTime(user->id, banSeconds); + } + } + } + } + + result.replace("[BAN" + banTimeStr + "]", ""); + } + + return result; +} + +QString CommandProcessor::parseAPI(const QString &response, const QString &sender) +{ + QString result = response; + + if (result.contains("[FOLLOW]", Qt::CaseInsensitive)) { + if (m_context.userManager && m_context.twitchAPI) { + User* user = m_context.userManager->findUser(sender); + if (user) { + if (user->id.isEmpty() && !user->login.isEmpty()) { + User fullUser = m_context.twitchAPI->getUserByLogin(user->login); + user->id = fullUser.id; + } + + if (!user->id.isEmpty()) { + QDate followDate = m_context.twitchAPI->getFollow(user->id); + if (followDate.isValid()) { + QString follow = getDateDifferenceString(followDate); + result.replace("[FOLLOW]", follow, Qt::CaseInsensitive); + } + } + } + } + if (result.contains("[FOLLOW]", Qt::CaseInsensitive)) { + result.replace("[FOLLOW]", "неизвестно", Qt::CaseInsensitive); + } + } + + if (result.contains("[AGE]", Qt::CaseInsensitive)) { + if (m_context.userManager && m_context.twitchAPI) { + User* user = m_context.userManager->findUser(sender); + if (user && !user->login.isEmpty()) { + User fullUser = m_context.twitchAPI->getUserByLogin(user->login); + if (fullUser.createdAt.isValid()) { + QString age = getDateDifferenceString(fullUser.createdAt); + result.replace("[AGE]", age, Qt::CaseInsensitive); + } + } + } + if (result.contains("[AGE]", Qt::CaseInsensitive)) { + result.replace("[AGE]", "неизвестно", Qt::CaseInsensitive); + } + } + + if (result.contains("[STAT]", Qt::CaseInsensitive)) { + if (m_context.twitchAPI && !m_context.channel.isEmpty()) { + int avgViewers = 0, maxViewers = 0, hoursWatched = 0, followers = 0, followersTotal = 0; + m_context.twitchAPI->getTTWStat(m_context.channel, avgViewers, maxViewers, + hoursWatched, followers, followersTotal); + + QString stat = QString("Средний онлайн: %1; Максимальный онлайн: %2; " + "Часов просмотра: %3; Подписчиков за месяц: %4; " + "Всего подписчиков: %5") + .arg(avgViewers).arg(maxViewers).arg(hoursWatched) + .arg(followers).arg(followersTotal); + result.replace("[STAT]", stat, Qt::CaseInsensitive); + } else { + result.replace("[STAT]", "Статистика недоступна", Qt::CaseInsensitive); + } + } + + return result; +} + +QString CommandProcessor::parseAI(const QString &response, const QString &question) +{ + QString res = response; + if (!m_context.neuralManager) { + return res.replace("[AI]", "Нейросеть недоступна", Qt::CaseInsensitive); + } + + if (question.isEmpty()) { + return res.replace("[AI]", "Ошибка: не указан вопрос для нейросети", Qt::CaseInsensitive); + } + + QString aiResponse; + bool responseReceived = false; + bool errorOccurred = false; + QString errorMessage; + + QEventLoop eventLoop; + + auto conn1 = connect(m_context.neuralManager, &NeuralNetworkManager::responseReceived, + [&](const QString &response1) { + aiResponse = response1; + responseReceived = true; + eventLoop.quit(); + }); + + auto conn2 = connect(m_context.neuralManager, &NeuralNetworkManager::errorOccurred, + [&](const QString &error) { + errorMessage = error; + errorOccurred = true; + eventLoop.quit(); + }); + + m_context.neuralManager->sendMessage(question, NeuralNetworkManager::DeepSeek); + + QTimer::singleShot(60000, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); + + disconnect(conn1); + disconnect(conn2); + + if (errorOccurred) { + return res.replace("[AI]", + QString("Ошибка нейросети: %1").arg(errorMessage), + Qt::CaseInsensitive); + } + + if (!responseReceived) { + return res.replace("[AI]", + "Таймаут при ожидании ответа от нейросети", + Qt::CaseInsensitive); + } + + return res.replace("[AI]", aiResponse, Qt::CaseInsensitive); +} + +QString CommandProcessor::getRandomResponseFromGroup(const QString &groupName) +{ + if (m_context.randomResponses) { + return m_context.randomResponses->getResponse(groupName); + } + return QString("Ответ из группы: " + groupName); +} + +QString CommandProcessor::getTextFileContent(const QString &fileName) +{ + QFile file(fileName); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + QTextStream stream(&file); + stream.setCodec("UTF-8"); + QString content = stream.readAll(); + file.close(); + return content; + } + return QString(); +} + +QString CommandProcessor::getDateDifferenceString(const QDate &inputDate) +{ + if (!inputDate.isValid() || inputDate.year() < 2000) { + return "неизвестно"; + } + + QDate currentDate = QDate::currentDate(); + if (currentDate < inputDate) { + return "в будущем"; + } + + int years = inputDate.daysTo(currentDate) / 365; + QDate tempDate = inputDate.addYears(years); + + int months = 0; + while (tempDate.addMonths(1) <= currentDate) { + months++; + tempDate = tempDate.addMonths(1); + } + + int days = tempDate.daysTo(currentDate); + if (days >= 30) { + months += days / 30; + days = days % 30; + } + + QString result; + if (years > 0) { + result += QString("%1 %2 ").arg(years).arg(getPeriodEnding(years, 0)); + } + if (months > 0) { + result += QString("%1 %2 ").arg(months).arg(getPeriodEnding(months, 1)); + } + if (days > 0 || result.isEmpty()) { + result += QString("%1 %2").arg(days).arg(getPeriodEnding(days, 2)); + } + + return result.trimmed(); +} + +QString CommandProcessor::getPeriodEnding(int n, int r) +{ + static const QVector> endings = { + {"год", "года", "лет"}, + {"месяц", "месяца", "месяцев"}, + {"день", "дня", "дней"} + }; + + if (r < 0 || r >= endings.size()) return ""; + + if (n % 10 == 1 && n % 100 != 11) { + return endings[r][0]; + } else if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) { + return endings[r][1]; + } else { + return endings[r][2]; + } +} diff --git a/commandprocessor.h b/commandprocessor.h new file mode 100644 index 0000000..9a9e3a9 --- /dev/null +++ b/commandprocessor.h @@ -0,0 +1,74 @@ +#ifndef COMMANDPROCESSOR_H +#define COMMANDPROCESSOR_H + +#include +#include +#include +#include +#include +#include "mediafilemanager.h" +#include "user_manager.h" +#include "ttw_api.h" +#include "soundmanager.h" +#include "neuralnetworkmanager.h" +#include "randommanager.h" +#include "randomresponses.h" + +class CommandProcessor : public QObject +{ + Q_OBJECT + +public: + struct Command { + QString command; + QString response; + }; + + struct Context { + UserManager* userManager = nullptr; + TTwAPI* twitchAPI = nullptr; + SoundManager* soundManager = nullptr; + NeuralNetworkManager* neuralManager = nullptr; + RandomManager* randomManager = nullptr; + RandomResponses* randomResponses = nullptr; + MediaFileManager* mediaFileManager = nullptr; + QString channel; + int notifyVolume = 50; + }; + + explicit CommandProcessor(QObject *parent = nullptr); + + void setContext(const Context& context); + + QString generateResponse(QString userIndex, const QString &command, const QString &message = ""); + + void addCommand(const QString &command, const QString &response); + void addCommands(const QVector &commands); + void clearCommands(); + + Command findCommand(const QString &commandName) const; + +private: + Context m_context; + QVector m_commands; + + QString processCommand(const QString &sender, const QString &fullCommand, const QString &rawResponse); + QString parseStatic(const QString &response, const QString &sender, const QString ¶meters); + QString parseRandomNumbers(const QString &response); + QString parseSounds(const QString &response); + QString parseTextFiles(const QString &response); + QString parseRandomGroups(const QString &response); + QString parseBan(const QString &response, const QString &sender); + QString parseAPI(const QString &response, const QString &sender); + QString parseAI(const QString &response, const QString &question); + + QString extractParameters(const QString &fullCommand); + QString getUsernameByIndex(QString userIndex) const; + QString getDateDifferenceString(const QDate &inputDate); + QString getPeriodEnding(int n, int r); + + QString getRandomResponseFromGroup(const QString &groupName); + QString getTextFileContent(const QString &fileName); +}; + +#endif // COMMANDPROCESSOR_H diff --git a/emoteprovider.cpp b/emoteprovider.cpp index 3ce9283..ab4b2cb 100644 --- a/emoteprovider.cpp +++ b/emoteprovider.cpp @@ -19,7 +19,7 @@ void EmoteProvider::setLogCallback(LogCallback callback) { m_logCallback = callback; } -void EmoteProvider::log(const QString &method, const QString &message, LogLevel level) { +void EmoteProvider::log(const QString &method, const QString &message, LogLevelemt level) { if (m_logCallback) { m_logCallback(metaObject()->className(), method, message, level); } diff --git a/emoteprovider.h b/emoteprovider.h index 824668e..f38aadc 100644 --- a/emoteprovider.h +++ b/emoteprovider.h @@ -9,16 +9,18 @@ #include #include -enum LogLevel { +enum LogLevelemt { LOG_INFO = 0, LOG_WARNING = 1, - LOG_ERROR = 2 + LOG_ERROR = 2, + LOG_DEBUG = 3 + }; using LogCallback = std::function; + LogLevelemt level)>; struct BTTVEmote { QString id; @@ -50,7 +52,7 @@ signals: void emotesLoaded(); protected: - void log(const QString &method, const QString &message, LogLevel level); + void log(const QString &method, const QString &message, LogLevelemt level); virtual QString getBaseUrl() const = 0; virtual void parseGlobalResponse(const QByteArray &data) = 0; virtual void parseCustomResponse(const QByteArray &data, const QString &userId) = 0; diff --git a/fcreatenotify.cpp b/fcreatenotify.cpp index 9f1b99e..fe068f4 100644 --- a/fcreatenotify.cpp +++ b/fcreatenotify.cpp @@ -1,4 +1,5 @@ #include "fcreatenotify.h" +#include "udatabase.h" #include "ui_fcreatenotify.h" #include #include @@ -7,15 +8,19 @@ #include #include #include +#include "filemanager.h" -FCreateNotify::FCreateNotify(QWidget *parent) : +FCreateNotify::FCreateNotify(uDataBase *database, QWidget *parent) : QDialog(parent), ui(new Ui::FCreateNotify), - m_server(nullptr), - m_notificationCounter(0) + m_isEditMode(false), + m_database(database), + m_existingServerName(""), + m_server(nullptr), // Добавьте + m_notificationCounter(0) // Добавьте { ui->setupUi(this); - + FileManager::instance().initializeFolderStructure(); // Создаем структуру папок для статических файлов QDir appDir(QApplication::applicationDirPath()); appDir.mkpath("sounds"); @@ -27,8 +32,7 @@ FCreateNotify::FCreateNotify(QWidget *parent) : "donation", "follow", "subscription", "raid", "bits", "host", "merch", "goal", "poll", "prediction" }; - ui->cbEvent->addItems(events); - ui->cbEvent->setCurrentIndex(0); + // Устанавливаем значения по умолчанию ui->edtHeader->setText("Тестовый пользователь"); ui->edtMessage->setText("Это тестовое уведомление!"); @@ -40,7 +44,7 @@ FCreateNotify::FCreateNotify(QWidget *parent) : // Информация для пользователя ui->btnTest->setToolTip("Создать тестовое уведомление и открыть браузер"); ui->btnAdd->setToolTip("Добавить уведомление для отображения"); - m_server = new HttpServer(nullptr); + } FCreateNotify::~FCreateNotify() @@ -101,25 +105,7 @@ void FCreateNotify::on_btnTest_clicked() createNotification(true); } -void FCreateNotify::on_btnAdd_clicked() -{ - // Создаем сервер с текущими настройками - createServer(); - if (!m_server) { - QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер"); - return; - } - - // Получаем название сервера - QString name = QString("Уведомления (порт %1)").arg(m_server->port()); - - // Отправляем сигнал с созданным сервером - emit serverCreated(m_server, name); - - // Закрываем окно - accept(); -} void FCreateNotify::createNotification(bool isTest) { @@ -142,39 +128,24 @@ void FCreateNotify::createNotification(bool isTest) QString("Тестовое сообщение #%1").arg(m_notificationCounter) : ui->edtMessage->text(); - // Добавляем тип события - notif.content = QString("[%1] %2") - .arg(ui->cbEvent->currentText()) - .arg(notif.content); - // Картинка QString imgPath = ui->edtFileImg->text(); if (!imgPath.isEmpty() && QFile::exists(imgPath)) { - QFileInfo imgInfo(imgPath); - QString destFileName = imgInfo.fileName(); - QString destPath = getAbsolutePath("imgs/" + destFileName); - - // Копируем, если файл еще не существует - if (!QFile::exists(destPath)) { - QFile::copy(imgPath, destPath); + QString newFileName; + if (FileManager::instance().copyToUserData(imgPath, FileManager::WebServerImages, + FileManager::CopyWithNewName, &newFileName)) { + notif.url = FileManager::instance().getWebPath(FileManager::WebServerImages, newFileName); } - - notif.url = "/imgs/" + destFileName; } // Звук QString soundPath = ui->edtFileSong->text(); if (!soundPath.isEmpty() && QFile::exists(soundPath)) { - QFileInfo soundInfo(soundPath); - QString destFileName = soundInfo.fileName(); - QString destPath = getAbsolutePath("sounds/" + destFileName); - - // Копируем, если файл еще не существует - if (!QFile::exists(destPath)) { - QFile::copy(soundPath, destPath); + QString newFileName; + if (FileManager::instance().copyToUserData(soundPath, FileManager::WebServerSounds, + FileManager::CopyWithNewName, &newFileName)) { + notif.soundURL = FileManager::instance().getWebPath(FileManager::WebServerSounds, newFileName); } - - notif.soundURL = "/sounds/" + destFileName; } // ПОЛУЧАЕМ НАСТРОЙКИ ИЗ ВИДЖЕТОВ @@ -277,26 +248,6 @@ void FCreateNotify::createNotification(bool isTest) } } -void FCreateNotify::copyFileToAppDir(const QString &sourcePath, const QString &destSubDir) -{ - QFileInfo sourceInfo(sourcePath); - QString destPath = getAbsolutePath(destSubDir + "/" + sourceInfo.fileName()); - - // Копируем файл, если он еще не существует или изменился - if (!QFile::exists(destPath) || - QFileInfo(sourcePath).lastModified() > QFileInfo(destPath).lastModified()) { - QFile::remove(destPath); // Удаляем старую версию - if (QFile::copy(sourcePath, destPath)) { - } else { - } - } -} - -QString FCreateNotify::getAbsolutePath(const QString &relativePath) -{ - return QApplication::applicationDirPath() + "/" + relativePath; -} - void FCreateNotify::on_btnOpenImg_clicked() { QString fileName = QFileDialog::getOpenFileName(this, @@ -336,3 +287,154 @@ void FCreateNotify::onServerStarted(bool success) QMessageBox::warning(this, "Ошибка", "Не удалось запустить веб-сервер"); } } + +void FCreateNotify::setEditMode(bool isEditMode) +{ + m_isEditMode = isEditMode; + if (isEditMode) { + ui->btnAdd->setText("Изменить"); + setWindowTitle("TTW Bot app: Редактировать уведомления"); + } else { + ui->btnAdd->setText("Создать"); + setWindowTitle("TTW Bot app: Создать уведомления"); + } +} + +void FCreateNotify::loadExistingServer(HttpServer *server, const QString &name) +{ + if (!server) return; + + m_isEditMode = true; + m_server = server; + m_existingServerName = name; + + setEditMode(true); + + // Устанавливаем порт + FSettingsWS *settingsWS = ui->widget; + if (settingsWS) { + settingsWS->sbPort->setValue(server->port()); + settingsWS->sbTime->setValue(server->getDuration()); + } + + // Устанавливаем название + ui->lineEdit->setText(name); // Предполагается, что есть lineEdit для имени + + // Устанавливаем цвета + FColorSetting *colorSetting = ui->wBlock; + if (colorSetting) { + colorSetting->cbBlockColor->setCurrentText(server->getBlockColor()); + colorSetting->cbBorderColor->setCurrentText(server->getBorderColor()); + colorSetting->sbBorderSize->setValue(server->getBorderSize()); + colorSetting->cbBackgroundColor->setCurrentText(server->getPageBackgroundColor()); + colorSetting->hsBlockTransparant->setValue(server->getTransparency()); + } + + // Устанавливаем шрифты + FFontSetting *fontHeaderSetting = ui->wFont; + if (fontHeaderSetting) { + QString titleFamily; + int titleSize; + QString titleColor; + server->getTitleFont(titleFamily, titleSize, titleColor); + + fontHeaderSetting->cbFontStyle->setCurrentText(titleFamily); + fontHeaderSetting->sbFontSize->setValue(titleSize); + fontHeaderSetting->cbFontColor->setCurrentText(titleColor); + } + + FFontSetting *fontMessageSetting = ui->wFont_2; + if (fontMessageSetting) { + QString contentFamily; + int contentSize; + QString contentColor; + server->getContentFont(contentFamily, contentSize, contentColor); + + fontMessageSetting->cbFontStyle->setCurrentText(contentFamily); + fontMessageSetting->sbFontSize->setValue(contentSize); + fontMessageSetting->cbFontColor->setCurrentText(contentColor); + } +} + +// Обновите on_btnAdd_clicked для поддержки редактирования: +void FCreateNotify::on_btnAdd_clicked() +{ + if (m_isEditMode && m_server) { + // Режим редактирования + FSettingsWS *settingsWS = ui->widget; + FColorSetting *colorSetting = ui->wBlock; + FFontSetting *fontHeaderSetting = ui->wFont; + FFontSetting *fontMessageSetting = ui->wFont_2; + + if (!settingsWS || !colorSetting || !fontHeaderSetting || !fontMessageSetting) { + QMessageBox::warning(this, "Ошибка", "Не найдены настройки сервера"); + return; + } + + int newPort = settingsWS->sbPort->value(); + bool portChanged = (newPort != m_server->port()); + + if (portChanged) { + m_server->stop(); + delete m_server; + m_server = nullptr; + createServer(); + } + + if (!m_server) { + QMessageBox::warning(this, "Ошибка", "Не удалось обновить сервер"); + return; + } + + // Применяем настройки к серверу + // Цвета + m_server->setBlockColor(colorSetting->cbBlockColor->currentText()); + m_server->setBorderColor(colorSetting->cbBorderColor->currentText()); + m_server->setBorderSize(colorSetting->sbBorderSize->value()); + m_server->setPageBackgroundColor(colorSetting->cbBackgroundColor->currentText()); + m_server->setTransparency(colorSetting->hsBlockTransparant->value()); + + // Шрифты + m_server->setTitleFont( + fontHeaderSetting->cbFontStyle->currentText(), + fontHeaderSetting->sbFontSize->value(), + fontHeaderSetting->cbFontColor->currentText() + ); + + m_server->setContentFont( + fontMessageSetting->cbFontStyle->currentText(), + fontMessageSetting->sbFontSize->value(), + fontMessageSetting->cbFontColor->currentText() + ); + + // Длительность + m_server->setDuration(settingsWS->sbTime->value()); + + QString newName = ui->lineEdit->text(); + if (newName.isEmpty()) { + newName = QString("Уведомления (порт %1)").arg(m_server->port()); + } + + emit serverUpdated(m_server, newName); + accept(); + } else { + // Режим создания (существующий код) + createServer(); + + if (!m_server) { + QMessageBox::warning(this, "Ошибка", "Не удалось создать сервер"); + return; + } + + createNotification(false); + + QString name = ui->lineEdit->text(); + if (name.isEmpty()) { + name = QString("Уведомления (порт %1)").arg(m_server->port()); + } + + emit serverCreated(m_server, name); + m_server = nullptr; + accept(); + } +} diff --git a/fcreatenotify.h b/fcreatenotify.h index beceb53..a8a69b5 100644 --- a/fcreatenotify.h +++ b/fcreatenotify.h @@ -3,6 +3,7 @@ #include "webservernotify.h" #include +#include "udatabase.h" namespace Ui { class FCreateNotify; @@ -14,11 +15,12 @@ class FCreateNotify : public QDialog signals: void serverCreated(HttpServer *server, const QString &name); - + void serverUpdated(HttpServer *server, const QString &name); public: - explicit FCreateNotify(QWidget *parent = nullptr); + explicit FCreateNotify(uDataBase *database = nullptr, QWidget *parent = nullptr); ~FCreateNotify(); - + void loadExistingServer(HttpServer *server, const QString &name); + void setEditMode(bool isEditMode); protected: void closeEvent(QCloseEvent *event) override; @@ -34,11 +36,14 @@ private: void createNotification(bool isTest = false); QString getAbsolutePath(const QString &relativePath); void copyFileToAppDir(const QString &sourcePath, const QString &destSubDir); - + bool m_isEditMode; + uDataBase *m_database; + QString m_existingServerName; // Сервер HttpServer *m_server; int m_notificationCounter; void createServer(); + void applyCurrentSettingsToServer(); }; #endif // FCREATENOTIFY_H diff --git a/fcreatenotify.ui b/fcreatenotify.ui index 9c03538..317c697 100644 --- a/fcreatenotify.ui +++ b/fcreatenotify.ui @@ -137,7 +137,7 @@ 370 150 351 - 181 + 141 @@ -149,7 +149,7 @@ 9 19 331 - 152 + 111 @@ -163,8 +163,8 @@ - - + + ... @@ -173,6 +173,13 @@ + + + + ... + + + @@ -180,23 +187,6 @@ - - - - ... - - - - - - - Событие - - - - - - @@ -204,7 +194,7 @@ 370 - 340 + 300 80 24 @@ -217,7 +207,7 @@ 460 - 340 + 300 81 24 @@ -226,6 +216,16 @@ Создать + + + + 370 + 400 + 231 + 22 + + + diff --git a/ffontsetting.cpp b/ffontsetting.cpp index d16d2a8..08f0ffe 100644 --- a/ffontsetting.cpp +++ b/ffontsetting.cpp @@ -1,4 +1,5 @@ #include "ffontsetting.h" +#include "filemanager.h" #include "qdebug.h" #include "qdir.h" #include "ui_ffontsetting.h" @@ -48,58 +49,45 @@ void FFontSetting::setupColorComboBox(QComboBox* comboBox) void FFontSetting::loadFonts() { - QString fontsPath = "fonts"; - QDir fontsDir(fontsPath); + // Загружаем шрифты из пользовательской папки + QString userFontsPath = FileManager::instance().getPath(FileManager::Fonts); + QDir userFontsDir(userFontsPath); - if (fontsDir.exists()) { - QStringList fontFiles = fontsDir.entryList(QStringList() << "*.ttf" << "*.otf" << "*.ttc", - QDir::Files); + // Список всех загруженных шрифтов + QStringList allFontFamilies; + + // Загружаем пользовательские шрифты + if (userFontsDir.exists()) { + QStringList fontFiles = userFontsDir.entryList(QStringList() << "*.ttf" << "*.otf" << "*.ttc", QDir::Files); foreach (const QString &fontFile, fontFiles) { - QString fontPath = fontsDir.absoluteFilePath(fontFile); + QString fontPath = userFontsDir.absoluteFilePath(fontFile); int fontId = QFontDatabase::addApplicationFont(fontPath); if (fontId != -1) { QStringList fontFamilies = QFontDatabase::applicationFontFamilies(fontId); - } else { + allFontFamilies.append(fontFamilies); } } + } - // ОБНОВЛЯЕМ СПИСОК ШРИФТОВ В КОМБОБОКСЕ - ui->cbFontStyle->clear(); // Очищаем старый список + // Добавляем системные шрифты Windows + QFontDatabase fontDatabase; + QStringList systemFamilies = fontDatabase.families(); + allFontFamilies.append(systemFamilies); - // Получаем все шрифты через объект QFontDatabase - QFontDatabase fontDatabase; - QStringList fontFamilies = fontDatabase.families(); // Получаем все шрифты + // Удаляем дубликаты + allFontFamilies.removeDuplicates(); + allFontFamilies.sort(); - // ИЛИ используйте статический метод с параметром: - // QStringList fontFamilies = QFontDatabase::families(QFontDatabase::Any); + // Заполняем ComboBox + ui->cbFontStyle->clear(); + ui->cbFontStyle->addItems(allFontFamilies); - ui->cbFontStyle->addItems(fontFamilies); // Добавляем все шрифты - - // Устанавливаем шрифт по умолчанию - int defaultIndex = ui->cbFontStyle->findText("Arial"); - if (defaultIndex >= 0) { - ui->cbFontStyle->setCurrentIndex(defaultIndex); - } - - // Показываем количество доступных шрифтов - } else { - - // Загружаем системные шрифты, если папки нет - QFontDatabase fontDatabase; - QStringList fontFamilies = fontDatabase.families(); - ui->cbFontStyle->clear(); - ui->cbFontStyle->addItems(fontFamilies); - - int defaultIndex = ui->cbFontStyle->findText("Arial"); - if (defaultIndex >= 0) { - ui->cbFontStyle->setCurrentIndex(defaultIndex); - } - - // Проверяем альтернативные пути - QString appDir = QApplication::applicationDirPath(); - QString altPath = appDir + "/fonts"; + // Устанавливаем шрифт по умолчанию + int defaultIndex = ui->cbFontStyle->findText("Arial"); + if (defaultIndex >= 0) { + ui->cbFontStyle->setCurrentIndex(defaultIndex); } } diff --git a/filemanager.cpp b/filemanager.cpp new file mode 100644 index 0000000..4b8ee41 --- /dev/null +++ b/filemanager.cpp @@ -0,0 +1,183 @@ +#include "filemanager.h" +#include + +FileManager::FileManager() +{ + m_systemPath = QCoreApplication::applicationDirPath(); + m_userDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); +} + +FileManager& FileManager::instance() +{ + static FileManager instance; + return instance; +} + +QString FileManager::systemPath() const +{ + return m_systemPath; +} + +QString FileManager::userDataPath() const +{ + return m_userDataPath; +} + +QString FileManager::getPath(FileType type, const QString& subPath) const +{ + QString basePath; + + // Определяем базовый путь (системный или пользовательский) + switch (type) { + case Icons: + case SystemStyles: + basePath = m_systemPath; + break; + default: + basePath = m_userDataPath; + break; + } + + // Добавляем подпапку + QString folder; + switch (type) { + case Sounds: folder = "sounds"; break; + case Images: folder = "images"; break; + case Styles: folder = "styles"; break; + case Voices: folder = "voices"; break; + case Fonts: folder = "fonts"; break; + case Temp: folder = "temp"; break; + case Backups: folder = "backups"; break; + case Exports: folder = "exports"; break; + case Logs: folder = "logs"; break; + case Cache: folder = "cache"; break; + case WebServerImages: folder = "webserver/images"; break; + case WebServerSounds: folder = "webserver/sounds"; break; + case Icons: folder = "ico"; break; + case SystemStyles: folder = "styles"; break; + default: folder = ""; + } + + QString path = QString("%1/%2").arg(basePath).arg(folder); + if (!subPath.isEmpty()) { + path += "/" + subPath; + } + + return path; +} + +QString FileManager::getFullPath(FileType type, const QString& fileName) const +{ + QString path = getPath(type); + return QString("%1/%2").arg(path).arg(fileName); +} + +bool FileManager::copyToUserData(const QString& sourceFile, FileType type, + CopyMode mode, QString* newFileName) +{ + QFileInfo sourceInfo(sourceFile); + if (!sourceInfo.exists()) { + qWarning() << "FileManager: Source file doesn't exist:" << sourceFile; + return false; + } + + QString destFolder = getPath(type); + QDir dir(destFolder); + if (!dir.exists()) { + if (!dir.mkpath(".")) { + qWarning() << "FileManager: Cannot create folder:" << destFolder; + return false; + } + } + + QString destFileName = sourceInfo.fileName(); + QString destPath = getFullPath(type, destFileName); + + // Обрабатываем конфликты имен + if (QFile::exists(destPath)) { + switch (mode) { + case CopyIfNotExists: + // Файл уже существует, не копируем + if (newFileName) *newFileName = destFileName; + return true; + + case CopyAlways: + // Перезаписываем существующий файл + QFile::remove(destPath); + break; + + case CopyWithNewName: + // Генерируем уникальное имя + int counter = 1; + QString baseName = sourceInfo.baseName(); + QString suffix = sourceInfo.suffix(); + + while (QFile::exists(destPath)) { + destFileName = QString("%1_%2.%3").arg(baseName).arg(counter).arg(suffix); + destPath = getFullPath(type, destFileName); + counter++; + } + break; + } + } + + // Копируем файл + if (QFile::copy(sourceFile, destPath)) { + if (newFileName) *newFileName = destFileName; + return true; + } + + qWarning() << "FileManager: Failed to copy file from" << sourceFile << "to" << destPath; + return false; +} + +bool FileManager::existsInUserData(FileType type, const QString& fileName) const +{ + QString path = getFullPath(type, fileName); + return QFile::exists(path); +} + +void FileManager::initializeFolderStructure() +{ + // Создаем все пользовательские папки + for (int i = Sounds; i <= WebServerSounds; ++i) { + FileType type = static_cast(i); + QString path = getPath(type); + QDir dir(path); + if (!dir.exists()) { + dir.mkpath("."); + } + } +} + +QString FileManager::getWebPath(FileType type, const QString& fileName) const +{ + QString folder; + switch (type) { + case WebServerImages: folder = "imgs"; break; + case WebServerSounds: folder = "sounds"; break; + default: return QString(); + } + + return QString("/%1/%2").arg(folder).arg(fileName); +} + +void FileManager::copyDefaultFiles() +{ + // Копируем системные стили в пользовательскую папку при первом запуске + QString systemStylesPath = getPath(SystemStyles); + QDir systemStylesDir(systemStylesPath); + + if (systemStylesDir.exists()) { + QStringList qssFiles = systemStylesDir.entryList(QStringList() << "*.qss" << "*.QSS", QDir::Files); + + for (const QString& file : qssFiles) { + QString sourcePath = systemStylesDir.absoluteFilePath(file); + QString destPath = getFullPath(Styles, file); + + if (!QFile::exists(destPath)) { + QFile::copy(sourcePath, destPath); + } + } + } +} diff --git a/filemanager.h b/filemanager.h new file mode 100644 index 0000000..e2a4044 --- /dev/null +++ b/filemanager.h @@ -0,0 +1,75 @@ +#ifndef FILEMANAGER_H +#define FILEMANAGER_H + +#include +#include +#include +#include +#include + +class FileManager +{ +public: + // Типы файлов/папок + enum FileType { + Sounds, + Images, + Styles, + Voices, + Fonts, + Temp, + Backups, + Exports, + Logs, + Cache, + WebServerImages, + WebServerSounds, + Icons, + SystemStyles + }; + + // Режим копирования + enum CopyMode { + CopyIfNotExists, // Копировать только если не существует + CopyAlways, // Всегда копировать (перезаписывать) + CopyWithNewName // Копировать с новым именем (если существует) + }; + + static FileManager& instance(); + + // Основные пути + QString systemPath() const; + QString userDataPath() const; + + // Получение путей + QString getPath(FileType type, const QString& subPath = "") const; + QString getFullPath(FileType type, const QString& fileName) const; + + // Копирование файлов + bool copyToUserData(const QString& sourceFile, FileType type, + CopyMode mode = CopyIfNotExists, + QString* newFileName = nullptr); + + // Проверка существования + bool existsInUserData(FileType type, const QString& fileName) const; + + // Инициализация структуры папок + void initializeFolderStructure(); + + // Получение относительного пути для веб-сервера + QString getWebPath(FileType type, const QString& fileName) const; + + // Копирование файлов из системных в пользовательские (при первом запуске) + void copyDefaultFiles(); + +private: + FileManager(); + ~FileManager() = default; + FileManager(const FileManager&) = delete; + FileManager& operator=(const FileManager&) = delete; + + QString m_systemPath; + QString m_userDataPath; +}; + +#endif // FILEMANAGER_H diff --git a/logmanager.cpp b/logmanager.cpp new file mode 100644 index 0000000..afbd4f5 --- /dev/null +++ b/logmanager.cpp @@ -0,0 +1,832 @@ +#include "logmanager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ============================================================================ +// СТАТИЧЕСКИЕ ПЕРЕМЕННЫЕ +// ============================================================================ + +LogManager* LogManager::m_instance = nullptr; + +// ============================================================================ +// МЕТОДЫ LOGENTRY +// ============================================================================ + +QString LogEntry::toString() const +{ + QStringList parts; + + if (!timestamp.isNull()) { + parts.append(timestamp.toString("hh:mm:ss")); + } + + parts.append(levelToString(level)); + + if (!module.isEmpty()) { + parts.append(module); + } + + if (!method.isEmpty()) { + parts.append(method); + } + + parts.append(message); + + return parts.join(" | "); +} + +QString LogEntry::toHtml() const +{ + QString color = levelToColor(level).name(); + + QString html = QString( + "
" + "%2 " + "[%3] " + "%4 " + "%5: " + "%6" + "
") + .arg(color) + .arg(timestamp.toString("hh:mm:ss")) + .arg(levelToString(level)) + .arg(module) + .arg(method) + .arg(message.toHtmlEscaped()); + + return html; +} + +QString LogEntry::toCsv() const +{ + return QString("\"%1\",\"%2\",\"%3\",\"%4\",\"%5\"") + .arg(timestamp.toString("yyyy-MM-dd hh:mm:ss")) + .arg(levelToString(level)) + .arg(module) + .arg(method) + .arg(message); +} + +QString LogEntry::toJson() const +{ + QJsonObject obj; + obj["timestamp"] = timestamp.toString(Qt::ISODate); + obj["level"] = levelToString(level); + obj["module"] = module; + obj["method"] = method; + obj["message"] = message; + obj["threadId"] = static_cast(threadId); + + if (!sourceFile.isEmpty()) { + obj["sourceFile"] = sourceFile; + obj["sourceLine"] = sourceLine; + } + + QJsonDocument doc(obj); + return doc.toJson(QJsonDocument::Compact); +} + +QString LogEntry::levelToString(LogLevel level) +{ + switch (level) { + case LogLevel::Info: return "INFO"; + case LogLevel::Warning: return "WARNING"; + case LogLevel::Error: return "ERROR"; + case LogLevel::Debug: return "DEBUG"; + case LogLevel::Critical: return "CRITICAL"; + default: return "UNKNOWN"; + } +} + +QColor LogEntry::levelToColor(LogLevel level) +{ + switch (level) { + case LogLevel::Info: return Qt::darkGreen; + case LogLevel::Warning: return Qt::darkYellow; + case LogLevel::Error: return Qt::red; + case LogLevel::Debug: return Qt::gray; + case LogLevel::Critical: return Qt::darkRed; + default: return Qt::black; + } +} + +QString LogEntry::formatDateTime(const QDateTime& dt) +{ + return dt.toString("dd.MM.yyyy hh:mm:ss.zzz"); +} + +// ============================================================================ +// РЕАЛИЗАЦИЯ LOGMANAGER +// ============================================================================ + +LogManager::LogManager(QObject* parent) + : QObject(parent) + , m_maxEntries(10000) +{ + // Настройки по умолчанию + m_settings.logToFile = true; + m_settings.logToConsole = true; + m_settings.logToDatabase = false; + + // Стандартный путь к файлу логов + QString appDataPath = QStandardPaths::writableLocation( + QStandardPaths::AppDataLocation); + QDir dir(appDataPath); + if (!dir.exists()) { + dir.mkpath("."); + } + m_settings.logFilePath = dir.filePath("application.log"); + + // Цвета по умолчанию + m_settings.colors[LogLevel::Info] = Qt::darkGreen; + m_settings.colors[LogLevel::Warning] = Qt::darkYellow; + m_settings.colors[LogLevel::Error] = Qt::red; + m_settings.colors[LogLevel::Debug] = Qt::gray; + m_settings.colors[LogLevel::Critical] = Qt::darkRed; + + m_settings.showTimestamp = true; + m_settings.showModule = true; + m_settings.showMethod = true; +} + +LogManager::~LogManager() +{ + if (m_logFile.isOpen()) { + m_logFile.close(); + } +} + +LogManager* LogManager::instance() +{ + if (!m_instance) { + m_instance = new LogManager(); + } + return m_instance; +} + +void LogManager::initialize(const LogSettings& settings) +{ + if (m_instance) { + m_instance->updateSettings(settings); + } else { + m_instance = new LogManager(); + m_instance->updateSettings(settings); + } +} + +void LogManager::cleanup() +{ + if (m_instance) { + delete m_instance; + m_instance = nullptr; + } +} + +void LogManager::log(LogLevel level, const QString& module, + const QString& method, const QString& message) +{ + log(level, module, method, message, "", 0); +} + +void LogManager::log(LogLevel level, const QString& module, + const QString& method, const QString& message, + const QString& sourceFile, int sourceLine) +{ + QMutexLocker locker(&m_mutex); + + // Создаем запись + LogEntry entry; + entry.timestamp = QDateTime::currentDateTime(); + entry.level = level; + entry.module = module; + entry.method = method; + entry.message = message; + entry.threadId = reinterpret_cast(QThread::currentThreadId()); + + if (!sourceFile.isEmpty()) { + entry.sourceFile = sourceFile; + entry.sourceLine = sourceLine; + } + + // Добавляем в список + m_entries.append(entry); + + // Ограничиваем количество записей + if (m_entries.size() > m_maxEntries) { + m_entries.removeFirst(); + } + + // Записываем в файл + if (m_settings.logToFile && !m_settings.logFilePath.isEmpty()) { + writeToFile(entry); + } + + // Выводим в консоль + if (m_settings.logToConsole) { + QString formatted = formatForConsole(entry); + switch (level) { + case LogLevel::Error: + case LogLevel::Critical: + qCritical().noquote() << formatted; + break; + case LogLevel::Warning: + qWarning().noquote() << formatted; + break; + case LogLevel::Debug: + qDebug().noquote() << formatted; + break; + default: + qInfo().noquote() << formatted; + break; + } + } + + locker.unlock(); + + // Отправляем сигналы + emit entryAdded(entry); + + // Сигналы для конкретных уровней + switch (level) { + case LogLevel::Info: + emit infoAdded(entry); + break; + case LogLevel::Warning: + emit warningAdded(entry); + break; + case LogLevel::Error: + emit errorAdded(entry); + break; + case LogLevel::Debug: + emit debugAdded(entry); + break; + case LogLevel::Critical: + emit criticalAdded(entry); + break; + } +} + +void LogManager::info(const QString& module, const QString& method, + const QString& message) +{ + log(LogLevel::Info, module, method, message); +} + +void LogManager::warning(const QString& module, const QString& method, + const QString& message) +{ + log(LogLevel::Warning, module, method, message); +} + +void LogManager::error(const QString& module, const QString& method, + const QString& message) +{ + log(LogLevel::Error, module, method, message); +} + +void LogManager::debug(const QString& module, const QString& method, + const QString& message) +{ + log(LogLevel::Debug, module, method, message); +} + +void LogManager::critical(const QString& module, const QString& method, + const QString& message) +{ + log(LogLevel::Critical, module, method, message); +} + +QList LogManager::allEntries() const +{ + QMutexLocker locker(&m_mutex); + return m_entries; +} + +QList LogManager::entriesByTime(const QDateTime& from, + const QDateTime& to) const +{ + QMutexLocker locker(&m_mutex); + QList result; + + for (const auto& entry : m_entries) { + if (entry.timestamp >= from && entry.timestamp <= to) { + result.append(entry); + } + } + + return result; +} + +QList LogManager::entriesByLevel(LogLevel level) const +{ + QMutexLocker locker(&m_mutex); + QList result; + + for (const auto& entry : m_entries) { + if (entry.level == level) { + result.append(entry); + } + } + + return result; +} + +QList LogManager::entriesByModule(const QString& module) const +{ + QMutexLocker locker(&m_mutex); + QList result; + + for (const auto& entry : m_entries) { + if (entry.module == module) { + result.append(entry); + } + } + + return result; +} + +QList LogManager::search(const QString& text, bool caseSensitive) const +{ + QMutexLocker locker(&m_mutex); + QList result; + + Qt::CaseSensitivity sensitivity = caseSensitive ? + Qt::CaseSensitive : Qt::CaseInsensitive; + + for (const auto& entry : m_entries) { + if (entry.message.contains(text, sensitivity) || + entry.module.contains(text, sensitivity) || + entry.method.contains(text, sensitivity)) { + result.append(entry); + } + } + + return result; +} + +QList LogManager::filter(const QList& levels, + const QString& moduleFilter, + const QString& methodFilter) const +{ + QMutexLocker locker(&m_mutex); + QList result; + + for (const auto& entry : m_entries) { + // Проверка уровня + if (!levels.contains(entry.level)) { + continue; + } + + // Проверка модуля + if (!moduleFilter.isEmpty() && entry.module != moduleFilter) { + continue; + } + + // Проверка метода + if (!methodFilter.isEmpty() && entry.method != methodFilter) { + continue; + } + + result.append(entry); + } + + return result; +} + +void LogManager::clear() +{ + QMutexLocker locker(&m_mutex); + m_entries.clear(); + locker.unlock(); + + emit logCleared(); +} + +bool LogManager::saveToFile(const QString& filePath, bool append) +{ + QMutexLocker locker(&m_mutex); + + QFile file(filePath); + QIODevice::OpenMode mode = QIODevice::WriteOnly | QIODevice::Text; + if (append) { + mode |= QIODevice::Append; + } + + if (!file.open(mode)) { + emit fileError(QString("Не удалось открыть файл: %1").arg(file.errorString())); + return false; + } + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + + // Заголовок + stream << "Дата,Уровень,Модуль,Метод,Сообщение\n"; + + for (const auto& entry : m_entries) { + stream << entry.toCsv() << "\n"; + } + + file.close(); + return true; +} + +bool LogManager::loadFromFile(const QString& filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + return false; + } + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + + // Пропускаем заголовок + QString header = stream.readLine(); + + QList loadedEntries; + + while (!stream.atEnd()) { + QString line = stream.readLine(); + QStringList parts = line.split('"'); + + if (parts.size() < 9) continue; + + LogEntry entry; + entry.timestamp = QDateTime::fromString(parts[1], "yyyy-MM-dd hh:mm:ss"); + + QString levelStr = parts[3]; + if (levelStr == "INFO") entry.level = LogLevel::Info; + else if (levelStr == "WARNING") entry.level = LogLevel::Warning; + else if (levelStr == "ERROR") entry.level = LogLevel::Error; + else if (levelStr == "DEBUG") entry.level = LogLevel::Debug; + else if (levelStr == "CRITICAL") entry.level = LogLevel::Critical; + + entry.module = parts[5]; + entry.method = parts[7]; + entry.message = parts[9]; + + loadedEntries.append(entry); + } + + QMutexLocker locker(&m_mutex); + m_entries = loadedEntries; + + return true; +} + +bool LogManager::exportToFormat(const QString& filePath, const QString& format) +{ + QMutexLocker locker(&m_mutex); + + QFile file(filePath); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + return false; + } + + QTextStream stream(&file); + stream.setCodec("UTF-8"); + + if (format.toLower() == "html") { + stream << "\n\n\n"; + stream << "\n"; + stream << "Логи приложения\n"; + stream << "\n"; + stream << "\n\n"; + + for (const auto& entry : m_entries) { + stream << entry.toHtml() << "\n"; + } + + stream << "\n"; + + } else if (format.toLower() == "json") { + QJsonArray array; + for (const auto& entry : m_entries) { + QJsonObject obj; + obj["timestamp"] = entry.timestamp.toString(Qt::ISODate); + obj["level"] = LogEntry::levelToString(entry.level); + obj["module"] = entry.module; + obj["method"] = entry.method; + obj["message"] = entry.message; + array.append(obj); + } + + QJsonDocument doc(array); + stream << doc.toJson(); + + } else { // TXT (по умолчанию) + for (const auto& entry : m_entries) { + stream << entry.toString() << "\n"; + } + } + + file.close(); + return true; +} + +QMap LogManager::statistics() const +{ + QMutexLocker locker(&m_mutex); + QMap stats; + + for (const auto& entry : m_entries) { + stats[entry.level]++; + } + + return stats; +} + +int LogManager::count() const +{ + QMutexLocker locker(&m_mutex); + return m_entries.size(); +} + +int LogManager::maxEntries() const +{ + QMutexLocker locker(&m_mutex); + return m_maxEntries; +} + +void LogManager::setMaxEntries(int max) +{ + QMutexLocker locker(&m_mutex); + m_maxEntries = max; + + // Удаляем лишние записи + while (m_entries.size() > m_maxEntries) { + m_entries.removeFirst(); + } +} + +LogSettings LogManager::settings() const +{ + QMutexLocker locker(&m_mutex); + return m_settings; +} + +void LogManager::updateSettings(const LogSettings& newSettings) +{ + QMutexLocker locker(&m_mutex); + + // Закрываем старый файл, если изменился путь + if (m_settings.logFilePath != newSettings.logFilePath && m_logFile.isOpen()) { + m_logFile.close(); + } + + m_settings = newSettings; + + // Инициализируем файл + if (m_settings.logToFile && !m_settings.logFilePath.isEmpty()) { + if (!m_logFile.isOpen()) { + m_logFile.setFileName(m_settings.logFilePath); + if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { + emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString())); + } else { + m_logStream.setDevice(&m_logFile); + m_logStream.setCodec("UTF-8"); + } + } + } + + locker.unlock(); + emit settingsChanged(); +} + +void LogManager::saveSettings(QSettings& settings) +{ + QMutexLocker locker(&m_mutex); + + settings.beginGroup("Logging"); + settings.setValue("logToFile", m_settings.logToFile); + settings.setValue("logToConsole", m_settings.logToConsole); + settings.setValue("logFilePath", m_settings.logFilePath); + settings.setValue("maxFileSizeMB", m_settings.maxFileSizeMB); + settings.setValue("maxFileCount", m_settings.maxFileCount); + settings.setValue("showTimestamp", m_settings.showTimestamp); + settings.setValue("showModule", m_settings.showModule); + settings.setValue("showMethod", m_settings.showMethod); + settings.setValue("maxEntries", m_maxEntries); + settings.endGroup(); +} + +void LogManager::loadSettings(QSettings& settings) +{ + QMutexLocker locker(&m_mutex); + + settings.beginGroup("Logging"); + m_settings.logToFile = settings.value("logToFile", true).toBool(); + m_settings.logToConsole = settings.value("logToConsole", true).toBool(); + m_settings.logFilePath = settings.value("logFilePath", m_settings.logFilePath).toString(); + m_settings.maxFileSizeMB = settings.value("maxFileSizeMB", 10).toInt(); + m_settings.maxFileCount = settings.value("maxFileCount", 5).toInt(); + m_settings.showTimestamp = settings.value("showTimestamp", true).toBool(); + m_settings.showModule = settings.value("showModule", true).toBool(); + m_settings.showMethod = settings.value("showMethod", true).toBool(); + m_maxEntries = settings.value("maxEntries", 10000).toInt(); + settings.endGroup(); +} + +void LogManager::setLogToFileEnabled(bool enabled) +{ + QMutexLocker locker(&m_mutex); + m_settings.logToFile = enabled; + + if (enabled && !m_logFile.isOpen()) { + if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { + emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString())); + } + } else if (!enabled && m_logFile.isOpen()) { + m_logFile.close(); + } +} + +void LogManager::setLogToConsoleEnabled(bool enabled) +{ + QMutexLocker locker(&m_mutex); + m_settings.logToConsole = enabled; +} + +void LogManager::setLogFilePath(const QString& path) +{ + QMutexLocker locker(&m_mutex); + + if (m_logFile.isOpen()) { + m_logFile.close(); + } + + m_settings.logFilePath = path; + + if (m_settings.logToFile) { + m_logFile.setFileName(path); + if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { + emit fileError(QString("Не удалось открыть файл логов: %1").arg(m_logFile.errorString())); + } + } +} + +void LogManager::setLevelColors(const QMap& colors) +{ + QMutexLocker locker(&m_mutex); + m_settings.colors = colors; +} + +void LogManager::setLevelColor(LogLevel level, const QColor& color) +{ + QMutexLocker locker(&m_mutex); + m_settings.colors[level] = color; +} + +void LogManager::setFormat(bool showTimestamp, bool showModule, bool showMethod) +{ + QMutexLocker locker(&m_mutex); + m_settings.showTimestamp = showTimestamp; + m_settings.showModule = showModule; + m_settings.showMethod = showMethod; +} + +void LogManager::writeToFile(const LogEntry& entry) +{ + if (!m_logFile.isOpen()) { + return; + } + + QString formatted = formatForFile(entry); + m_logStream << formatted << "\n"; + m_logStream.flush(); + + // Проверяем размер файла + if (checkFileSize()) { + rotateLogFile(); + } +} + +void LogManager::rotateLogFile() +{ + if (!m_logFile.isOpen()) { + return; + } + + m_logFile.close(); + + QString basePath = m_settings.logFilePath; + QFileInfo fi(basePath); + QString baseName = fi.baseName(); + QString suffix = fi.suffix(); + QString dir = fi.path(); + + // Удаляем самый старый файл + QString oldestFile = QString("%1/%2.%3.%4") + .arg(dir) + .arg(baseName) + .arg(m_settings.maxFileCount) + .arg(suffix); + + if (QFile::exists(oldestFile)) { + QFile::remove(oldestFile); + } + + // Переименовываем остальные файлы + for (int i = m_settings.maxFileCount - 1; i >= 1; i--) { + QString oldFile = QString("%1/%2.%3.%4") + .arg(dir) + .arg(baseName) + .arg(i) + .arg(suffix); + QString newFile = QString("%1/%2.%3.%4") + .arg(dir) + .arg(baseName) + .arg(i + 1) + .arg(suffix); + + if (QFile::exists(oldFile)) { + QFile::rename(oldFile, newFile); + } + } + + // Переименовываем текущий файл + QString firstBackup = QString("%1/%2.%3.%4") + .arg(dir) + .arg(baseName) + .arg(1) + .arg(suffix); + + QFile::rename(basePath, firstBackup); + + // Открываем новый файл + if (!m_logFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + emit fileError(QString("Не удалось открыть файл логов после ротации: %1") + .arg(m_logFile.errorString())); + } +} + +bool LogManager::checkFileSize() +{ + if (!m_logFile.isOpen()) { + return false; + } + + qint64 size = m_logFile.size(); + qint64 maxSize = m_settings.maxFileSizeMB * 1024 * 1024; + + return size > maxSize; +} + +QString LogManager::formatEntry(const LogEntry& entry) const +{ + QStringList parts; + + if (m_settings.showTimestamp) { + parts.append(entry.timestamp.toString("hh:mm:ss")); + } + + parts.append(LogEntry::levelToString(entry.level)); + + if (m_settings.showModule && !entry.module.isEmpty()) { + parts.append(entry.module); + } + + if (m_settings.showMethod && !entry.method.isEmpty()) { + parts.append(entry.method); + } + + parts.append(entry.message); + + return parts.join(" | "); +} + +QString LogManager::formatForConsole(const LogEntry& entry) const +{ + QString formatted = formatEntry(entry); + + // Добавляем цвет для консоли (если поддерживается) + if (m_settings.colors.contains(entry.level)) { + // Для консоли цвет добавляется через escape-последовательности + QColor color = m_settings.colors[entry.level]; + // Это упрощенный вариант, можно расширить для реальной консоли + return formatted; + } + + return formatted; +} + +QString LogManager::formatForFile(const LogEntry& entry) const +{ + // Для файла используем CSV формат + return entry.toCsv(); +} diff --git a/logmanager.h b/logmanager.h new file mode 100644 index 0000000..29cc0b7 --- /dev/null +++ b/logmanager.h @@ -0,0 +1,394 @@ +#ifndef LOGMANAGER_H +#define LOGMANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +// ============================================================================ +// ПЕРЕЧИСЛЕНИЯ И СТРУКТУРЫ +// ============================================================================ + +/** + * @brief Уровни логирования + */ +enum class LogLevel { + Info = 0, // Информационные сообщения + Warning = 1, // Предупреждения + Error = 2, // Ошибки + Debug = 3, // Отладочная информация + Critical = 4 // Критические ошибки +}; + +/** + * @brief Настройки логирования + */ +struct LogSettings { + bool logToFile = true; // Сохранять в файл + bool logToConsole = true; // Выводить в консоль + bool logToDatabase = false; // Сохранять в БД + QString logFilePath = ""; // Путь к файлу логов + int maxFileSizeMB = 10; // Максимальный размер файла (МБ) + int maxFileCount = 5; // Максимальное количество файлов + bool showTimestamp = true; // Показывать время + bool showModule = true; // Показывать модуль + bool showMethod = true; // Показывать метод + QMap colors; // Цвета для уровней +}; + +/** + * @brief Запись лога + */ +struct LogEntry { + QDateTime timestamp; // Время записи + LogLevel level; // Уровень + QString module; // Модуль/компонент + QString method; // Метод/функция + QString message; // Сообщение + qint64 threadId = 0; // ID потока + QString sourceFile = ""; // Исходный файл + int sourceLine = 0; // Строка в файле + + // Методы для форматирования + QString toString() const; + QString toHtml() const; + QString toCsv() const; + QString toJson() const; + + // Статические методы + static QString levelToString(LogLevel level); + static QColor levelToColor(LogLevel level); + static QString formatDateTime(const QDateTime& dt); +}; + +// ============================================================================ +// КЛАСС LOGMANAGER +// ============================================================================ + +/** + * @brief Менеджер логирования приложения + */ +class LogManager : public QObject +{ + Q_OBJECT + +public: + // ======================================================================== + // СИНГЛТОН + // ======================================================================== + + /** + * @brief Получить экземпляр менеджера (синглтон) + */ + static LogManager* instance(); + + /** + * @brief Инициализировать менеджер + * @param settings Настройки логирования + */ + static void initialize(const LogSettings& settings = LogSettings()); + + /** + * @brief Очистить менеджер (освободить ресурсы) + */ + static void cleanup(); + + // ======================================================================== + // ОСНОВНОЙ ИНТЕРФЕЙС + // ======================================================================== + + /** + * @brief Добавить запись в лог + * @param level Уровень логирования + * @param module Модуль/компонент + * @param method Метод/функция + * @param message Сообщение + */ + void log(LogLevel level, const QString& module, + const QString& method, const QString& message); + + /** + * @brief Добавить запись в лог с информацией об исходном коде + */ + void log(LogLevel level, const QString& module, + const QString& method, const QString& message, + const QString& sourceFile, int sourceLine); + + // Быстрые методы для разных уровней + void info(const QString& module, const QString& method, + const QString& message); + void warning(const QString& module, const QString& method, + const QString& message); + void error(const QString& module, const QString& method, + const QString& message); + void debug(const QString& module, const QString& method, + const QString& message); + void critical(const QString& module, const QString& method, + const QString& message); + + // ======================================================================== + // ФИЛЬТРАЦИЯ И ПОИСК + // ======================================================================== + + /** + * @brief Получить все записи + */ + QList allEntries() const; + + /** + * @brief Получить записи за указанный период + */ + QList entriesByTime(const QDateTime& from, + const QDateTime& to) const; + + /** + * @brief Получить записи по уровню + */ + QList entriesByLevel(LogLevel level) const; + + /** + * @brief Получить записи по модулю + */ + QList entriesByModule(const QString& module) const; + + /** + * @brief Поиск по тексту сообщения + */ + QList search(const QString& text, bool caseSensitive = false) const; + + /** + * @brief Применить фильтр + */ + QList filter(const QList& levels, + const QString& moduleFilter = "", + const QString& methodFilter = "") const; + + // ======================================================================== + // УПРАВЛЕНИЕ + // ======================================================================== + + /** + * @brief Очистить все записи + */ + void clear(); + + /** + * @brief Сохранить логи в файл + */ + bool saveToFile(const QString& filePath, bool append = false); + + /** + * @brief Загрузить логи из файла + */ + bool loadFromFile(const QString& filePath); + + /** + * @brief Экспортировать в формат + */ + bool exportToFormat(const QString& filePath, const QString& format = "txt"); + + /** + * @brief Получить статистику + */ + QMap statistics() const; + + /** + * @brief Получить количество записей + */ + int count() const; + + /** + * @brief Получить максимальное количество записей в памяти + */ + int maxEntries() const; + + /** + * @brief Установить максимальное количество записей в памяти + */ + void setMaxEntries(int max); + + // ======================================================================== + // НАСТРОЙКИ + // ======================================================================== + + /** + * @brief Получить текущие настройки + */ + LogSettings settings() const; + + /** + * @brief Обновить настройки + */ + void updateSettings(const LogSettings& newSettings); + + /** + * @brief Сохранить настройки в конфиг + */ + void saveSettings(QSettings& settings); + + /** + * @brief Загрузить настройки из конфига + */ + void loadSettings(QSettings& settings); + + // ======================================================================== + // УТИЛИТЫ + // ======================================================================== + + /** + * @brief Включить/выключить логирование в файл + */ + void setLogToFileEnabled(bool enabled); + + /** + * @brief Включить/выключить логирование в консоль + */ + void setLogToConsoleEnabled(bool enabled); + + /** + * @brief Установить путь к файлу логов + */ + void setLogFilePath(const QString& path); + + /** + * @brief Установить цвета для уровней + */ + void setLevelColors(const QMap& colors); + + /** + * @brief Установить цвет для уровня + */ + void setLevelColor(LogLevel level, const QColor& color); + + /** + * @brief Установить формат вывода + */ + void setFormat(bool showTimestamp, bool showModule, bool showMethod); + +signals: + // ======================================================================== + // СИГНАЛЫ + // ======================================================================== + + /** + * @brief Добавлена новая запись в лог + */ + void entryAdded(const LogEntry& entry); + + /** + * @brief Логи очищены + */ + void logCleared(); + + /** + * @brief Настройки изменены + */ + void settingsChanged(); + + /** + * @brief Ошибка при записи в файл + */ + void fileError(const QString& error); + + // Сигналы для конкретных уровней (для удобства) + void infoAdded(const LogEntry& entry); + void warningAdded(const LogEntry& entry); + void errorAdded(const LogEntry& entry); + void debugAdded(const LogEntry& entry); + void criticalAdded(const LogEntry& entry); + +private: + // ======================================================================== + // ПРИВАТНЫЙ КОНСТРУКТОР (синглтон) + // ======================================================================== + explicit LogManager(QObject* parent = nullptr); + ~LogManager(); + + // ======================================================================== + // ПРИВАТНЫЕ МЕТОДЫ + // ======================================================================== + + /** + * @brief Записать в файл + */ + void writeToFile(const LogEntry& entry); + + /** + * @brief Ротация лог-файлов + */ + void rotateLogFile(); + + /** + * @brief Проверить размер файла + */ + bool checkFileSize(); + + /** + * @brief Форматирование записи для вывода + */ + QString formatEntry(const LogEntry& entry) const; + + /** + * @brief Форматирование для консоли + */ + QString formatForConsole(const LogEntry& entry) const; + + /** + * @brief Форматирование для файла + */ + QString formatForFile(const LogEntry& entry) const; + + // ======================================================================== + // ЧЛЕНЫ КЛАССА + // ======================================================================== + + static LogManager* m_instance; // Экземпляр синглтона + QList m_entries; // Список записей + LogSettings m_settings; // Настройки + mutable QMutex m_mutex; // Мьютекс для потокобезопасности + QFile m_logFile; // Файл логов + QTextStream m_logStream; // Поток для записи в файл + int m_maxEntries = 10000; // Максимальное количество записей + + // Предварительное объявление для Q_DISABLE_COPY + LogManager(const LogManager&) = delete; + LogManager& operator=(const LogManager&) = delete; +}; + +// ============================================================================ +// МАКРОСЫ ДЛЯ УДОБСТВА +// ============================================================================ + +// Базовый макрос +#define LOG(level, module, method, message) \ +LogManager::instance()->log(level, module, method, message, __FILE__, __LINE__) + +// Макросы для уровней +#define LOG_INFO(module, method, message) \ + LOG(LogLevel::Info, module, method, message) + +#define LOG_WARNING(module, method, message) \ + LOG(LogLevel::Warning, module, method, message) + +#define LOG_ERROR(module, method, message) \ + LOG(LogLevel::Error, module, method, message) + +#define LOG_DEBUG(module, method, message) \ + LOG(LogLevel::Debug, module, method, message) + +#define LOG_CRITICAL(module, method, message) \ + LOG(LogLevel::Critical, module, method, message) + +// Макрос для проверки на отладку +#ifdef QT_DEBUG +#define LOG_DEBUG_ONLY(module, method, message) LOG_DEBUG(module, method, message) +#else +#define LOG_DEBUG_ONLY(module, method, message) Q_UNUSED(module); Q_UNUSED(method); Q_UNUSED(message) +#endif + +#endif // LOGMANAGER_H diff --git a/mediafilemanager.cpp b/mediafilemanager.cpp new file mode 100644 index 0000000..eeaa96e --- /dev/null +++ b/mediafilemanager.cpp @@ -0,0 +1,101 @@ +#include "MediaFileManager.h" +#include + +MediaFileManager::MediaFileManager() +{ + // Конструктор может быть использован для инициализации +} + +bool MediaFileManager::addFile(const QString& name, const QString& filePath) +{ + // Проверка на пустые значения + if (name.isEmpty() || filePath.isEmpty()) { + qWarning() << "Имя файла или путь не могут быть пустыми"; + return false; + } + + // Проверка на уникальность имени + if (contains(name)) { + qWarning() << "Файл с именем" << name << "уже существует"; + return false; + } + + // Добавление нового файла + mediaFiles.append(MediaFile(name, filePath)); + qDebug() << "Файл" << name << "добавлен с путем:" << filePath; + return true; +} + +bool MediaFileManager::removeFile(const QString& name) +{ + int index = findFileIndex(name); + + if (index == -1) { + qWarning() << "Файл с именем" << name << "не найден"; + return false; + } + + mediaFiles.remove(index); + qDebug() << "Файл" << name << "удален"; + return true; +} + +bool MediaFileManager::updateFile(const QString& oldName, const QString& newName, const QString& newFilePath) +{ + int index = findFileIndex(oldName); + + if (index == -1) { + qWarning() << "Файл с именем" << oldName << "не найден"; + return false; + } + + // Проверяем, не используется ли новое имя другим файлом + if (oldName != newName && contains(newName)) { + qWarning() << "Файл с именем" << newName << "уже существует"; + return false; + } + + // Обновляем информацию о файле + mediaFiles[index].name = newName; + mediaFiles[index].filePath = newFilePath; + + qDebug() << "Файл обновлен:" << oldName << "->" << newName << "путь:" << newFilePath; + return true; +} + +QString MediaFileManager::getFilePathByName(const QString& name) const +{ + int index = findFileIndex(name); + + if (index == -1) { + qWarning() << "Файл с именем" << name << "не найден"; + return QString(); + } + + return mediaFiles[index].filePath; +} + +int MediaFileManager::getFileCount() const +{ + return mediaFiles.size(); +} + +QVector MediaFileManager::getAllFiles() const +{ + return mediaFiles; +} + +bool MediaFileManager::contains(const QString& name) const +{ + return findFileIndex(name) != -1; +} + +int MediaFileManager::findFileIndex(const QString& name) const +{ + for (int i = 0; i < mediaFiles.size(); ++i) { + if (mediaFiles[i].name == name) { + return i; + } + } + return -1; +} diff --git a/mediafilemanager.h b/mediafilemanager.h new file mode 100644 index 0000000..0283eea --- /dev/null +++ b/mediafilemanager.h @@ -0,0 +1,50 @@ +#ifndef MEDIAFILEMANAGER_H +#define MEDIAFILEMANAGER_H + +#include +#include + +// Структура для хранения информации о медиафайле +struct MediaFile { + QString name; // Имя файла + QString filePath; // Полный путь к файлу + + MediaFile() = default; + MediaFile(const QString& name, const QString& filePath) + : name(name), filePath(filePath) {} +}; + +class MediaFileManager +{ +public: + MediaFileManager(); + + // Добавление файла + bool addFile(const QString& name, const QString& filePath); + + // Удаление файла по имени + bool removeFile(const QString& name); + + // Изменение информации о файле + bool updateFile(const QString& oldName, const QString& newName, const QString& newFilePath); + + // Получение пути к файлу по имени + QString getFilePathByName(const QString& name) const; + + // Получение количества файлов + int getFileCount() const; + + // Получение всех файлов (для отладки или отображения) + QVector getAllFiles() const; + + // Проверка существования файла по имени + bool contains(const QString& name) const; + +private: + QVector mediaFiles; + + // Поиск индекса файла по имени + int findFileIndex(const QString& name) const; +}; + +#endif // MEDIAFILEMANAGER_H diff --git a/object_script.TTW_Bot.Debug b/object_script.TTW_Bot.Debug index 5baffde..a2dc543 100644 --- a/object_script.TTW_Bot.Debug +++ b/object_script.TTW_Bot.Debug @@ -1,12 +1,18 @@ +debug/commandprocessor.o debug/emoteprovider.o debug/fcolorsetting.o debug/fcreatechat.o debug/fcreatenotify.o debug/ffontsetting.o +debug/filemanager.o debug/fsettingsws.o debug/fsinglegrid.o +debug/logmanager.o debug/main.o +debug/mediafilemanager.o debug/neuralnetworkmanager.o +debug/randommanager.o +debug/randomresponses.o debug/soundmanager.o debug/tauth.o debug/ttw_api.o @@ -20,6 +26,7 @@ debug/userwidget.o debug/webserverchat.o debug/webservernotify.o debug/websocketclient.o +debug/moc_commandprocessor.o debug/moc_emoteprovider.o debug/moc_fcolorsetting.o debug/moc_fcreatechat.o @@ -27,7 +34,10 @@ debug/moc_fcreatenotify.o debug/moc_ffontsetting.o debug/moc_fsettingsws.o debug/moc_fsinglegrid.o +debug/moc_logmanager.o debug/moc_neuralnetworkmanager.o +debug/moc_randommanager.o +debug/moc_randomresponses.o debug/moc_soundmanager.o debug/moc_tauth.o debug/moc_ttw_api.o diff --git a/object_script.TTW_Bot.Release b/object_script.TTW_Bot.Release index 2580578..140adb3 100644 --- a/object_script.TTW_Bot.Release +++ b/object_script.TTW_Bot.Release @@ -1,12 +1,18 @@ +release/commandprocessor.o release/emoteprovider.o release/fcolorsetting.o release/fcreatechat.o release/fcreatenotify.o release/ffontsetting.o +release/filemanager.o release/fsettingsws.o release/fsinglegrid.o +release/logmanager.o release/main.o +release/mediafilemanager.o release/neuralnetworkmanager.o +release/randommanager.o +release/randomresponses.o release/soundmanager.o release/tauth.o release/ttw_api.o @@ -20,6 +26,7 @@ release/userwidget.o release/webserverchat.o release/webservernotify.o release/websocketclient.o +release/moc_commandprocessor.o release/moc_emoteprovider.o release/moc_fcolorsetting.o release/moc_fcreatechat.o @@ -27,7 +34,10 @@ release/moc_fcreatenotify.o release/moc_ffontsetting.o release/moc_fsettingsws.o release/moc_fsinglegrid.o +release/moc_logmanager.o release/moc_neuralnetworkmanager.o +release/moc_randommanager.o +release/moc_randomresponses.o release/moc_soundmanager.o release/moc_tauth.o release/moc_ttw_api.o diff --git a/randommanager.cpp b/randommanager.cpp new file mode 100644 index 0000000..a029b1f --- /dev/null +++ b/randommanager.cpp @@ -0,0 +1,294 @@ +#include "randommanager.h" +#include +#include + +RandomManager::RandomManager(QObject *parent) + : QObject(parent) + , m_database(nullptr) +{ +} + +RandomManager::~RandomManager() +{ + // Сохраняем данные при уничтожении + if (m_database) { + saveToDatabase(); + } +} + +bool RandomManager::initialize(uDataBase *database) +{ + if (!database || !database->isConnected()) { + qWarning() << "RandomManager: База данных не подключена"; + return false; + } + + m_database = database; + return loadFromDatabase(); +} + +bool RandomManager::addRange(const QString &name, int startValue, int endValue) +{ + // Проверка входных данных + if (name.isEmpty()) { + qWarning() << "RandomManager: Нельзя добавить диапазон с пустым именем"; + return false; + } + + if (!isValidRange(startValue, endValue)) { + qWarning() << "RandomManager: Некорректный диапазон:" << startValue << "-" << endValue; + return false; + } + + // Проверка на дубликат + if (contains(name)) { + qWarning() << "RandomManager: Диапазон с именем" << name << "уже существует"; + return false; + } + + // Добавляем в список + RandomStruct newRange(name, startValue, endValue); + m_ranges.append(newRange); + + // Сохраняем в БД + if (!saveToDatabase()) { + m_ranges.removeLast(); // Откатываем если не удалось сохранить + return false; + } + + emit rangeAdded(name, startValue, endValue); + emit dataChanged(); + + qDebug() << "RandomManager: Добавлен диапазон" << name << startValue << "-" << endValue; + return true; +} + +bool RandomManager::removeRange(const QString &name) +{ + int index = findIndex(name); + if (index == -1) { + qWarning() << "RandomManager: Диапазон" << name << "не найден"; + return false; + } + + // Сохраняем данные для сигнала + RandomStruct removedRange = m_ranges.at(index); + + // Удаляем из списка + m_ranges.removeAt(index); + + // Сохраняем в БД + if (!saveToDatabase()) { + // Откатываем изменения + m_ranges.insert(index, removedRange); + return false; + } + + emit rangeRemoved(name); + emit dataChanged(); + + qDebug() << "RandomManager: Удален диапазон" << name; + return true; +} + +bool RandomManager::updateRange(const QString &oldName, const QString &newName, + int startValue, int endValue) +{ + int index = findIndex(oldName); + if (index == -1) { + qWarning() << "RandomManager: Диапазон" << oldName << "не найден"; + return false; + } + + if (!isValidRange(startValue, endValue)) { + qWarning() << "RandomManager: Некорректный диапазон:" << startValue << "-" << endValue; + return false; + } + + // Проверяем, не занято ли новое имя другим диапазоном (если имя изменилось) + if (oldName != newName && contains(newName)) { + qWarning() << "RandomManager: Имя" << newName << "уже занято"; + return false; + } + + // Сохраняем старые данные для отката + RandomStruct oldRange = m_ranges.at(index); + + // Обновляем + m_ranges[index].name = newName; + m_ranges[index].startValue = startValue; + m_ranges[index].endValue = endValue; + + // Сохраняем в БД + if (!saveToDatabase()) { + // Откатываем изменения + m_ranges[index] = oldRange; + return false; + } + + emit rangeUpdated(oldName, newName); + emit dataChanged(); + + qDebug() << "RandomManager: Обновлен диапазон" << oldName << "->" << newName + << startValue << "-" << endValue; + return true; +} + +bool RandomManager::contains(const QString &name) const +{ + return findIndex(name) != -1; +} + +int RandomManager::getRandomValue(const QString &name) const +{ + int index = findIndex(name); + if (index == -1) { + qWarning() << "RandomManager: Диапазон" << name << "не найден"; + return 0; + } + + const RandomStruct &range = m_ranges.at(index); + if (range.startValue == range.endValue) { + return range.startValue; + } + + int min = qMin(range.startValue, range.endValue); + int max = qMax(range.startValue, range.endValue); + + return QRandomGenerator::global()->bounded(min, max + 1); +} + +QPair RandomManager::getRange(const QString &name) const +{ + int index = findIndex(name); + if (index == -1) { + return qMakePair(0, 0); + } + + const RandomStruct &range = m_ranges.at(index); + return qMakePair(range.startValue, range.endValue); +} + +bool RandomManager::saveToDatabase() +{ + if (!m_database) { + qWarning() << "RandomManager: База данных не установлена"; + return false; + } + + // Создаем временную таблицу для сохранения + QTableWidget tempTable; + + // Настраиваем таблицу + QStringList headers; + headers << "Имя" << "От" << "До"; + tempTable.setColumnCount(headers.size()); + tempTable.setHorizontalHeaderLabels(headers); + + // Заполняем таблицу данными + tempTable.setRowCount(m_ranges.size()); + + for (int i = 0; i < m_ranges.size(); ++i) { + const RandomStruct &range = m_ranges.at(i); + + // Имя + QTableWidgetItem *nameItem = new QTableWidgetItem(range.name); + tempTable.setItem(i, 0, nameItem); + + // Начальное значение + QTableWidgetItem *startItem = new QTableWidgetItem(QString::number(range.startValue)); + startItem->setTextAlignment(Qt::AlignCenter); + tempTable.setItem(i, 1, startItem); + + // Конечное значение + QTableWidgetItem *endItem = new QTableWidgetItem(QString::number(range.endValue)); + endItem->setTextAlignment(Qt::AlignCenter); + tempTable.setItem(i, 2, endItem); + } + + // Сохраняем в БД + // Используем метод SaveTableWidget, который сохраняет данные в соответствующую таблицу + m_database->SaveTableWidget(&tempTable); + + qDebug() << "RandomManager: Сохранено" << m_ranges.size() << "диапазонов в БД"; + return true; +} + +bool RandomManager::loadFromDatabase() +{ + if (!m_database) { + qWarning() << "RandomManager: База данных не установлена"; + return false; + } + + // Создаем временную таблицу для загрузки + QTableWidget tempTable; + + // Настраиваем таблицу + QStringList headers; + headers << "Имя" << "От" << "До"; + tempTable.setColumnCount(headers.size()); + tempTable.setHorizontalHeaderLabels(headers); + + // Загружаем данные из БД + m_database->LoadTableWidget(&tempTable); + + // Очищаем текущий список + m_ranges.clear(); + + // Заполняем список из таблицы + for (int row = 0; row < tempTable.rowCount(); ++row) { + QTableWidgetItem *nameItem = tempTable.item(row, 0); + QTableWidgetItem *startItem = tempTable.item(row, 1); + QTableWidgetItem *endItem = tempTable.item(row, 2); + + if (!nameItem || !startItem || !endItem) { + qWarning() << "RandomManager: Пропущена строка" << row << "из-за отсутствия данных"; + continue; + } + + QString name = nameItem->text().trimmed(); + if (name.isEmpty()) { + qWarning() << "RandomManager: Пропущена строка" << row << "с пустым именем"; + continue; + } + + bool okStart, okEnd; + int startValue = startItem->text().toInt(&okStart); + int endValue = endItem->text().toInt(&okEnd); + + if (!okStart || !okEnd) { + qWarning() << "RandomManager: Некорректные числовые значения в строке" << row; + continue; + } + + if (!isValidRange(startValue, endValue)) { + qWarning() << "RandomManager: Некорректный диапазон в строке" << row + << startValue << "-" << endValue; + continue; + } + + m_ranges.append(RandomStruct(name, startValue, endValue)); + } + + qDebug() << "RandomManager: Загружено" << m_ranges.size() << "диапазонов из БД"; + emit dataChanged(); + return true; +} + +bool RandomManager::isValidRange(int start, int end) const +{ + // Допускаются любые целые числа, даже если start > end + // Мы будем использовать qMin и qMax при генерации случайного числа + return true; +} + +int RandomManager::findIndex(const QString &name) const +{ + for (int i = 0; i < m_ranges.size(); ++i) { + if (m_ranges.at(i).name == name) { + return i; + } + } + return -1; +} diff --git a/randommanager.h b/randommanager.h new file mode 100644 index 0000000..9a1fd0c --- /dev/null +++ b/randommanager.h @@ -0,0 +1,66 @@ +#ifndef RANDOMMANAGER_H +#define RANDOMMANAGER_H + +#include +#include +#include +#include +#include "udatabase.h" + +class RandomManager : public QObject +{ + Q_OBJECT + + + +public: + struct RandomStruct { + QString name; + int startValue; + int endValue; + + RandomStruct() : startValue(0), endValue(0) {} + RandomStruct(const QString &name, int start, int end) + : name(name), startValue(start), endValue(end) {} + + bool operator==(const QString &otherName) const { return name == otherName; } + }; + + explicit RandomManager(QObject *parent = nullptr); + ~RandomManager(); + + // Основные методы + bool initialize(uDataBase *database); + + // Работа с диапазонами + bool addRange(const QString &name, int startValue, int endValue); + bool removeRange(const QString &name); + bool updateRange(const QString &oldName, const QString &newName, int startValue, int endValue); + + // Получение данных + QList getAllRanges() const { return m_ranges; } + bool contains(const QString &name) const; + int getRandomValue(const QString &name) const; + QPair getRange(const QString &name) const; + + // Сохранение/загрузка + bool saveToDatabase(); + bool loadFromDatabase(); + + // Валидация + bool isValidRange(int start, int end) const; + +signals: + void dataChanged(); + void rangeAdded(const QString &name, int start, int end); + void rangeRemoved(const QString &name); + void rangeUpdated(const QString &oldName, const QString &newName); + +private: + uDataBase *m_database; + QList m_ranges; + + int findIndex(const QString &name) const; +}; + +#endif // RANDOMMANAGER_H diff --git a/randomresponses.cpp b/randomresponses.cpp new file mode 100644 index 0000000..8dd72cd --- /dev/null +++ b/randomresponses.cpp @@ -0,0 +1,103 @@ +#include "RandomResponses.h" +#include + +RandomResponses::RandomResponses(QObject *parent) + : QObject(parent) + , m_randomGenerator(QRandomGenerator::global()->generate()) +{ +} + +QString RandomResponses::getResponse(const QString &groupName) +{ + // Проверяем существование группы + if (!m_groups.contains(groupName)) { + qWarning() << "Группа" << groupName << "не найдена"; + return QString(); + } + + const ResponseGroup &group = m_groups[groupName]; + + // Проверяем наличие ответов в группе + if (group.responses.isEmpty()) { + qWarning() << "Группа" << groupName << "не содержит ответов"; + return QString(); + } + + // Генерируем случайный индекс + int randomIndex = m_randomGenerator.bounded(group.responses.size()); + + // Возвращаем случайный ответ + return group.responses.at(randomIndex); +} + +bool RandomResponses::addResponse(const QString &groupName, const QString &response) +{ + if (response.isEmpty()) { + qWarning() << "Пустой ответ не может быть добавлен"; + return false; + } + + // Если группа существует, добавляем ответ в существующую + if (m_groups.contains(groupName)) { + m_groups[groupName].responses.append(response); + } + // Иначе создаем новую группу + else { + ResponseGroup newGroup; + newGroup.name = groupName; + newGroup.responses.append(response); + m_groups.insert(groupName, newGroup); + } + + return true; +} + +bool RandomResponses::removeResponse(const QString &groupName, const QString &response) +{ + // Проверяем существование группы + if (!m_groups.contains(groupName)) { + qWarning() << "Группа" << groupName << "не найдена"; + return false; + } + + ResponseGroup &group = m_groups[groupName]; + + // Удаляем все вхождения ответа + int removedCount = group.responses.removeAll(response); + + // Если группа стала пустой, удаляем ее + if (group.responses.isEmpty()) { + m_groups.remove(groupName); + } + + return removedCount > 0; +} + +bool RandomResponses::removeGroup(const QString &groupName) +{ + return m_groups.remove(groupName) > 0; +} + +QStringList RandomResponses::getGroupNames() const +{ + return m_groups.keys(); +} + +QStringList RandomResponses::getGroupResponses(const QString &groupName) const +{ + if (m_groups.contains(groupName)) { + return m_groups[groupName].responses; + } + + return QStringList(); +} + +bool RandomResponses::hasGroup(const QString &groupName) const +{ + return m_groups.contains(groupName); +} + +void RandomResponses::clear() +{ + m_groups.clear(); +} diff --git a/randomresponses.h b/randomresponses.h new file mode 100644 index 0000000..e1f532b --- /dev/null +++ b/randomresponses.h @@ -0,0 +1,54 @@ +#ifndef RANDOMRESPONSES_H +#define RANDOMRESPONSES_H + +#include +#include +#include +#include +#include +#include +#include + +// Структура для хранения группы ответов +struct ResponseGroup { + QString name; // Имя группы + QStringList responses; // Список возможных ответов +}; + +class RandomResponses : public QObject +{ + Q_OBJECT + +public: + explicit RandomResponses(QObject *parent = nullptr); + + // Получить случайный ответ из группы + QString getResponse(const QString &groupName); + + // Добавить группу или дополнить существующую + bool addResponse(const QString &groupName, const QString &response); + + // Удалить конкретный ответ из группы + bool removeResponse(const QString &groupName, const QString &response); + + // Удалить всю группу + bool removeGroup(const QString &groupName); + + // Получить список всех групп + QStringList getGroupNames() const; + + // Получить все ответы группы + QStringList getGroupResponses(const QString &groupName) const; + + // Проверить существование группы + bool hasGroup(const QString &groupName) const; + + // Очистить все данные + void clear(); + +private: + QMap m_groups; // Хранение групп по имени + QRandomGenerator m_randomGenerator; // Генератор случайных чисел +}; + +#endif // RANDOMRESPONSES_H diff --git a/ttw_api.cpp b/ttw_api.cpp index 11c687e..71aff93 100644 --- a/ttw_api.cpp +++ b/ttw_api.cpp @@ -323,7 +323,7 @@ void TTwAPI::banUserTime(const QString &id, int timeMinutes) toLog(2, "TTwAPI::banUserTime", "Не удалось получить botId"); return; } - +toLog(2, "TTwAPI::баним", id); QJsonObject innerData; innerData["user_id"] = id; innerData["duration"] = timeMinutes * 60; @@ -503,7 +503,7 @@ void TTwAPI::toLog(int level, const QString &method, const QString &message) Q_UNUSED(level); Q_UNUSED(method); Q_UNUSED(message); - + qDebug() << message; // uGeneral.toLog("ttw_api", method, message, level); } diff --git a/udatabase.cpp b/udatabase.cpp index 0d30619..4fd2c78 100644 --- a/udatabase.cpp +++ b/udatabase.cpp @@ -1179,3 +1179,225 @@ bool uDataBase::updateChat(const QString &name, HttpServerChat *server, return true; } + + +bool uDataBase::createNotificationsTable() +{ + QSqlQuery query(m_db); + QString sql = + "CREATE TABLE IF NOT EXISTS notifications (" + " id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT NOT NULL," + " type TEXT NOT NULL," // "notification" + " port INTEGER NOT NULL," + " block_color TEXT," + " border_color TEXT," + " border_size INTEGER," + " transparency INTEGER," + " page_background_color TEXT," + " title_family TEXT," + " title_size INTEGER," + " title_color TEXT," + " content_family TEXT," + " content_size INTEGER," + " content_color TEXT," + " duration INTEGER," + " created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP" + ")"; + + if (!query.exec(sql)) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to create notifications table:" << m_lastError; + return false; + } + + // Создаем индекс для быстрого поиска по порту + sql = "CREATE INDEX IF NOT EXISTS idx_notifications_port ON notifications (port)"; + query.exec(sql); + + return true; +} + +bool uDataBase::saveNotification(const QString &name, HttpServer *server) +{ + if (!server) { + m_lastError = "Server is null"; + return false; + } + + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return false; + } + + // Создаем таблицу если не существует + if (!tableExists("notifications")) { + if (!createNotificationsTable()) { + return false; + } + } + + QSqlQuery query(m_db); + query.prepare( + "INSERT INTO notifications (" + " name, type, port, block_color, border_color, border_size," + " transparency, page_background_color, title_family, title_size," + " title_color, content_family, content_size, content_color, duration" + ") VALUES (" + " :name, :type, :port, :block_color, :border_color, :border_size," + " :transparency, :page_background_color, :title_family, :title_size," + " :title_color, :content_family, :content_size, :content_color, :duration" + ")" + ); + + // Используем реальные значения из сервера + query.bindValue(":name", name); + query.bindValue(":type", "notification"); + query.bindValue(":port", server->port()); + query.bindValue(":block_color", server->getBlockColor()); + query.bindValue(":border_color", server->getBorderColor()); + query.bindValue(":border_size", server->getBorderSize()); + query.bindValue(":transparency", server->getTransparency()); + query.bindValue(":page_background_color", server->getPageBackgroundColor()); + query.bindValue(":title_family", server->getTitleFamily()); + query.bindValue(":title_size", server->getTitleSize()); + query.bindValue(":title_color", server->getTitleColor()); + query.bindValue(":content_family", server->getContentFamily()); + query.bindValue(":content_size", server->getContentSize()); + query.bindValue(":content_color", server->getContentColor()); + query.bindValue(":duration", server->getDuration()); + + if (!query.exec()) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to save notification:" << m_lastError; + return false; + } + + return true; +} + +bool uDataBase::updateNotification(const QString &name, HttpServer *server, int oldPort) +{ + if (!server) { + m_lastError = "Server is null"; + return false; + } + + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return false; + } + + QSqlQuery query(m_db); + query.prepare( + "UPDATE notifications SET " + " name = :name, " + " port = :port, " + " block_color = :block_color, " + " border_color = :border_color, " + " border_size = :border_size, " + " transparency = :transparency, " + " page_background_color = :page_background_color, " + " title_family = :title_family, " + " title_size = :title_size, " + " title_color = :title_color, " + " content_family = :content_family, " + " content_size = :content_size, " + " content_color = :content_color, " + " duration = :duration " + "WHERE port = :old_port" + ); + + query.bindValue(":name", name); + query.bindValue(":port", server->port()); + query.bindValue(":block_color", server->getBlockColor()); + query.bindValue(":border_color", server->getBorderColor()); + query.bindValue(":border_size", server->getBorderSize()); + query.bindValue(":transparency", server->getTransparency()); + query.bindValue(":page_background_color", server->getPageBackgroundColor()); + query.bindValue(":title_family", server->getTitleFamily()); + query.bindValue(":title_size", server->getTitleSize()); + query.bindValue(":title_color", server->getTitleColor()); + query.bindValue(":content_family", server->getContentFamily()); + query.bindValue(":content_size", server->getContentSize()); + query.bindValue(":content_color", server->getContentColor()); + query.bindValue(":duration", server->getDuration()); + query.bindValue(":old_port", oldPort); + + if (!query.exec()) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to update notification:" << m_lastError; + return false; + } + + return true; +} + +QList uDataBase::loadAllNotifications() +{ + QList notifications; + + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return notifications; + } + + if (!tableExists("notifications")) { + // Если таблицы нет, ничего не загружаем + return notifications; + } + + QSqlQuery query(m_db); + QString sql = "SELECT * FROM notifications ORDER BY created_at"; + + if (!query.exec(sql)) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to load notifications:" << m_lastError; + return notifications; + } + + while (query.next()) { + NotificationSettings settings; + settings.id = query.value("id").toInt(); + settings.name = query.value("name").toString(); + settings.type = query.value("type").toString(); + settings.port = query.value("port").toInt(); + settings.blockColor = query.value("block_color").toString(); + settings.borderColor = query.value("border_color").toString(); + settings.borderSize = query.value("border_size").toInt(); + settings.transparency = query.value("transparency").toInt(); + settings.pageBackgroundColor = query.value("page_background_color").toString(); + settings.titleFamily = query.value("title_family").toString(); + settings.titleSize = query.value("title_size").toInt(); + settings.titleColor = query.value("title_color").toString(); + settings.contentFamily = query.value("content_family").toString(); + settings.contentSize = query.value("content_size").toInt(); + settings.contentColor = query.value("content_color").toString(); + settings.duration = query.value("duration").toInt(); + settings.createdAt = query.value("created_at").toDateTime(); + + notifications.append(settings); + } + + return notifications; +} + +bool uDataBase::deleteNotification(int port) +{ + if (!m_db.isOpen()) { + m_lastError = "Database is not open"; + return false; + } + + QSqlQuery query(m_db); + query.prepare("DELETE FROM notifications WHERE port = :port"); + query.bindValue(":port", port); + + if (!query.exec()) { + m_lastError = query.lastError().text(); + qWarning() << "Failed to delete notification:" << m_lastError; + return false; + } + + return true; +} diff --git a/udatabase.h b/udatabase.h index 1366a5c..15c6e72 100644 --- a/udatabase.h +++ b/udatabase.h @@ -13,6 +13,7 @@ #include #include "timerinfo.h" #include "webserverchat.h" +#include "webservernotify.h" struct ChatSettings { int id; @@ -35,6 +36,28 @@ struct ChatSettings { bool deleteByTime; }; +struct NotificationSettings { + int id; + QString name; + QString type; + int port; + QString blockColor; + QString borderColor; + int borderSize; + int transparency; + QString pageBackgroundColor; + QString titleFamily; + int titleSize; + QString titleColor; + QString contentFamily; + int contentSize; + QString contentColor; + int duration; + QDateTime createdAt; +}; + + + class uDataBase : public QObject { Q_OBJECT @@ -84,6 +107,12 @@ public: bool deleteChat(int port); QList loadAllChats(); + // Методы для уведомлений + bool createNotificationsTable(); + bool saveNotification(const QString &name, HttpServer *server); + bool updateNotification(const QString &name, HttpServer *server, int oldPort); + QList loadAllNotifications(); + bool deleteNotification(int port); // Проверка подключения к БД bool isConnected() const; diff --git a/ugeneral.cpp b/ugeneral.cpp index 12a852e..3ee19bf 100644 --- a/ugeneral.cpp +++ b/ugeneral.cpp @@ -1,5 +1,7 @@ #include "ugeneral.h" #include "fcreatenotify.h" +#include "filemanager.h" +#include "logmanager.h" #include "ui_ugeneral.h" #include #include @@ -25,6 +27,7 @@ #include #include #include "timerinfo.h" +#include "mediafilemanager.h" uGeneral::uGeneral(QWidget *parent) : QMainWindow(parent) @@ -34,36 +37,79 @@ uGeneral::uGeneral(QWidget *parent) , m_authDA(nullptr) , fLinkForm(nullptr) , m_isTwitchConnected(false) + , m_notificationServers() + , m_chatServers() , m_createNotifyDialog(nullptr) , m_createChatDialog(nullptr) - + , m_randomManager(nullptr) { // ============================================================================ // ИНИЦИАЛИЗАЦИЯ ИНТЕРФЕЙСА // ============================================================================ ui->setupUi(this); setWindowTitle("TTW Bot app"); - QString romPath = getUserDataPath(); - QString sysPath = getSystemPath(); + + LogSettings logSettings; + logSettings.logToFile = true; + logSettings.logToConsole = true; + //logSettings.maxEntries = 5000; + + // Цвета для разных уровней + logSettings.colors[LogLevel::Info] = Qt::darkGreen; + logSettings.colors[LogLevel::Warning] = Qt::darkYellow; + logSettings.colors[LogLevel::Error] = Qt::red; + logSettings.colors[LogLevel::Debug] = Qt::gray; + logSettings.colors[LogLevel::Critical] = Qt::darkRed; + + LogManager::initialize(logSettings); + + // Подключение сигналов к UI + LogManager* logger = LogManager::instance(); + connect(logger, &LogManager::entryAdded, + this, &uGeneral::onLogEntryAdded); + connect(logger, &LogManager::logCleared, + this, &uGeneral::onLogCleared); + connect(logger, &LogManager::errorAdded, + this, &uGeneral::onLogEntryAdded); + connect(logger, &LogManager::warningAdded, + this, &uGeneral::onLogEntryAdded); + connect(logger, &LogManager::infoAdded, + this, &uGeneral::onLogEntryAdded); + connect(logger, &LogManager::debugAdded, + this, &uGeneral::onLogEntryAdded); + connect(logger, &LogManager::criticalAdded, + this, &uGeneral::onLogEntryAdded); + + // Запуск приложения - используем новый логгер + LogManager::instance()->info("uGeneral", "constructor", "Главное окно создано"); + + // Инициализируем структуру папок + FileManager::instance().initializeFolderStructure(); + FileManager::instance().copyDefaultFiles(); + // Получаем пути через FileManager + QString sysPath = FileManager::instance().systemPath(); + + // Загружаем иконки tabIcons = { - QIcon(sysPath + "/ico/settings.png"), - QIcon(sysPath + "/ico/ai.png"), - QIcon(sysPath + "/ico/chat.png"), - QIcon(sysPath + "/ico/skill.png"), - QIcon(sysPath + "/ico/obs.png"), - QIcon(sysPath + "/ico/notify.png"), - QIcon(sysPath + "/ico/user.png"), - QIcon(sysPath + "/ico/auto.png"), - QIcon(sysPath + "/ico/log.png"), - - QIcon(sysPath + "/ico/add.png"), - QIcon(sysPath + "/ico/edit.png"), - QIcon(sysPath + "/ico/rm.png"), - QIcon(sysPath + "/ico/rmfolder.png"), - QIcon(sysPath + "/ico/open.png") + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "settings.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "ai.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "chat.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "skill.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "obs.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "notify.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "user.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "auto.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "log.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "add.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "edit.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "rm.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "rmfolder.png")), + QIcon(FileManager::instance().getFullPath(FileManager::Icons, "open.png")), }; + + for (int i = 0; i < ui->www->count() && i < tabIcons.size(); i++) { ui->www->setTabIcon(i, tabIcons[i]); } @@ -95,16 +141,16 @@ uGeneral::uGeneral(QWidget *parent) // ============================================================================ loadSettings(); - // ============================================================================ - // ИНИЦИАЛИЗАЦИЯ МЕНЕДЖЕРОВ - // ============================================================================ - initializeManagers(); - // ============================================================================ // НАСТРОЙКА TWITCH API И WEBSOCKET // ============================================================================ setupTwitchComponents(); + // ============================================================================ + // ИНИЦИАЛИЗАЦИЯ МЕНЕДЖЕРОВ + // ============================================================================ + initializeManagers(); + // ============================================================================ // НАСТРОЙКА ВИДЖЕТА ПОЛЬЗОВАТЕЛЕЙ // ============================================================================ @@ -129,9 +175,16 @@ uGeneral::uGeneral(QWidget *parent) ui->sgWebServers->horizontalHeader()->setStretchLastSection(true); // Инициализируем окна создания - m_createNotifyDialog = new FCreateNotify(this); + m_createNotifyDialog = new FCreateNotify(db, this); m_createChatDialog = new FCreateChat(db, this); + // Добавьте эту строку в конец метода: + initializeNotificationSounds(); + + if (db) { + m_randomManager = new RandomManager(this); + m_randomManager->initialize(db); + } } @@ -139,15 +192,6 @@ uGeneral::uGeneral(QWidget *parent) // ПРИВАТНЫЕ МЕТОДЫ ДЛЯ ИНИЦИАЛИЗАЦИИ // ============================================================================ -/** - * @brief Получить путь к системным файлам (exe, библиотеки) - * @return Путь к системным файлам - */ -QString uGeneral::getSystemPath() const -{ - // Путь к установочной директории - return QCoreApplication::applicationDirPath(); -} void uGeneral::setupButtonIcons() { @@ -187,52 +231,8 @@ void uGeneral::setupButtonIcons() { } } -/** - * @brief Получить путь к пользовательским данным (настройки, звуки, стили и т.д.) - * @return Путь к пользовательским данным в AppData - */ -QString uGeneral::getUserDataPath() const -{ - QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - // Создаем структуру папок для разных типов данных - QDir dir(path); - if (!dir.exists()) { - dir.mkpath("."); - } - // Создаем подпапки для разных типов данных - QStringList subdirs = { - "sounds", - "images", - "styles", - "voices", - "fonts", - "temp", - "backups", - "exports" - }; - - for (const QString &subdir : subdirs) { - if (!dir.exists(subdir)) { - dir.mkpath(subdir); - } - } - - return path; -} - -/** - * @brief Получить полный путь к пользовательскому файлу - * @param type Тип файла (sounds, images, styles, voices, fonts) - * @param fileName Имя файла - * @return Полный путь к файлу - */ -QString uGeneral::getUserFilePath(const QString &type, const QString &fileName) const -{ - QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - return QString("%1/%2/%3").arg(path).arg(type).arg(fileName); -} /** @@ -252,9 +252,9 @@ void uGeneral::initializeDatabase() db = new uDataBase(roamingPath, this); if (!db->isConnected()) { - toLog("uGeneral", "Create", "Ошибка подключения к БД", 2); + LogManager::instance()->error("uGeneral", "Create", "Ошибка подключения к БД"); } else { - toLog("uGeneral", "Create", "Успешное подключение к БД", 3); + LogManager::instance()->info("uGeneral", "Create", "Успешное подключение к БД"); } } @@ -406,8 +406,167 @@ void uGeneral::initializeManagers() // Менеджер пользователей m_userManager = new UserManager(this); + + // Менеджер случайных диапазонов + m_randomManager = new RandomManager(this); + if (db) { + m_randomManager->initialize(db); + m_randomManager->loadFromDatabase(); + } + + // Менеджер случайных ответов (групп) + m_randomResponses = new RandomResponses(this); + + // Менеджер нейросети (если есть) + nnManager = new NeuralNetworkManager(this); + // Настройка нейросети (пример) + // nnManager->setApiKey(NeuralNetworkManager::DeepSeek, "ваш_api_ключ"); + // nnManager->setModel(NeuralNetworkManager::DeepSeek, "deepseek-chat"); + + m_SoundFiles = new MediaFileManager(); + + // Менеджер команд + m_commandProcessor = new CommandProcessor(this); + + if (db) { + + // Загрузка команд из таблицы sgCommands в CommandProcessor + loadCommandsFromTableWidget(); + + // Загрузка случайных диапазонов из таблицы sgRandomInt в RandomManager + loadRandomRangesFromTableWidget(); + + // Загрузка групп случайных ответов из базы данных + loadRandomResponseGroupsFromDatabase(); + + // Загрузка звуков + loadSoundsFromDatabase(); + + + // Настройка контекста для обработчика команд + CommandProcessor::Context context; + context.userManager = m_userManager; + context.twitchAPI = twitchAPI; + context.soundManager = soundManager; + context.neuralManager = nnManager; + context.randomManager = m_randomManager; + context.randomResponses = m_randomResponses; + context.mediaFileManager = m_SoundFiles; + context.channel = ui->edtChannel->text(); + context.notifyVolume = ui->tbNotifyVolume->value(); + + m_commandProcessor->setContext(context); + } } +void uGeneral::loadCommandsFromTableWidget() +{ + if (!db || !m_commandProcessor) return; + + QTableWidget* table = ui->sgCommands; + if (!table) return; + + for (int row = 0; row < table->rowCount(); ++row) { + QTableWidgetItem *commandItem = table->item(row, 0); + QTableWidgetItem *responseItem = table->item(row, 1); + + if (commandItem && responseItem) { + QString command = commandItem->text().trimmed(); + QString response = responseItem->text(); + + if (!command.isEmpty() && !response.isEmpty()) { + m_commandProcessor->addCommand(command, response); + } + } + } +} + +/** + * @brief Загрузка звуков + */ +void uGeneral::loadSoundsFromDatabase() +{ + if (!db || !m_SoundFiles) return; + + QTableWidget* table = ui->widget->tableWidget(); + if (!table) return; + for (int row = 0; row < table->rowCount(); ++row) { + QTableWidgetItem *nameItem = table->item(row, 0); + QTableWidgetItem *fileItem = table->item(row, 1); + if (nameItem && fileItem) { + QString name = nameItem->text().trimmed(); + QString file = fileItem->text().trimmed(); + if (!name.isEmpty() && !file.isEmpty()) { + m_SoundFiles->addFile(name, file); + } + } + } +} +/** + * @brief Загрузка случайных диапазонов из таблицы sgRandomInt в RandomManager + */ +void uGeneral::loadRandomRangesFromTableWidget() +{ + if (!db || !m_randomManager) return; + + QTableWidget* table = ui->sgRandomInt; + if (!table) return; + + for (int row = 0; row < table->rowCount(); ++row) { + QTableWidgetItem *nameItem = table->item(row, 0); + QTableWidgetItem *startItem = table->item(row, 1); + QTableWidgetItem *endItem = table->item(row, 2); + + if (nameItem && startItem && endItem) { + QString name = nameItem->text().trimmed(); + int start = startItem->text().toInt(); + int end = endItem->text().toInt(); + + if (!name.isEmpty() && start <= end) { + m_randomManager->addRange(name, start, end); + } + } + } +} + +/** + * @brief Загрузка групп случайных ответов из базы данных + */ +void uGeneral::loadRandomResponseGroupsFromDatabase() +{ + if (!db || !m_randomResponses) return; + + // Создаем временный QListWidget для получения списка групп + QListWidget tempGroupList; + if (!db->LoadRandomGroups(&tempGroupList)) { + qWarning() << "Failed to load random groups from database"; + return; + } + + // Для каждой группы загружаем её ответы + for (int i = 0; i < tempGroupList.count(); ++i) { + QString groupName = tempGroupList.item(i)->text().trimmed(); + if (groupName.isEmpty()) continue; + + // Создаем временный QListWidget для получения ответов группы + QListWidget tempResponseList; + if (!db->LoadRandomResponses(groupName, &tempResponseList)) { + qWarning() << "Failed to load responses for group:" << groupName; + continue; + } + + // Добавляем каждый ответ в RandomResponses + for (int j = 0; j < tempResponseList.count(); ++j) { + QString response = tempResponseList.item(j)->text(); + if (!response.isEmpty()) { + m_randomResponses->addResponse(groupName, response); + } + } + } +} + + + /** * @brief Настройка компонентов Twitch */ @@ -464,7 +623,6 @@ void uGeneral::setupUserWidget() connect(m_userWidget, &UserWidget::banUserRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->banUser(userId); - toLog("uGeneral", "banUser", QString("Пользователь %1 забанен").arg(userName), 0); } }); @@ -472,8 +630,7 @@ void uGeneral::setupUserWidget() connect(m_userWidget, &UserWidget::timeoutUserRequested, this, [this](const QString &userId, const QString &userName, int minutes) { if (twitchAPI) { twitchAPI->banUserTime(userId, minutes); - toLog("uGeneral", "timeoutUser", - QString("Пользователю %1 выдан таймаут на %2 минут").arg(userName).arg(minutes), 0); + } }); @@ -481,7 +638,6 @@ void uGeneral::setupUserWidget() connect(m_userWidget, &UserWidget::unbanUserRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->unbanUser(userId); - toLog("uGeneral", "unbanUser", QString("Пользователь %1 разбанен").arg(userName), 0); } }); @@ -497,7 +653,6 @@ void uGeneral::setupUserWidget() m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } - toLog("uGeneral", "setModerator", QString("Пользователь %1 назначен модератором").arg(userName), 0); } }); @@ -513,7 +668,6 @@ void uGeneral::setupUserWidget() m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } - toLog("uGeneral", "removeModerator", QString("Пользователь %1 лишен прав модератора").arg(userName), 0); } }); @@ -529,7 +683,6 @@ void uGeneral::setupUserWidget() m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } - toLog("uGeneral", "setVIP", QString("Пользователь %1 назначен VIP").arg(userName), 0); } }); @@ -545,7 +698,6 @@ void uGeneral::setupUserWidget() m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } - toLog("uGeneral", "removeVIP", QString("Пользователь %1 лишен статуса VIP").arg(userName), 0); } }); @@ -591,7 +743,7 @@ void uGeneral::setupAuthHandlers() connect(m_authBot, &TAuth::tokenReceived, this, &uGeneral::onTokenReceived); connect(m_authBot, &TAuth::errorOccurred, this, &uGeneral::onAuthError); connect(m_authBot, &TAuth::serverStarted, this, [this](int port) { - toLog("TAuth", "Bot", QString("Сервер запущен на порту %1").arg(port), 0); + LogManager::instance()->info("General", "setupAuthHandlers", "Сервер авторизации запущен"); }); // ======================================================================== @@ -601,7 +753,7 @@ void uGeneral::setupAuthHandlers() connect(m_authStreamer, &TAuth::tokenReceived, this, &uGeneral::onTokenReceived2); connect(m_authStreamer, &TAuth::errorOccurred, this, &uGeneral::onAuthError); connect(m_authStreamer, &TAuth::serverStarted, this, [this](int port) { - toLog("TAuth", "Streamer", QString("Сервер запущен на порту %1").arg(port), 0); + LogManager::instance()->info("General", "setupAuthHandlers", "Сервер авторизации запущен"); }); // ======================================================================== @@ -611,7 +763,7 @@ void uGeneral::setupAuthHandlers() connect(m_authDA, &TAuth::codeReceived, this, &uGeneral::onTokenReceived3); connect(m_authDA, &TAuth::errorOccurred, this, &uGeneral::onAuthError); connect(m_authDA, &TAuth::serverStarted, this, [this](int port) { - toLog("TAuth", "DA", QString("Сервер запущен на порту %1").arg(port), 0); + LogManager::instance()->info("General", "setupAuthHandlers", "Сервер авторизации запущен"); }); } @@ -643,6 +795,17 @@ uGeneral::~uGeneral() } m_chatServers.clear(); + if (m_commandProcessor) { + delete m_commandProcessor; + m_commandProcessor = nullptr; + } + + if (m_randomManager) { + delete m_randomManager; + m_randomManager = nullptr; + } + + delete m_createNotifyDialog; delete m_createChatDialog; delete ui; @@ -669,10 +832,6 @@ uGeneral::~uGeneral() // ============================================================================ - - - - void uGeneral::setTwitchConnected(bool connected) { m_isTwitchConnected = connected; @@ -719,11 +878,7 @@ void uGeneral::loadChatBadges() twitchAPI->getCustomChatBadges(customBadges); m_chatBadges.append(customBadges); - toLog("uGeneral", "loadChatBadges", - QString("Загружено %1 бейджей (глобальных: %2, кастомных: %3)") - .arg(m_chatBadges.size()) - .arg(globalBadges.size()) - .arg(customBadges.size()), 0); + } void uGeneral::updateTimerTable() { @@ -784,8 +939,7 @@ void uGeneral::stopTimer(TimerInfo& timerInfo) { void uGeneral::sendTimedMessage(const TimerInfo& timerInfo) { // Не отправляем сообщение, если нет подключения if (!m_isTwitchConnected) { - toLog("uGeneral", "sendTimedMessage", - "Попытка отправить сообщение при отсутствии подключения к Twitch", 1); + return; } @@ -943,7 +1097,7 @@ void uGeneral::on_sgTimers_cellClicked(int row, int column) void uGeneral::onTimerTimeout(int timerId) { - toLog("uGeneral", "", &"Сработал таймер " [ timerId], 3); + } void uGeneral::on_sgTimers_cellDoubleClicked(int row, int column) { @@ -994,7 +1148,8 @@ void uGeneral::onNeuralResponse(const QString &response) { } void uGeneral::onNeuralError(const QString &error) { - toLog("uGeneral", "onNeuralError", error, 2); + + LogManager::instance()->error("General", "onNeuralError", error); } void uGeneral::onSoundGridDoubleClicked() @@ -1025,8 +1180,8 @@ void uGeneral::initTwitchAPI() if (botClientID.isEmpty() || botToken.isEmpty() || botTokenStreamer.isEmpty() || channel.isEmpty() || botName.isEmpty()) { - toLog("uGeneral", "initTwitchAPI", - "Не все данные для инициализации TTwAPI заполнены. API не инициализировано.", 1); + LogManager::instance()->warning("uGeneral", "initTwitchAPI", + "Не все данные для инициализации TTwAPI заполнены. API не инициализировано."); return; } @@ -1050,23 +1205,23 @@ void uGeneral::initTwitchAPI() botName ); - toLog("uGeneral", "initTwitchAPI", "TTwAPI успешно инициализировано", 0); + LogManager::instance()->info("uGeneral", "initTwitchAPI", "TTwAPI успешно инициализировано"); } void uGeneral::onApiError(const QString &method, const QString &error) { - toLog("ttw_api", method, error, 2); + LogManager::instance()->error("ttw_api", method, error); } void uGeneral::onTokenExpired(const QString &error) { - toLog("uGeneral", "onTokenExpired", error, 2); + LogManager::instance()->error("uGeneral", "onTokenExpired", error); } void uGeneral::onRateLimit() { - toLog("uGeneral", "onRateLimit", "onRateLimit", 1); + LogManager::instance()->warning("uGeneral", "onRateLimit", "Превышен лимит запросов"); } UserManager* uGeneral::getUserManager() @@ -1078,7 +1233,6 @@ UserManager* uGeneral::getUserManager() // Обработчик ответа от AI void uGeneral::onAIResponseReceived(const QString &response) { - toLog("AI", "Response", "Получен ответ: " + response.left(100) + "...", 0); // Отправляем ответ в чат if (m_twitchClient) { @@ -1094,7 +1248,7 @@ void uGeneral::onAIResponseReceived(const QString &response) // Обработчик ошибок AI void uGeneral::onAIErrorOccurred(const QString &error) { - toLog("AI", "Error", "Ошибка AI: " + error, 2); + LogManager::instance()->error("General", "onAIErrorOccurred", error); // Можно отправить сообщение об ошибке в чат if (m_twitchClient) { @@ -1107,7 +1261,8 @@ void uGeneral::onAIErrorOccurred(const QString &error) void uGeneral::handleConnected() { - toLog("uGeneral", "handleConnected","WS Connected", 0); + + LogManager::instance()->info("General", "handleConnected", "Connectiong"); setTwitchConnected(true); loadChatBadges(); // Запускаем активные таймеры, если подключены к Twitch @@ -1117,12 +1272,13 @@ void uGeneral::handleConnected() startTimer(timer); } } + LogManager::instance()->info("General", "handleConnected", "Connected"); } } void uGeneral::handleDisconnected() { - toLog("uGeneral", "handleDisconnected","WS Disconnected", 0); + LogManager::instance()->info("General", "handleDisconnected", "Disconnected"); setTwitchConnected(false); // Запускаем активные таймеры, если подключены к Twitch if (m_isTwitchConnected) { @@ -1153,61 +1309,38 @@ void uGeneral::toCommands(QString command, QString response) } -void uGeneral::toLog(QString aModule, QString aMethod, QString aMessage, int aCode) + +void uGeneral::onLogEntryAdded(const LogEntry& entry) { - // Определяем тип сообщения и цвет - QString msgType; - QColor textColor; - - switch (aCode) { - case 0: // INFO - msgType = "INFO"; - textColor = Qt::darkGreen; - break; - case 1: // WARNING - msgType = "WARNING"; - textColor = Qt::darkYellow; - break; - case 2: // ERROR - msgType = "ERROR"; - textColor = Qt::red; - break; - case 3: // DEBUG - msgType = "DEBUG"; - textColor = Qt::gray; - break; - default: - msgType = "UNKNOWN"; - textColor = Qt::black; - break; + // Проверяем фильтры + if (!shouldShowLogEntry(entry.level)) { + return; } - // Сохраняем запись в общий список - LogEntry entry; - entry.time = QDateTime::currentDateTime(); - entry.module = aModule; - entry.method = aMethod; - entry.message = aMessage; - entry.code = aCode; - entry.msgType = msgType; - entry.color = textColor; - - allLogs.append(entry); - - // Обновляем отображение (показываем только если чекбокс активен) - if (shouldShowLogEntry(aCode)) { - addLogToTable(entry, ui->sgLog->rowCount()); - } + // Добавляем запись в таблицу + addLogToTable(entry, ui->sgLog->rowCount()); } -bool uGeneral::shouldShowLogEntry(int code) +void uGeneral::onLogCleared() { - switch (code) { - case 0: return ui->cbInfo->isChecked(); - case 1: return ui->cbWarning->isChecked(); - case 2: return ui->cbError->isChecked(); - case 3: return ui->cbDebug->isChecked(); - default: return true; + ui->sgLog->setRowCount(0); +} + +bool uGeneral::shouldShowLogEntry(LogLevel level) +{ + switch (level) { + case LogLevel::Info: + return ui->cbInfo->isChecked(); + case LogLevel::Warning: + return ui->cbWarning->isChecked(); + case LogLevel::Error: + return ui->cbError->isChecked(); + case LogLevel::Debug: + return ui->cbDebug->isChecked(); + case LogLevel::Critical: + return true; // Критические ошибки всегда показываем + default: + return true; } } @@ -1216,34 +1349,38 @@ void uGeneral::addLogToTable(const LogEntry &entry, int row) // Добавляем новую строку ui->sgLog->insertRow(row); - // Колонка 0: Время - QTableWidgetItem *item0 = new QTableWidgetItem(entry.time.toString("hh:mm:ss")); - item0->setForeground(entry.color); + // Получаем цвет для уровня из настроек LogManager + LogSettings settings = LogManager::instance()->settings(); + QColor color = settings.colors.value(entry.level, LogEntry::levelToColor(entry.level)); - // Колонка 1: Тип сообщения - QTableWidgetItem *item1 = new QTableWidgetItem(entry.msgType); - item1->setForeground(entry.color); + // Колонка 0: Дата + QTableWidgetItem* timeItem = new QTableWidgetItem( + entry.timestamp.toString("hh:mm:ss")); + timeItem->setForeground(color); + + // Колонка 1: Тип + QTableWidgetItem* typeItem = new QTableWidgetItem( + LogEntry::levelToString(entry.level)); + typeItem->setForeground(color); // Колонка 2: Модуль - QTableWidgetItem *item2 = new QTableWidgetItem(entry.module); - item2->setForeground(entry.color); + QTableWidgetItem* moduleItem = new QTableWidgetItem(entry.module); + moduleItem->setForeground(color); // Колонка 3: Метод - QTableWidgetItem *item3 = new QTableWidgetItem(entry.method); - item3->setForeground(entry.color); + QTableWidgetItem* methodItem = new QTableWidgetItem(entry.method); + methodItem->setForeground(color); // Колонка 4: Сообщение - QTableWidgetItem *item4 = new QTableWidgetItem(entry.message); - item4->setForeground(entry.color); + QTableWidgetItem* messageItem = new QTableWidgetItem(entry.message); + messageItem->setForeground(color); - // Устанавливаем элементы в таблицу - ui->sgLog->setItem(row, 0, item0); - ui->sgLog->setItem(row, 1, item1); - ui->sgLog->setItem(row, 2, item2); - ui->sgLog->setItem(row, 3, item3); - ui->sgLog->setItem(row, 4, item4); + ui->sgLog->setItem(row, 0, timeItem); + ui->sgLog->setItem(row, 1, typeItem); + ui->sgLog->setItem(row, 2, moduleItem); + ui->sgLog->setItem(row, 3, methodItem); + ui->sgLog->setItem(row, 4, messageItem); - // Прокручиваем к последней строке ui->sgLog->scrollToBottom(); } @@ -1252,9 +1389,12 @@ void uGeneral::applyLogFilter() // Очищаем таблицу ui->sgLog->setRowCount(0); + // Получаем все записи из LogManager + QList allEntries = LogManager::instance()->allEntries(); + // Добавляем только те записи, которые соответствуют фильтру - for (const LogEntry &entry : qAsConst(allLogs)) { - if (shouldShowLogEntry(entry.code)) { + for (const LogEntry &entry : allEntries) { + if (shouldShowLogEntry(entry.level)) { addLogToTable(entry, ui->sgLog->rowCount()); } } @@ -1349,6 +1489,7 @@ void uGeneral::loadSettings(){ db->LoadTableWidget(ui->sgWebServers); loadSavedChats(); + loadSavedNotifications(); } @@ -1394,9 +1535,6 @@ void uGeneral::on_btnGetTockenBot_clicked() // Запускаем сервер без открытия браузера (false) m_authBot->startServer(authUrl, false); - toLog("uGeneral", "on_btnGetTockenBot_clicked", - QString("Запущен сервер для получения токена бота. Откройте ссылку в окне. Порт: %1") - .arg(m_authBot->getServerPort()), 0); } @@ -1436,9 +1574,7 @@ void uGeneral::on_btnGetTokenStreamer_clicked() // Запускаем сервер с открытием браузера (true) m_authStreamer->startServer(authUrl, true); - toLog("uGeneral", "on_btnGetTokenStreamer_clicked", - QString("Запущен сервер для получения токена стримера. Порт: %1") - .arg(m_authStreamer->getServerPort()), 0); + } // Обработчик получения токена @@ -1460,7 +1596,7 @@ void uGeneral::onTokenReceived3(const QString &token) void uGeneral::onAuthError(const QString &error) { -toLog("uGeneral", "onAuthError", error, 2); + } void uGeneral::on_btnDAGetCode_clicked() @@ -1485,143 +1621,19 @@ QString encodedRedirectUri = QString::fromUtf8(QUrl::toPercentEncoding(redirectU // Запускаем сервер с открытием браузера m_authDA->startServer(authUrl, true); - toLog("uGeneral", "on_btnDAGetCode_clicked", - QString("Запущен сервер для получения кода DonationAlerts. Порт: %1") - .arg(m_authDA->getServerPort()), 0); + } void uGeneral::on_btnImportSettings_clicked() { - QString userExportsPath = getUserFilePath("exports", ""); - QString sourceFile = QFileDialog::getOpenFileName(this, - "Выберите файл настроек для импорта", - userExportsPath, - "Файлы базы данных (*.db *.sqlite *.sqlite3);;Все файлы (*.*)"); - if (sourceFile.isEmpty()) { - return; // Пользователь отменил выбор - } - - QString destinationFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - destinationFile += "/settings.db"; - - QFileInfo fileInfo(destinationFile); - QString destinationDir = fileInfo.path(); - - // Проверяем, существует ли файл для импорта - QFile source(sourceFile); - if (!source.exists()) { - QMessageBox::critical(this, "Ошибка", - "Выбранный файл не существует или недоступен"); - return; - } - - // Спрашиваем подтверждение у пользователя - QMessageBox::StandardButton reply; - reply = QMessageBox::question(this, "Подтверждение импорта", - "Импорт настроек перезапишет все текущие настройки.\n" - "Вы уверены, что хотите продолжить?", - QMessageBox::Yes | QMessageBox::No); - - if (reply != QMessageBox::Yes) { - return; - } - - // Закрываем текущее соединение с БД - if (db) { - db->close(); - delete db; - db = nullptr; - } - - // Копируем файл - bool copySuccess = false; - QString errorMessage; - - try { - // Удаляем старый файл, если он существует - if (QFile::exists(destinationFile)) { - if (!QFile::remove(destinationFile)) { - errorMessage = "Не удалось удалить старый файл настроек"; - throw std::runtime_error(errorMessage.toStdString()); - } - } - - // Копируем новый файл - if (!QFile::copy(sourceFile, destinationFile)) { - errorMessage = "Не удалось скопировать файл"; - throw std::runtime_error(errorMessage.toStdString()); - } - - // Устанавливаем правильные права доступа к файлу - QFile newDbFile(destinationFile); - if (!newDbFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | - QFile::ReadUser | QFile::WriteUser)) { - qWarning() << "Не удалось установить права доступа к файлу"; - } - - copySuccess = true; - - } catch (const std::exception& e) { - errorMessage = e.what(); - copySuccess = false; - } - - if (!copySuccess) { - QMessageBox::critical(this, "Ошибка импорта", - "Не удалось импортировать настройки:\n" + errorMessage); - - // Пытаемся пересоздать пустую БД - db = new uDataBase(destinationFile, this); - return; - } - - // Пересоздаем объект БД с новым файлом - db = new uDataBase(destinationFile, this); - - if (db->isConnected()) { - QMessageBox::information(this, "Импорт завершен", - "Настройки успешно импортированы.\n" - "Приложение будет использовать новые настройки."); - - } else { - QMessageBox::critical(this, "Ошибка", - "Настройки импортированы, но не удалось подключиться к новой базе данных:\n" + - db->lastError()); - } } void uGeneral::on_btnExportSettings_clicked() { - // Получаем текущий файл БД - QString userExportsPath = getUserFilePath("exports", ""); - // Открываем диалог сохранения файла - QString destinationFile = QFileDialog::getSaveFileName(this, - "Экспорт настроек", - userExportsPath + "/settings_backup_" + - QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss") + ".db", - "Файлы базы данных (*.db);;Все файлы (*.*)"); - if (destinationFile.isEmpty()) { - return; // Пользователь отменил - } - - // Добавляем расширение .db, если его нет - if (!destinationFile.endsWith(".db", Qt::CaseInsensitive)) { - destinationFile += ".db"; - } - - // Копируем файл - if (QFile::copy(userExportsPath, destinationFile)) { - QMessageBox::information(this, "Экспорт завершен", - "Настройки успешно экспортированы в файл:\n" + destinationFile); - } else { - QMessageBox::critical(this, "Ошибка экспорта", - "Не удалось экспортировать настройки.\n" - "Проверьте права доступа к выбранной папке."); - } } @@ -1743,27 +1755,7 @@ void uGeneral::on_btnNotifyCheckSub_clicked() void uGeneral::on_btnNotifyOpen_clicked() { - QString userSoundsPath = getUserFilePath("sounds", ""); - QString sourceFile = QFileDialog::getOpenFileName(this, - "Выберите файл для уведомлений", - userSoundsPath, - "Звуковые файлы (*.mp3 *.wav *.ogg);;Все файлы (*.*)"); - - if (sourceFile.isEmpty()) { - return; - } - - QFileInfo fileInfo(sourceFile); - QString destPath = getUserFilePath("sounds", fileInfo.fileName()); - - if (sourceFile != destPath && !QFile::exists(destPath)) { - if (QFile::copy(sourceFile, destPath)) { - sourceFile = destPath; - } - } - - ui->edtNotifyFileName->setText(sourceFile); } void uGeneral::on_btnNotifyOpenMod_clicked() @@ -1841,9 +1833,15 @@ void uGeneral::on_btnNotifyOpenSub_clicked() if (!fn.isEmpty()) { - soundManager->loadSound(SoundManager::Channel2, fn); - soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeMod->value()); - soundManager->playSound(SoundManager::Channel2); + // Проверяем, существует ли файл + if (QFile::exists(fn)) { + // Загружаем и воспроизводим звук + soundManager->loadSound(SoundManager::Channel2, fn); + soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeMod->value()); + soundManager->playSound(SoundManager::Channel2); + } else { + + } return; } } @@ -1863,8 +1861,11 @@ void uGeneral::on_btnNotifyOpenSub_clicked() soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeVip->value()); soundManager->playSound(SoundManager::Channel2); - return; + } else { + } + return; + } // Проверяем подписчика (третий приоритет) @@ -1882,49 +1883,137 @@ void uGeneral::on_btnNotifyOpenSub_clicked() soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeSub->value()); soundManager->playSound(SoundManager::Channel2); - return; + } else { + } + return; } // Проверяем общий звук для всех (низший приоритет) if (ui->chEnNotify->isChecked() && !ui->edtNotifyFileName->text().isEmpty()) { QString fn = ui->edtNotifyFileName->text(); - soundManager->loadSound(SoundManager::Channel2, fn); - soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolume->value()); - soundManager->playSound(SoundManager::Channel2); + if (QFile::exists(fn)) { + // Загружаем и воспроизводим звук + soundManager->loadSound(SoundManager::Channel2, fn); + soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolume->value()); + soundManager->playSound(SoundManager::Channel2); + } else { + + } return; } } - void uGeneral::handleNewMessage( const QString &message){ - toLog("General", "handleNewMessage", message, 3); - TwitchMessage msg = TwitchMessage::parse(message); - QString userId = m_userManager->checkUser(msg.displayName, msg); - m_userManager->m_totalMessages++; - m_userWidget->updateStatistics(); - toLog("General", "handleNewMessage", msg.displayName + ": " + msg.message, 0); - playNotify(msg.isMod, msg.isVIP, msg.isSubscriber); + /** + * @brief Инициализирует звуковые уведомления + */ + void uGeneral::initializeNotificationSounds() + { + // Загружаем звук для обычных уведомлений + QString notifyFile = ui->edtNotifyFileName->text(); + if (!notifyFile.isEmpty() && QFile::exists(notifyFile)) { + soundManager->loadSound(SoundManager::Channel2, notifyFile); + soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolume->value()); - QString processedMessage = processTwitchMessage(msg); - QString formattedNickname = formatNicknameWithBadges(msg); - addChatMessage(formattedNickname, processedMessage); - - - QString cleanedMessage; - if (msg.message.startsWith("!!!")){ - cleanedMessage = msg.message.mid(3); - // speachMessage(cleanedMessage); - } else if (msg.message.startsWith("!")) { - cleanedMessage = msg.message.mid(1); - execCommand(msg.displayName, cleanedMessage); } else { - // execClearMessage(cleanedMessage); + + } + + // Загружаем звук для модераторов + QString notifyModFile = ui->edtNotifyFileNameMod->text(); + if (!notifyModFile.isEmpty() && QFile::exists(notifyModFile)) { + // Загружаем в отдельный канал, если SoundManager поддерживает несколько каналов + // Или используем ту же логику, что и в playNotify + + } + + // Загружаем звук для VIP + QString notifyVipFile = ui->edtNotifyFileNameVip->text(); + if (!notifyVipFile.isEmpty() && QFile::exists(notifyVipFile)) { + + } + + // Загружаем звук для подписчиков + QString notifySubFile = ui->edtNotifyFileNameSub->text(); + if (!notifySubFile.isEmpty() && QFile::exists(notifySubFile)) { + } } + void uGeneral::handleNewMessage( const QString &message){ + TwitchMessage msg = TwitchMessage::parse(message); + + // Проверяем и регистрируем пользователя + QString userId = m_userManager->checkUser(msg.displayName, msg); + + m_userManager->m_totalMessages++; + + // Обновляем статистику и воспроизводим уведомление + if (m_userWidget) { + m_userWidget->updateStatistics(); + } + playNotify(msg.isMod, msg.isVIP, msg.isSubscriber); + + // Обрабатываем сообщение Twitch (смайлы, бейджи и т.д.) + QString processedMessage = processTwitchMessage(msg); + QString formattedNickname = formatNicknameWithBadges(msg); + + // Добавляем сообщение в чат + addChatMessage(formattedNickname, processedMessage); + + // Обрабатываем команды если сообщение начинается с "!" + if (msg.message.startsWith("!!!")) { + QString cleanedMessage = msg.message.mid(3); + // speachMessage(cleanedMessage); + } else if (msg.message.startsWith("!")) { + QString cleanedMessage = msg.message.mid(1); + processUserCommand(userId, cleanedMessage); + } else { + // execClearMessage(msg.message); + } + } + + /** + * @brief Обработка команды от пользователя + */ + void uGeneral::processUserCommand(const QString &username, const QString &commandText) + { + if (!m_commandProcessor || !m_userManager) return; + + // Разделяем команду и параметры + QString command; + QString parameters; + int spacePos = commandText.indexOf(' '); + + if (spacePos != -1) { + command = commandText.left(spacePos).trimmed().toLower(); + parameters = commandText.mid(spacePos + 1).trimmed(); + } else { + command = commandText.trimmed().toLower(); + } + + // Если команда пустая, ничего не делаем + if (command.isEmpty()) return; + + // Генерируем ответ через CommandProcessor + QString response = m_commandProcessor->generateResponse(username, command, parameters); + + // Если есть ответ, отправляем его в чат + if (!response.isEmpty()) { + sendChatResponse(response); + } + } + + /** + * @brief Отправка ответа команды в чат + */ + void uGeneral::sendChatResponse(const QString &response) + { + m_twitchClient->sendChatMessage(ui->edtChannel->text(), response); + } + void uGeneral::handleError(const QString &errorMessage){ - toLog("General", "handleError",errorMessage,2); } // Метод для обработки Twitch сообщения с эмодзи @@ -2086,387 +2175,15 @@ void uGeneral::on_pushButton_2_clicked() } -void uGeneral::on_btnSaveSettings_clicked() -{ - QList lineEdits = ui->tab->findChildren(); - for (QLineEdit* edit : lineEdits) { - db->writeSetting(edit->objectName(), edit->text()); - } - QList checkBoxes = ui->tab->findChildren(); - for (QCheckBox* cb : checkBoxes) { - db->writeSetting(cb->objectName(), cb->isChecked() ? "True" : "False"); - } - db->SaveTableWidget(ui->sgWebServers); -} void uGeneral::execCommand(const QString &sender, const QString &message) { - QStringList parts = message.split(' ', Qt::SkipEmptyParts); - if (parts.isEmpty()) { - return; - } - - QString commandName = parts.first().toLower(); - QStringList params = parts.mid(1); // Все остальные слова - параметры - - bool commandFound = false; - QString response; - - for (int row = 0; row < ui->sgCommands->rowCount(); ++row) { - QTableWidgetItem *commandItem = ui->sgCommands->item(row, 0); - - if (commandItem) { - QString tableCommand = commandItem->text().toLower(); - - if (tableCommand.startsWith('!')) { - tableCommand = tableCommand.mid(1); - } - - if (tableCommand == commandName) { - QTableWidgetItem *responseItem = ui->sgCommands->item(row, 1); - if (responseItem) { - response = responseItem->text(); - commandFound = true; - - // Заменяем плейсхолдеры - response = response.replace("{sender}", sender); - - // Заменяем параметры {0}, {1}, {2} и т.д. - for (int i = 0; i < params.size(); ++i) { - QString placeholder = "{" + QString::number(i) + "}"; - response = response.replace(placeholder, params[i]); - } - } - break; - } - } - } - - if (commandFound && !response.isEmpty()) { - QString p = ""; - QString pall = ""; - - if (params.count() > 0) - { - p = params[0]; - pall = params.join(" "); - } - - // группы ответов - response = db->ProcessResponseTemplate(response); - response = db->ProcessResponseTemplate(response); - response = db->ProcessResponseTemplate(response); - // статичные автозамены - response = ResponsParserStatic(response, sender, p); - // рандомные числа - response = ResponsParserRandoms(response); - // АПИ команды - response = responsParserAPI(response, sender); - // Звуки - response = ResponsParserSounds(response); - // Текстовые файлы - response = ResponsParserText(response); - // ГПТ - response = ResponsParserAI(response, pall); + LogManager::instance()->debug("uGeneral", "execCommand", + QString("Обработка команды от %1: %2").arg(sender).arg(message)); - // АИ команды - // АИ картинки - - //счетчики - - m_twitchClient->sendChatMessage(ui->edtChannel->text(), response); - } } -QString uGeneral::ResponsParserAI(const QString &response, const QString &pall) -{ - // Проверяем, содержит ли строка маркер для нейросети - if (!response.contains("[AI]", Qt::CaseInsensitive)) { - return response; // Если маркера нет, возвращаем как есть - } - - // Извлекаем вопрос из pall - QString question = pall.trimmed(); - if (question.isEmpty()) { - QString res = response; - return res.replace("[AI]", "Ошибка: не указан вопрос для нейросети", - Qt::CaseInsensitive);; - } - - // Запоминаем оригинальный формат ответа - QString originalTemplate = response; - - // Создаем временный объект для асинхронного запроса - // Используем QEventLoop для ожидания ответа - QEventLoop eventLoop; - QString aiResponse; - bool responseReceived = false; - bool errorOccurred = false; - QString errorMessage; - - // Подключаем сигналы от нейросети через лямбда-выражения - QMetaObject::Connection conn1 = QObject::connect( - nnManager, &NeuralNetworkManager::responseReceived, - [&](const QString &response) { - aiResponse = response; - responseReceived = true; - eventLoop.quit(); - } - ); - - QMetaObject::Connection conn2 = QObject::connect( - nnManager, &NeuralNetworkManager::errorOccurred, - [&](const QString &error) { - errorMessage = error; - errorOccurred = true; - eventLoop.quit(); - } - ); - - // Отправляем запрос к нейросети - sendRequestToAI(question); - - // Ждем ответа (таймаут 60 секунд) - QTimer::singleShot(60000, &eventLoop, &QEventLoop::quit); - eventLoop.exec(); - - // Отключаем соединения - QObject::disconnect(conn1); - QObject::disconnect(conn2); - - // Обрабатываем результат - if (errorOccurred) { - return originalTemplate.replace("[AI]", - QString("Ошибка нейросети: %1").arg(errorMessage), - Qt::CaseInsensitive); - } - - if (!responseReceived) { - return originalTemplate.replace("[AI]", - "Таймаут при ожидании ответа от нейросети", - Qt::CaseInsensitive); - } - - // Заменяем [AI] на полученный ответ - QString result = originalTemplate; - result = result.replace("[AI]", aiResponse, Qt::CaseInsensitive); - - return result; -} - -QString uGeneral::ResponsParserStatic(const QString &inMess, const QString &adName, const QString &aCommandText) -{ - QString res = inMess; - - // Замена [USERNAME] на @ + adName - res = res.replace("[USERNAME]", "@" + adName); - - // Замена [TO] на aCommandText - res = res.replace("[TO]", aCommandText); - - // Обработка [RANDOMUSER] - if (res.contains("[RANDOMUSER]")) { - QString randomUserName; - - // Если aCommandText содержит '@', используем его - if (aCommandText.contains('@')) { - randomUserName = aCommandText; - } else { - // Если список пользователей пуст, оставляем пустую строку или значение по умолчанию - randomUserName = "@viewer"; - } - - res = res.replace("[RANDOMUSER]", randomUserName); - } - - return res; -} - - -QString uGeneral::ResponsParserRandoms(const QString &inMess) -{ - QString result = inMess; - - // Функция для получения случайного числа в диапазоне - auto randomInRange = [](int minValue, int maxValue) -> int { - if (minValue > maxValue) { - qSwap(minValue, maxValue); - } - return QRandomGenerator::global()->bounded(minValue, maxValue + 1); - }; - - // Ищем все вхождения [[имя]] - int startPos = 0; - while ((startPos = result.indexOf("[[", startPos)) != -1) { - int endPos = result.indexOf("]]", startPos + 2); - if (endPos == -1) { - break; // Нет закрывающих скобок - } - - // Извлекаем имя между [[ и ]] - QString rndName = result.mid(startPos + 2, endPos - startPos - 2); - bool found = false; - - // Ищем имя в таблице sgRandomInt - for (int row = 0; row < ui->sgRandomInt->rowCount(); ++row) { - // Предполагаем, что в первом столбце имя, во втором - минимальное значение, в третьем - максимальное - QTableWidgetItem *nameItem = ui->sgRandomInt->item(row, 0); - QTableWidgetItem *minItem = ui->sgRandomInt->item(row, 1); - QTableWidgetItem *maxItem = ui->sgRandomInt->item(row, 2); - - if (nameItem && minItem && maxItem && nameItem->text() == rndName) { - // Получаем значения диапазона - bool okMin, okMax; - int minValue = minItem->text().toInt(&okMin); - int maxValue = maxItem->text().toInt(&okMax); - - if (okMin && okMax) { - // Генерируем случайное число - int randomNumber = randomInRange(minValue, maxValue); - - // Заменяем [[имя]] на случайное число - QString token = "[[" + rndName + "]]"; - result.replace(token, QString::number(randomNumber)); - - found = true; - break; - } - } - } - - if (!found) { - // Если имя не найдено в таблице, оставляем как есть или удаляем - startPos = endPos + 2; // Переходим к следующему поиску - } else { - // После замены начинаем поиск с начала строки, так как длина строки изменилась - startPos = 0; - } - } - - return result; -} - -QString uGeneral::ResponsParserSounds(const QString &inMess) -{ - QString result = inMess; - - // Ищем все вхождения ||команда|| - int startPos = 0; - while ((startPos = result.indexOf("||", startPos)) != -1) { - int endPos = result.indexOf("||", startPos + 2); - if (endPos == -1) { - break; // Нет закрывающих разделителей - } - - // Извлекаем команду между || и || - QString soundCommand = result.mid(startPos + 2, endPos - startPos - 2); - - // Ищем команду в таблице sgSounds - bool found = false; - for (int row = 0; row < ui->widget->tableWidget()->rowCount(); ++row) { - QTableWidgetItem *commandItem = ui->widget->tableWidget()->item(row, 0); - - if (commandItem && commandItem->text() == soundCommand) { - // Получаем путь к файлу из второго столбца - QTableWidgetItem *filePathItem = ui->widget->tableWidget()->item(row, 1); - - if (filePathItem && !filePathItem->text().isEmpty()) { - QString fn = filePathItem->text(); - - // Воспроизводим звук - soundManager->loadSound(SoundManager::Channel1, fn); - soundManager->setVolume(SoundManager::Channel1, ui->tbNotifyVolume->value()); - soundManager->playSound(SoundManager::Channel1); - } - - // Заменяем ||команда|| на пустую строку - QString token = "||" + soundCommand + "||"; - result.replace(token, ""); - - found = true; - break; - } - } - - if (!found) { - // Если команда не найдена в таблице, просто удаляем ||команда|| - QString token = "||" + soundCommand + "||"; - result.replace(token, ""); - } - - // Начинаем поиск снова с начала строки, так как длина строки изменилась - startPos = 0; - } - - return result; -} - -QString uGeneral::ResponsParserText(const QString &inMess) -{ - QString result = inMess; - - // Ищем все вхождения |(имя_файла|( - int startPos = 0; - while ((startPos = result.indexOf("|(", startPos)) != -1) { - int endPos = result.indexOf("|(", startPos + 2); - if (endPos == -1) { - break; // Нет закрывающего разделителя - } - - // Извлекаем имя файла между |( и |( - QString fileName = result.mid(startPos + 2, endPos - startPos - 2).trimmed(); - - // Ищем имя файла в таблице widget_2 - QString fileContent = ""; - bool found = false; - - for (int row = 0; row < ui->widget_2->tableWidget()->rowCount(); ++row) { - QTableWidgetItem *nameItem = ui->widget_2->tableWidget()->item(row, 0); - - if (nameItem && nameItem->text() == fileName) { - // Получаем путь к файлу из второго столбца - QTableWidgetItem *filePathItem = ui->widget_2->tableWidget()->item(row, 1); - - if (filePathItem && !filePathItem->text().isEmpty()) { - QString filePath = filePathItem->text().trimmed(); - - // Читаем содержимое файла - QFile file(filePath); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { - QTextStream stream(&file); - stream.setCodec("UTF-8"); // Установите нужную кодировку - fileContent = stream.readAll(); - file.close(); - found = true; - } else { - qWarning() << "Не удалось открыть файл:" << filePath; - fileContent = "[Ошибка: файл не найден]"; - found = true; - } - } else { - fileContent = "[Ошибка: путь к файлу не указан]"; - found = true; - } - break; - } - } - - // Заменяем |(имя_файла|( на содержимое файла - QString token = "|(" + fileName + "|("; - if (found) { - result.replace(token, fileContent); - } else { - // Если имя файла не найдено в таблице, заменяем на сообщение об ошибке - result.replace(token, "[Ошибка: файл '" + fileName + "' не найден в списке]"); - } - - // После замены начинаем поиск сначала, так как длина строки изменилась - startPos = 0; - } - - return result; -} void uGeneral::on_lbRandomGroup_itemClicked(QListWidgetItem *item) @@ -2475,157 +2192,10 @@ void uGeneral::on_lbRandomGroup_itemClicked(QListWidgetItem *item) } -QString uGeneral::responsParserAPI(const QString &inMess, const QString &adName) -{ - QString res = inMess; - - // Используем UserManager вместо userList - User* user = m_userManager->findUser(adName); - - if (!user) { - qWarning() << "User not found:" << adName; - return res; - } - - // Функция для склонения слов по числам - auto getPeriodEnding = [](int n, int r) -> QString { - QVector> res = { - {"год", "года", "лет"}, - {"месяц", "месяца", "месяцев"}, - {"день", "дня", "дней"}, - {"раз", "раза", "раз"} - }; - - if (r < 0 || r >= res.size()) { - return ""; - } - - if (n % 10 == 1 && n % 100 != 11) { - return res[r][0]; - } else if (n % 10 >= 2 && n % 10 <= 4 && - (n % 100 < 10 || n % 100 >= 20)) { - return res[r][1]; - } else { - return res[r][2]; - } - }; - - // Функция вычисления разницы дат - auto getDateDifference = [&getPeriodEnding](const QDate &inputDate) -> QString { - if (!inputDate.isValid() || inputDate.year() < 2000) { - return ""; - } - - QDate currentDate = QDate::currentDate(); - - if (currentDate < inputDate) { - return ""; - } - - int years = 0; - int months = 0; - int days = 0; - - // Вычисляем годы - years = inputDate.daysTo(currentDate) / 365; - QDate tempDate = inputDate.addYears(years); - - // Вычисляем месяцы - while (tempDate.addMonths(1) <= currentDate) { - months++; - tempDate = tempDate.addMonths(1); - } - - // Вычисляем дни - days = tempDate.daysTo(currentDate); - - // Корректировка месяцев и дней - if (days >= 30) { - months += days / 30; - days = days % 30; - } - - return QString("%1 %2 %3 %4 %5 %6") - .arg(years) - .arg(getPeriodEnding(years, 0)) - .arg(months) - .arg(getPeriodEnding(months, 1)) - .arg(days) - .arg(getPeriodEnding(days, 2)); - }; - - // Обработка [FOLLOW] - if (res.contains("[FOLLOW]", Qt::CaseInsensitive)) { - - if (user->followAt.year() < 2000 && !user->id.isEmpty()) { - // Используем m_api вместо ttwAPI - QDate followDate = twitchAPI->getFollow(user->id); - if (followDate.isValid()) { - user->followAt = followDate; - // Обновляем пользователя в менеджере - m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); - } - } - - if (user->followAt.isValid() && user->followAt.year() >= 2000) { - QString follow = getDateDifference(user->followAt); - res.replace("[FOLLOW]", follow, Qt::CaseInsensitive); - } - } - - // Обработка [AGE] - if (res.contains("[AGE]", Qt::CaseInsensitive)) { - if (user->createdAt.year() < 2000 && !user->login.isEmpty()) { - // Используем m_api вместо ttwAPI - User updatedUser = twitchAPI->getUserByLogin(user->login); - if (updatedUser.createdAt.isValid()) { - user->createdAt = updatedUser.createdAt; - // Обновляем пользователя в менеджере - m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); - } - } - - if (user->createdAt.isValid() && user->createdAt.year() >= 2000) { - QString age = getDateDifference(user->createdAt); - res.replace("[AGE]", age, Qt::CaseInsensitive); - } - } - - // Обработка [STAT] - if (res.contains("[STAT]", Qt::CaseInsensitive)) { - int avgViewers = 0; - int maxViewers = 0; - int hoursWatched = 0; - int followers = 0; - int followersTotal = 0; - - // Используем m_api вместо ttwAPI и правильное имя канала - twitchAPI->getTTWStat(ui->edtChannel->text(), avgViewers, maxViewers, - hoursWatched, followers, followersTotal); - - QString resultStat = QString( - "Статистика канала за месяц: " - "Средний онлайн: %1; " - "Максимальный онлайн: %2; " - "Часов просмотра: %3; " - "Подписчиков за месяц: %4; " - "Всего подписчиков: %5") - .arg(avgViewers) - .arg(maxViewers) - .arg(hoursWatched) - .arg(followers) - .arg(followersTotal); - - res.replace("[STAT]", resultStat, Qt::CaseInsensitive); - } - - return res; -} void uGeneral::on_pushButton_clicked() { - allLogs.clear(); - ui->sgLog->setRowCount(0); + LogManager::instance()->clear(); } @@ -2659,32 +2229,7 @@ void uGeneral::on_btnRandAdd_clicked() return; } - // Получаем текущее количество строк в таблице - int row = ui->sgRandomInt->rowCount(); - - // Добавляем новую строку - ui->sgRandomInt->insertRow(row); - - // Создаем и заполняем ячейки - - // Колонка 0: Название - QTableWidgetItem *itemName = new QTableWidgetItem(name); - ui->sgRandomInt->setItem(row, 0, itemName); - - // Колонка 1: Значение "От" - QTableWidgetItem *itemOt = new QTableWidgetItem(QString::number(otValue)); - itemOt->setTextAlignment(Qt::AlignCenter); // Выравнивание по центру - ui->sgRandomInt->setItem(row, 1, itemOt); - - // Колонка 2: Значение "До" - QTableWidgetItem *itemTo = new QTableWidgetItem(QString::number(toValue)); - itemTo->setTextAlignment(Qt::AlignCenter); // Выравнивание по центру - ui->sgRandomInt->setItem(row, 2, itemTo); - - ui->edtRandomName->clear(); - ui->edtOt->setValue(0); // или значение по умолчанию - ui->edtTo->setValue(100); // или значение по умолчанию - db->SaveTableWidget(ui->sgRandomInt); + m_randomManager->rangeAdded(name, otValue, toValue); } @@ -2699,9 +2244,7 @@ void uGeneral::on_btnRandDel_clicked() // Получаем индекс выделенной строки int row = ui->sgRandomInt->currentItem()->row(); - - ui->sgRandomInt->removeRow(row); - db->SaveTableWidget(ui->sgRandomInt); + m_randomManager->rangeRemoved(ui->sgRandomInt->item(row, 0)->text()); } @@ -2847,94 +2390,82 @@ void uGeneral::on_btnGetChannelStat_clicked() void uGeneral::on_btnAddCommand_clicked() { - // Получаем данные из полей ввода QString name = ui->edtCommand->text().trimmed(); - QString respons = ui->textBrowser->toPlainText(); + QString response = ui->textBrowser->toPlainText(); - // Проверяем, что имя не пустое if (name.isEmpty()) { QMessageBox::warning(this, "Внимание", "Введите команду!"); - ui->edtCommand->setFocus(); + LogManager::instance()->warning("uGeneral", "on_btnAddCommand_clicked", + "Попытка добавить команду с пустым именем"); return; } - // Получаем текущее количество строк в таблице + // Просто добавляем в таблицу и сохраняем в БД + // CommandProcessor будет читать из таблицы при обработке + + // Добавляем новую строку в таблицу int row = ui->sgCommands->rowCount(); - ui->sgCommands->insertRow(row); + ui->sgCommands->setItem(row, 0, new QTableWidgetItem(name)); + ui->sgCommands->setItem(row, 1, new QTableWidgetItem(response)); - QTableWidgetItem *itemName = new QTableWidgetItem(name); - ui->sgCommands->setItem(row, 0, itemName); - - QTableWidgetItem *itemOt = new QTableWidgetItem(respons); - ui->sgCommands->setItem(row, 1, itemOt); - - ui->edtCommand->clear(); - ui->textBrowser->setPlainText(""); // или значение по умолчанию - + // Сохраняем в БД db->SaveTableWidget(ui->sgCommands); + + LogManager::instance()->info("uGeneral", "on_btnAddCommand_clicked", + QString("Добавлена команда: %1").arg(name)); } void uGeneral::on_btnRmCommand_clicked() { - // Проверяем, есть ли выделенная строка if (!ui->sgCommands->currentItem()) { - QMessageBox::warning(this, "Внимание", "Выберите строку для удаления!"); + QMessageBox::warning(this, "Внимание", "Выберите команду для удаления!"); + LogManager::instance()->warning("uGeneral", "on_btnRmCommand_clicked", + "Не выбрана команда для удаления"); return; } - // Получаем индекс выделенной строки + int row = ui->sgCommands->currentItem()->row(); + QString commandName = ui->sgCommands->item(row, 0)->text(); + ui->sgCommands->removeRow(row); db->SaveTableWidget(ui->sgCommands); + + LogManager::instance()->info("uGeneral", "on_btnRmCommand_clicked", + QString("Удалена команда: %1").arg(commandName)); } void uGeneral::on_btnEdtCommand_clicked() { - // Проверяем, есть ли выделенная строка if (!ui->sgCommands->currentItem()) { - QMessageBox::warning(this, "Внимание", "Выберите строку для редактирования!"); + QMessageBox::warning(this, "Внимание", "Выберите команду для редактирования!"); + LogManager::instance()->warning("uGeneral", "on_btnEdtCommand_clicked", + "Не выбрана команда для редактирования"); return; } - // Получаем индекс выделенной строки int row = ui->sgCommands->currentItem()->row(); + QString newName = ui->edtCommand->text().trimmed(); + QString response = ui->textBrowser->toPlainText(); - // Получаем данные из выбранной строки - ui->sgCommands->item(row, 0)->setText(ui->edtCommand->text()); - ui->sgCommands->item(row, 1)->setText(ui->textBrowser->toPlainText()); + if (newName.isEmpty()) { + QMessageBox::warning(this, "Внимание", "Введите новое имя команды!"); + return; + } + ui->sgCommands->item(row, 0)->setText(newName); + ui->sgCommands->item(row, 1)->setText(response); db->SaveTableWidget(ui->sgCommands); + + LogManager::instance()->info("uGeneral", "on_btnEdtCommand_clicked", + QString("Отредактирована команда в строке %1").arg(row)); } void uGeneral::loadQssFiles() { - // Очищаем ComboBox перед загрузкой - ui->cbTheme->clear(); - // Добавляем пустой элемент (отсутствие темы) - ui->cbTheme->addItem("Без темы", ""); - - // Путь к системным стилям (вместе с программой) - QDir systemStylesDir(getSystemPath() + "/styles"); - - // Путь к пользовательским стилям (в AppData) - QString userStylesPath = getUserFilePath("styles", ""); - QDir userStylesDir(userStylesPath); - - // Проверяем существование пользовательской папки со стилями - if (!userStylesDir.exists()) { - userStylesDir.mkpath("."); - } - - // Сначала загружаем системные стили (только для чтения) - loadStylesFromDirectory(systemStylesDir, "Системные"); - - // Затем загружаем пользовательские стили - loadStylesFromDirectory(userStylesDir, "Пользовательские"); - - // Можно добавить дополнительную обработку для выбора темы по умолчанию } void uGeneral::loadStylesFromDirectory(const QDir &stylesDir, const QString &category) @@ -2955,75 +2486,6 @@ void uGeneral::loadStylesFromDirectory(const QDir &stylesDir, const QString &cat } } -/** - * @brief Инициализирует структуру папок в AppData - */ -void uGeneral::initializeAppDataStructure() -{ - QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - - // Создаем основную папку - QDir appDataDir(appDataPath); - if (!appDataDir.exists()) { - appDataDir.mkpath("."); - } - - // Создаем подпапки - QStringList subdirs = { - "sounds", - "images", - "styles", - "voices", - "fonts", - "temp", - "backups", - "exports", - "logs", - "cache" - }; - - for (const QString &subdir : subdirs) { - if (!appDataDir.exists(subdir)) { - appDataDir.mkpath(subdir); - } - } - - // Копируем системные файлы по умолчанию в AppData при первом запуске - copyDefaultFiles(); -} - -/** - * @brief Копирует файлы по умолчанию из системной папки в AppData - */ -void uGeneral::copyDefaultFiles() -{ - QSettings settings; - bool firstRun = settings.value("FirstRun", true).toBool(); - - if (firstRun) { - // Копируем примеры стилей - QDir systemStylesDir(getSystemPath() + "/styles"); - QString userStylesPath = getUserFilePath("styles", ""); - - if (systemStylesDir.exists()) { - QStringList filters; - filters << "*.qss" << "*.QSS"; - QStringList qssFiles = systemStylesDir.entryList(filters, QDir::Files); - - for (const QString &file : qssFiles) { - QString sourcePath = systemStylesDir.absoluteFilePath(file); - QString destPath = QString("%1/%2").arg(userStylesPath).arg(file); - - if (!QFile::exists(destPath)) { - QFile::copy(sourcePath, destPath); - } - } - } - - // Устанавливаем флаг, что первый запуск завершен - settings.setValue("FirstRun", false); - } -} void uGeneral::on_cbTheme_currentIndexChanged(int index) { @@ -3084,7 +2546,7 @@ void uGeneral::on_btnWSCreateChat_clicked() void uGeneral::on_btnWSCreateNotify_clicked() { // Создаем диалог каждый раз - FCreateNotify *createDialog = new FCreateNotify(this); + FCreateNotify *createDialog = new FCreateNotify(db, this); connect(createDialog, &FCreateNotify::serverCreated, this, &uGeneral::onNotifyServerCreated); createDialog->setAttribute(Qt::WA_DeleteOnClose); // Автоматическое удаление при закрытии createDialog->exec(); @@ -3105,11 +2567,62 @@ void uGeneral::onChatServerCreated(HttpServerChat *server, const QString &name) void uGeneral::onNotifyServerCreated(HttpServer *server, const QString &name) { - if (server) { - // Устанавливаем родителя - главное окно - server->setParent(this); + if (!server) return; - addNotificationServer(server, name); + // Сохраняем сервер в базе данных + if (!db->saveNotification(name, server)) { + + } + + // Добавляем сервер в список + m_notificationServers.append(server); + + // Добавляем в таблицу + QString url = QString("http://localhost:%1").arg(server->port()); + addServerToTable(name, "Уведомления", server->port(), url, server); +} + +void uGeneral::onNotifyServerUpdated(HttpServer *server, const QString &name) +{ + if (!server) return; + + // Найдем старый порт из таблицы + int oldPort = 0; + for (int row = 0; row < ui->sgWebServers->rowCount(); ++row) { + QTableWidgetItem *nameItem = ui->sgWebServers->item(row, 0); + if (nameItem && nameItem->data(Qt::UserRole).value() == server) { + QTableWidgetItem *portItem = ui->sgWebServers->item(row, 2); + if (portItem) { + oldPort = portItem->text().toInt(); + } + break; + } + } + + // Обновляем сервер в базе данных + if (!db->updateNotification(name, server, oldPort)) { + + } + + // Обновляем сервер в списке + int index = m_notificationServers.indexOf(server); + if (index >= 0) { + m_notificationServers[index] = server; + } + + // Обновляем строку в таблице + for (int row = 0; row < ui->sgWebServers->rowCount(); ++row) { + QTableWidgetItem *nameItem = ui->sgWebServers->item(row, 0); + if (nameItem && nameItem->data(Qt::UserRole).value() == server) { + // Обновляем имя + nameItem->setText(name); + // Обновляем порт + ui->sgWebServers->item(row, 2)->setText(QString::number(server->port())); + // Обновляем ссылку + QString url = QString("http://localhost:%1").arg(server->port()); + ui->sgWebServers->item(row, 3)->setText(url); + break; + } } } @@ -3405,6 +2918,63 @@ void uGeneral::on_sgWebServers_cellDoubleClicked(int row, int column) createChatDialog->setAttribute(Qt::WA_DeleteOnClose); createChatDialog->exec(); } + } else if (serverType.toLower() == "уведомления") { + // Получаем объект сервера из user data + QObject *serverObj = nameItem->data(Qt::UserRole).value(); + HttpServer *existingServer = qobject_cast(serverObj); + + if (existingServer) { + FCreateNotify *createNotifyDialog = new FCreateNotify(db, this); + createNotifyDialog->loadExistingServer(existingServer, nameItem->text()); + connect(createNotifyDialog, &FCreateNotify::serverUpdated, + this, &uGeneral::onNotifyServerUpdated); + createNotifyDialog->setAttribute(Qt::WA_DeleteOnClose); + createNotifyDialog->exec(); + } + } +} + +void uGeneral::loadSavedNotifications() +{ + QList notifications = db->loadAllNotifications(); + + for (const NotificationSettings &settings : notifications) { + if (settings.type.toLower() == "notification") { + // Создаем сервер уведомлений + HttpServer *server = new HttpServer(this); + + // Настраиваем сервер + server->setBlockColor(settings.blockColor); + server->setBorderColor(settings.borderColor); + server->setBorderSize(settings.borderSize); + server->setTransparency(settings.transparency); + server->setPageBackgroundColor(settings.pageBackgroundColor); + server->setTitleFont( + settings.titleFamily, + settings.titleSize, + settings.titleColor + ); + server->setContentFont( + settings.contentFamily, + settings.contentSize, + settings.contentColor + ); + server->setDuration(settings.duration); + + // Запускаем сервер + if (server->start(settings.port)) { + m_notificationServers.append(server); + + // Добавляем в таблицу + QString url = QString("http://localhost:%1").arg(settings.port); + addServerToTable(settings.name, "Уведомления", settings.port, url, server); + + + } else { + delete server; + + } + } } } @@ -3508,11 +3078,6 @@ void uGeneral::on_edtDACode_editingFinished() } -void uGeneral::on_cbDAAutoLogin_checkStateChanged(const Qt::CheckState &arg1) -{ - -} - void uGeneral::on_cbDAAutoLogin_stateChanged(int arg1) { @@ -3666,9 +3231,7 @@ void uGeneral::on_cbNotifyFileAgain3_stateChanged(int arg1) void uGeneral::on_btnThemesFolder_clicked() { - QString stylesPath = getUserFilePath("styles", ""); - QUrl url = QUrl::fromLocalFile(stylesPath); - QDesktopServices::openUrl(url); + } @@ -3710,14 +3273,10 @@ void uGeneral::loadSavedChats() QString url = QString("http://localhost:%1").arg(settings.port); addServerToTable(settings.name, "Чат", settings.port, url, server); - toLog("uGeneral", "loadSavedChats", - QString("Загружен и запущен чат '%1' на порту %2") - .arg(settings.name).arg(settings.port), 0); + } else { delete server; - toLog("uGeneral", "loadSavedChats", - QString("Не удалось запустить чат '%1' на порту %2") - .arg(settings.name).arg(settings.port), 2); + } } } diff --git a/ugeneral.h b/ugeneral.h index b50c6a8..9227824 100644 --- a/ugeneral.h +++ b/ugeneral.h @@ -9,8 +9,10 @@ #include #include #include +#include "commandprocessor.h" #include "fcreatechat.h" #include "fcreatenotify.h" +#include "logmanager.h" #include "neuralnetworkmanager.h" #include "ttw_api.h" #include "user_manager.h" @@ -71,17 +73,6 @@ public: RListCommands(const QString &r1, const QString &r2) : R1(r1), R2(r2) {} }; - // Запись лога - struct LogEntry { - QDateTime time; - QString module; - QString method; - QString message; - int code; - QString msgType; - QColor color; - }; - // ======================================================================== // ПУБЛИЧНЫЕ ЧЛЕНЫ КЛАССА @@ -95,7 +86,7 @@ public: TTwAPI *twitchAPI; // API для работы с Twitch // Методы логирования и работы с командами - void toLog(QString aModule, QString aMethod, QString aMessage, int aCode); + void toCommands(QString command, QString response); private slots: @@ -128,7 +119,6 @@ private slots: void loadSettings(); // Загрузка настроек void on_btnImportSettings_clicked(); // Импорт настроек void on_btnExportSettings_clicked(); // Экспорт настроек - void on_btnSaveSettings_clicked(); // Сохранение настроек // ======================================================================== // СЛОТЫ ДЛЯ РАБОТЫ С УВЕДОМЛЕНИЯМИ @@ -159,12 +149,7 @@ private slots: // СЛОТЫ ДЛЯ РАБОТЫ С КОМАНДАМИ И ОТВЕТАМИ // ======================================================================== void execCommand(const QString &sender, const QString &message); - QString ResponsParserStatic(const QString &inMess, const QString &adName, const QString &aCommandText); - QString ResponsParserRandoms(const QString &inMess); - QString ResponsParserSounds(const QString &inMess); - QString ResponsParserText(const QString &inMess); - QString ResponsParserAI(const QString &response, const QString &pall); - QString responsParserAPI(const QString &inMess, const QString &adName); + // ======================================================================== // СЛОТЫ ДЛЯ РАБОТЫ С ИСКУССТВЕННЫМ ИНТЕЛЛЕКТОМ @@ -199,9 +184,13 @@ private slots: // ======================================================================== // СЛОТЫ ДЛЯ РАБОТЫ С ЛОГАМИ // ======================================================================== - void applyLogFilter(); + void onLogEntryAdded(const LogEntry& entry); + void onLogCleared(); + + // Добавляем методы для фильтрации void addLogToTable(const LogEntry &entry, int row); - bool shouldShowLogEntry(int code); + void applyLogFilter(); + bool shouldShowLogEntry(LogLevel level); // Изменяем тип параметра // ======================================================================== // СЛОТЫ ДЛЯ РАБОТЫ С ТАБЛИЦАМИ И ГРИДАМИ @@ -305,8 +294,6 @@ private slots: void on_edtDACode_editingFinished(); - void on_cbDAAutoLogin_checkStateChanged(const Qt::CheckState &arg1); - void on_cbDAAutoLogin_stateChanged(int arg1); void on_edtGPTPrefix_editingFinished(); @@ -359,6 +346,7 @@ private slots: void on_btnThemesFolder_clicked(); + void onNotifyServerUpdated(HttpServer *server, const QString &name); public slots: // Установка статуса подключения к Twitch void setTwitchConnected(bool connected); @@ -372,15 +360,17 @@ private: TAuth *m_authBot; // Авторизация бота TAuth *m_authStreamer; // Авторизация стримера TAuth *m_authDA; // Авторизация DA - + RandomResponses *m_randomResponses; uLink *fLinkForm; // Форма ссылок TTTVAuth *TTVAuth; // Данные авторизации Twitch UserManager *m_userManager; // Менеджер пользователей - QList allLogs; // Все записи логов - + CommandProcessor* m_commandProcessor; // Процессор команд WebSocketClient *m_twitchClient; // WebSocket клиент для Twitch UserWidget* m_userWidget; // Виджет пользователя NeuralNetworkManager *nnManager; // Менеджер нейронных сетей + RandomManager *m_randomManager; + MediaFileManager *m_SoundFiles; + MediaFileManager *m_TextFiles; QList m_timers; // Список таймеров int m_nextTimerId = 1; // Следующий ID таймера @@ -430,7 +420,7 @@ private: // Метод для применения QSS темы void applyStyleSheet(const QString &filename); - QString getUserFilePath(const QString &type, const QString &fileName) const; + QString getUserDataPath() const; QString getSystemPath() const; void setupButtonIcons(); @@ -455,7 +445,10 @@ private: // Воспроизведение уведомлений void playNotify(const bool &isMod, const bool &isVip, const bool &isSub); - + void loadCommandsFromTableWidget(); + void loadRandomRangesFromTableWidget(); + void loadRandomResponseGroupsFromDatabase(); + void loadSoundsFromDatabase(); // Инициализация базы данных void initializeDatabase(); // Настройка пользовательского интерфейса @@ -476,10 +469,19 @@ private: void loadEmoties(); void loadChatBadges(); + /** + * @brief Инициализирует звуковые уведомления + */ + void initializeNotificationSounds(); + // Обработка эмодзи void processEmotes(QString &message); void loadSavedChats(); + void loadSavedNotifications(); + + void processUserCommand(const QString &username, const QString &commandText); + void sendChatResponse(const QString &response); }; #endif // UGENERAL_H diff --git a/user_manager.cpp b/user_manager.cpp index ab2a4e3..b89704b 100644 --- a/user_manager.cpp +++ b/user_manager.cpp @@ -6,6 +6,7 @@ #include #include #include +#include UserManager::UserManager(QObject *parent) : QObject(parent) @@ -295,16 +296,32 @@ QVector UserManager::findUsersByLogin(const QString &login) return result; } -User* UserManager::getUserByIndex(int index) +User* UserManager::getUserByIndex(const QString& userID) { - if (index >= 0 && index < m_users.size()) { - auto it = m_users.begin(); - std::advance(it, index); + auto it = m_users.find(userID); + if (it != m_users.end()) { return &it.value(); } return nullptr; } +const User* UserManager::getRandomUser() const { + if (m_users.isEmpty()) { + return nullptr; + } + + QList keys = m_users.keys(); + int randomIndex = QRandomGenerator::global()->bounded(keys.size()); + + // Возвращаем указатель на существующий объект в контейнере + const auto it = m_users.constFind(keys[randomIndex]); + if (it != m_users.constEnd()) { + return &(it.value()); // Указатель на существующий объект + } + + return nullptr; +} + const QMap& UserManager::getAllUsers() const { return m_users; diff --git a/user_manager.h b/user_manager.h index 9af7666..94477f2 100644 --- a/user_manager.h +++ b/user_manager.h @@ -38,7 +38,7 @@ public: QVector getVIPs() const; QVector getSubscribers() const; QVector getActiveUsers(int minutes = 10) const; - + const User* getRandomUser() const; // Статистика int getUserCount() const; int getMessageCount() const; @@ -48,7 +48,7 @@ public: bool loadFromFile(const QString &filename); // Геттеры - User* getUserByIndex(int index); + User* getUserByIndex(const QString &userID); const QMap& getAllUsers() const; signals: diff --git a/webservernotify.cpp b/webservernotify.cpp index 3fd72fb..ba26b37 100644 --- a/webservernotify.cpp +++ b/webservernotify.cpp @@ -10,6 +10,17 @@ HttpServer::HttpServer(QObject *parent) : QObject(parent) , m_server(nullptr) , m_pageBackgroundColor("transparent") + , m_blockColor("#FFFFFF") + , m_borderColor("#000000") + , m_borderSize(2) + , m_transparency(0) + , m_titleFamily("Arial") + , m_titleSize(20) + , m_titleColor("#FFFFFF") + , m_contentFamily("Arial") + , m_contentSize(16) + , m_contentColor("#F5F5F5") + , m_duration(10) { } @@ -78,8 +89,6 @@ void HttpServer::addNotification(const Notification ¬ification) m_notifications.append(notif); - - // Обновляем всех подключенных клиентов for (auto client : m_clients) { if (client->state() == QAbstractSocket::ConnectedState) { @@ -236,7 +245,7 @@ QString HttpServer::generateHTML() "\n" "Web Notifications\n" "\n" - "\n" - "\n" + "\n" + "\n" + "\n" "
\n" "