#include "ugeneral.h" #include "fcreatenotify.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" 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_createNotifyDialog(nullptr) , m_createChatDialog(nullptr) { // ============================================================================ // ИНИЦИАЛИЗАЦИЯ ИНТЕРФЕЙСА // ============================================================================ ui->setupUi(this); setWindowTitle("TTW Bot app"); QString romPath = getUserDataPath(); QString sysPath = getSystemPath(); tabIcons = { QIcon(sysPath + "/ico/settings.png"), QIcon(sysPath + "/ico/ai.png"), QIcon(sysPath + "/ico/chat.png"), QIcon(sysPath + "/ico/skill.png"), QIcon(sysPath + "/ico/obs.png"), QIcon(sysPath + "/ico/notify.png"), QIcon(sysPath + "/ico/user.png"), QIcon(sysPath + "/ico/auto.png"), QIcon(sysPath + "/ico/log.png"), QIcon(sysPath + "/ico/add.png"), QIcon(sysPath + "/ico/edit.png"), QIcon(sysPath + "/ico/rm.png"), QIcon(sysPath + "/ico/rmfolder.png"), QIcon(sysPath + "/ico/open.png") }; 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(); // ============================================================================ // ИНИЦИАЛИЗАЦИЯ МЕНЕДЖЕРОВ // ============================================================================ initializeManagers(); // ============================================================================ // НАСТРОЙКА TWITCH API И WEBSOCKET // ============================================================================ setupTwitchComponents(); // ============================================================================ // НАСТРОЙКА ВИДЖЕТА ПОЛЬЗОВАТЕЛЕЙ // ============================================================================ setupUserWidget(); // ============================================================================ // НАСТРОЙКА АВТОРИЗАЦИИ // ============================================================================ setupAuthHandlers(); // ============================================================================ // ИНИЦИАЛИЗАЦИЯ НЕЙРОННОЙ СЕТИ // ============================================================================ initializeNeuralNetwork(); loadEmoties(); // Настройка таблицы веб-серверов QStringList headers = {"Название", "Тип", "Порт", "Ссылка", "Статус"}; ui->sgWebServers->setColumnCount(headers.size()); ui->sgWebServers->setHorizontalHeaderLabels(headers); ui->sgWebServers->horizontalHeader()->setStretchLastSection(true); // Инициализируем окна создания m_createNotifyDialog = new FCreateNotify(this); m_createChatDialog = new FCreateChat(this); } // ============================================================================ // ПРИВАТНЫЕ МЕТОДЫ ДЛЯ ИНИЦИАЛИЗАЦИИ // ============================================================================ /** * @brief Получить путь к системным файлам (exe, библиотеки) * @return Путь к системным файлам */ QString uGeneral::getSystemPath() const { // Путь к установочной директории return QCoreApplication::applicationDirPath(); } 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 Получить путь к пользовательским данным (настройки, звуки, стили и т.д.) * @return Путь к пользовательским данным в AppData */ QString uGeneral::getUserDataPath() const { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); // Создаем структуру папок для разных типов данных QDir dir(path); if (!dir.exists()) { dir.mkpath("."); } // Создаем подпапки для разных типов данных QStringList subdirs = { "sounds", "images", "styles", "voices", "fonts", "temp", "backups", "exports" }; for (const QString &subdir : subdirs) { if (!dir.exists(subdir)) { dir.mkpath(subdir); } } return path; } /** * @brief Получить полный путь к пользовательскому файлу * @param type Тип файла (sounds, images, styles, voices, fonts) * @param fileName Имя файла * @return Полный путь к файлу */ QString uGeneral::getUserFilePath(const QString &type, const QString &fileName) const { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); return QString("%1/%2/%3").arg(path).arg(type).arg(fileName); } /** * @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()) { toLog("uGeneral", "Create", "Ошибка подключения к БД", 2); } else { toLog("uGeneral", "Create", "Успешное подключение к БД", 3); } } /** * @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); } /** * @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); toLog("uGeneral", "banUser", QString("Пользователь %1 забанен").arg(userName), 0); } }); // Таймаут пользователя connect(m_userWidget, &UserWidget::timeoutUserRequested, this, [this](const QString &userId, const QString &userName, int minutes) { if (twitchAPI) { twitchAPI->banUserTime(userId, minutes); toLog("uGeneral", "timeoutUser", QString("Пользователю %1 выдан таймаут на %2 минут").arg(userName).arg(minutes), 0); } }); // Разбан пользователя connect(m_userWidget, &UserWidget::unbanUserRequested, this, [this](const QString &userId, const QString &userName) { if (twitchAPI) { twitchAPI->unbanUser(userId); toLog("uGeneral", "unbanUser", QString("Пользователь %1 разбанен").arg(userName), 0); } }); // Назначение модератором 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()); } toLog("uGeneral", "setModerator", QString("Пользователь %1 назначен модератором").arg(userName), 0); } }); // Удаление прав модератора 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()); } toLog("uGeneral", "removeModerator", QString("Пользователь %1 лишен прав модератора").arg(userName), 0); } }); // Назначение 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()); } toLog("uGeneral", "setVIP", QString("Пользователь %1 назначен VIP").arg(userName), 0); } }); // Удаление статуса 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()); } toLog("uGeneral", "removeVIP", QString("Пользователь %1 лишен статуса VIP").arg(userName), 0); } }); // Запрос информации о пользователе 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) { toLog("TAuth", "Bot", QString("Сервер запущен на порту %1").arg(port), 0); }); // ======================================================================== // НАСТРОЙКА АВТОРИЗАЦИИ СТРИМЕРА // ======================================================================== 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) { toLog("TAuth", "Streamer", QString("Сервер запущен на порту %1").arg(port), 0); }); // ======================================================================== // НАСТРОЙКА АВТОРИЗАЦИИ 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) { toLog("TAuth", "DA", QString("Сервер запущен на порту %1").arg(port), 0); }); } /** * @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(); 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); toLog("uGeneral", "loadChatBadges", QString("Загружено %1 бейджей (глобальных: %2, кастомных: %3)") .arg(m_chatBadges.size()) .arg(globalBadges.size()) .arg(customBadges.size()), 0); } 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) { toLog("uGeneral", "sendTimedMessage", "Попытка отправить сообщение при отсутствии подключения к Twitch", 1); 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) { toLog("uGeneral", "", &"Сработал таймер " [ timerId], 3); } 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) { toLog("uGeneral", "onNeuralError", error, 2); } 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()) { toLog("uGeneral", "initTwitchAPI", "Не все данные для инициализации TTwAPI заполнены. API не инициализировано.", 1); 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 ); toLog("uGeneral", "initTwitchAPI", "TTwAPI успешно инициализировано", 0); } void uGeneral::onApiError(const QString &method, const QString &error) { toLog("ttw_api", method, error, 2); } void uGeneral::onTokenExpired(const QString &error) { toLog("uGeneral", "onTokenExpired", error, 2); } void uGeneral::onRateLimit() { toLog("uGeneral", "onRateLimit", "onRateLimit", 1); } UserManager* uGeneral::getUserManager() { return m_userManager; } // Обработчик ответа от AI void uGeneral::onAIResponseReceived(const QString &response) { toLog("AI", "Response", "Получен ответ: " + response.left(100) + "...", 0); // Отправляем ответ в чат 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) { toLog("AI", "Error", "Ошибка AI: " + error, 2); // Можно отправить сообщение об ошибке в чат if (m_twitchClient) { m_twitchClient->sendChatMessage(ui->edtChannel->text(), "Извините, нейросеть временно недоступна"); } } void uGeneral::handleConnected() { toLog("uGeneral", "handleConnected","WS Connected", 0); setTwitchConnected(true); loadChatBadges(); // Запускаем активные таймеры, если подключены к Twitch if (m_isTwitchConnected) { for (TimerInfo& timer : m_timers) { if (timer.isActive && !timer.timer) { startTimer(timer); } } } } void uGeneral::handleDisconnected() { toLog("uGeneral", "handleDisconnected","WS Disconnected", 0); 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::toLog(QString aModule, QString aMethod, QString aMessage, int aCode) { // Определяем тип сообщения и цвет QString msgType; QColor textColor; switch (aCode) { case 0: // INFO msgType = "INFO"; textColor = Qt::darkGreen; break; case 1: // WARNING msgType = "WARNING"; textColor = Qt::darkYellow; break; case 2: // ERROR msgType = "ERROR"; textColor = Qt::red; break; case 3: // DEBUG msgType = "DEBUG"; textColor = Qt::gray; break; default: msgType = "UNKNOWN"; textColor = Qt::black; break; } // Сохраняем запись в общий список LogEntry entry; entry.time = QDateTime::currentDateTime(); entry.module = aModule; entry.method = aMethod; entry.message = aMessage; entry.code = aCode; entry.msgType = msgType; entry.color = textColor; allLogs.append(entry); // Обновляем отображение (показываем только если чекбокс активен) if (shouldShowLogEntry(aCode)) { addLogToTable(entry, ui->sgLog->rowCount()); } } bool uGeneral::shouldShowLogEntry(int code) { switch (code) { case 0: return ui->cbInfo->isChecked(); case 1: return ui->cbWarning->isChecked(); case 2: return ui->cbError->isChecked(); case 3: return ui->cbDebug->isChecked(); default: return true; } } void uGeneral::addLogToTable(const LogEntry &entry, int row) { // Добавляем новую строку ui->sgLog->insertRow(row); // Колонка 0: Время QTableWidgetItem *item0 = new QTableWidgetItem(entry.time.toString("hh:mm:ss")); item0->setForeground(entry.color); // Колонка 1: Тип сообщения QTableWidgetItem *item1 = new QTableWidgetItem(entry.msgType); item1->setForeground(entry.color); // Колонка 2: Модуль QTableWidgetItem *item2 = new QTableWidgetItem(entry.module); item2->setForeground(entry.color); // Колонка 3: Метод QTableWidgetItem *item3 = new QTableWidgetItem(entry.method); item3->setForeground(entry.color); // Колонка 4: Сообщение QTableWidgetItem *item4 = new QTableWidgetItem(entry.message); item4->setForeground(entry.color); // Устанавливаем элементы в таблицу ui->sgLog->setItem(row, 0, item0); ui->sgLog->setItem(row, 1, item1); ui->sgLog->setItem(row, 2, item2); ui->sgLog->setItem(row, 3, item3); ui->sgLog->setItem(row, 4, item4); // Прокручиваем к последней строке ui->sgLog->scrollToBottom(); } void uGeneral::applyLogFilter() { // Очищаем таблицу ui->sgLog->setRowCount(0); // Добавляем только те записи, которые соответствуют фильтру for (const LogEntry &entry : qAsConst(allLogs)) { if (shouldShowLogEntry(entry.code)) { 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()); } 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); toLog("uGeneral", "on_btnGetTockenBot_clicked", QString("Запущен сервер для получения токена бота. Откройте ссылку в окне. Порт: %1") .arg(m_authBot->getServerPort()), 0); } 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); toLog("uGeneral", "on_btnGetTokenStreamer_clicked", QString("Запущен сервер для получения токена стримера. Порт: %1") .arg(m_authStreamer->getServerPort()), 0); } // Обработчик получения токена 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) { toLog("uGeneral", "onAuthError", error, 2); } 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); toLog("uGeneral", "on_btnDAGetCode_clicked", QString("Запущен сервер для получения кода DonationAlerts. Порт: %1") .arg(m_authDA->getServerPort()), 0); } void uGeneral::on_btnImportSettings_clicked() { QString userExportsPath = getUserFilePath("exports", ""); QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл настроек для импорта", userExportsPath, "Файлы базы данных (*.db *.sqlite *.sqlite3);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; // Пользователь отменил выбор } QString destinationFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); destinationFile += "/settings.db"; QFileInfo fileInfo(destinationFile); QString destinationDir = fileInfo.path(); // Проверяем, существует ли файл для импорта QFile source(sourceFile); if (!source.exists()) { QMessageBox::critical(this, "Ошибка", "Выбранный файл не существует или недоступен"); return; } // Спрашиваем подтверждение у пользователя QMessageBox::StandardButton reply; reply = QMessageBox::question(this, "Подтверждение импорта", "Импорт настроек перезапишет все текущие настройки.\n" "Вы уверены, что хотите продолжить?", QMessageBox::Yes | QMessageBox::No); if (reply != QMessageBox::Yes) { return; } // Закрываем текущее соединение с БД if (db) { db->close(); delete db; db = nullptr; } // Копируем файл bool copySuccess = false; QString errorMessage; try { // Удаляем старый файл, если он существует if (QFile::exists(destinationFile)) { if (!QFile::remove(destinationFile)) { errorMessage = "Не удалось удалить старый файл настроек"; throw std::runtime_error(errorMessage.toStdString()); } } // Копируем новый файл if (!QFile::copy(sourceFile, destinationFile)) { errorMessage = "Не удалось скопировать файл"; throw std::runtime_error(errorMessage.toStdString()); } // Устанавливаем правильные права доступа к файлу QFile newDbFile(destinationFile); if (!newDbFile.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser)) { qWarning() << "Не удалось установить права доступа к файлу"; } copySuccess = true; } catch (const std::exception& e) { errorMessage = e.what(); copySuccess = false; } if (!copySuccess) { QMessageBox::critical(this, "Ошибка импорта", "Не удалось импортировать настройки:\n" + errorMessage); // Пытаемся пересоздать пустую БД db = new uDataBase(destinationFile, this); return; } // Пересоздаем объект БД с новым файлом db = new uDataBase(destinationFile, this); if (db->isConnected()) { QMessageBox::information(this, "Импорт завершен", "Настройки успешно импортированы.\n" "Приложение будет использовать новые настройки."); } else { QMessageBox::critical(this, "Ошибка", "Настройки импортированы, но не удалось подключиться к новой базе данных:\n" + db->lastError()); } } void uGeneral::on_btnExportSettings_clicked() { // Получаем текущий файл БД QString userExportsPath = getUserFilePath("exports", ""); // Открываем диалог сохранения файла QString destinationFile = QFileDialog::getSaveFileName(this, "Экспорт настроек", userExportsPath + "/settings_backup_" + QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss") + ".db", "Файлы базы данных (*.db);;Все файлы (*.*)"); if (destinationFile.isEmpty()) { return; // Пользователь отменил } // Добавляем расширение .db, если его нет if (!destinationFile.endsWith(".db", Qt::CaseInsensitive)) { destinationFile += ".db"; } // Копируем файл if (QFile::copy(userExportsPath, destinationFile)) { QMessageBox::information(this, "Экспорт завершен", "Настройки успешно экспортированы в файл:\n" + destinationFile); } else { QMessageBox::critical(this, "Ошибка экспорта", "Не удалось экспортировать настройки.\n" "Проверьте права доступа к выбранной папке."); } } 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 userSoundsPath = getUserFilePath("sounds", ""); QString sourceFile = QFileDialog::getOpenFileName(this, "Выберите файл для уведомлений", userSoundsPath, "Звуковые файлы (*.mp3 *.wav *.ogg);;Все файлы (*.*)"); if (sourceFile.isEmpty()) { return; } QFileInfo fileInfo(sourceFile); QString destPath = getUserFilePath("sounds", fileInfo.fileName()); if (sourceFile != destPath && !QFile::exists(destPath)) { if (QFile::copy(sourceFile, destPath)) { sourceFile = destPath; } } ui->edtNotifyFileName->setText(sourceFile); } void uGeneral::on_btnNotifyOpenMod_clicked() { 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()) { soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolumeMod->value()); soundManager->playSound(SoundManager::Channel2); 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); 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(); soundManager->loadSound(SoundManager::Channel2, fn); soundManager->setVolume(SoundManager::Channel2, ui->tbNotifyVolume->value()); soundManager->playSound(SoundManager::Channel2); return; } } void uGeneral::handleNewMessage( const QString &message){ toLog("General", "handleNewMessage", message, 3); TwitchMessage msg = TwitchMessage::parse(message); QString userId = m_userManager->checkUser(msg.displayName, msg); m_userManager->m_totalMessages++; m_userWidget->updateStatistics(); toLog("General", "handleNewMessage", msg.displayName + ": " + msg.message, 0); playNotify(msg.isMod, msg.isVIP, msg.isSubscriber); QString processedMessage = processTwitchMessage(msg); QString formattedNickname = formatNicknameWithBadges(msg); addChatMessage(formattedNickname, processedMessage); QString cleanedMessage; if (msg.message.startsWith("!!!")){ cleanedMessage = msg.message.mid(3); // speachMessage(cleanedMessage); } else if (msg.message.startsWith("!")) { cleanedMessage = msg.message.mid(1); execCommand(msg.displayName, cleanedMessage); } else { // execClearMessage(cleanedMessage); } } void uGeneral::handleError(const QString &errorMessage){ toLog("General", "handleError",errorMessage,2); } // Метод для обработки 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::on_btnSaveSettings_clicked() { QList lineEdits = ui->tab->findChildren(); for (QLineEdit* edit : lineEdits) { db->writeSetting(edit->objectName(), edit->text()); } QList checkBoxes = ui->tab->findChildren(); for (QCheckBox* cb : checkBoxes) { db->writeSetting(cb->objectName(), cb->isChecked() ? "True" : "False"); } } void uGeneral::execCommand(const QString &sender, const QString &message) { QStringList parts = message.split(' ', Qt::SkipEmptyParts); if (parts.isEmpty()) { return; } QString commandName = parts.first().toLower(); QStringList params = parts.mid(1); // Все остальные слова - параметры bool commandFound = false; QString response; for (int row = 0; row < ui->sgCommands->rowCount(); ++row) { QTableWidgetItem *commandItem = ui->sgCommands->item(row, 0); if (commandItem) { QString tableCommand = commandItem->text().toLower(); if (tableCommand.startsWith('!')) { tableCommand = tableCommand.mid(1); } if (tableCommand == commandName) { QTableWidgetItem *responseItem = ui->sgCommands->item(row, 1); if (responseItem) { response = responseItem->text(); commandFound = true; // Заменяем плейсхолдеры response = response.replace("{sender}", sender); // Заменяем параметры {0}, {1}, {2} и т.д. for (int i = 0; i < params.size(); ++i) { QString placeholder = "{" + QString::number(i) + "}"; response = response.replace(placeholder, params[i]); } } break; } } } if (commandFound && !response.isEmpty()) { QString p = ""; QString pall = ""; if (params.count() > 0) { p = params[0]; pall = params.join(" "); } // группы ответов response = db->ProcessResponseTemplate(response); response = db->ProcessResponseTemplate(response); response = db->ProcessResponseTemplate(response); // статичные автозамены response = ResponsParserStatic(response, sender, p); // рандомные числа response = ResponsParserRandoms(response); // АПИ команды response = responsParserAPI(response, sender); // Звуки response = ResponsParserSounds(response); // Текстовые файлы response = ResponsParserText(response); // ГПТ response = ResponsParserAI(response, pall); // АИ команды // АИ картинки //счетчики m_twitchClient->sendChatMessage(ui->edtChannel->text(), response); } } QString uGeneral::ResponsParserAI(const QString &response, const QString &pall) { // Проверяем, содержит ли строка маркер для нейросети if (!response.contains("[AI]", Qt::CaseInsensitive)) { return response; // Если маркера нет, возвращаем как есть } // Извлекаем вопрос из pall QString question = pall.trimmed(); if (question.isEmpty()) { QString res = response; return res.replace("[AI]", "Ошибка: не указан вопрос для нейросети", Qt::CaseInsensitive);; } // Запоминаем оригинальный формат ответа QString originalTemplate = response; // Создаем временный объект для асинхронного запроса // Используем QEventLoop для ожидания ответа QEventLoop eventLoop; QString aiResponse; bool responseReceived = false; bool errorOccurred = false; QString errorMessage; // Подключаем сигналы от нейросети через лямбда-выражения QMetaObject::Connection conn1 = QObject::connect( nnManager, &NeuralNetworkManager::responseReceived, [&](const QString &response) { aiResponse = response; responseReceived = true; eventLoop.quit(); } ); QMetaObject::Connection conn2 = QObject::connect( nnManager, &NeuralNetworkManager::errorOccurred, [&](const QString &error) { errorMessage = error; errorOccurred = true; eventLoop.quit(); } ); // Отправляем запрос к нейросети sendRequestToAI(question); // Ждем ответа (таймаут 60 секунд) QTimer::singleShot(60000, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // Отключаем соединения QObject::disconnect(conn1); QObject::disconnect(conn2); // Обрабатываем результат if (errorOccurred) { return originalTemplate.replace("[AI]", QString("Ошибка нейросети: %1").arg(errorMessage), Qt::CaseInsensitive); } if (!responseReceived) { return originalTemplate.replace("[AI]", "Таймаут при ожидании ответа от нейросети", Qt::CaseInsensitive); } // Заменяем [AI] на полученный ответ QString result = originalTemplate; result = result.replace("[AI]", aiResponse, Qt::CaseInsensitive); return result; } QString uGeneral::ResponsParserStatic(const QString &inMess, const QString &adName, const QString &aCommandText) { QString res = inMess; // Замена [USERNAME] на @ + adName res = res.replace("[USERNAME]", "@" + adName); // Замена [TO] на aCommandText res = res.replace("[TO]", aCommandText); // Обработка [RANDOMUSER] if (res.contains("[RANDOMUSER]")) { QString randomUserName; // Если aCommandText содержит '@', используем его if (aCommandText.contains('@')) { randomUserName = aCommandText; } else { // Если список пользователей пуст, оставляем пустую строку или значение по умолчанию randomUserName = "@viewer"; } res = res.replace("[RANDOMUSER]", randomUserName); } return res; } QString uGeneral::ResponsParserRandoms(const QString &inMess) { QString result = inMess; // Функция для получения случайного числа в диапазоне auto randomInRange = [](int minValue, int maxValue) -> int { if (minValue > maxValue) { qSwap(minValue, maxValue); } return QRandomGenerator::global()->bounded(minValue, maxValue + 1); }; // Ищем все вхождения [[имя]] int startPos = 0; while ((startPos = result.indexOf("[[", startPos)) != -1) { int endPos = result.indexOf("]]", startPos + 2); if (endPos == -1) { break; // Нет закрывающих скобок } // Извлекаем имя между [[ и ]] QString rndName = result.mid(startPos + 2, endPos - startPos - 2); bool found = false; // Ищем имя в таблице sgRandomInt for (int row = 0; row < ui->sgRandomInt->rowCount(); ++row) { // Предполагаем, что в первом столбце имя, во втором - минимальное значение, в третьем - максимальное QTableWidgetItem *nameItem = ui->sgRandomInt->item(row, 0); QTableWidgetItem *minItem = ui->sgRandomInt->item(row, 1); QTableWidgetItem *maxItem = ui->sgRandomInt->item(row, 2); if (nameItem && minItem && maxItem && nameItem->text() == rndName) { // Получаем значения диапазона bool okMin, okMax; int minValue = minItem->text().toInt(&okMin); int maxValue = maxItem->text().toInt(&okMax); if (okMin && okMax) { // Генерируем случайное число int randomNumber = randomInRange(minValue, maxValue); // Заменяем [[имя]] на случайное число QString token = "[[" + rndName + "]]"; result.replace(token, QString::number(randomNumber)); found = true; break; } } } if (!found) { // Если имя не найдено в таблице, оставляем как есть или удаляем startPos = endPos + 2; // Переходим к следующему поиску } else { // После замены начинаем поиск с начала строки, так как длина строки изменилась startPos = 0; } } return result; } QString uGeneral::ResponsParserSounds(const QString &inMess) { QString result = inMess; // Ищем все вхождения ||команда|| int startPos = 0; while ((startPos = result.indexOf("||", startPos)) != -1) { int endPos = result.indexOf("||", startPos + 2); if (endPos == -1) { break; // Нет закрывающих разделителей } // Извлекаем команду между || и || QString soundCommand = result.mid(startPos + 2, endPos - startPos - 2); // Ищем команду в таблице sgSounds bool found = false; for (int row = 0; row < ui->widget->tableWidget()->rowCount(); ++row) { QTableWidgetItem *commandItem = ui->widget->tableWidget()->item(row, 0); if (commandItem && commandItem->text() == soundCommand) { // Получаем путь к файлу из второго столбца QTableWidgetItem *filePathItem = ui->widget->tableWidget()->item(row, 1); if (filePathItem && !filePathItem->text().isEmpty()) { QString fn = filePathItem->text(); // Воспроизводим звук soundManager->loadSound(SoundManager::Channel1, fn); soundManager->setVolume(SoundManager::Channel1, ui->tbNotifyVolume->value()); soundManager->playSound(SoundManager::Channel1); } // Заменяем ||команда|| на пустую строку QString token = "||" + soundCommand + "||"; result.replace(token, ""); found = true; break; } } if (!found) { // Если команда не найдена в таблице, просто удаляем ||команда|| QString token = "||" + soundCommand + "||"; result.replace(token, ""); } // Начинаем поиск снова с начала строки, так как длина строки изменилась startPos = 0; } return result; } QString uGeneral::ResponsParserText(const QString &inMess) { QString result = inMess; // Ищем все вхождения |(имя_файла|( int startPos = 0; while ((startPos = result.indexOf("|(", startPos)) != -1) { int endPos = result.indexOf("|(", startPos + 2); if (endPos == -1) { break; // Нет закрывающего разделителя } // Извлекаем имя файла между |( и |( QString fileName = result.mid(startPos + 2, endPos - startPos - 2).trimmed(); // Ищем имя файла в таблице widget_2 QString fileContent = ""; bool found = false; for (int row = 0; row < ui->widget_2->tableWidget()->rowCount(); ++row) { QTableWidgetItem *nameItem = ui->widget_2->tableWidget()->item(row, 0); if (nameItem && nameItem->text() == fileName) { // Получаем путь к файлу из второго столбца QTableWidgetItem *filePathItem = ui->widget_2->tableWidget()->item(row, 1); if (filePathItem && !filePathItem->text().isEmpty()) { QString filePath = filePathItem->text().trimmed(); // Читаем содержимое файла QFile file(filePath); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); stream.setCodec("UTF-8"); // Установите нужную кодировку fileContent = stream.readAll(); file.close(); found = true; } else { qWarning() << "Не удалось открыть файл:" << filePath; fileContent = "[Ошибка: файл не найден]"; found = true; } } else { fileContent = "[Ошибка: путь к файлу не указан]"; found = true; } break; } } // Заменяем |(имя_файла|( на содержимое файла QString token = "|(" + fileName + "|("; if (found) { result.replace(token, fileContent); } else { // Если имя файла не найдено в таблице, заменяем на сообщение об ошибке result.replace(token, "[Ошибка: файл '" + fileName + "' не найден в списке]"); } // После замены начинаем поиск сначала, так как длина строки изменилась startPos = 0; } return result; } void uGeneral::on_lbRandomGroup_itemClicked(QListWidgetItem *item) { db->LoadRandomResponses(item->text(), ui->lbRandomRespons); } QString uGeneral::responsParserAPI(const QString &inMess, const QString &adName) { QString res = inMess; // Используем UserManager вместо userList User* user = m_userManager->findUser(adName); if (!user) { qWarning() << "User not found:" << adName; return res; } // Функция для склонения слов по числам auto getPeriodEnding = [](int n, int r) -> QString { QVector> res = { {"год", "года", "лет"}, {"месяц", "месяца", "месяцев"}, {"день", "дня", "дней"}, {"раз", "раза", "раз"} }; if (r < 0 || r >= res.size()) { return ""; } if (n % 10 == 1 && n % 100 != 11) { return res[r][0]; } else if (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20)) { return res[r][1]; } else { return res[r][2]; } }; // Функция вычисления разницы дат auto getDateDifference = [&getPeriodEnding](const QDate &inputDate) -> QString { if (!inputDate.isValid() || inputDate.year() < 2000) { return ""; } QDate currentDate = QDate::currentDate(); if (currentDate < inputDate) { return ""; } int years = 0; int months = 0; int days = 0; // Вычисляем годы years = inputDate.daysTo(currentDate) / 365; QDate tempDate = inputDate.addYears(years); // Вычисляем месяцы while (tempDate.addMonths(1) <= currentDate) { months++; tempDate = tempDate.addMonths(1); } // Вычисляем дни days = tempDate.daysTo(currentDate); // Корректировка месяцев и дней if (days >= 30) { months += days / 30; days = days % 30; } return QString("%1 %2 %3 %4 %5 %6") .arg(years) .arg(getPeriodEnding(years, 0)) .arg(months) .arg(getPeriodEnding(months, 1)) .arg(days) .arg(getPeriodEnding(days, 2)); }; // Обработка [FOLLOW] if (res.contains("[FOLLOW]", Qt::CaseInsensitive)) { if (user->followAt.year() < 2000 && !user->id.isEmpty()) { // Используем m_api вместо ttwAPI QDate followDate = twitchAPI->getFollow(user->id); if (followDate.isValid()) { user->followAt = followDate; // Обновляем пользователя в менеджере m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } } if (user->followAt.isValid() && user->followAt.year() >= 2000) { QString follow = getDateDifference(user->followAt); res.replace("[FOLLOW]", follow, Qt::CaseInsensitive); } } // Обработка [AGE] if (res.contains("[AGE]", Qt::CaseInsensitive)) { if (user->createdAt.year() < 2000 && !user->login.isEmpty()) { // Используем m_api вместо ttwAPI User updatedUser = twitchAPI->getUserByLogin(user->login); if (updatedUser.createdAt.isValid()) { user->createdAt = updatedUser.createdAt; // Обновляем пользователя в менеджере m_userManager->updateUserFromMessage(user->displayName, TwitchMessage()); } } if (user->createdAt.isValid() && user->createdAt.year() >= 2000) { QString age = getDateDifference(user->createdAt); res.replace("[AGE]", age, Qt::CaseInsensitive); } } // Обработка [STAT] if (res.contains("[STAT]", Qt::CaseInsensitive)) { int avgViewers = 0; int maxViewers = 0; int hoursWatched = 0; int followers = 0; int followersTotal = 0; // Используем m_api вместо ttwAPI и правильное имя канала twitchAPI->getTTWStat(ui->edtChannel->text(), avgViewers, maxViewers, hoursWatched, followers, followersTotal); QString resultStat = QString( "Статистика канала за месяц: " "Средний онлайн: %1; " "Максимальный онлайн: %2; " "Часов просмотра: %3; " "Подписчиков за месяц: %4; " "Всего подписчиков: %5") .arg(avgViewers) .arg(maxViewers) .arg(hoursWatched) .arg(followers) .arg(followersTotal); res.replace("[STAT]", resultStat, Qt::CaseInsensitive); } return res; } void uGeneral::on_pushButton_clicked() { allLogs.clear(); ui->sgLog->setRowCount(0); } 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; } // Получаем текущее количество строк в таблице int row = ui->sgRandomInt->rowCount(); // Добавляем новую строку ui->sgRandomInt->insertRow(row); // Создаем и заполняем ячейки // Колонка 0: Название QTableWidgetItem *itemName = new QTableWidgetItem(name); ui->sgRandomInt->setItem(row, 0, itemName); // Колонка 1: Значение "От" QTableWidgetItem *itemOt = new QTableWidgetItem(QString::number(otValue)); itemOt->setTextAlignment(Qt::AlignCenter); // Выравнивание по центру ui->sgRandomInt->setItem(row, 1, itemOt); // Колонка 2: Значение "До" QTableWidgetItem *itemTo = new QTableWidgetItem(QString::number(toValue)); itemTo->setTextAlignment(Qt::AlignCenter); // Выравнивание по центру ui->sgRandomInt->setItem(row, 2, itemTo); ui->edtRandomName->clear(); ui->edtOt->setValue(0); // или значение по умолчанию ui->edtTo->setValue(100); // или значение по умолчанию db->SaveTableWidget(ui->sgRandomInt); } void uGeneral::on_btnRandDel_clicked() { // Проверяем, есть ли выделенная строка if (!ui->sgRandomInt->currentItem()) { QMessageBox::warning(this, "Внимание", "Выберите строку для удаления!"); return; } // Получаем индекс выделенной строки int row = ui->sgRandomInt->currentItem()->row(); ui->sgRandomInt->removeRow(row); db->SaveTableWidget(ui->sgRandomInt); } 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 respons = ui->textBrowser->toPlainText(); // Проверяем, что имя не пустое if (name.isEmpty()) { QMessageBox::warning(this, "Внимание", "Введите команду!"); ui->edtCommand->setFocus(); return; } // Получаем текущее количество строк в таблице int row = ui->sgCommands->rowCount(); ui->sgCommands->insertRow(row); QTableWidgetItem *itemName = new QTableWidgetItem(name); ui->sgCommands->setItem(row, 0, itemName); QTableWidgetItem *itemOt = new QTableWidgetItem(respons); ui->sgCommands->setItem(row, 1, itemOt); ui->edtCommand->clear(); ui->textBrowser->setPlainText(""); // или значение по умолчанию db->SaveTableWidget(ui->sgCommands); } void uGeneral::on_btnRmCommand_clicked() { // Проверяем, есть ли выделенная строка if (!ui->sgCommands->currentItem()) { QMessageBox::warning(this, "Внимание", "Выберите строку для удаления!"); return; } // Получаем индекс выделенной строки int row = ui->sgCommands->currentItem()->row(); ui->sgCommands->removeRow(row); db->SaveTableWidget(ui->sgCommands); } void uGeneral::on_btnEdtCommand_clicked() { // Проверяем, есть ли выделенная строка if (!ui->sgCommands->currentItem()) { QMessageBox::warning(this, "Внимание", "Выберите строку для редактирования!"); return; } // Получаем индекс выделенной строки int row = ui->sgCommands->currentItem()->row(); // Получаем данные из выбранной строки ui->sgCommands->item(row, 0)->setText(ui->edtCommand->text()); ui->sgCommands->item(row, 1)->setText(ui->textBrowser->toPlainText()); db->SaveTableWidget(ui->sgCommands); } void uGeneral::loadQssFiles() { // Очищаем ComboBox перед загрузкой ui->cbTheme->clear(); // Добавляем пустой элемент (отсутствие темы) ui->cbTheme->addItem("Без темы", ""); // Путь к системным стилям (вместе с программой) QDir systemStylesDir(getSystemPath() + "/styles"); // Путь к пользовательским стилям (в AppData) QString userStylesPath = getUserFilePath("styles", ""); QDir userStylesDir(userStylesPath); // Проверяем существование пользовательской папки со стилями if (!userStylesDir.exists()) { userStylesDir.mkpath("."); } // Сначала загружаем системные стили (только для чтения) loadStylesFromDirectory(systemStylesDir, "Системные"); // Затем загружаем пользовательские стили loadStylesFromDirectory(userStylesDir, "Пользовательские"); // Можно добавить дополнительную обработку для выбора темы по умолчанию } void uGeneral::loadStylesFromDirectory(const QDir &stylesDir, const QString &category) { 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)); } } /** * @brief Инициализирует структуру папок в AppData */ void uGeneral::initializeAppDataStructure() { QString appDataPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); // Создаем основную папку QDir appDataDir(appDataPath); if (!appDataDir.exists()) { appDataDir.mkpath("."); } // Создаем подпапки QStringList subdirs = { "sounds", "images", "styles", "voices", "fonts", "temp", "backups", "exports", "logs", "cache" }; for (const QString &subdir : subdirs) { if (!appDataDir.exists(subdir)) { appDataDir.mkpath(subdir); } } // Копируем системные файлы по умолчанию в AppData при первом запуске copyDefaultFiles(); } /** * @brief Копирует файлы по умолчанию из системной папки в AppData */ void uGeneral::copyDefaultFiles() { QSettings settings; bool firstRun = settings.value("FirstRun", true).toBool(); if (firstRun) { // Копируем примеры стилей QDir systemStylesDir(getSystemPath() + "/styles"); QString userStylesPath = getUserFilePath("styles", ""); if (systemStylesDir.exists()) { QStringList filters; filters << "*.qss" << "*.QSS"; QStringList qssFiles = systemStylesDir.entryList(filters, QDir::Files); for (const QString &file : qssFiles) { QString sourcePath = systemStylesDir.absoluteFilePath(file); QString destPath = QString("%1/%2").arg(userStylesPath).arg(file); if (!QFile::exists(destPath)) { QFile::copy(sourcePath, destPath); } } } // Устанавливаем флаг, что первый запуск завершен settings.setValue("FirstRun", false); } } void uGeneral::on_cbTheme_currentIndexChanged(int index) { 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(this); connect(createDialog, &FCreateChat::serverCreated, this, &uGeneral::onChatServerCreated); createDialog->setAttribute(Qt::WA_DeleteOnClose); // Автоматическое удаление при закрытии createDialog->exec(); } void uGeneral::on_btnWSCreateNotify_clicked() { // Создаем диалог каждый раз FCreateNotify *createDialog = new FCreateNotify(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) { // Устанавливаем родителя - главное окно server->setParent(this); addNotificationServer(server, name); } } // Методы добавления серверов: 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(this); createChatDialog->loadExistingServer(existingServer, nameItem->text()); connect(createChatDialog, &FCreateChat::serverUpdated, this, &uGeneral::onChatServerUpdated); createChatDialog->setAttribute(Qt::WA_DeleteOnClose); createChatDialog->exec(); } } } 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_checkStateChanged(const Qt::CheckState &arg1) { } 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() { QString stylesPath = getUserFilePath("styles", ""); QUrl url = QUrl::fromLocalFile(stylesPath); QDesktopServices::openUrl(url); }