#include "ugeneral.h" #include "fcreatenotify.h" #include "filemanager.h" #include "logmanager.h" #include "ttw_types.h" #include "ui_ugeneral.h" #include #include #include #include #include #include #include #include #include #include "ulink.h" #include "udatabase.h" #include "soundmanager.h" #include "userwidget.h" #include "websocketclient.h" #include "twitchmessage.h" #include "user.h" #include "user_manager.h" #include "emoteprovider.h" #include "fcreatechat.h" #include #include #include #include #include "timerinfo.h" #include "mediafilemanager.h" uGeneral::uGeneral(QWidget *parent) : QMainWindow(parent) , ui(new Ui::uGeneral) , m_authBot(nullptr) , m_authStreamer(nullptr) , m_authDA(nullptr) , fLinkForm(nullptr) , m_randomManager(nullptr) , m_neuralTemplateManager(nullptr) , m_isTwitchConnected(false) , m_notificationServers() , m_chatServers() , m_createNotifyDialog(nullptr) , m_createChatDialog(nullptr) { // ============================================================================ // ИНИЦИАЛИЗАЦИЯ ИНТЕРФЕЙСА // ============================================================================ ui->setupUi(this); setWindowTitle("TTW Bot app"); LogSettings logSettings; logSettings.logToFile = true; logSettings.logToConsole = true; // Цвета для разных уровней 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(); // Загружаем иконки tabIcons = { 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]); } ui->gbActionsAudio->setVisible(false); ui->gbActionsNotify->setVisible(false); setupButtonIcons(); // Загружаем QSS файлы при создании формы loadQssFiles(); // ============================================================================ // ИНИЦИАЛИЗАЦИЯ БАЗЫ ДАННЫХ // ============================================================================ initializeDatabase(); // ============================================================================ // НАСТРОЙКА ИНТЕРФЕЙСА ПОЛЬЗОВАТЕЛЯ // ============================================================================ setupUI(); // ============================================================================ // НАСТРОЙКА ТАБЛИЦ (с адаптивными параметрами) // ============================================================================ setupTables(); // ============================================================================ // ЗАГРУЗКА НАСТРОЕК // ============================================================================ loadSettings(); // ============================================================================ // НАСТРОЙКА TWITCH API И WEBSOCKET // ============================================================================ setupTwitchComponents(); // ============================================================================ // ИНИЦИАЛИЗАЦИЯ МЕНЕДЖЕРОВ // ============================================================================ initializeManagers(); // ============================================================================ // НАСТРОЙКА ВИДЖЕТА ПОЛЬЗОВАТЕЛЕЙ // ============================================================================ setupUserWidget(); // ============================================================================ // НАСТРОЙКА АВТОРИЗАЦИИ // ============================================================================ setupAuthHandlers(); loadEmoties(); // Настройка таблицы веб-серверов QStringList headers = {"Название", "Тип", "Порт", "Ссылка", "Статус"}; ui->sgWebServers->setColumnCount(headers.size()); ui->sgWebServers->setHorizontalHeaderLabels(headers); ui->sgWebServers->horizontalHeader()->setStretchLastSection(true); // Инициализируем окна создания m_createNotifyDialog = new FCreateNotify(db, this); m_createChatDialog = new FCreateChat(db, this); initializeNotificationSounds(); } // ============================================================================ // ПРИВАТНЫЕ МЕТОДЫ ДЛЯ ИНИЦИАЛИЗАЦИИ // ============================================================================ void uGeneral::setupButtonIcons() { QList buttons = ui->www->findChildren(); foreach (QPushButton *button, buttons) { QString buttonName = button->objectName().toLower(); if (buttonName.contains("add")) { button->setIcon(tabIcons[9]); } else if (buttonName.contains("del") || buttonName.contains("rm")) { if (button->objectName().contains("RmGroup")) { button->setIcon(tabIcons[12]); } else { button->setIcon(tabIcons[11]); } } else if (buttonName.contains("edt") || (buttonName.contains("edit"))) { button->setIcon(tabIcons[10]); } else if (buttonName.contains("open")) { button->setIcon(tabIcons[13]); } if (!button->icon().isNull()) { button->setIconSize(QSize(16, 16)); } } } void uGeneral::initializeDatabase() { QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir dir(roamingPath); if (!dir.exists()) { dir.mkpath("."); } roamingPath += "/settings.db"; db = new uDataBase(roamingPath, this); if (!db->isConnected()) { LogManager::instance()->error("uGeneral", "Create", "Ошибка подключения к БД"); } else { LogManager::instance()->info("uGeneral", "Create", "Успешное подключение к БД"); } } void uGeneral::setupUI() { // Настройка видимости элементов ui->lblAPI3->setText(""); ui->lblAPI3->setVisible(false); ui->edtAIP3->setVisible(false); ui->cbOllama->setVisible(false); // Настройка режимов отображения паролей QList passwordFields = { ui->edtDACode, ui->edtBotToken, ui->edtKandiKey, ui->edtDAClientID, ui->edtBotClientID, ui->edtKandiSecret, ui->edtDAClientSecret, ui->edtBotTokenStreamer, ui->edtAIP1, ui->edtAIP2, ui->edtAIP3 }; for (auto field : passwordFields) { field->setEchoMode(QLineEdit::Password); } // Подключение фильтров логов connect(ui->cbInfo, &QCheckBox::stateChanged, this, &uGeneral::applyLogFilter); connect(ui->cbWarning, &QCheckBox::stateChanged, this, &uGeneral::applyLogFilter); connect(ui->cbError, &QCheckBox::stateChanged, this, &uGeneral::applyLogFilter); connect(ui->cbDebug, &QCheckBox::stateChanged, this, &uGeneral::applyLogFilter); } void uGeneral::setupTables() { // ======================================================================== // НАСТРОЙКА ТАБЛИЦЫ ЛОГОВ (адаптивная) // ======================================================================== QStringList headers; headers.clear(); headers << "Дата" << "Тип" << "Модуль" << "Метод" << "Сообщение"; ui->sgLog->setHorizontalHeaderLabels(headers); ui->sgLog->setSelectionBehavior(QAbstractItemView::SelectRows); ui->sgLog->setSelectionMode(QAbstractItemView::SingleSelection); ui->sgLog->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->sgLog->setWordWrap(true); ui->sgLog->horizontalHeader()->setStretchLastSection(true); ui->sgLog->setColumnWidth(0, 100); ui->sgLog->setColumnWidth(1, 100); ui->sgLog->setColumnWidth(2, 170); ui->sgLog->setColumnWidth(3, 170); // ======================================================================== // НАСТРОЙКА ТАБЛИЦЫ КОМАНД (адаптивная) // ======================================================================== headers.clear(); headers << "Команда" << "Ответ"; ui->sgCommands->setHorizontalHeaderLabels(headers); ui->sgCommands->setSelectionBehavior(QAbstractItemView::SelectRows); ui->sgCommands->setSelectionMode(QAbstractItemView::SingleSelection); ui->sgCommands->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->sgCommands->setWordWrap(true); ui->sgCommands->horizontalHeader()->setStretchLastSection(true); ui->sgCommands->setColumnWidth(0, 135); // ======================================================================== // НАСТРОЙКА ТАБЛИЦЫ РАНДОМНЫХ ИНТЕРВАЛОВ (адаптивная) // ======================================================================== headers.clear(); headers << "Имя" << "От" << "До"; ui->sgRandomInt->setHorizontalHeaderLabels(headers); ui->sgRandomInt->setSelectionBehavior(QAbstractItemView::SelectRows); ui->sgRandomInt->setSelectionMode(QAbstractItemView::SingleSelection); ui->sgRandomInt->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->sgRandomInt->setWordWrap(true); ui->sgRandomInt->horizontalHeader()->setStretchLastSection(true); ui->sgRandomInt->setColumnWidth(0, 70); ui->sgRandomInt->setColumnWidth(1, 30); ui->sgRandomInt->setColumnWidth(2, 30); // ======================================================================== // НАСТРОЙКА ТАБЛИЦЫ ВЕБ СЕРВЕРОВ (адаптивная) // ======================================================================== headers.clear(); headers << "Порт" << "Тип" << "Ссылка"; ui->sgWebServers->setColumnCount(headers.size()); ui->sgWebServers->setHorizontalHeaderLabels(headers); ui->sgWebServers->setSelectionBehavior(QAbstractItemView::SelectRows); ui->sgWebServers->setSelectionMode(QAbstractItemView::SingleSelection); ui->sgWebServers->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->sgWebServers->setWordWrap(true); ui->sgWebServers->horizontalHeader()->setStretchLastSection(true); ui->sgWebServers->setColumnWidth(0, 80); ui->sgWebServers->setColumnWidth(1, 100); // Подключение двойного клика connect(ui->sgWebServers, &QTableWidget::cellDoubleClicked, this, &uGeneral::on_sgWebServers_cellDoubleClicked); // ======================================================================== // НАСТРОЙКА ТАБЛИЦЫ ТАЙМЕРОВ (адаптивная) // ======================================================================== ui->sgTimers->setSelectionBehavior(QAbstractItemView::SelectRows); ui->sgTimers->setSelectionMode(QAbstractItemView::SingleSelection); ui->sgTimers->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->sgTimers->setWordWrap(true); ui->sgTimers->horizontalHeader()->setStretchLastSection(true); ui->sgTimers->setColumnWidth(0, 40); ui->sgTimers->setColumnWidth(1, 400); ui->sgTimers->setColumnWidth(2, 80); ui->sgTimers->setColumnWidth(3, 40); // Подключение сигналов таблицы таймеров connect(ui->sgTimers, &QTableWidget::cellClicked, this, &uGeneral::on_sgTimers_cellClicked); connect(ui->sgTimers, &QTableWidget::cellDoubleClicked, this, &uGeneral::on_sgTimers_cellDoubleClicked); db->LoadTimers(ui->sgTimers, m_timers); updateTimerTable(); // ======================================================================== // ИНИЦИАЛИЗАЦИЯ СПЕЦИАЛЬНЫХ ВИДЖЕТОВ (адаптивные) // ======================================================================== ui->widget->initForm("Звук", "sgSounds", true); ui->widget_2->initForm("Файлы", "sgFiles", true); ui->widget_3->initForm("Нейроконструктор", "sgNeiro"); // Принудительно устанавливаем Expanding политику для правильной работы в layout ui->widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); ui->widget_2->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); ui->widget_3->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // Подключение двойных кликов для специальных виджетов connect(ui->widget->tableWidget(), &QTableWidget::cellDoubleClicked, this, &uGeneral::onSoundGridDoubleClicked); connect(ui->widget_2->tableWidget(), &QTableWidget::cellDoubleClicked, this, &uGeneral::onFilesGridDoubleClicked); connect(ui->widget_3->tableWidget(), &QTableWidget::cellDoubleClicked, this, &uGeneral::onNeiroGridDoubleClicked); } void uGeneral::initializeManagers() { soundManager = new SoundManager(this); 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->setPrefix(ui->edtGPTPrefix->text()); if (ui->rbGC->isChecked()) { nnManager->setCurrentNetworkType(NeuralNetworkManager::GigaChat); nnManager->setGigaChatCredentials(ui->edtAIP1->text(), ui->edtAIP2->text()); } else if (ui->rbDS->isChecked()) { nnManager->setCurrentNetworkType(NeuralNetworkManager::DeepSeek); nnManager->setApiKey(NeuralNetworkManager::DeepSeek, ui->edtAIP1->text()); } else if (ui->rbCG->isChecked()) { nnManager->setCurrentNetworkType(NeuralNetworkManager::ChatGPT); nnManager->setApiKey(NeuralNetworkManager::ChatGPT, ui->edtAIP1->text()); } else if (ui->RBCustom->isChecked()) { nnManager->setCurrentNetworkType(NeuralNetworkManager::Ollama); nnManager->setApiKey(NeuralNetworkManager::Ollama, ui->edtAIP1->text()); nnManager->setOllamaUrl(ui->edtAIP2->text()); } m_SoundFiles = new MediaFileManager(); ui->widget->setSoundManager(m_SoundFiles); ui->widget->setManagerType(FSingleGrid::SoundManager); ui->widget->setDatabase(db); m_TextFiles = new MediaFileManager(); ui->widget_2->setTextManager(m_TextFiles); ui->widget_2->setManagerType(FSingleGrid::TextManager); ui->widget_2->setDatabase(db); m_neuralTemplateManager = new NeuralTemplateManager(this); ui->widget_3->setTemplateManager(m_neuralTemplateManager); ui->widget_3->setManagerType(FSingleGrid::TemplateManager); ui->widget_3->setDatabase(db); m_counterManager = new CounterManager(this); if (db) { m_counterManager->initialize(db); } // Настраиваем таблицу счётчиков (предполагаем, что она уже есть в .ui с objectName "sgCounters") setupCountersTable(); // Подключаем сигналы для обновления таблицы при изменениях connect(m_counterManager, &CounterManager::dataChanged, this, &uGeneral::updateCountersTable); connect(m_counterManager, &CounterManager::counterAdded, this, [this](const QString&, int) { updateCountersTable(); }); connect(m_counterManager, &CounterManager::counterRemoved, this, [this](const QString&) { updateCountersTable(); }); connect(m_counterManager, &CounterManager::counterUpdated, this, [this](const QString&, const QString&) { updateCountersTable(); }); connect(m_counterManager, &CounterManager::counterIncremented, this, [this](const QString&, int) { updateCountersTable(); }); m_commandProcessor = new CommandProcessor(this); if (db) { loadCommandsFromTableWidget(); loadRandomRangesFromTableWidget(); loadRandomResponseGroupsFromDatabase(); loadSoundsFromDatabase(); loadTextFromDatabase(); loadTemplatesFromDatabase(); CommandProcessor::Context context; context.userManager = m_userManager; context.twitchAPI = twitchAPI; context.soundManager = soundManager; context.neuralManager = nnManager; context.randomManager = m_randomManager; context.neuralTemplateManager = m_neuralTemplateManager; context.randomResponses = m_randomResponses; context.mediaFileManager = m_SoundFiles; context.channel = ui->edtChannel->text(); context.notifyVolume = ui->tbNotifyVolume->value(); context.counterManager = m_counterManager; m_commandProcessor->setContext(context); } } void uGeneral::setupCountersTable(){ // Предполагаем, что sgCounters уже есть в ui ui->sgCounters->setColumnCount(2); QStringList headers; headers << "Счётчик" << "Значение"; ui->sgCounters->setHorizontalHeaderLabels(headers); ui->sgCounters->setSelectionBehavior(QAbstractItemView::SelectRows); ui->sgCounters->setSelectionMode(QAbstractItemView::SingleSelection); ui->sgCounters->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->sgCounters->horizontalHeader()->setStretchLastSection(true); ui->sgCounters->setColumnWidth(0, 200); ui->sgCounters->setColumnWidth(1, 80); updateCountersTable(); } void uGeneral::updateCountersTable() { ui->sgCounters->setRowCount(0); ui->cbCounters->clear(); if (!m_counterManager) return; QVector counters = m_counterManager->getAllCounters(); for (const auto &c : counters) { int row = ui->sgCounters->rowCount(); ui->sgCounters->insertRow(row); ui->sgCounters->setItem(row, 0, new QTableWidgetItem(c.name)); ui->sgCounters->setItem(row, 1, new QTableWidgetItem(QString::number(c.count))); ui->cbCounters->addItem(c.name); } } 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); } } } } 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); } } } } void uGeneral::loadTextFromDatabase() { if (!db || !m_TextFiles) return; QTableWidget* table = ui->widget_2->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_TextFiles->addFile(name, file); } } } } void uGeneral::loadTemplatesFromDatabase() { if (!db || !m_neuralTemplateManager) return; QTableWidget* table = ui->widget_3->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_neuralTemplateManager->addTemplate(name, file); } } } } 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); } } } } void uGeneral::loadRandomResponseGroupsFromDatabase() { if (!db || !m_randomResponses) return; 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 tempResponseList; if (!db->LoadRandomResponses(groupName, &tempResponseList)) { qWarning() << "Failed to load responses for group:" << groupName; continue; } for (int j = 0; j < tempResponseList.count(); ++j) { QString response = tempResponseList.item(j)->text(); if (!response.isEmpty()) { m_randomResponses->addResponse(groupName, response); } } } } void uGeneral::setupTwitchComponents() { m_twitchClient = new WebSocketClient(this); connect(m_twitchClient, &WebSocketClient::onNewMessage, this, &uGeneral::handleNewMessage); connect(m_twitchClient, &WebSocketClient::onConnected, this, &uGeneral::handleConnected); connect(m_twitchClient, &WebSocketClient::onDisconnected, this, &uGeneral::handleDisconnected); connect(m_twitchClient, &WebSocketClient::onError, this, &uGeneral::handleError); twitchAPI = new TTwAPI(this); connect(twitchAPI, &TTwAPI::apiError, this, &uGeneral::onApiError); connect(twitchAPI, &TTwAPI::tokenExpired, this, &uGeneral::onTokenExpired); connect(twitchAPI, &TTwAPI::rateLimitExceeded, this, &uGeneral::onRateLimit); initTwitchAPI(); } void uGeneral::setupUserWidget() { m_userWidget = new UserWidget(m_userManager, this); QVBoxLayout* layout = qobject_cast(ui->tab_7->layout()); if (!layout) { layout = new QVBoxLayout(ui->tab_7); ui->tab_7->setLayout(layout); } layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_userWidget); connect(m_userWidget, &UserWidget::banUserRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->banUser(userId); } }); connect(m_userWidget, &UserWidget::timeoutUserRequested, this, [this](const QString &userId, const QString &userName, int minutes) { if (twitchAPI) { twitchAPI->banUserTime(userId, minutes); } }); connect(m_userWidget, &UserWidget::unbanUserRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->unbanUser(userId); } }); connect(m_userWidget, &UserWidget::setModeratorRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->setModerator(userId); User* user = m_userManager->findUserById(userId); if (user) { user->isModerator = true; m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } } }); connect(m_userWidget, &UserWidget::removeModeratorRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->delModerator(userId); User* user = m_userManager->findUserById(userId); if (user) { user->isModerator = false; m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } } }); connect(m_userWidget, &UserWidget::setVIPRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->setVIP(userId); User* user = m_userManager->findUserById(userId); if (user) { user->isVIP = true; m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } } }); connect(m_userWidget, &UserWidget::removeVIPRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->delVIP(userId); User* user = m_userManager->findUserById(userId); if (user) { user->isVIP = false; m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } } }); connect(m_userWidget, &UserWidget::userInfoRequested, this, [this](const QString &userId, const QString &userName) { Q_UNUSED(userName); User* user = m_userManager->findUserById(userId); if (user) { QString info = QString( "Информация о пользователе:\n" "Имя: %1\n" "ID: %2\n" "Логин: %3\n" "Сообщений: %4\n" "Модератор: %5\n" "VIP: %6\n" "Подписчик: %7\n" "Последняя активность: %8" ).arg(user->displayName) .arg(user->id) .arg(user->login) .arg(user->messageCount) .arg(user->isModerator ? "Да" : "Нет") .arg(user->isVIP ? "Да" : "Нет") .arg(user->isSubscriber ? "Да" : "Нет") .arg(user->lastMessageTime.isValid() ? user->lastMessageTime.toString("dd.MM.yyyy hh:mm:ss") : "Неизвестно"); QMessageBox::information(this, "Информация о пользователе", info); } }); } void uGeneral::setupAuthHandlers() { m_authBot = new TAuth(this); 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) { LogManager::instance()->info("General", "setupAuthHandlers", "Сервер авторизации запущен"); }); m_authStreamer = new TAuth(this); 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) { LogManager::instance()->info("General", "setupAuthHandlers", "Сервер авторизации запущен"); }); m_authDA = new TAuth(this); 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) { LogManager::instance()->info("General", "setupAuthHandlers", "Сервер авторизации запущен"); }); } uGeneral::~uGeneral() { for (HttpServer *server : m_notificationServers) { server->stop(); delete server; } m_notificationServers.clear(); for (HttpServerChat *server : m_chatServers) { server->stop(); delete server; } m_chatServers.clear(); if (m_commandProcessor) { delete m_commandProcessor; m_commandProcessor = nullptr; } if (m_randomManager) { delete m_randomManager; m_randomManager = nullptr; } if (m_neuralTemplateManager) { delete m_neuralTemplateManager; m_neuralTemplateManager = nullptr; } delete m_counterManager; delete m_createNotifyDialog; delete m_createChatDialog; delete ui; delete m_userManager; delete fLinkForm; delete m_twitchClient; delete m_authBot; delete m_authStreamer; delete m_authDA; } // ============================================================================ // ОСТАЛЬНЫЕ МЕТОДЫ (без изменений, кроме адаптивных настроек выше) // ============================================================================ void uGeneral::setTwitchConnected(bool connected) { m_isTwitchConnected = connected; if (connected) { for (TimerInfo& timer : m_timers) { if (timer.isActive && !timer.timer) { startTimer(timer); } } } else { for (TimerInfo& timer : m_timers) { if (timer.timer) { stopTimer(timer); } } } } void uGeneral::loadEmoties() { bttvProvider.fetchGlobal(); sevenTVProvider.fetchGlobal(); User user = twitchAPI->getUserByLogin(ui->edtChannel->text()); bttvProvider.fetchCustom(user.id); sevenTVProvider.fetchCustom(user.id); } void uGeneral::loadChatBadges() { if (!twitchAPI) return; m_chatBadges.clear(); QVector globalBadges; twitchAPI->getGlobalChatBadges(globalBadges); m_chatBadges.append(globalBadges); QVector customBadges; twitchAPI->getCustomChatBadges(customBadges); m_chatBadges.append(customBadges); } void uGeneral::updateTimerTable() { ui->sgTimers->setRowCount(0); for (const TimerInfo& timer : m_timers) { int row = ui->sgTimers->rowCount(); ui->sgTimers->insertRow(row); QTableWidgetItem* enabledItem = new QTableWidgetItem(); enabledItem->setCheckState(timer.isActive ? Qt::Checked : Qt::Unchecked); ui->sgTimers->setItem(row, 0, enabledItem); QTableWidgetItem* messageItem = new QTableWidgetItem(timer.message); ui->sgTimers->setItem(row, 1, messageItem); QTableWidgetItem* intervalItem = new QTableWidgetItem(QString::number(timer.interval)); ui->sgTimers->setItem(row, 2, intervalItem); QTableWidgetItem* announcementItem = new QTableWidgetItem(); announcementItem->setCheckState(timer.isAnnouncement ? Qt::Checked : Qt::Unchecked); ui->sgTimers->setItem(row, 3, announcementItem); } } void uGeneral::startTimer(TimerInfo& timerInfo) { if (!m_isTwitchConnected) { return; } if (timerInfo.timer) { timerInfo.timer->stop(); delete timerInfo.timer; } timerInfo.timer = new QTimer(this); connect(timerInfo.timer, &QTimer::timeout, this, [this, timerInfo]() { sendTimedMessage(timerInfo); }); timerInfo.timer->start(timerInfo.interval * 60000); } void uGeneral::stopTimer(TimerInfo& timerInfo) { if (timerInfo.timer) { timerInfo.timer->stop(); delete timerInfo.timer; timerInfo.timer = nullptr; } } void uGeneral::sendTimedMessage(const TimerInfo& timerInfo) { if (!m_isTwitchConnected) { return; } if (timerInfo.isAnnouncement) { if (twitchAPI) { twitchAPI->sendAnnouncement(timerInfo.message); } } else { if (m_twitchClient) { m_twitchClient->sendChatMessage(ui->edtChannel->text(), timerInfo.message); } } } void uGeneral::on_btnTimerAdd_clicked() { QString message = ui->edtTimerMessage->text().trimmed(); QString intervalStr = ui->edtTimerInterval->text().trimmed(); if (message.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите сообщение!"); return; } bool ok; int interval = intervalStr.toInt(&ok); if (!ok || interval <= 0) { QMessageBox::warning(this, "Ошибка", "Введите корректный интервал в минутах!"); return; } TimerInfo newTimer; newTimer.id = m_nextTimerId++; newTimer.message = message; newTimer.interval = interval; newTimer.isActive = true; newTimer.isAnnouncement = false; newTimer.timer = nullptr; m_timers.append(newTimer); if (m_isTwitchConnected) { startTimer(m_timers.last()); } updateTimerTable(); db->SaveTimers(ui->sgTimers, m_timers); ui->edtTimerMessage->clear(); ui->edtTimerInterval->setText("10"); } void uGeneral::on_btnTimerEdit_clicked() { int currentRow = ui->sgTimers->currentRow(); if (currentRow < 0 || currentRow >= m_timers.size()) { QMessageBox::warning(this, "Ошибка", "Выберите таймер для редактирования!"); return; } QString message = ui->edtTimerMessage->text().trimmed(); QString intervalStr = ui->edtTimerInterval->text().trimmed(); if (message.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите сообщение!"); return; } bool ok; int interval = intervalStr.toInt(&ok); if (!ok || interval <= 0) { QMessageBox::warning(this, "Ошибка", "Введите корректный интервал в минутах!"); return; } TimerInfo& timer = m_timers[currentRow]; bool wasActive = timer.isActive; if (wasActive) { stopTimer(timer); } timer.message = message; timer.interval = interval; if (wasActive && m_isTwitchConnected) { startTimer(timer); } updateTimerTable(); db->SaveTimers(ui->sgTimers, m_timers); } void uGeneral::on_btnTimerDelete_clicked() { int currentRow = ui->sgTimers->currentRow(); if (currentRow < 0 || currentRow >= m_timers.size()) { QMessageBox::warning(this, "Ошибка", "Выберите таймер для удаления!"); return; } TimerInfo timer = m_timers[currentRow]; stopTimer(timer); m_timers.removeAt(currentRow); updateTimerTable(); db->SaveTimers(ui->sgTimers, m_timers); } void uGeneral::on_btnTimerTest_clicked() { int currentRow = ui->sgTimers->currentRow(); if (currentRow < 0 || currentRow >= m_timers.size()) { QMessageBox::warning(this, "Ошибка", "Выберите таймер для теста!"); return; } const TimerInfo& timer = m_timers[currentRow]; sendTimedMessage(timer); } void uGeneral::on_sgTimers_cellClicked(int row, int column) { if (row < 0 || row >= m_timers.size()) return; TimerInfo& timer = m_timers[row]; if (column == 0) { bool newState = ui->sgTimers->item(row, column)->checkState() == Qt::Checked; if (newState != timer.isActive) { m_timers[row].isActive = newState; if (newState) { if (m_isTwitchConnected) { startTimer(m_timers[row]); } } else { stopTimer(m_timers[row]); } db->SaveTimers(ui->sgTimers, m_timers); } } else if (column == 3) { bool newState = ui->sgTimers->item(row, column)->checkState() == Qt::Checked; if (newState != timer.isAnnouncement) { m_timers[row].isAnnouncement = newState; db->SaveTimers(ui->sgTimers, m_timers); } } } void uGeneral::onTimerTimeout(int timerId) { Q_UNUSED(timerId); } void uGeneral::on_sgTimers_cellDoubleClicked(int row, int column) { Q_UNUSED(column); if (row < 0 || row >= m_timers.size()) return; const TimerInfo& timer = m_timers[row]; ui->edtTimerMessage->setText(timer.message); ui->edtTimerInterval->setText(QString::number(timer.interval)); } void uGeneral::sendRequestToAI(const QString &q) { NeuralNetworkManager::NetworkType type; if (ui->rbGC->isChecked()) { type = NeuralNetworkManager::GigaChat; nnManager->setGigaChatCredentials( ui->edtAIP1->text(), ui->edtAIP2->text() ); } if (ui->rbCG->isChecked()) { type = NeuralNetworkManager::ChatGPT; } if (ui->rbDS->isChecked()) { type = NeuralNetworkManager::DeepSeek; } if (ui->RBCustom->isChecked()) { type = NeuralNetworkManager::Ollama; } nnManager->setApiKey(type, ui->edtAIP1->text()); nnManager->setPrefix(ui->edtGPTPrefix->text()); nnManager->setOllamaUrl(ui->edtAIP2->text()); nnManager->sendMessage(q, type); } void uGeneral::onSoundGridDoubleClicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("||" + ui->widget->tableWidget()->item(ui->widget->tableWidget()->currentRow(), 0)->text() + "||"); } void uGeneral::onFilesGridDoubleClicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("|(" + ui->widget_2->tableWidget()->item(ui->widget_2->tableWidget()->currentRow(), 0)->text() + "|("); } void uGeneral::onNeiroGridDoubleClicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("<|" + ui->widget_3->tableWidget()->item(ui->widget_3->tableWidget()->currentRow(), 0)->text() + "<|"); } void uGeneral::initTwitchAPI() { QString botClientID = ui->edtBotClientID->text(); QString botToken = ui->edtBotToken->text(); QString botTokenStreamer = ui->edtBotTokenStreamer->text(); QString channel = ui->edtChannel->text(); QString botName = ui->edtBotName->text(); if (botClientID.isEmpty() || botToken.isEmpty() || botTokenStreamer.isEmpty() || channel.isEmpty() || botName.isEmpty()) { LogManager::instance()->warning("uGeneral", "initTwitchAPI", "Не все данные для инициализации TTwAPI заполнены. API не инициализировано."); return; } if (twitchAPI) { delete twitchAPI; } twitchAPI = new TTwAPI(this); connect(twitchAPI, &TTwAPI::apiError, this, &uGeneral::onApiError); connect(twitchAPI, &TTwAPI::tokenExpired, this, &uGeneral::onTokenExpired); connect(twitchAPI, &TTwAPI::rateLimitExceeded, this, &uGeneral::onRateLimit); twitchAPI->init( botClientID, botToken, botTokenStreamer, channel, botName ); LogManager::instance()->info("uGeneral", "initTwitchAPI", "TTwAPI успешно инициализировано"); } void uGeneral::onApiError(const QString &method, const QString &error) { LogManager::instance()->error("ttw_api", method, error); } void uGeneral::onTokenExpired(const QString &error) { LogManager::instance()->error("uGeneral", "onTokenExpired", error); } void uGeneral::onRateLimit() { LogManager::instance()->warning("uGeneral", "onRateLimit", "Превышен лимит запросов"); } UserManager* uGeneral::getUserManager() { return m_userManager; } void uGeneral::onAIResponseReceived(const QString &response) { if (m_twitchClient) { QString chatResponse = response; if (chatResponse.length() > 400) { chatResponse = chatResponse.left(397) + "..."; } m_twitchClient->sendChatMessage(ui->edtChannel->text(), chatResponse); } } void uGeneral::onAIErrorOccurred(const QString &error) { LogManager::instance()->error("General", "onAIErrorOccurred", error); if (m_twitchClient) { m_twitchClient->sendChatMessage(ui->edtChannel->text(), "Извините, нейросеть временно недоступна"); } } void uGeneral::handleConnected() { LogManager::instance()->info("General", "handleConnected", "Connected"); setTwitchConnected(true); loadChatBadges(); if (m_isTwitchConnected) { for (TimerInfo& timer : m_timers) { if (timer.isActive && !timer.timer) { startTimer(timer); } } LogManager::instance()->info("General", "handleConnected", "Connected"); } } void uGeneral::handleDisconnected() { LogManager::instance()->info("General", "handleDisconnected", "Disconnected"); setTwitchConnected(false); if (m_isTwitchConnected) { for (TimerInfo& timer : m_timers) { if (timer.isActive && !timer.timer) { stopTimer(timer); } } } } void uGeneral::closeEvent(QCloseEvent *event) { Q_UNUSED(event); } void uGeneral::toCommands(QString command, QString response) { int row = ui->sgCommands->rowCount(); ui->sgCommands->insertRow(row); QTableWidgetItem *commandItem = new QTableWidgetItem(command); QTableWidgetItem *responseItem = new QTableWidgetItem(response); ui->sgCommands->setItem(row, 0, commandItem); ui->sgCommands->setItem(row, 1, responseItem); ui->sgCommands->scrollToBottom(); } void uGeneral::onLogEntryAdded(const LogEntry& entry) { if (!shouldShowLogEntry(entry.level)) { return; } addLogToTable(entry, ui->sgLog->rowCount()); } void uGeneral::onLogCleared() { 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; } } void uGeneral::addLogToTable(const LogEntry &entry, int row) { ui->sgLog->insertRow(row); LogSettings settings = LogManager::instance()->settings(); QColor color = settings.colors.value(entry.level, LogEntry::levelToColor(entry.level)); QTableWidgetItem* timeItem = new QTableWidgetItem( entry.timestamp.toString("hh:mm:ss")); timeItem->setForeground(color); QTableWidgetItem* typeItem = new QTableWidgetItem( LogEntry::levelToString(entry.level)); typeItem->setForeground(color); QTableWidgetItem* moduleItem = new QTableWidgetItem(entry.module); moduleItem->setForeground(color); QTableWidgetItem* methodItem = new QTableWidgetItem(entry.method); methodItem->setForeground(color); QTableWidgetItem* messageItem = new QTableWidgetItem(entry.message); messageItem->setForeground(color); 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(); } void uGeneral::applyLogFilter() { ui->sgLog->setRowCount(0); QList allEntries = LogManager::instance()->allEntries(); for (const LogEntry &entry : allEntries) { if (shouldShowLogEntry(entry.level)) { addLogToTable(entry, ui->sgLog->rowCount()); } } } void uGeneral::loadSettings(){ ui->edtBotName->setText(db->readSetting(ui->edtBotName->objectName(),"")); ui->edtChannel->setText(db->readSetting(ui->edtChannel->objectName(),"")); ui->edtBotToken->setText(db->readSetting(ui->edtBotToken->objectName(),"")); ui->edtBotClientID->setText(db->readSetting(ui->edtBotClientID->objectName(),"")); ui->edtBotTokenStreamer->setText(db->readSetting(ui->edtBotTokenStreamer->objectName(),"")); ui->cbTTVAutoLogin->setChecked(db->readSetting(ui->cbTTVAutoLogin->objectName()) == "True"); ui->edtDACode->setText(db->readSetting(ui->edtDACode->objectName(),"")); ui->edtDAClientID->setText(db->readSetting(ui->edtDAClientID->objectName(),"")); ui->edtDARedirectURL->setText(db->readSetting(ui->edtDARedirectURL->objectName(),"")); ui->edtDAClientSecret->setText(db->readSetting(ui->edtDAClientSecret->objectName(),"")); ui->cbDAAutoLogin->setChecked(db->readSetting(ui->cbDAAutoLogin->objectName()) == "True"); ui->edtGPTPrefix->setText(db->readSetting(ui->edtGPTPrefix->objectName(),"")); ui->edtAIP1->setText(db->readSetting(ui->edtAIP1->objectName(),"")); ui->edtAIP2->setText(db->readSetting(ui->edtAIP2->objectName(),"")); ui->edtKandiKey->setText(db->readSetting(ui->edtKandiKey->objectName(),"")); ui->edtKandiSecret->setText(db->readSetting(ui->edtKandiSecret->objectName(),"")); int rg = db->readSetting("aiIndex","0").toInt(); switch (rg) { case 0: ui->rbGC->setChecked(true); ui->rbDS->setChecked(false); ui->rbCG->setChecked(false); ui->RBCustom->setChecked(false); break; case 1: ui->rbGC->setChecked(false); ui->rbDS->setChecked(true); ui->rbCG->setChecked(false); ui->RBCustom->setChecked(false); break; case 2: ui->rbGC->setChecked(false); ui->rbDS->setChecked(false); ui->rbCG->setChecked(true); ui->RBCustom->setChecked(false); break; case 3: ui->rbGC->setChecked(false); ui->rbDS->setChecked(false); ui->rbCG->setChecked(false); ui->RBCustom->setChecked(true); break; } ui->edtNotifyFileName->setText(db->readSetting(ui->edtNotifyFileName->objectName(),"")); ui->edtNotifyFileNameMod->setText(db->readSetting(ui->edtNotifyFileNameMod->objectName(),"")); ui->edtNotifyFileNameSub->setText(db->readSetting(ui->edtNotifyFileNameSub->objectName(),"")); ui->edtNotifyFileNameVip->setText(db->readSetting(ui->edtNotifyFileNameVip->objectName(),"")); ui->chEnNotify->setChecked(db->readSetting(ui->chEnNotify->objectName()) == "True"); ui->chEnNotifyMod->setChecked(db->readSetting(ui->chEnNotifyMod->objectName()) == "True"); ui->chEnNotifySub->setChecked(db->readSetting(ui->chEnNotifySub->objectName()) == "True"); ui->chEnNotifyVip->setChecked(db->readSetting(ui->chEnNotifyVip->objectName()) == "True"); ui->tbNotifyVolume->setValue(db->readSetting(ui->tbNotifyVolume->objectName()).toInt()); ui->tbNotifyVolumeMod->setValue(db->readSetting(ui->tbNotifyVolumeMod->objectName()).toInt()); ui->tbNotifyVolumeSub->setValue(db->readSetting(ui->tbNotifyVolumeSub->objectName()).toInt()); ui->tbNotifyVolumeVip->setValue(db->readSetting(ui->tbNotifyVolumeVip->objectName()).toInt()); ui->cbNotifyFileAgain1->setChecked(db->readSetting(ui->cbNotifyFileAgain1->objectName()) == "True"); ui->cbNotifyFileAgain2->setChecked(db->readSetting(ui->cbNotifyFileAgain2->objectName()) == "True"); ui->cbNotifyFileAgain3->setChecked(db->readSetting(ui->cbNotifyFileAgain3->objectName()) == "True"); db->LoadTableWidget(ui->sgCommands); db->LoadTableWidget(ui->sgRandomInt); FSingleGrid *form = ui->widget; form->setDatabase(db); QTableWidget *table = form->findChild("sgSounds"); db->LoadTableWidget(table); form = ui->widget_2; form->setDatabase(db); table = form->findChild("sgFiles"); db->LoadTableWidget(table); form = ui->widget_3; form->setDatabase(db); table = form->findChild("sgNeiro"); db->LoadTableWidget(table); db->LoadRandomGroups(ui->lbRandomGroup); ui->cbTheme->setCurrentIndex(db->readSetting(ui->cbTheme->objectName(), "0").toInt()); db->LoadTableWidget(ui->sgWebServers); loadSavedChats(); loadSavedNotifications(); } void uGeneral::on_btnGetTockenBot_clicked() { QString clientId = ui->edtBotClientID->text(); if (clientId.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите Client ID"); return; } QString scope = "moderator:manage:shoutouts+" "moderator:manage:announcements+" "moderator:manage:banned_users+" "moderator:manage:warnings+" "moderator:read:followers+" "channel:manage:raids+" "channel:manage:moderators+" "channel:read:redemptions+" "chat:read+" "chat:edit+" "user:read:emotes"; scope = scope.replace(":", "%3A"); QString authUrl = QString("https://id.twitch.tv/oauth2/authorize?" "client_id=%1&" "redirect_uri=http://localhost:8089&" "response_type=token&" "scope=%2") .arg(clientId) .arg(scope); if (!fLinkForm) { fLinkForm = new uLink(this); } fLinkForm->setLinkText(authUrl); fLinkForm->show(); m_authBot->startServer(authUrl, false); } void uGeneral::on_btnOpenFolderSettings_clicked() { QString roamingPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QUrl url = QUrl::fromLocalFile(roamingPath); QDesktopServices::openUrl(url); } void uGeneral::on_btnGetTokenStreamer_clicked() { QString clientId = ui->edtBotClientID->text(); if (clientId.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите Client ID"); return; } QString scope = "channel:manage:broadcast+" "channel:read:subscriptions+" "channel:read:redemptions+" "user:read:email"; scope = scope.replace(":", "%3A"); QString authUrl = QString("https://id.twitch.tv/oauth2/authorize?" "client_id=%1&" "redirect_uri=http://localhost:8089&" "response_type=token&" "scope=%2") .arg(clientId) .arg(scope); m_authStreamer->startServer(authUrl, true); } void uGeneral::onTokenReceived(const QString &token) { ui->edtBotToken->setText(token); if (fLinkForm && fLinkForm->isVisible()) { fLinkForm->close(); } db->writeSetting(ui->edtBotToken->objectName(), ui->edtBotToken->text()); } void uGeneral::onTokenReceived2(const QString &token) { ui->edtBotTokenStreamer->setText(token); db->writeSetting(ui->edtBotTokenStreamer->objectName(), ui->edtBotTokenStreamer->text()); } void uGeneral::onTokenReceived3(const QString &token) { ui->edtDACode->setText(token); db->writeSetting(ui->edtDACode->objectName(), ui->edtDACode->text()); } void uGeneral::onAuthError(const QString &error) { Q_UNUSED(error); } void uGeneral::on_btnDAGetCode_clicked() { QString clientId = ui->edtDAClientID->text(); QString clientSecret = ui->edtDAClientSecret->text(); QString redirectUri = ui->edtDARedirectURL->text(); if (clientId.isEmpty() || clientSecret.isEmpty() || redirectUri.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Заполните все поля для DonationAlerts"); return; } QString encodedRedirectUri = QString::fromUtf8(QUrl::toPercentEncoding(redirectUri)); QString authUrl = QString("https://www.donationalerts.com/oauth/authorize?" "client_id=%1&" "redirect_uri=%2&" "response_type=code&" "scope=oauth-donation-index") .arg(clientId) .arg(encodedRedirectUri); m_authDA->startServer(authUrl, true); } void uGeneral::on_btnImportSettings_clicked() { // TODO } void uGeneral::on_btnExportSettings_clicked() { // TODO } void uGeneral::on_RBCustom_pressed() { ui->lblAPI1->setText("API Token"); ui->lblAPI1->setVisible(true); ui->edtAIP1->setVisible(true); ui->lblAPI2->setText("URL"); ui->lblAPI2->setVisible(true); ui->edtAIP2->setVisible(true); ui->lblAPI3->setText(""); ui->lblAPI3->setVisible(false); ui->edtAIP3->setVisible(false); ui->cbOllama->setVisible(true); ui->edtAIP1->setEchoMode(QLineEdit::Password); ui->edtAIP2->setEchoMode(QLineEdit::Normal); ui->edtAIP3->setEchoMode(QLineEdit::Password); } void uGeneral::on_rbGC_clicked() { ui->lblAPI1->setText("ClientID"); ui->lblAPI1->setVisible(true); ui->edtAIP1->setVisible(true); ui->lblAPI2->setText("Autorization Code"); ui->lblAPI2->setVisible(true); ui->edtAIP2->setVisible(true); ui->lblAPI3->setText(""); ui->lblAPI3->setVisible(false); ui->edtAIP3->setVisible(false); ui->cbOllama->setVisible(false); ui->edtAIP1->setEchoMode(QLineEdit::Password); ui->edtAIP2->setEchoMode(QLineEdit::Password); ui->edtAIP3->setEchoMode(QLineEdit::Password); } void uGeneral::on_rbDS_clicked() { ui->lblAPI1->setText("API Token"); ui->lblAPI1->setVisible(true); ui->edtAIP1->setVisible(true); ui->lblAPI2->setText(""); ui->lblAPI2->setVisible(false); ui->edtAIP2->setVisible(false); ui->lblAPI3->setText(""); ui->lblAPI3->setVisible(false); ui->edtAIP3->setVisible(false); ui->cbOllama->setVisible(false); ui->edtAIP1->setEchoMode(QLineEdit::Password); ui->edtAIP2->setEchoMode(QLineEdit::Password); ui->edtAIP3->setEchoMode(QLineEdit::Password); } void uGeneral::on_rbCG_clicked() { ui->lblAPI1->setText("API Token"); ui->lblAPI1->setVisible(true); ui->edtAIP1->setVisible(true); ui->lblAPI2->setText(""); ui->lblAPI2->setVisible(false); ui->edtAIP2->setVisible(false); ui->lblAPI3->setText(""); ui->lblAPI3->setVisible(false); ui->edtAIP3->setVisible(false); ui->cbOllama->setVisible(false); ui->edtAIP1->setEchoMode(QLineEdit::Password); ui->edtAIP2->setEchoMode(QLineEdit::Password); ui->edtAIP3->setEchoMode(QLineEdit::Password); } void uGeneral::on_btnNotifyCheck_clicked() { QString fn = ui->edtNotifyFileName->text(); soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolume->value()); soundManager->playSound(SoundManager::Channel2); } void uGeneral::on_btnNotifyCheckMod_clicked() { QString fn = ui->edtNotifyFileNameMod->text(); soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeMod->value()); soundManager->playSound(SoundManager::Channel2); } void uGeneral::on_btnNotifyCheckVip_clicked() { QString fn = ui->edtNotifyFileNameVip->text(); soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeVip->value()); soundManager->playSound(SoundManager::Channel2); } void uGeneral::on_btnNotifyCheckSub_clicked() { QString fn = ui->edtNotifyFileNameSub->text(); soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeSub->value()); soundManager->playSound(SoundManager::Channel2); } void uGeneral::on_btnNotifyOpen_clicked() { QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл для уведомлений", QDir::homePath(), "Звуковой файл (*.mp3);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; } ui->edtNotifyFileName->setText(sourceFile); } void uGeneral::on_btnNotifyOpenMod_clicked() { QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл для уведомлений", QDir::homePath(), "Звуковой файл (*.mp3);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; } ui->edtNotifyFileNameMod->setText(sourceFile); } void uGeneral::on_btnNotifyOpenVip_clicked() { QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл для уведомлений", QDir::homePath(), "Звуковой файл (*.mp3);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; } ui->edtNotifyFileNameVip->setText(sourceFile); } void uGeneral::on_btnNotifyOpenSub_clicked() { QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл для уведомлений", QDir::homePath(), "Звуковой файл (*.mp3);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; } ui->edtNotifyFileNameSub->setText(sourceFile); } void uGeneral::playNotify(const bool &isMod, const bool &isVip, const bool &isSub) { if (isMod && ui->chEnNotifyMod->isChecked() && !ui->edtNotifyFileNameMod->text().isEmpty()) { QString fn; if (ui->cbNotifyFileAgain1->isChecked()) fn = ui->edtNotifyFileName->text(); else fn = ui->edtNotifyFileNameMod->text(); if (!fn.isEmpty()) { if (QFile::exists(fn)) { soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeMod->value()); soundManager->playSound(SoundManager::Channel2); } return; } } if (isVip && ui->chEnNotifyVip->isChecked() && !ui->edtNotifyFileNameVip->text().isEmpty()) { QString fn; if (ui->cbNotifyFileAgain2->isChecked()) fn = ui->edtNotifyFileName->text(); else fn = ui->edtNotifyFileNameVip->text(); if (!fn.isEmpty()) { soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeVip->value()); soundManager->playSound(SoundManager::Channel2); } return; } if (isSub && ui->chEnNotifySub->isChecked() && !ui->edtNotifyFileNameSub->text().isEmpty()) { QString fn; if (ui->cbNotifyFileAgain3->isChecked()) fn = ui->edtNotifyFileName->text(); else fn = ui->edtNotifyFileNameSub->text(); if (!fn.isEmpty()) { soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeSub->value()); soundManager->playSound(SoundManager::Channel2); } return; } if (ui->chEnNotify->isChecked() && !ui->edtNotifyFileName->text().isEmpty()) { QString fn = ui->edtNotifyFileName->text(); if (QFile::exists(fn)) { soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolume->value()); soundManager->playSound(SoundManager::Channel2); } return; } } 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 notifyModFile = ui->edtNotifyFileNameMod->text(); if (!notifyModFile.isEmpty() && QFile::exists(notifyModFile)) { // загружается по необходимости } 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); LogManager::instance()->debug("uGeneral", "handleNewMessage", message); m_userManager->m_totalMessages++; if (m_userWidget) { m_userWidget->updateStatistics(); } if (ui->cbTextToSpeach->isChecked()) { return; } playNotify(msg.isMod, msg.isVIP, msg.isSubscriber); QString processedMessage = processTwitchMessage(msg); QString formattedNickname = formatNicknameWithBadges(msg); addChatMessage(formattedNickname, processedMessage); if (m_counterManager) { m_counterManager->processMessage(msg.message); // или cleanedText } QString cleanedText = cleanMessageFromAllEmotes(msg.message); // Удаляем ссылки (опционально) cleanedText.remove(QRegularExpression("https?://\\S+")); cleanedText = cleanedText.trimmed(); // Проверяем наличие русских букв bool hasRussian = false; for (const QChar& ch : cleanedText) { if (ch.unicode() >= 0x0400 && ch.unicode() <= 0x04FF) { hasRussian = true; break; } } if (!hasRussian && !cleanedText.isEmpty()) { // Здесь нужно вызвать перевод (например, через API) // sendToTranslate(cleanedText); LogManager::instance()->debug("uGeneral", "handleNewMessage", "Требуется перевод: " + cleanedText); } 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); } } QString uGeneral::cleanMessageFromAllEmotes(const QString& message) const { QString cleaned = message; cleaned = bttvProvider.cleanMessage(cleaned); cleaned = sevenTVProvider.cleanMessage(cleaned); return cleaned; } 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; QString response = m_commandProcessor->generateResponse(username, command, parameters); if (!response.isEmpty()) { sendChatResponse(response); } } void uGeneral::sendChatResponse(const QString &response) { m_twitchClient->sendChatMessage(ui->edtChannel->text(), response); } void uGeneral::handleError(const QString &errorMessage){ Q_UNUSED(errorMessage); } QString uGeneral::processTwitchMessage(const TwitchMessage &msg) { QString processedMessage = msg.message; if (!msg.emotes.isEmpty()) { processedMessage = replaceTwitchEmotes(msg.emotes, processedMessage); } processedMessage = replaceCustomEmotes(processedMessage); return processedMessage; } QString uGeneral::replaceTwitchEmotes(const QString &emotesData, const QString &message) { if (emotesData.isEmpty()) return message; QString result = message; QStringList emoteList = emotesData.split('/', Qt::SkipEmptyParts); struct Replacement { int start; int end; QString html; }; QList replacements; for (const QString &emote : emoteList) { QStringList parts = emote.split(':'); if (parts.size() != 2) continue; QString emoteId = parts[0]; QStringList positions = parts[1].split(',', Qt::SkipEmptyParts); for (const QString &pos : positions) { QStringList startEnd = pos.split('-'); if (startEnd.size() != 2) continue; bool ok; int start = startEnd[0].toInt(&ok); if (!ok) continue; int end = startEnd[1].toInt(&ok); if (!ok) continue; QString emoteUrl = QString("https://static-cdn.jtvnw.net/emoticons/v2/%1/default/dark/1.0").arg(emoteId); QString emoteHtml = QString("\"%2\"") .arg(emoteUrl) .arg(message.mid(start, end - start + 1)); replacements.append({start, end, emoteHtml}); } } std::sort(replacements.begin(), replacements.end(), [](const Replacement &a, const Replacement &b) { return a.start > b.start; }); for (const auto &replacement : replacements) { result.replace(replacement.start, replacement.end - replacement.start + 1, replacement.html); } return result; } QString uGeneral::replaceCustomEmotes(const QString &message) { QString result = message; QRegularExpression wordRegex("([a-zA-Z0-9_-]+)"); QRegularExpressionMatchIterator matches = wordRegex.globalMatch(message); struct EmoteReplacement { int start; int length; QString html; }; QList replacements; while (matches.hasNext()) { QRegularExpressionMatch match = matches.next(); QString word = match.captured(1); int start = match.capturedStart(1); int length = match.capturedLength(1); if (word.length() < 2) continue; QString bttvUrl = bttvProvider.getEmoteUrl(word); if (!bttvUrl.isEmpty()) { QString emoteHtml = QString("\"%2\"") .arg(bttvUrl) .arg(word); replacements.append({start, length, emoteHtml}); continue; } QString sevenTVUrl = sevenTVProvider.getEmoteUrl(word); if (!sevenTVUrl.isEmpty()) { QString emoteHtml = QString("\"%2\"") .arg(sevenTVUrl) .arg(word); replacements.append({start, length, emoteHtml}); continue; } } std::sort(replacements.begin(), replacements.end(), [](const EmoteReplacement &a, const EmoteReplacement &b) { return a.start > b.start; }); for (const auto &replacement : replacements) { result.replace(replacement.start, replacement.length, replacement.html); } return result; } void uGeneral::on_pushButton_2_clicked() { if (ui->pushButton_2->text() == "Отключиться") { disconnectFromTwitch(); return; } connectToTwitch(); } void uGeneral::connectToTwitch() { int botTokenDays = 0; int streamerTokenDays = 0; QString botToken = ui->edtBotToken->text(); QString streamerToken = ui->edtBotTokenStreamer->text(); if (!twitchAPI->validateTwitchToken("Бот", botToken, botTokenDays)) { return; } if (!twitchAPI->validateTwitchToken("Стример", streamerToken, streamerTokenDays)) { return; } QString oauthToken = "oauth:" + botToken; QString nickname = ui->edtBotName->text(); QString channel = ui->edtChannel->text(); if (m_twitchClient->connectToTwitchChat(oauthToken, nickname, channel)) { ui->pushButton_2->setText("Отключиться"); ui->lbBotDays->setText(QString::number(botTokenDays)); ui->lbStreamerDays->setText(QString::number(streamerTokenDays)); setTwitchConnected(true); } } void uGeneral::disconnectFromTwitch() { m_twitchClient->disconnectFromServer(); m_userManager->clear(); ui->pushButton_2->setText("Подключиться"); setTwitchConnected(false); } void uGeneral::on_lbRandomGroup_itemClicked(QListWidgetItem *item) { db->LoadRandomResponses(item->text(), ui->lbRandomRespons); } void uGeneral::on_pushButton_clicked() { LogManager::instance()->clear(); } void uGeneral::on_sgCommands_cellDoubleClicked(int row, int column) { Q_UNUSED(column); ui->edtCommand->setText(ui->sgCommands->item(row, 0)->text()); ui->textBrowser->setPlainText(ui->sgCommands->item(row, 1)->text()); } void uGeneral::on_btnRandAdd_clicked() { QString name = ui->edtRandomName->text().trimmed(); int otValue = ui->edtOt->value(); int toValue = ui->edtTo->value(); if (name.isEmpty()) { QMessageBox::warning(this, "Внимание", "Введите название!"); ui->edtRandomName->setFocus(); return; } if (otValue > toValue) { QMessageBox::warning(this, "Внимание", "Значение 'От' не может быть больше значения 'До'!"); ui->edtOt->setFocus(); return; } m_randomManager->rangeAdded(name, otValue, toValue); } void uGeneral::on_btnRandDel_clicked() { if (!ui->sgRandomInt->currentItem()) { QMessageBox::warning(this, "Внимание", "Выберите строку для удаления!"); return; } int row = ui->sgRandomInt->currentItem()->row(); m_randomManager->rangeRemoved(ui->sgRandomInt->item(row, 0)->text()); } void uGeneral::on_sgRandomInt_cellClicked(int row, int column) { Q_UNUSED(column); ui->edtRandomName->setText(ui->sgRandomInt->item(row, 0)->text()); ui->edtOt->setValue(ui->sgRandomInt->item(row, 1)->text().toInt()); ui->edtTo->setValue(ui->sgRandomInt->item(row, 2)->text().toInt()); } void uGeneral::on_sgRandomInt_cellDoubleClicked(int row, int column) { Q_UNUSED(column); QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[[" + ui->sgRandomInt->item(row, 0)->text() + "]]"); } void uGeneral::on_lbRandomGroup_doubleClicked(const QModelIndex &index) { Q_UNUSED(index); QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("{{" + ui->lbRandomGroup->currentItem()->text() + "}}"); } void uGeneral::on_lbRandomRespons_itemDoubleClicked(QListWidgetItem *item) { ui->edtRandomGroup->setText(ui->lbRandomGroup->currentItem()->text()); ui->edtRandomRespons->setText(item->text()); } void uGeneral::on_btnRandomAdd_clicked() { QString groupName = ui->edtRandomGroup->text().trimmed(); QString response = ui->edtRandomRespons->text().trimmed(); if (groupName.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите название группы!"); return; } if (response.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите ответ!"); return; } if (!db->AddGroupResponse(groupName, response)) { QMessageBox::critical(this, "Ошибка", "Не удалось добавить ответ в базу данных:\n" + db->lastError()); return; } db->LoadRandomGroups(ui->lbRandomGroup); QList items = ui->lbRandomGroup->findItems(groupName, Qt::MatchExactly); if (!items.isEmpty()) { ui->lbRandomGroup->setCurrentItem(items.first()); } if (ui->lbRandomGroup->currentItem()) { db->LoadRandomResponses(ui->lbRandomGroup->currentItem()->text(), ui->lbRandomRespons); } else { ui->lbRandomRespons->clear(); } ui->edtRandomRespons->clear(); m_randomResponses->addResponse(groupName, response); } void uGeneral::on_btnRandomDel_clicked() { db->DeleteResponse(ui->lbRandomGroup->currentItem()->text(), ui->lbRandomRespons->currentItem()->text()); db->LoadRandomResponses(ui->lbRandomGroup->currentItem()->text(), ui->lbRandomRespons); m_randomResponses->removeResponse(ui->lbRandomGroup->currentItem()->text(), ui->lbRandomRespons->currentItem()->text()); } void uGeneral::on_btnRmGroup_clicked() { db->DeleteGroup(ui->lbRandomGroup->currentItem()->text()); db->LoadRandomGroups(ui->lbRandomGroup); m_randomResponses->removeGroup(ui->lbRandomGroup->currentItem()->text()); } void uGeneral::on_btnGetDateFollow_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[FOLLOW]"); } void uGeneral::on_btnGetAgeAccaunt_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[AGE]"); } void uGeneral::on_btnGPT_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[AI]"); } void uGeneral::on_btnAIPic_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[Kandinsky]"); } void uGeneral::on_btnRandomUserName_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[RANDOMUSER]"); } void uGeneral::on_btnGetChannelStat_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[STAT]"); } void uGeneral::on_btnAddCommand_clicked() { QString name = ui->edtCommand->text().trimmed(); QString response = ui->textBrowser->toPlainText(); if (name.isEmpty()) { QMessageBox::warning(this, "Внимание", "Введите команду!"); LogManager::instance()->warning("uGeneral", "on_btnAddCommand_clicked", "Попытка добавить команду с пустым именем"); return; } 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)); db->SaveTableWidget(ui->sgCommands); m_commandProcessor->addCommand(name, response); LogManager::instance()->info("uGeneral", "on_btnAddCommand_clicked", QString("Добавлена команда: %1").arg(name)); } void uGeneral::on_btnRmCommand_clicked() { if (!ui->sgCommands->currentItem()) { 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); m_commandProcessor->deleteCommand(commandName); LogManager::instance()->info("uGeneral", "on_btnRmCommand_clicked", QString("Удалена команда: %1").arg(commandName)); } void uGeneral::on_btnEdtCommand_clicked() { if (!ui->sgCommands->currentItem()) { 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(); if (newName.isEmpty()) { QMessageBox::warning(this, "Внимание", "Введите новое имя команды!"); return; } QString oldCommand = ui->sgCommands->item(row, 0)->text(); ui->sgCommands->item(row, 0)->setText(newName); ui->sgCommands->item(row, 1)->setText(response); db->SaveTableWidget(ui->sgCommands); m_commandProcessor->editCommand(oldCommand, newName, response); LogManager::instance()->info("uGeneral", "on_btnEdtCommand_clicked", QString("Отредактирована команда в строке %1").arg(row)); } void uGeneral::loadQssFiles() { ui->cbTheme->clear(); ui->cbTheme->addItem("Без темы", ""); QString systemStylesPath = FileManager::instance().getPath(FileManager::SystemStyles); QDir systemStylesDir(systemStylesPath); if (systemStylesDir.exists()) { loadStylesFromDirectory(systemStylesDir, "Системные"); } else { qWarning() << "Папка системных стилей не найдена:" << systemStylesPath; systemStylesDir.mkpath("."); } QString userStylesPath = FileManager::instance().getPath(FileManager::Styles); QDir userStylesDir(userStylesPath); if (userStylesDir.exists()) { loadStylesFromDirectory(userStylesDir, "Пользовательские"); } else { qWarning() << "Папка пользовательских стилей не найдена:" << userStylesPath; userStylesDir.mkpath("."); } if (ui->cbTheme->count() <= 1) { qWarning() << "Темы не найдены, создаем базовые"; } } void uGeneral::loadStylesFromDirectory(const QDir &stylesDir, const QString &category) { if (!stylesDir.exists()) { return; } QStringList filters; filters << "*.qss" << "*.QSS"; QStringList qssFiles = stylesDir.entryList(filters, QDir::Files); foreach (const QString &file, qssFiles) { QString displayName = QString("%1: %2").arg(category).arg(file); ui->cbTheme->addItem(displayName, stylesDir.absoluteFilePath(file)); } } void uGeneral::on_cbTheme_currentIndexChanged(int index) { if (index < 0) return; QString filePath = ui->cbTheme->itemData(index).toString(); if (filePath.isEmpty()) { qApp->setStyleSheet(""); return; } applyStyleSheet(filePath); db->writeSetting(ui->cbTheme->objectName(), QString::number(index)); } void uGeneral::applyStyleSheet(const QString &filename) { QFile file(filename); if (!file.exists()) { qWarning() << "QSS файл не найден:" << filename; return; } if (!file.open(QFile::ReadOnly | QFile::Text)) { qWarning() << "Не удалось открыть QSS файл:" << filename; return; } QTextStream stream(&file); QString styleSheet = stream.readAll(); file.close(); qApp->setStyleSheet(styleSheet); } void uGeneral::on_btnWSCreateChat_clicked() { FCreateChat *createDialog = new FCreateChat(db, this); connect(createDialog, &FCreateChat::serverCreated, this, &uGeneral::onChatServerCreated); connect(createDialog, &FCreateChat::serverUpdated, this, &uGeneral::onChatServerUpdated); createDialog->setAttribute(Qt::WA_DeleteOnClose); createDialog->exec(); } void uGeneral::on_btnWSCreateNotify_clicked() { FCreateNotify *createDialog = new FCreateNotify(db, this); connect(createDialog, &FCreateNotify::serverCreated, this, &uGeneral::onNotifyServerCreated); createDialog->setAttribute(Qt::WA_DeleteOnClose); createDialog->exec(); } void uGeneral::onChatServerCreated(HttpServerChat *server, const QString &name) { if (server) { server->setParent(this); addChatServer(server, name); } } void uGeneral::onNotifyServerCreated(HttpServer *server, const QString &name) { if (!server) return; 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; } } } void uGeneral::addNotificationServer(HttpServer *server, const QString &name) { if (!server) return; m_notificationServers.append(server); QString serverName = name.isEmpty() ? QString("Уведомления %1").arg(m_notificationServers.size()) : name; QString url = QString("http://localhost:%1").arg(server->port()); addServerToTable(serverName, "Уведомления", server->port(), url, server); } void uGeneral::addChatServer(HttpServerChat *server, const QString &name) { if (!server) return; m_chatServers.append(server); QString serverName = name.isEmpty() ? QString("Чат %1").arg(m_chatServers.size()) : name; QString url = QString("http://localhost:%1").arg(server->port()); addServerToTable(serverName, "Чат", server->port(), url, server); } void uGeneral::addServerToTable(const QString &name, const QString &type, quint16 port, const QString &url, QObject *serverObj) { int row = ui->sgWebServers->rowCount(); ui->sgWebServers->insertRow(row); QTableWidgetItem *nameItem = new QTableWidgetItem(name); nameItem->setData(Qt::UserRole, QVariant::fromValue(serverObj)); ui->sgWebServers->setItem(row, 0, nameItem); ui->sgWebServers->setItem(row, 1, new QTableWidgetItem(type)); ui->sgWebServers->setItem(row, 2, new QTableWidgetItem(QString::number(port))); QTableWidgetItem *linkItem = new QTableWidgetItem(url); linkItem->setForeground(QBrush(Qt::blue)); linkItem->setFlags(linkItem->flags() | Qt::ItemIsEditable); ui->sgWebServers->setItem(row, 3, linkItem); } void uGeneral::addNotification(const QString &nickname, double amount, const QString &type) { if (m_notificationServers.isEmpty()) { return; } HttpServer *server = m_notificationServers.first(); Notification notif; notif.nickname = nickname.isEmpty() ? "Аноним" : nickname; notif.content = QString("%1 пожертвовал %2 руб.").arg(notif.nickname).arg(amount); notif.blockColor = "#4CAF50"; notif.borderColor = "#2E7D32"; notif.borderSize = 2; notif.duration = 10; notif.titleColor = "#FFFFFF"; notif.titleFamily = "Arial"; notif.titleSize = 20; notif.contentColor = "#F5F5F5"; notif.contentFamily = "Arial"; notif.contentSize = 16; notif.pageBackgroundColor = "transparent"; if (type == "donation") { notif.soundURL = "/sounds/donation.mp3"; } server->addNotification(notif); } void uGeneral::addChatMessage(const QString &nickname, const QString &message) { if (m_chatServers.isEmpty()) { return; } QString formattedNickname = nickname; for (HttpServerChat *server : qAsConst(m_chatServers)) { sendMessageToServer(server, formattedNickname, message); } } QString uGeneral::getBadgesHTML(const TwitchMessage &msg) { QString badgesHtml; for (const TwitchMessage::Badge &badge : msg.badges) { QString badgeUrl = getBadgeUrl(badge.name, badge.version); if (!badgeUrl.isEmpty()) { badgesHtml += QString("\"%2\"") .arg(badgeUrl) .arg(badge.name) .arg(QString("%1 (версия %2)").arg(badge.name).arg(badge.version)); } } auto addSystemBadge = [&](const QString &badgeName, bool condition) { if (condition) { bool alreadyHas = false; for (const TwitchMessage::Badge &badge : msg.badges) { if (badge.name == badgeName) { alreadyHas = true; break; } } if (!alreadyHas) { QString badgeUrl = getBadgeUrl(badgeName, 1); if (!badgeUrl.isEmpty()) { badgesHtml += QString("\"%2\"") .arg(badgeUrl) .arg(badgeName); } } } }; addSystemBadge("moderator", msg.isMod); addSystemBadge("vip", msg.isVIP); addSystemBadge("subscriber", msg.isSubscriber); if (!badgesHtml.isEmpty()) { badgesHtml += " "; } return badgesHtml; } QString uGeneral::getBadgeUrl(const QString &setId, int versionId) { for (const ChatBadge &badge : m_chatBadges) { if (badge.setId == setId) { for (const BadgeVersion &version : badge.versions) { if (version.id == versionId) { return version.imageUrl1x; } } if (!badge.versions.isEmpty()) { return badge.versions.first().imageUrl1x; } } } static const QMap defaultBadgeUrls = { {"moderator", "https://static-cdn.jtvnw.net/badges/v1/3267646d-33f0-4b17-b3df-f923a41db1d0/%1"}, {"vip", "https://static-cdn.jtvnw.net/badges/v1/1923c73d-70c9-4b1a-9c93-c8d8bd9e2f31/%1"}, {"subscriber", "https://static-cdn.jtvnw.net/badges/v1/5d9f2208-5dd8-11e7-8513-2ff4adfae661/%1"}, {"broadcaster", "https://static-cdn.jtvnw.net/badges/v1/5527c58c-fb7d-422d-b71b-f309dcb85cc1/%1"}, {"admin", "https://static-cdn.jtvnw.net/badges/v1/9ef7e029-4cdf-4d4d-a0d5-e2b3fb2583fe/%1"}, {"staff", "https://static-cdn.jtvnw.net/badges/v1/d97c37bd-a6f5-4c38-8f57-4e4bef88af34/%1"}, {"turbo", "https://static-cdn.jtvnw.net/badges/v1/bd444ec6-8f34-4bf9-91f4-af1e3428d80f/%1"}, {"partner", "https://static-cdn.jtvnw.net/badges/v1/d12a2e27-16f6-41d0-ab77-b780518f00a3/%1"}, {"bits", "https://static-cdn.jtvnw.net/badges/v1/73b5c3fb-24f9-4a82-a852-2f475b59411c/%1"} }; if (defaultBadgeUrls.contains(setId)) { return defaultBadgeUrls[setId].arg(versionId); } if (setId.startsWith("subscriber-")) { QString level = setId.mid(11); return QString("https://static-cdn.jtvnw.net/badges/v1/5d9f2208-5dd8-11e7-8513-2ff4adfae661/%1").arg(level); } if (setId.startsWith("bits-")) { QString level = setId.mid(5); return QString("https://static-cdn.jtvnw.net/badges/v1/73b5c3fb-24f9-4a82-a852-2f475b59411c/%1").arg(level); } return QString("https://static-cdn.jtvnw.net/badges/v1/%1/%2").arg(setId).arg(versionId); } QString uGeneral::formatNicknameWithBadges(const TwitchMessage &msg) { QString result = getBadgesHTML(msg); QString escapedName = msg.displayName.toHtmlEscaped(); QString escapedColor = msg.color.toHtmlEscaped(); if (!escapedColor.isEmpty() && escapedColor != "#000000") { result += QString("%2") .arg(escapedColor) .arg(escapedName); } else { result += escapedName; } return result; } void uGeneral::sendMessageToServer(HttpServerChat *server, const QString &nickname, const QString &message) { if (!server) return; StyleChat style; style.nick = nickname; style.context = message; style.fontFamily = server->getFontFamily(); style.fontSize = server->getFontSize(); style.fontColor = server->getFontColor(); style.blockColor = server->getBlockColor(); style.borderColor = server->getBorderColor(); style.borderSize = server->getBorderSize(); style.padding = server->getPadding(); style.timeMsg = server->getMessageTimeout(); style.bColor = server->getBackgroundColor(); style.transparency = server->getTransparency(); style.nick = nickname; style.context = message; server->addMessage(style); } void uGeneral::on_sgWebServers_cellDoubleClicked(int row, int column) { Q_UNUSED(column); if (row < 0 || row >= ui->sgWebServers->rowCount()) { return; } QTableWidgetItem *nameItem = ui->sgWebServers->item(row, 0); QTableWidgetItem *typeItem = ui->sgWebServers->item(row, 1); if (!nameItem || !typeItem) { return; } QString serverType = typeItem->text(); if (serverType.toLower() == "чат") { QObject *serverObj = nameItem->data(Qt::UserRole).value(); HttpServerChat *existingServer = qobject_cast(serverObj); if (existingServer) { FCreateChat *createChatDialog = new FCreateChat(db, this); createChatDialog->loadExistingServer(existingServer, nameItem->text()); connect(createChatDialog, &FCreateChat::serverUpdated, this, &uGeneral::onChatServerUpdated); createChatDialog->setAttribute(Qt::WA_DeleteOnClose); createChatDialog->exec(); } } else if (serverType.toLower() == "уведомления") { 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; } } } } void uGeneral::onChatServerUpdated(HttpServerChat *server, const QString &name) { if (!server) return; int index = m_chatServers.indexOf(server); if (index >= 0) { m_chatServers[index] = server; for (int row = 0; row < ui->sgWebServers->rowCount(); ++row) { QTableWidgetItem *portItem = ui->sgWebServers->item(row, 0); if (portItem && portItem->text().toUShort() == server->port()) { ui->sgWebServers->setItem(row, 0, new QTableWidgetItem(name)); ui->sgWebServers->setItem(row, 0, new QTableWidgetItem(QString::number(server->port()))); QString url = QString("http://localhost:%1").arg(server->port()); ui->sgWebServers->setItem(row, 2, new QTableWidgetItem(url)); break; } } } } void uGeneral::on_www_currentChanged(int index) { Q_UNUSED(index); } void uGeneral::on_edtBotName_selectionChanged() { } void uGeneral::on_edtBotName_editingFinished() { db->writeSetting(ui->edtBotName->objectName(), ui->edtBotName->text()); } void uGeneral::on_edtBotToken_textEdited(const QString &arg1) { Q_UNUSED(arg1); } void uGeneral::on_edtBotToken_editingFinished() { db->writeSetting(ui->edtBotToken->objectName(), ui->edtBotToken->text()); } void uGeneral::on_edtBotTokenStreamer_editingFinished() { db->writeSetting(ui->edtBotTokenStreamer->objectName(), ui->edtBotTokenStreamer->text()); } void uGeneral::on_edtBotClientID_editingFinished() { db->writeSetting(ui->edtBotClientID->objectName(), ui->edtBotClientID->text()); } void uGeneral::on_edtChannel_editingFinished() { db->writeSetting(ui->edtChannel->objectName(), ui->edtChannel->text()); } void uGeneral::on_edtDAClientID_editingFinished() { db->writeSetting(ui->edtDAClientID->objectName(), ui->edtDAClientID->text()); } void uGeneral::on_edtDAClientSecret_editingFinished() { db->writeSetting(ui->edtDAClientSecret->objectName(), ui->edtDAClientSecret->text()); } void uGeneral::on_edtDARedirectURL_editingFinished() { db->writeSetting(ui->edtDARedirectURL->objectName(), ui->edtDARedirectURL->text()); } void uGeneral::on_edtDACode_editingFinished() { db->writeSetting(ui->edtDACode->objectName(), ui->edtDACode->text()); } void uGeneral::on_cbDAAutoLogin_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->cbDAAutoLogin->objectName(), ui->cbDAAutoLogin->isChecked() ? "True" : "False"); } void uGeneral::on_edtGPTPrefix_editingFinished() { db->writeSetting(ui->edtGPTPrefix->objectName(), ui->edtGPTPrefix->text()); } void uGeneral::on_edtAIP1_editingFinished() { db->writeSetting(ui->edtAIP1->objectName(), ui->edtAIP1->text()); } void uGeneral::on_edtAIP2_editingFinished() { db->writeSetting(ui->edtAIP2->objectName(), ui->edtAIP2->text()); } void uGeneral::on_edtAIP3_editingFinished() { db->writeSetting(ui->edtAIP3->objectName(), ui->edtAIP3->text()); } void uGeneral::on_edtKandiKey_editingFinished() { db->writeSetting(ui->edtKandiKey->objectName(), ui->edtKandiKey->text()); } void uGeneral::on_edtKandiSecret_editingFinished() { db->writeSetting(ui->edtKandiSecret->objectName(), ui->edtKandiSecret->text()); } void uGeneral::on_cbOllama_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->cbOllama->objectName(), ui->cbOllama->isChecked() ? "True" : "False"); } void uGeneral::on_tbNotifyVolume_valueChanged(int value) { Q_UNUSED(value); } void uGeneral::on_edtNotifyFileName_editingFinished() { db->writeSetting(ui->edtNotifyFileName->objectName(), ui->edtNotifyFileName->text()); } void uGeneral::on_edtNotifyFileNameMod_editingFinished() { db->writeSetting(ui->edtNotifyFileNameMod->objectName(), ui->edtNotifyFileNameMod->text()); } void uGeneral::on_edtNotifyFileNameVip_editingFinished() { db->writeSetting(ui->edtNotifyFileNameVip->objectName(), ui->edtNotifyFileNameVip->text()); } void uGeneral::on_edtNotifyFileNameSub_editingFinished() { db->writeSetting(ui->edtNotifyFileNameSub->objectName(), ui->edtNotifyFileNameSub->text()); } void uGeneral::on_chEnNotify_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->chEnNotify->objectName(), ui->chEnNotify->isChecked() ? "True" : "False"); } void uGeneral::on_chEnNotifyMod_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->chEnNotifyMod->objectName(), ui->chEnNotifyMod->isChecked() ? "True" : "False"); } void uGeneral::on_chEnNotifyVip_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->chEnNotifyVip->objectName(), ui->chEnNotifyVip->isChecked() ? "True" : "False"); } void uGeneral::on_chEnNotifySub_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->chEnNotifySub->objectName(), ui->chEnNotifySub->isChecked() ? "True" : "False"); } void uGeneral::on_tbNotifyVolume_sliderPressed() { } void uGeneral::on_tbNotifyVolume_sliderReleased() { db->writeSetting(ui->tbNotifyVolume->objectName(), QString::number(ui->tbNotifyVolume->value())); } void uGeneral::on_tbNotifyVolumeMod_sliderReleased() { db->writeSetting(ui->tbNotifyVolumeMod->objectName(), QString::number(ui->tbNotifyVolumeMod->value())); } void uGeneral::on_tbNotifyVolumeVip_sliderReleased() { db->writeSetting(ui->tbNotifyVolumeVip->objectName(), QString::number(ui->tbNotifyVolumeVip->value())); } void uGeneral::on_tbNotifyVolumeSub_sliderReleased() { db->writeSetting(ui->tbNotifyVolumeSub->objectName(), QString::number(ui->tbNotifyVolumeSub->value())); } void uGeneral::on_cbNotifyFileAgain1_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->cbNotifyFileAgain1->objectName(), ui->cbNotifyFileAgain1->isChecked() ? "True" : "False"); } void uGeneral::on_cbNotifyFileAgain2_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->cbNotifyFileAgain2->objectName(), ui->cbNotifyFileAgain2->isChecked() ? "True" : "False"); } void uGeneral::on_cbNotifyFileAgain3_stateChanged(int arg1) { Q_UNUSED(arg1); db->writeSetting(ui->cbNotifyFileAgain3->objectName(), ui->cbNotifyFileAgain3->isChecked() ? "True" : "False"); } void uGeneral::on_btnThemesFolder_clicked() { QString userThemesPath = FileManager::instance().getPath(FileManager::Styles); QDesktopServices::openUrl(QUrl::fromLocalFile(userThemesPath)); } void uGeneral::loadSavedChats() { QList chats = db->loadAllChats(); for (const ChatSettings &settings : chats) { if (settings.type.toLower() == "chat") { HttpServerChat *server = new HttpServerChat( settings.fontList, settings.port, settings.backgroundColor, this ); server->setBlockColor(settings.blockColor); server->setBorderColor(settings.borderColor); server->setBorderSize(settings.borderSize); server->setPadding(settings.padding); server->setTransparency(settings.transparency); server->setFontFamily(settings.fontFamily); server->setFontSize(settings.fontSize); server->setFontColor(settings.fontColor); server->setFreez(settings.freez); server->setMessageTimeout(settings.messageTimeout); server->setDeleteMode(settings.deleteByTime, settings.maxMsgCount); if (server->start()) { m_chatServers.append(server); QString url = QString("http://localhost:%1").arg(settings.port); addServerToTable(settings.name, "Чат", settings.port, url, server); } else { delete server; } } } } void uGeneral::on_btnOpenStream_clicked() { QDesktopServices::openUrl("https://www.twitch.tv/" + ui->edtChannel->text()); } void uGeneral::on_btnAUserName_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[USERNAME]"); } void uGeneral::on_btnRmWebService_clicked() { int currentRow = ui->sgWebServers->currentRow(); if (currentRow < 0) { QMessageBox::warning(this, "Ошибка", "Выберите веб-сервис для удаления!"); return; } QTableWidgetItem *nameItem = ui->sgWebServers->item(currentRow, 0); QTableWidgetItem *typeItem = ui->sgWebServers->item(currentRow, 1); QTableWidgetItem *portItem = ui->sgWebServers->item(currentRow, 2); if (!nameItem || !typeItem || !portItem) { QMessageBox::warning(this, "Ошибка", "Не удалось получить данные о сервисе!"); return; } QString serviceName = nameItem->text(); QString serviceType = typeItem->text(); quint16 port = portItem->text().toUShort(); int result = QMessageBox::question(this, "Подтверждение удаления", QString("Вы уверены, что хотите удалить сервис '%1'?\n" "Тип: %2\n" "Порт: %3").arg(serviceName, serviceType, QString::number(port)), QMessageBox::Yes | QMessageBox::No); if (result != QMessageBox::Yes) { return; } QObject *serverObj = nameItem->data(Qt::UserRole).value(); bool deletedFromDB = false; if (serviceType.toLower() == "чат" || serviceType.toLower() == "chat") { HttpServerChat *chatServer = qobject_cast(serverObj); if (chatServer) { chatServer->stop(); int chatIndex = m_chatServers.indexOf(chatServer); if (chatIndex >= 0) { m_chatServers.removeAt(chatIndex); } deletedFromDB = db->deleteChat(port); delete chatServer; } } else if (serviceType.toLower() == "уведомления" || serviceType.toLower() == "notification") { HttpServer *notifyServer = qobject_cast(serverObj); if (notifyServer) { notifyServer->stop(); int notifyIndex = m_notificationServers.indexOf(notifyServer); if (notifyIndex >= 0) { m_notificationServers.removeAt(notifyIndex); } deletedFromDB = db->deleteNotification(port); delete notifyServer; } } ui->sgWebServers->removeRow(currentRow); if (deletedFromDB) { LogManager::instance()->info("uGeneral", "on_btnRmWebService_clicked", QString("Удален веб-сервис: %1 (тип: %2, порт: %3)") .arg(serviceName, serviceType, QString::number(port))); QMessageBox::information(this, "Успех", QString("Сервис '%1' успешно удален!").arg(serviceName)); } else { LogManager::instance()->warning("uGeneral", "on_btnRmWebService_clicked", QString("Сервис удален из интерфейса, но возникли проблемы с БД: %1") .arg(serviceName)); QMessageBox::warning(this, "Внимание", QString("Сервис '%1' удален из интерфейса, но не удалось удалить его из базы данных!") .arg(serviceName)); } } void uGeneral::on_sgCounters_cellClicked(int row, int column) { Q_UNUSED(column); QString name = ui->sgCounters->item(row, 0)->text(); int value = ui->sgCounters->item(row, 1)->text().toInt(); ui->edtWordCounter->setText(name); ui->sbStartCounter->setValue(value); } void uGeneral::on_sgCounters_cellDoubleClicked(int row, int column) { } void uGeneral::on_btnCounterAdd_clicked() { QString name = ui->edtWordCounter->text().trimmed(); // предположим, есть поле ввода if (name.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите название счётчика!"); return; } int initial = ui->sbStartCounter->value(); // спинбокс для начального значения if (m_counterManager->addCounter(name, initial)) { ui->edtWordCounter->clear(); ui->sbStartCounter->setValue(0); updateCountersTable(); } else { QMessageBox::warning(this, "Ошибка", "Не удалось добавить счётчик (возможно, уже существует)"); } } void uGeneral::on_btnCounterDelete_clicked() { int row = ui->sgCounters->currentRow(); if (row < 0) { QMessageBox::warning(this, "Ошибка", "Выберите счётчик для удаления!"); return; } QString name = ui->sgCounters->item(row, 0)->text(); if (m_counterManager->removeCounter(name)) { updateCountersTable(); } } void uGeneral::on_btnCounterEdit_clicked() { int row = ui->sgCounters->currentRow(); if (row < 0) { QMessageBox::warning(this, "Ошибка", "Выберите счётчик для редактирования!"); return; } QString oldName = ui->sgCounters->item(row, 0)->text(); QString newName = ui->edtWordCounter->text().trimmed(); int newValue = ui->sbStartCounter->value(); if (newName.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите новое название счётчика!"); return; } if (m_counterManager->updateCounter(oldName, newName, newValue)) { ui->edtWordCounter->clear(); ui->sbStartCounter->setValue(0); updateCountersTable(); } else { QMessageBox::warning(this, "Ошибка", "Не удалось обновить счётчик"); } } void uGeneral::on_btnCounterAtoText_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("|)" + ui->cbCounters->currentText() + "|)"); } void uGeneral::on_btnCRGet_clicked() { // Очистка старых данных qDeleteAll(m_rewards); m_rewards.clear(); // 1. Получаем ВСЕ награды канала QVector allRewards; twitchAPI->getCustomRewards(allRewards, false); // 2. Получаем только управляемые ботом QVector manageableRewards; twitchAPI->getCustomRewards(manageableRewards, true); // 3. Формируем множество ID управляемых наград QSet manageableIds; for (auto *r : manageableRewards) { manageableIds.insert(r->id); } // 4. Освобождаем manageableRewards (они больше не нужны) qDeleteAll(manageableRewards); manageableRewards.clear(); // 5. Проставляем флаг для всех наград for (auto *r : allRewards) { r->isManagedByBroadcaster = manageableIds.contains(r->id); } // 6. Сохраняем в член класса m_rewards = allRewards; // 7. Заполняем таблицу ui->sgCustomRewards->setRowCount(0); ui->sgCustomRewards->setColumnCount(3); QStringList headers = {"Название", "Цена", "Описание"}; ui->sgCustomRewards->setHorizontalHeaderLabels(headers); ui->sgCustomRewards->setRowCount(m_rewards.size()); for (int row = 0; row < m_rewards.size(); ++row) { TCustomReward *reward = m_rewards[row]; QTableWidgetItem *nameItem = new QTableWidgetItem(reward->title); nameItem->setData(Qt::UserRole, reward->id); ui->sgCustomRewards->setItem(row, 0, nameItem); QTableWidgetItem *costItem = new QTableWidgetItem(QString::number(reward->cost)); ui->sgCustomRewards->setItem(row, 1, costItem); QTableWidgetItem *descItem = new QTableWidgetItem(reward->prompt); ui->sgCustomRewards->setItem(row, 2, descItem); // Устанавливаем цвет фона QColor bgColor = reward->isManagedByBroadcaster ? QColor(144,238,144) : QColor(255,200,200); nameItem->setForeground(bgColor); costItem->setForeground(bgColor); descItem->setForeground(bgColor); } // Сбрасываем поля и кнопки ui->edtCRName->clear(); ui->edtCRPrompt->clear(); ui->sbCRCost->setValue(0); ui->btnCREdit->setEnabled(false); ui->btnCRDelete->setEnabled(false); } void uGeneral::on_sgCustomRewards_cellClicked(int row, int column) { // Получаем ID награды из первого столбца QTableWidgetItem *idItem = ui->sgCustomRewards->item(row, 0); if (!idItem) return; QString rewardId = idItem->data(Qt::UserRole).toString(); // Ищем объект награды в m_rewards по ID TCustomReward *reward = nullptr; for (auto *r : m_rewards) { if (r->id == rewardId) { reward = r; break; } } if (!reward) return; // защита от ошибок // Заполняем поля ввода ui->edtCRName->setText(reward->title); ui->edtCRPrompt->setText(reward->prompt); ui->sbCRCost->setValue(reward->cost); // Активируем кнопки только если награда управляется ботом bool canEdit = reward->isManagedByBroadcaster; ui->btnCREdit->setEnabled(canEdit); ui->btnCRDelete->setEnabled(canEdit); } void uGeneral::on_sgCustomRewards_cellDoubleClicked(int row, int column) { } void uGeneral::on_btnCRAdd_clicked() { // 1. Получаем данные из интерфейса QString title = ui->edtCRName->text().trimmed(); QString prompt = ui->edtCRPrompt->text().trimmed(); int cost = ui->sbCRCost->value(); // 2. Простейшая валидация if (title.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Название награды не может быть пустым."); return; } if (cost <= 0) { QMessageBox::warning(this, "Ошибка", "Стоимость должна быть больше 0."); return; } // 3. Вызываем API для создания награды // Предполагаем, что чекбокс "Требуется ввод пользователя" отсутствует, ставим false TCustomReward *newReward = twitchAPI->createCustomReward(title, QString::number(cost), prompt, false); if (!newReward) { QMessageBox::critical(this, "Ошибка", "Не удалось создать награду. Проверьте подключение к Twitch и права токена."); return; } // 4. Уведомляем пользователя об успехе QMessageBox::information(this, "Успех", "Награда успешно создана."); // 5. Очищаем поля ввода (опционально) ui->edtCRName->clear(); ui->edtCRPrompt->clear(); ui->sbCRCost->setValue(0); // 6. Обновляем список наград, чтобы новая появилась в таблице on_btnCRGet_clicked(); // перезагружает и обновляет таблицу // Важно: newReward был создан в куче, но после вызова on_btnCRGet_clicked() // старые указатели будут удалены, а новые получены. Указатель newReward // становится недействительным, поэтому не используем его дальше. } void uGeneral::on_btnCREdit_clicked() { // 1. Проверяем, что выбрана строка в таблице int currentRow = ui->sgCustomRewards->currentRow(); if (currentRow < 0) { QMessageBox::warning(this, "Ошибка", "Выберите награду для редактирования."); return; } // 2. Извлекаем ID награды из первого столбца (хранится в Qt::UserRole) QTableWidgetItem *idItem = ui->sgCustomRewards->item(currentRow, 0); if (!idItem) return; QString rewardId = idItem->data(Qt::UserRole).toString(); // 3. Ищем объект награды в векторе m_rewards TCustomReward *reward = nullptr; for (auto *r : m_rewards) { if (r->id == rewardId) { reward = r; break; } } if (!reward) { QMessageBox::critical(this, "Ошибка", "Не удалось найти данные награды."); return; } // 4. Проверяем, что награда управляется ботом (кнопка должна быть активна только для таких) if (!reward->isManagedByBroadcaster) { QMessageBox::warning(this, "Ошибка", "Нельзя редактировать награду, созданную не ботом."); return; } // 5. Получаем новые значения из полей ввода QString newTitle = ui->edtCRName->text().trimmed(); QString newPrompt = ui->edtCRPrompt->text().trimmed(); int newCost = ui->sbCRCost->value(); // 6. Валидация if (newTitle.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Название не может быть пустым."); return; } if (newCost <= 0) { QMessageBox::warning(this, "Ошибка", "Стоимость должна быть больше 0."); return; } // 7. Обновляем данные в объекте reward->title = newTitle; reward->prompt = newPrompt; reward->cost = newCost; // 8. Вызываем API для обновления награды twitchAPI->updateCustomReward(reward); // предполагается, что метод возвращает void // 9. Обновляем список наград (гарантирует синхронизацию с Twitch) on_btnCRGet_clicked(); // 10. Уведомляем пользователя об успехе QMessageBox::information(this, "Успех", "Награда обновлена."); } void uGeneral::on_btnCRDelete_clicked() { // 1. Проверяем, что выбрана строка в таблице int currentRow = ui->sgCustomRewards->currentRow(); if (currentRow < 0) { QMessageBox::warning(this, "Ошибка", "Выберите награду для удаления."); return; } // 2. Извлекаем ID награды из первого столбца QTableWidgetItem *idItem = ui->sgCustomRewards->item(currentRow, 0); if (!idItem) return; QString rewardId = idItem->data(Qt::UserRole).toString(); // 3. Находим объект награды в m_rewards (для проверки управляемости) TCustomReward *reward = nullptr; for (auto *r : m_rewards) { if (r->id == rewardId) { reward = r; break; } } if (!reward) { QMessageBox::critical(this, "Ошибка", "Не удалось найти данные награды."); return; } // 4. Проверяем, что награда управляется ботом if (!reward->isManagedByBroadcaster) { QMessageBox::warning(this, "Ошибка", "Нельзя удалить награду, созданную не ботом."); return; } // 5. Запрашиваем подтверждение QString title = reward->title; QMessageBox::StandardButton reply = QMessageBox::question( this, "Подтверждение удаления", QString("Вы уверены, что хотите удалить награду \"%1\"?").arg(title), QMessageBox::Yes | QMessageBox::No ); if (reply != QMessageBox::Yes) { return; // пользователь отменил } // 6. Вызываем API для удаления bool success = twitchAPI->deleteCustomReward(rewardId); // предполагаем, что метод возвращает bool if (!success) { QMessageBox::critical(this, "Ошибка", "Не удалось удалить награду. Проверьте подключение к Twitch и права токена."); return; } // 7. Уведомляем об успехе QMessageBox::information(this, "Успех", "Награда успешно удалена."); // 8. Обновляем список наград (удалённая исчезнет) on_btnCRGet_clicked(); // 9. Сбрасываем поля ввода и отключаем кнопки (так как выбор пропал) ui->edtCRName->clear(); ui->edtCRPrompt->clear(); ui->sbCRCost->setValue(0); ui->btnCREdit->setEnabled(false); ui->btnCRDelete->setEnabled(false); } void uGeneral::on_cbActions_currentIndexChanged(int index) { // 0 - ножатие кнопок // 1 - возспроизвести звук (вызвать веб сервис для звука) // 2 - вызвать вебсервис с параметрами switch (index) { case 0: ui->gbActionsKeys->setVisible(true); ui->gbActionsAudio->setVisible(false); ui->gbActionsNotify->setVisible(false); break; case 1: ui->gbActionsKeys->setVisible(false); ui->gbActionsAudio->setVisible(true); ui->gbActionsNotify->setVisible(false); break; case 2: ui->gbActionsKeys->setVisible(false); ui->gbActionsAudio->setVisible(false); ui->gbActionsNotify->setVisible(true); break; default: break; } } void uGeneral::on_btnOpenAudioFile_clicked() { QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл для уведомлений", QDir::homePath(), "Звуковой файл (*.mp3);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; } ui->edtActionAudio->setText(sourceFile); } void uGeneral::on_btnActionPicOpen_clicked() { QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл для уведомлений", QDir::homePath(), "Картинка (*.jpg;*.jpeg;*.png);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; } ui->edtActionPic->setText(sourceFile); } void uGeneral::on_btnActionAudioOpen_clicked() { QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл для уведомлений", QDir::homePath(), "Звуковой файл (*.mp3);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; } ui->edtActionSound->setText(sourceFile); }