3725 lines
130 KiB
C++
3725 lines
130 KiB
C++
#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);
|
||
}
|
||
}
|
||
}
|
||
}
|