From 451ddd9ae0e63be83f0961f1f24b8fa4b6aee9b2 Mon Sep 17 00:00:00 2001 From: PTyTb Date: Sat, 7 Feb 2026 08:28:56 +0300 Subject: [PATCH] refactor --- TTW_Bot.pro | 12 + commandprocessor.cpp | 476 ++++++++++ commandprocessor.h | 74 ++ emoteprovider.cpp | 2 +- emoteprovider.h | 10 +- fcreatenotify.cpp | 238 +++-- fcreatenotify.h | 13 +- fcreatenotify.ui | 46 +- ffontsetting.cpp | 66 +- filemanager.cpp | 183 ++++ filemanager.h | 75 ++ logmanager.cpp | 832 +++++++++++++++++ logmanager.h | 394 ++++++++ mediafilemanager.cpp | 101 ++ mediafilemanager.h | 50 + object_script.TTW_Bot.Debug | 10 + object_script.TTW_Bot.Release | 10 + randommanager.cpp | 294 ++++++ randommanager.h | 66 ++ randomresponses.cpp | 103 ++ randomresponses.h | 54 ++ ttw_api.cpp | 4 +- udatabase.cpp | 222 +++++ udatabase.h | 29 + ugeneral.cpp | 1663 ++++++++++++--------------------- ugeneral.h | 58 +- user_manager.cpp | 25 +- user_manager.h | 4 +- webservernotify.cpp | 72 +- webservernotify.h | 40 + 30 files changed, 3993 insertions(+), 1233 deletions(-) create mode 100644 commandprocessor.cpp create mode 100644 commandprocessor.h create mode 100644 filemanager.cpp create mode 100644 filemanager.h create mode 100644 logmanager.cpp create mode 100644 logmanager.h create mode 100644 mediafilemanager.cpp create mode 100644 mediafilemanager.h create mode 100644 randommanager.cpp create mode 100644 randommanager.h create mode 100644 randomresponses.cpp create mode 100644 randomresponses.h 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" "