#include "ugeneral.h" #include "fcreatenotify.h" #include "filemanager.h" #include "logmanager.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_isTwitchConnected(false) , m_notificationServers() , m_chatServers() , m_createNotifyDialog(nullptr) , m_createChatDialog(nullptr) , m_randomManager(nullptr) { // ============================================================================ // ИНИЦИАЛИЗАЦИЯ ИНТЕРФЕЙСА // ============================================================================ ui->setupUi(this); setWindowTitle("TTW Bot app"); LogSettings logSettings; logSettings.logToFile = true; logSettings.logToConsole = true; //logSettings.maxEntries = 5000; // Цвета для разных уровней logSettings.colors[LogLevel::Info] = Qt::darkGreen; logSettings.colors[LogLevel::Warning] = Qt::darkYellow; logSettings.colors[LogLevel::Error] = Qt::red; logSettings.colors[LogLevel::Debug] = Qt::gray; logSettings.colors[LogLevel::Critical] = Qt::darkRed; LogManager::initialize(logSettings); // Подключение сигналов к UI LogManager* logger = LogManager::instance(); connect(logger, &LogManager::entryAdded, this, &uGeneral::onLogEntryAdded); connect(logger, &LogManager::logCleared, this, &uGeneral::onLogCleared); connect(logger, &LogManager::errorAdded, this, &uGeneral::onLogEntryAdded); connect(logger, &LogManager::warningAdded, this, &uGeneral::onLogEntryAdded); connect(logger, &LogManager::infoAdded, this, &uGeneral::onLogEntryAdded); connect(logger, &LogManager::debugAdded, this, &uGeneral::onLogEntryAdded); connect(logger, &LogManager::criticalAdded, this, &uGeneral::onLogEntryAdded); // Запуск приложения - используем новый логгер LogManager::instance()->info("uGeneral", "constructor", "Главное окно создано"); // Инициализируем структуру папок FileManager::instance().initializeFolderStructure(); FileManager::instance().copyDefaultFiles(); // Получаем пути через FileManager QString sysPath = FileManager::instance().systemPath(); // Загружаем иконки tabIcons = { QIcon(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]); } setupButtonIcons(); // Загружаем QSS файлы при создании формы loadQssFiles(); // ============================================================================ // ИНИЦИАЛИЗАЦИЯ БАЗЫ ДАННЫХ // ============================================================================ initializeDatabase(); // ============================================================================ // НАСТРОЙКА ИНТЕРФЕЙСА ПОЛЬЗОВАТЕЛЯ // ============================================================================ setupUI(); // ============================================================================ // НАСТРОЙКА ТАБЛИЦ // ============================================================================ setupTables(); // ============================================================================ // ЗАГРУЗКА НАСТРОЕК // ============================================================================ loadSettings(); // ============================================================================ // НАСТРОЙКА TWITCH API И WEBSOCKET // ============================================================================ setupTwitchComponents(); // ============================================================================ // ИНИЦИАЛИЗАЦИЯ МЕНЕДЖЕРОВ // ============================================================================ initializeManagers(); // ============================================================================ // НАСТРОЙКА ВИДЖЕТА ПОЛЬЗОВАТЕЛЕЙ // ============================================================================ setupUserWidget(); // ============================================================================ // НАСТРОЙКА АВТОРИЗАЦИИ // ============================================================================ setupAuthHandlers(); // ============================================================================ // ИНИЦИАЛИЗАЦИЯ НЕЙРОННОЙ СЕТИ // ============================================================================ initializeNeuralNetwork(); 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(); if (db) { m_randomManager = new RandomManager(this); m_randomManager->initialize(db); } } // ============================================================================ // ПРИВАТНЫЕ МЕТОДЫ ДЛЯ ИНИЦИАЛИЗАЦИИ // ============================================================================ void uGeneral::setupButtonIcons() { // Получаем все кнопки на форме QList buttons = ui->www->findChildren(); // Если кнопки не в centralWidget, ищем во всем окне // QList buttons = 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")) { // Проверяем специальный случай RmGroup (регистрозависимый) if (button->objectName().contains("RmGroup")) { button->setIcon(tabIcons[12]); } else { button->setIcon(tabIcons[11]); } } else if (buttonName.contains("edt")) { button->setIcon(tabIcons[10]); } else if (buttonName.contains("open")) { button->setIcon(tabIcons[13]); } // Устанавливаем размер иконки (опционально) if (!button->icon().isNull()) { button->setIconSize(QSize(16, 16)); } } } /** * @brief Инициализация базы данных */ 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", "Успешное подключение к БД"); } } /** * @brief Настройка пользовательского интерфейса */ 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); } /** * @brief Настройка таблиц интерфейса */ 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->setColumnWidth(0, 100); // Дата ui->sgLog->setColumnWidth(1, 100); // Тип ui->sgLog->setColumnWidth(2, 170); // Модуль ui->sgLog->setColumnWidth(3, 170); // Метод ui->sgLog->setColumnWidth(4, 390); // Сообщение // ======================================================================== // НАСТРОЙКА ТАБЛИЦЫ КОМАНД // ======================================================================== headers.clear(); headers << "Команда" << "Ответ"; ui->sgCommands->setHorizontalHeaderLabels(headers); ui->sgCommands->setSelectionBehavior(QAbstractItemView::SelectRows); ui->sgCommands->setSelectionMode(QAbstractItemView::SingleSelection); ui->sgCommands->setEditTriggers(QAbstractItemView::NoEditTriggers); ui->sgCommands->setColumnWidth(0, 135); // Команда ui->sgCommands->setColumnWidth(1, 410); // Ответ // ======================================================================== // НАСТРОЙКА ТАБЛИЦЫ РАНДОМНЫХ ИНТЕРВАЛОВ // ======================================================================== headers.clear(); headers << "Имя" << "От" << "До"; ui->sgRandomInt->setHorizontalHeaderLabels(headers); ui->sgRandomInt->setSelectionBehavior(QAbstractItemView::SelectRows); ui->sgRandomInt->setSelectionMode(QAbstractItemView::SingleSelection); ui->sgRandomInt->setEditTriggers(QAbstractItemView::NoEditTriggers); 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->setColumnWidth(0, 80); // Порт ui->sgWebServers->setColumnWidth(1, 100); // Тип ui->sgWebServers->setColumnWidth(2, 250); // Ссылка // Подключение двойного клика 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); // Подключение сигналов таблицы таймеров 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"); // Подключение двойных кликов для специальных виджетов 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); } /** * @brief Инициализация менеджеров */ 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->setApiKey(NeuralNetworkManager::DeepSeek, "ваш_api_ключ"); // nnManager->setModel(NeuralNetworkManager::DeepSeek, "deepseek-chat"); m_SoundFiles = new MediaFileManager(); // Менеджер команд m_commandProcessor = new CommandProcessor(this); if (db) { // Загрузка команд из таблицы sgCommands в CommandProcessor loadCommandsFromTableWidget(); // Загрузка случайных диапазонов из таблицы sgRandomInt в RandomManager loadRandomRangesFromTableWidget(); // Загрузка групп случайных ответов из базы данных loadRandomResponseGroupsFromDatabase(); // Загрузка звуков loadSoundsFromDatabase(); // Настройка контекста для обработчика команд CommandProcessor::Context context; context.userManager = m_userManager; context.twitchAPI = twitchAPI; context.soundManager = soundManager; context.neuralManager = nnManager; context.randomManager = m_randomManager; context.randomResponses = m_randomResponses; context.mediaFileManager = m_SoundFiles; context.channel = ui->edtChannel->text(); context.notifyVolume = ui->tbNotifyVolume->value(); m_commandProcessor->setContext(context); } } void uGeneral::loadCommandsFromTableWidget() { if (!db || !m_commandProcessor) return; QTableWidget* table = ui->sgCommands; if (!table) return; for (int row = 0; row < table->rowCount(); ++row) { QTableWidgetItem *commandItem = table->item(row, 0); QTableWidgetItem *responseItem = table->item(row, 1); if (commandItem && responseItem) { QString command = commandItem->text().trimmed(); QString response = responseItem->text(); if (!command.isEmpty() && !response.isEmpty()) { m_commandProcessor->addCommand(command, response); } } } } /** * @brief Загрузка звуков */ void uGeneral::loadSoundsFromDatabase() { if (!db || !m_SoundFiles) return; QTableWidget* table = ui->widget->tableWidget(); if (!table) return; for (int row = 0; row < table->rowCount(); ++row) { QTableWidgetItem *nameItem = table->item(row, 0); QTableWidgetItem *fileItem = table->item(row, 1); if (nameItem && fileItem) { QString name = nameItem->text().trimmed(); QString file = fileItem->text().trimmed(); if (!name.isEmpty() && !file.isEmpty()) { m_SoundFiles->addFile(name, file); } } } } /** * @brief Загрузка случайных диапазонов из таблицы sgRandomInt в RandomManager */ void uGeneral::loadRandomRangesFromTableWidget() { if (!db || !m_randomManager) return; QTableWidget* table = ui->sgRandomInt; if (!table) return; for (int row = 0; row < table->rowCount(); ++row) { QTableWidgetItem *nameItem = table->item(row, 0); QTableWidgetItem *startItem = table->item(row, 1); QTableWidgetItem *endItem = table->item(row, 2); if (nameItem && startItem && endItem) { QString name = nameItem->text().trimmed(); int start = startItem->text().toInt(); int end = endItem->text().toInt(); if (!name.isEmpty() && start <= end) { m_randomManager->addRange(name, start, end); } } } } /** * @brief Загрузка групп случайных ответов из базы данных */ void uGeneral::loadRandomResponseGroupsFromDatabase() { if (!db || !m_randomResponses) return; // Создаем временный QListWidget для получения списка групп QListWidget tempGroupList; if (!db->LoadRandomGroups(&tempGroupList)) { qWarning() << "Failed to load random groups from database"; return; } // Для каждой группы загружаем её ответы for (int i = 0; i < tempGroupList.count(); ++i) { QString groupName = tempGroupList.item(i)->text().trimmed(); if (groupName.isEmpty()) continue; // Создаем временный QListWidget для получения ответов группы QListWidget tempResponseList; if (!db->LoadRandomResponses(groupName, &tempResponseList)) { qWarning() << "Failed to load responses for group:" << groupName; continue; } // Добавляем каждый ответ в RandomResponses for (int j = 0; j < tempResponseList.count(); ++j) { QString response = tempResponseList.item(j)->text(); if (!response.isEmpty()) { m_randomResponses->addResponse(groupName, response); } } } } /** * @brief Настройка компонентов Twitch */ void uGeneral::setupTwitchComponents() { // ======================================================================== // ИНИЦИАЛИЗАЦИЯ WEBSOCKET КЛИЕНТА ДЛЯ TWITCH // ======================================================================== m_twitchClient = new WebSocketClient(this); // Подключение сигналов WebSocket клиента 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); // ======================================================================== // ИНИЦИАЛИЗАЦИЯ TWITCH API // ======================================================================== twitchAPI = new TTwAPI(this); // Подключение сигналов Twitch API connect(twitchAPI, &TTwAPI::apiError, this, &uGeneral::onApiError); connect(twitchAPI, &TTwAPI::tokenExpired, this, &uGeneral::onTokenExpired); connect(twitchAPI, &TTwAPI::rateLimitExceeded, this, &uGeneral::onRateLimit); // Инициализация Twitch API initTwitchAPI(); } /** * @brief Настройка виджета пользователей */ void uGeneral::setupUserWidget() { // Создание виджета пользователей m_userWidget = new UserWidget(m_userManager, this); // Настройка layout для вкладки QVBoxLayout* layout = new QVBoxLayout(ui->tab_7); layout->setContentsMargins(0, 0, 0, 0); // Убираем отступы layout->addWidget(m_userWidget); ui->tab_7->setLayout(layout); // ======================================================================== // ПОДКЛЮЧЕНИЕ СИГНАЛОВ ВИДЖЕТА ПОЛЬЗОВАТЕЛЕЙ // ======================================================================== // Бан пользователя 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); // Обновляем статус в UserManager 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); // Обновляем статус в UserManager User* user = m_userManager->findUserById(userId); if (user) { user->isModerator = false; m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } } }); // Назначение VIP connect(m_userWidget, &UserWidget::setVIPRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->setVIP(userId); // Обновляем статус в UserManager User* user = m_userManager->findUserById(userId); if (user) { user->isVIP = true; m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } } }); // Удаление статуса VIP connect(m_userWidget, &UserWidget::removeVIPRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->delVIP(userId); // Обновляем статус в UserManager 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); } }); } /** * @brief Настройка обработчиков авторизации */ 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", "Сервер авторизации запущен"); }); // ======================================================================== // НАСТРОЙКА АВТОРИЗАЦИИ DA // ======================================================================== 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", "Сервер авторизации запущен"); }); } /** * @brief Инициализация нейронной сети */ void uGeneral::initializeNeuralNetwork() { nnManager = new NeuralNetworkManager(this); // Подключение сигналов менеджера нейронной сети connect(nnManager, &NeuralNetworkManager::responseReceived, this, &uGeneral::onNeuralResponse); connect(nnManager, &NeuralNetworkManager::errorOccurred, this, &uGeneral::onNeuralError); } 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; } 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) { // Не запускаем таймер, если нет подключения к Twitch 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) { // Отправка через API как оповещение 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) { } 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(), // RqUID ui->edtAIP2->text() // Basic авторизация в base64 ); } 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()); // Настройка Ollama nnManager->setOllamaUrl(ui->edtAIP2->text()); // Отправляем вопрос nnManager->sendMessage(q, type); } void uGeneral::onNeuralResponse(const QString &response) { // Отправляем ответ в Twitch чат qDebug() << "Ответ нейросети:" << response; } void uGeneral::onNeuralError(const QString &error) { LogManager::instance()->error("General", "onNeuralError", error); } 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; } // Обработчик ответа от AI void uGeneral::onAIResponseReceived(const QString &response) { // Отправляем ответ в чат if (m_twitchClient) { // Обрезаем ответ, если он слишком длинный для Twitch QString chatResponse = response; if (chatResponse.length() > 400) { chatResponse = chatResponse.left(397) + "..."; } m_twitchClient->sendChatMessage(ui->edtChannel->text(), chatResponse); } } // Обработчик ошибок AI 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", "Connectiong"); setTwitchConnected(true); loadChatBadges(); // Запускаем активные таймеры, если подключены к Twitch 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); // Запускаем активные таймеры, если подключены к Twitch 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); // Получаем цвет для уровня из настроек LogManager LogSettings settings = LogManager::instance()->settings(); QColor color = settings.colors.value(entry.level, LogEntry::levelToColor(entry.level)); // Колонка 0: Дата QTableWidgetItem* timeItem = new QTableWidgetItem( entry.timestamp.toString("hh:mm:ss")); timeItem->setForeground(color); // Колонка 1: Тип QTableWidgetItem* typeItem = new QTableWidgetItem( LogEntry::levelToString(entry.level)); typeItem->setForeground(color); // Колонка 2: Модуль QTableWidgetItem* moduleItem = new QTableWidgetItem(entry.module); moduleItem->setForeground(color); // Колонка 3: Метод QTableWidgetItem* methodItem = new QTableWidgetItem(entry.method); methodItem->setForeground(color); // Колонка 4: Сообщение 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); // Получаем все записи из LogManager 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); } // Устанавливаем ссылку в окно (предполагается, что в uLink есть метод setLink) fLinkForm->setLinkText(authUrl); fLinkForm->show(); // Запускаем сервер без открытия браузера (false) 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() { //ykui0quht3tvr06vfqhdj5idmhginn QString clientId = ui->edtBotClientID->text(); if (clientId.isEmpty()) { QMessageBox::warning(this, "Ошибка", "Введите Client ID"); return; } // Для стримера могут быть другие scope, если нужно 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); // Запускаем сервер с открытием браузера (true) m_authStreamer->startServer(authUrl, true); } // Обработчик получения токена void uGeneral::onTokenReceived(const QString &token) { ui->edtBotToken->setText(token); } void uGeneral::onTokenReceived2(const QString &token) { ui->edtBotTokenStreamer->setText(token); fLinkForm->close(); } void uGeneral::onTokenReceived3(const QString &token) { ui->edtDACode->setText(token); } void uGeneral::onAuthError(const QString &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() { } void uGeneral::on_btnExportSettings_clicked() { } 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() { } 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::onStatus(const QString &statusText, int statusCode) { qDebug() << "[STATUS]" << statusCode << statusText; } void uGeneral::onDisconnected(const QString &reason) { qDebug() << "[DISCONNECTED]" << reason; } void uGeneral::onJoined(const QString &nickname) { qDebug() << "[JOINED]" << nickname << "joined"; } void uGeneral::onMessage(const QString &message) { qDebug() << "[MESSAGE]" << message; } 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); } else { } return; } } // Проверяем VIP (второй приоритет) 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); } else { } 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); } else { } 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); } else { } return; } } /** * @brief Инициализирует звуковые уведомления */ void uGeneral::initializeNotificationSounds() { // Загружаем звук для обычных уведомлений QString notifyFile = ui->edtNotifyFileName->text(); if (!notifyFile.isEmpty() && QFile::exists(notifyFile)) { soundManager->loadSound(SoundManager::Channel2, notifyFile); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolume->value()); } else { } // Загружаем звук для модераторов QString notifyModFile = ui->edtNotifyFileNameMod->text(); if (!notifyModFile.isEmpty() && QFile::exists(notifyModFile)) { // Загружаем в отдельный канал, если SoundManager поддерживает несколько каналов // Или используем ту же логику, что и в playNotify } // Загружаем звук для VIP QString notifyVipFile = ui->edtNotifyFileNameVip->text(); if (!notifyVipFile.isEmpty() && QFile::exists(notifyVipFile)) { } // Загружаем звук для подписчиков QString notifySubFile = ui->edtNotifyFileNameSub->text(); if (!notifySubFile.isEmpty() && QFile::exists(notifySubFile)) { } } void uGeneral::handleNewMessage( const QString &message){ TwitchMessage msg = TwitchMessage::parse(message); // Проверяем и регистрируем пользователя QString userId = m_userManager->checkUser(msg.displayName, msg); m_userManager->m_totalMessages++; // Обновляем статистику и воспроизводим уведомление if (m_userWidget) { m_userWidget->updateStatistics(); } playNotify(msg.isMod, msg.isVIP, msg.isSubscriber); // Обрабатываем сообщение Twitch (смайлы, бейджи и т.д.) QString processedMessage = processTwitchMessage(msg); QString formattedNickname = formatNicknameWithBadges(msg); // Добавляем сообщение в чат addChatMessage(formattedNickname, processedMessage); // Обрабатываем команды если сообщение начинается с "!" if (msg.message.startsWith("!!!")) { QString cleanedMessage = msg.message.mid(3); // speachMessage(cleanedMessage); } else if (msg.message.startsWith("!")) { QString cleanedMessage = msg.message.mid(1); processUserCommand(userId, cleanedMessage); } else { // execClearMessage(msg.message); } } /** * @brief Обработка команды от пользователя */ void uGeneral::processUserCommand(const QString &username, const QString &commandText) { if (!m_commandProcessor || !m_userManager) return; // Разделяем команду и параметры QString command; QString parameters; int spacePos = commandText.indexOf(' '); if (spacePos != -1) { command = commandText.left(spacePos).trimmed().toLower(); parameters = commandText.mid(spacePos + 1).trimmed(); } else { command = commandText.trimmed().toLower(); } // Если команда пустая, ничего не делаем if (command.isEmpty()) return; // Генерируем ответ через CommandProcessor QString response = m_commandProcessor->generateResponse(username, command, parameters); // Если есть ответ, отправляем его в чат if (!response.isEmpty()) { sendChatResponse(response); } } /** * @brief Отправка ответа команды в чат */ void uGeneral::sendChatResponse(const QString &response) { m_twitchClient->sendChatMessage(ui->edtChannel->text(), response); } void uGeneral::handleError(const QString &errorMessage){ } // Метод для обработки Twitch сообщения с эмодзи QString uGeneral::processTwitchMessage(const TwitchMessage &msg) { QString processedMessage = msg.message; // 1. Обрабатываем Twitch эмодзи (из поля emotes) if (!msg.emotes.isEmpty()) { processedMessage = replaceTwitchEmotes(msg.emotes, processedMessage); } // 2. Обрабатываем BTTV и 7TV эмодзи processedMessage = replaceCustomEmotes(processedMessage); return processedMessage; } // Метод для замены Twitch эмодзи на HTML изображения QString uGeneral::replaceTwitchEmotes(const QString &emotesData, const QString &message) { if (emotesData.isEmpty()) return message; QString result = message; // Формат emotesData: "emote_id:start-end,start-end/emote_id2:start-end" 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; // Формируем HTML для эмодзи 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); // Пропускаем слишком короткие слова (меньше 2 символов) if (word.length() < 2) continue; // Проверяем BTTV эмодзи QString bttvUrl = bttvProvider.getEmoteUrl(word); if (!bttvUrl.isEmpty()) { QString emoteHtml = QString("\"%2\"") .arg(bttvUrl) .arg(word); replacements.append({start, length, emoteHtml}); continue; } // Проверяем 7TV эмодзи 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() != "Отключиться") { QString oauthToken = "oauth:" + ui->edtBotToken->text(); QString nickname = ui->edtBotName->text(); QString channel = ui->edtChannel->text(); m_twitchClient->connectToTwitchChat(oauthToken, nickname, channel); ui->pushButton_2->setText("Отключиться"); } else { m_twitchClient->disconnectFromServer(); m_userManager->clear(); ui->pushButton_2->setText("Подключиться"); setTwitchConnected(false); } } void uGeneral::execCommand(const QString &sender, const QString &message) { LogManager::instance()->debug("uGeneral", "execCommand", QString("Обработка команды от %1: %2").arg(sender).arg(message)); } 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(); } void uGeneral::on_btnRandomDel_clicked() { db->DeleteResponse(ui->lbRandomGroup->currentItem()->text(), ui->lbRandomRespons->currentItem()->text()); db->LoadRandomResponses(ui->lbRandomGroup->currentItem()->text(), ui->lbRandomRespons); } void uGeneral::on_btnRmGroup_clicked() { db->DeleteGroup(ui->lbRandomGroup->currentItem()->text()); db->LoadRandomGroups(ui->lbRandomGroup); } void uGeneral::on_btnAddUserName_clicked() { QTextCursor cursor = ui->textBrowser->textCursor(); cursor.insertText("[USERNAME]"); } 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; } // Просто добавляем в таблицу и сохраняем в БД // CommandProcessor будет читать из таблицы при обработке // Добавляем новую строку в таблицу int row = ui->sgCommands->rowCount(); ui->sgCommands->insertRow(row); ui->sgCommands->setItem(row, 0, new QTableWidgetItem(name)); ui->sgCommands->setItem(row, 1, new QTableWidgetItem(response)); // Сохраняем в БД db->SaveTableWidget(ui->sgCommands); LogManager::instance()->info("uGeneral", "on_btnAddCommand_clicked", QString("Добавлена команда: %1").arg(name)); } void uGeneral::on_btnRmCommand_clicked() { if (!ui->sgCommands->currentItem()) { QMessageBox::warning(this, "Внимание", "Выберите команду для удаления!"); LogManager::instance()->warning("uGeneral", "on_btnRmCommand_clicked", "Не выбрана команда для удаления"); return; } int row = ui->sgCommands->currentItem()->row(); QString commandName = ui->sgCommands->item(row, 0)->text(); ui->sgCommands->removeRow(row); db->SaveTableWidget(ui->sgCommands); LogManager::instance()->info("uGeneral", "on_btnRmCommand_clicked", QString("Удалена команда: %1").arg(commandName)); } void uGeneral::on_btnEdtCommand_clicked() { if (!ui->sgCommands->currentItem()) { QMessageBox::warning(this, "Внимание", "Выберите команду для редактирования!"); 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; } ui->sgCommands->item(row, 0)->setText(newName); ui->sgCommands->item(row, 1)->setText(response); db->SaveTableWidget(ui->sgCommands); LogManager::instance()->info("uGeneral", "on_btnEdtCommand_clicked", QString("Отредактирована команда в строке %1").arg(row)); } void uGeneral::loadQssFiles() { } void uGeneral::loadStylesFromDirectory(const QDir &stylesDir, const QString &category) { if (!stylesDir.exists()) { return; } // Получаем список QSS файлов в папке QStringList filters; filters << "*.qss" << "*.QSS"; QStringList qssFiles = stylesDir.entryList(filters, QDir::Files); // Добавляем файлы в ComboBox 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; // Получаем полный путь к файлу из userData QString filePath = ui->cbTheme->itemData(index).toString(); // Если выбрана "Без темы" (пустая строка в userData) 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); // Колонка 0: Название QTableWidgetItem *nameItem = new QTableWidgetItem(name); nameItem->setData(Qt::UserRole, QVariant::fromValue(serverObj)); ui->sgWebServers->setItem(row, 0, nameItem); // Колонка 1: Тип ui->sgWebServers->setItem(row, 1, new QTableWidgetItem(type)); // Колонка 2: Порт ui->sgWebServers->setItem(row, 2, new QTableWidgetItem(QString::number(port))); // Колонка 3: Ссылка (редактируемая для копирования) 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; } // Берем первый активный сервер (или можно выбрать по ID) 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); } } // Метод для получения HTML бейджей 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; } // Метод для получения URL бейджа 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; // или version.imageUrl2x для ретины } } // Если точной версии не нашли, возвращаем первую версию if (!badge.versions.isEmpty()) { return badge.versions.first().imageUrl1x; } } } // Стандартные Twitch бейджи (если не нашли в загруженных) 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); } // Для subscriber бейджей с уровнями 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); } // Для bits бейджей с уровнями 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); } // Пытаемся получить URL из стандартного API 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); // Экранируем HTML-символы 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(); qDebug() << "sendMessageToServer"; // Убираем HTML теги из обычного текста для совместимости // (если сервер их не поддерживает, можно оставить только для ника) 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() == "чат") { // Получаем объект сервера из user data 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() == "уведомления") { // Получаем объект сервера из user data QObject *serverObj = nameItem->data(Qt::UserRole).value(); HttpServer *existingServer = qobject_cast(serverObj); if (existingServer) { FCreateNotify *createNotifyDialog = new FCreateNotify(db, this); createNotifyDialog->loadExistingServer(existingServer, nameItem->text()); connect(createNotifyDialog, &FCreateNotify::serverUpdated, this, &uGeneral::onNotifyServerUpdated); createNotifyDialog->setAttribute(Qt::WA_DeleteOnClose); createNotifyDialog->exec(); } } } void uGeneral::loadSavedNotifications() { QList notifications = db->loadAllNotifications(); for (const NotificationSettings &settings : notifications) { if (settings.type.toLower() == "notification") { // Создаем сервер уведомлений HttpServer *server = new HttpServer(this); // Настраиваем сервер server->setBlockColor(settings.blockColor); server->setBorderColor(settings.borderColor); server->setBorderSize(settings.borderSize); server->setTransparency(settings.transparency); server->setPageBackgroundColor(settings.pageBackgroundColor); server->setTitleFont( settings.titleFamily, settings.titleSize, settings.titleColor ); server->setContentFont( settings.contentFamily, settings.contentSize, settings.contentColor ); server->setDuration(settings.duration); // Запускаем сервер if (server->start(settings.port)) { m_notificationServers.append(server); // Добавляем в таблицу QString url = QString("http://localhost:%1").arg(settings.port); addServerToTable(settings.name, "Уведомления", settings.port, url, server); } else { delete server; } } } } 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) { } 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) { } 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) { 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) { db->writeSetting(ui->cbOllama->objectName(), ui->cbOllama->isChecked() ? "True" : "False"); } void uGeneral::on_tbNotifyVolume_valueChanged(int 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) { db->writeSetting(ui->chEnNotify->objectName(), ui->chEnNotify->isChecked() ? "True" : "False"); } void uGeneral::on_chEnNotifyMod_stateChanged(int arg1) { db->writeSetting(ui->chEnNotifyMod->objectName(), ui->chEnNotifyMod->isChecked() ? "True" : "False"); } void uGeneral::on_chEnNotifyVip_stateChanged(int arg1) { db->writeSetting(ui->chEnNotifyVip->objectName(), ui->chEnNotifyVip->isChecked() ? "True" : "False"); } void uGeneral::on_chEnNotifySub_stateChanged(int 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) { db->writeSetting(ui->cbNotifyFileAgain1->objectName(), ui->cbNotifyFileAgain1->isChecked() ? "True" : "False"); } void uGeneral::on_cbNotifyFileAgain2_stateChanged(int arg1) { db->writeSetting(ui->cbNotifyFileAgain2->objectName(), ui->cbNotifyFileAgain2->isChecked() ? "True" : "False"); } void uGeneral::on_cbNotifyFileAgain3_stateChanged(int arg1) { db->writeSetting(ui->cbNotifyFileAgain3->objectName(), ui->cbNotifyFileAgain3->isChecked() ? "True" : "False"); } void uGeneral::on_btnThemesFolder_clicked() { } /** * @brief Загружает сохраненные чаты из базы данных и создает серверы */ 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; } } } }