Files
TTW_Bot/ugeneral.cpp
T

3725 lines
130 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "ugeneral.h"
#include "fcreatenotify.h"
#include "ui_ugeneral.h"
#include <QStandardPaths>
#include <QDesktopServices>
#include <QUrl>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QStandardItem>
#include <QDateTime>
#include <QRandomGenerator>
#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 <QInputDialog>
#include <QPair>
#include <QCache>
#include <QSettings>
#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(db, this);
}
// ============================================================================
// ПРИВАТНЫЕ МЕТОДЫ ДЛЯ ИНИЦИАЛИЗАЦИИ
// ============================================================================
/**
* @brief Получить путь к системным файлам (exe, библиотеки)
* @return Путь к системным файлам
*/
QString uGeneral::getSystemPath() const
{
// Путь к установочной директории
return QCoreApplication::applicationDirPath();
}
void uGeneral::setupButtonIcons() {
// Получаем все кнопки на форме
QList<QPushButton*> buttons = ui->www->findChildren<QPushButton*>();
// Если кнопки не в centralWidget, ищем во всем окне
// QList<QPushButton*> buttons = findChildren<QPushButton*>();
// Проходим по всем найденным кнопкам
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<QLineEdit*> 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<ChatBadge> globalBadges;
twitchAPI->getGlobalChatBadges(globalBadges);
m_chatBadges.append(globalBadges);
QVector<ChatBadge> 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<QTableWidget*>("sgSounds");
db->LoadTableWidget(table);
form = ui->widget_2;
form->setDatabase(db);
table = form->findChild<QTableWidget*>("sgFiles");
db->LoadTableWidget(table);
form = ui->widget_3;
form->setDatabase(db);
table = form->findChild<QTableWidget*>("sgNeiro");
db->LoadTableWidget(table);
db->LoadRandomGroups(ui->lbRandomGroup);
ui->cbTheme->setCurrentIndex(db->readSetting(ui->cbTheme->objectName(), "0").toInt());
db->LoadTableWidget(ui->sgWebServers);
loadSavedChats();
}
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<Replacement> 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("<img src=\"%1\" alt=\"%2\" style=\"height: 1em; vertical-align: middle;\">")
.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<EmoteReplacement> 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("<img src=\"%1\" alt=\"%2\" style=\"height: 1em; vertical-align: middle;\">")
.arg(bttvUrl)
.arg(word);
replacements.append({start, length, emoteHtml});
continue;
}
// Проверяем 7TV эмодзи
QString sevenTVUrl = sevenTVProvider.getEmoteUrl(word);
if (!sevenTVUrl.isEmpty()) {
QString emoteHtml = QString("<img src=\"%1\" alt=\"%2\" style=\"height: 1em; vertical-align: middle;\">")
.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<QLineEdit*> lineEdits = ui->tab->findChildren<QLineEdit*>();
for (QLineEdit* edit : lineEdits) {
db->writeSetting(edit->objectName(), edit->text());
}
QList<QCheckBox*> checkBoxes = ui->tab->findChildren<QCheckBox*>();
for (QCheckBox* cb : checkBoxes) {
db->writeSetting(cb->objectName(), cb->isChecked() ? "True" : "False");
}
db->SaveTableWidget(ui->sgWebServers);
}
void uGeneral::execCommand(const QString &sender, const QString &message)
{
QStringList parts = message.split(' ', Qt::SkipEmptyParts);
if (parts.isEmpty()) {
return;
}
QString commandName = parts.first().toLower();
QStringList params = parts.mid(1); // Все остальные слова - параметры
bool commandFound = false;
QString response;
for (int row = 0; row < ui->sgCommands->rowCount(); ++row) {
QTableWidgetItem *commandItem = ui->sgCommands->item(row, 0);
if (commandItem) {
QString tableCommand = commandItem->text().toLower();
if (tableCommand.startsWith('!')) {
tableCommand = tableCommand.mid(1);
}
if (tableCommand == commandName) {
QTableWidgetItem *responseItem = ui->sgCommands->item(row, 1);
if (responseItem) {
response = responseItem->text();
commandFound = true;
// Заменяем плейсхолдеры
response = response.replace("{sender}", sender);
// Заменяем параметры {0}, {1}, {2} и т.д.
for (int i = 0; i < params.size(); ++i) {
QString placeholder = "{" + QString::number(i) + "}";
response = response.replace(placeholder, params[i]);
}
}
break;
}
}
}
if (commandFound && !response.isEmpty()) {
QString p = "";
QString pall = "";
if (params.count() > 0)
{
p = params[0];
pall = params.join(" ");
}
// группы ответов
response = db->ProcessResponseTemplate(response);
response = db->ProcessResponseTemplate(response);
response = db->ProcessResponseTemplate(response);
// статичные автозамены
response = ResponsParserStatic(response, sender, p);
// рандомные числа
response = ResponsParserRandoms(response);
// АПИ команды
response = responsParserAPI(response, sender);
// Звуки
response = ResponsParserSounds(response);
// Текстовые файлы
response = ResponsParserText(response);
// ГПТ
response = ResponsParserAI(response, pall);
// АИ команды
// АИ картинки
//счетчики
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<QVector<QString>> 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<QListWidgetItem*> 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(db, this);
connect(createDialog, &FCreateChat::serverCreated, this, &uGeneral::onChatServerCreated);
connect(createDialog, &FCreateChat::serverUpdated, this, &uGeneral::onChatServerUpdated);
createDialog->setAttribute(Qt::WA_DeleteOnClose);
createDialog->exec();
}
void uGeneral::on_btnWSCreateNotify_clicked()
{
// Создаем диалог каждый раз
FCreateNotify *createDialog = new FCreateNotify(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<QObject*>(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("<img src=\"%1\" alt=\"%2\" title=\"%3\" "
"style=\"height: 18px; width: 18px; "
"vertical-align: middle; margin-right: 2px;\">")
.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("<img src=\"%1\" alt=\"%2\" title=\"%2\" "
"style=\"height: 18px; width: 18px; "
"vertical-align: middle; margin-right: 2px;\">")
.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<QString, QString> 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("<span style=\"color: %1;\">%2</span>")
.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<QObject*>();
HttpServerChat *existingServer = qobject_cast<HttpServerChat*>(serverObj);
if (existingServer) {
FCreateChat *createChatDialog = new FCreateChat(db, this);
createChatDialog->loadExistingServer(existingServer, nameItem->text());
connect(createChatDialog, &FCreateChat::serverUpdated, this, &uGeneral::onChatServerUpdated);
createChatDialog->setAttribute(Qt::WA_DeleteOnClose);
createChatDialog->exec();
}
}
}
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);
}
/**
* @brief Загружает сохраненные чаты из базы данных и создает серверы
*/
void uGeneral::loadSavedChats()
{
QList<ChatSettings> chats = db->loadAllChats();
for (const ChatSettings &settings : chats) {
if (settings.type.toLower() == "chat") {
// Создаем сервер чата
HttpServerChat *server = new HttpServerChat(
settings.fontList,
settings.port,
settings.backgroundColor,
this
);
// Настраиваем сервер
server->setBlockColor(settings.blockColor);
server->setBorderColor(settings.borderColor);
server->setBorderSize(settings.borderSize);
server->setPadding(settings.padding);
server->setTransparency(settings.transparency);
server->setFontFamily(settings.fontFamily);
server->setFontSize(settings.fontSize);
server->setFontColor(settings.fontColor);
server->setFreez(settings.freez);
server->setMessageTimeout(settings.messageTimeout);
server->setDeleteMode(settings.deleteByTime, settings.maxMsgCount);
// Запускаем сервер
if (server->start()) {
m_chatServers.append(server);
// Добавляем в таблицу
QString url = QString("http://localhost:%1").arg(settings.port);
addServerToTable(settings.name, "Чат", settings.port, url, server);
toLog("uGeneral", "loadSavedChats",
QString("Загружен и запущен чат '%1' на порту %2")
.arg(settings.name).arg(settings.port), 0);
} else {
delete server;
toLog("uGeneral", "loadSavedChats",
QString("Не удалось запустить чат '%1' на порту %2")
.arg(settings.name).arg(settings.port), 2);
}
}
}
}